Skip to content

Commit c9cbbc2

Browse files
committed
CoinEngine(unfinished) , DeployScript, HelperConfig
1 parent c10ca59 commit c9cbbc2

File tree

10 files changed

+442
-4
lines changed

10 files changed

+442
-4
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
[submodule "lib/openzeppelin-contracts"]
55
path = lib/openzeppelin-contracts
66
url = https://github.com/OpenZeppelin/openzeppelin-contracts
7+
[submodule "lib/chainlink-brownie-contracts"]
8+
path = lib/chainlink-brownie-contracts
9+
url = https://github.com/smartcontractkit/chainlink-brownie-contracts

foundry.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@
22
src = "src"
33
out = "out"
44
libs = ["lib"]
5-
6-
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
5+
remappings = [
6+
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
7+
'@chainlink/contracts/=lib/chainlink-brownie-contracts/contracts/',
8+
]

lib/chainlink-brownie-contracts

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "stable_coin",
3+
"version": "1.0.0",
4+
"description": "Stable Coin Project",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1"
7+
},
8+
"dependencies": {
9+
"@openzeppelin/contracts": "^4.9.0"
10+
}
11+
}

script/DeploySC.s.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
2+
pragma solidity 0.8;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {StableCoin} from "../src/StableCoin.sol";
6+
import {CoinEngine} from "../src/CoinEngine.sol";
7+
import {HelperConfig} from "./HelperConfig.s.sol";
8+
9+
contract DeploySC is Script {
10+
address[] public tokenAddress;
11+
address[] public priceFeedAddress;
12+
13+
function run() external {
14+
HelperConfig config = new HelperConfig();
15+
(address wethUsdPriceFeed, address wbtcUsdPriceFeed, address weth, address wbtc, uint256 deployerkey) =
16+
config.activeNetworkConfig();
17+
tokenAddress = [weth, wbtc];
18+
priceFeedAddress = [wethUsdPriceFeed, wbtcUsdPriceFeed];
19+
vm.startBroadcast(deployerkey);
20+
StableCoin sc = new StableCoin();
21+
CoinEngine engine = new CoinEngine(tokenAddress, priceFeedAddress, address(sc));
22+
sc.transferOwnership(address(engine));
23+
vm.stopBroadcast();
24+
}
25+
}

script/HelperConfig.s.sol

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
2+
pragma solidity 0.8;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {ERC20Mock} from "../test/mocks/ERC20Mock.sol";
6+
import {MockV3Aggregator} from "../test/mocks/MockV3Aggregator.sol";
7+
8+
contract HelperConfig is Script {
9+
struct NetworkConfig {
10+
address wethUsdPriceFeed;
11+
address wbtcUsdPriceFeed;
12+
address weth;
13+
address wbtc;
14+
uint256 deployerKey;
15+
}
16+
17+
NetworkConfig public activeNetworkConfig;
18+
19+
uint8 public constant DECIMALS = 8;
20+
int256 public constant ETH_USD_PRICE = 2000e8;
21+
int256 public constant BTC_USD_PRICE = 1000e8;
22+
23+
uint256 public DEFAULT_ANVIL_PRIVATE_KEY = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
24+
25+
constructor() {
26+
if (block.chainid == 11_155_111) {
27+
activeNetworkConfig = getSepoliaConfig();
28+
} else {
29+
activeNetworkConfig = getOrCreateAnvilConfig();
30+
}
31+
}
32+
33+
function getSepoliaConfig() public view returns (NetworkConfig memory SepoliaNetworkConfig) {
34+
SepoliaNetworkConfig = NetworkConfig({
35+
wethUsdPriceFeed: 0x694AA1769357215DE4FAC081bf1f309aDC325306, // ETH / USD
36+
wbtcUsdPriceFeed: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43,
37+
weth: 0xdd13E55209Fd76AfE204dBda4007C227904f0a81,
38+
wbtc: 0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063,
39+
deployerKey: vm.envUint("PRIVATE_KEY")
40+
});
41+
}
42+
43+
function getOrCreateAnvilConfig() public returns (NetworkConfig memory anvilNetworkConfig) {
44+
if (activeNetworkConfig.wethUsdPriceFeed != address(0)) {
45+
return activeNetworkConfig;
46+
}
47+
48+
vm.startBroadcast();
49+
MockV3Aggregator ethUsdPriceFeed = new MockV3Aggregator(DECIMALS, ETH_USD_PRICE);
50+
ERC20Mock wethMock = new ERC20Mock("WETH", "WETH", msg.sender, 1000e8);
51+
52+
MockV3Aggregator btcUsdPriceFeed = new MockV3Aggregator(DECIMALS, BTC_USD_PRICE);
53+
ERC20Mock wbtcMock = new ERC20Mock("WBTC", "WBTC", msg.sender, 1000e8);
54+
vm.stopBroadcast();
55+
56+
anvilNetworkConfig = NetworkConfig({
57+
wethUsdPriceFeed: address(ethUsdPriceFeed),
58+
wbtcUsdPriceFeed: address(btcUsdPriceFeed),
59+
weth: address(wethMock),
60+
wbtc: address(wbtcMock),
61+
deployerKey: DEFAULT_ANVIL_PRIVATE_KEY
62+
});
63+
}
64+
}

src/CoinEngine.sol

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Layout of Contract:
2+
// version
3+
// imports
4+
// errors
5+
// interfaces, libraries, contracts
6+
// Type declarations
7+
// State variables
8+
// Events
9+
// Modifiers
10+
// Functions (CONSTRUCTOR , EXTERNAL , PUBLIC , PRIVATE , INTERNAL)
11+
12+
// Layout of Functions:
13+
// constructor
14+
// receive function (if exists)
15+
// fallback function (if exists)
16+
// external
17+
// public
18+
// internal
19+
// private
20+
// internal & private view & pure functions
21+
// external & public view & pure functions
22+
23+
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
24+
pragma solidity 0.8;
25+
26+
import {StableCoin} from "./StableCoin.sol";
27+
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
28+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
29+
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
30+
31+
/**
32+
* @title Coin Engine
33+
* @author Vivek Tanna
34+
* This contract it the issential file for pegging our Stable coin 1SC = $1
35+
* ~Algorithmic Stable
36+
* ~Exogenously Collateral
37+
* ~Dollar Pegged
38+
*
39+
* Engine need to be overcollateral than total value of issued DSC
40+
* Total DSC issued < Total $ Collateral Backed Value in Engine
41+
*
42+
* @notice contract is core and handler of all the operation of minting , burning of SC, and depositing , withdrawal of Collateral
43+
*/
44+
contract CoinEngine is ReentrancyGuard {
45+
///////////////////
46+
// Errors
47+
///////////////////
48+
error CoinEngine_AmountShouldMoreThanZero();
49+
error CoinEngine_isAllowedToken(address token);
50+
error CoinEngine_TransferFailed();
51+
error CoinEngine_HealthFactorBroken();
52+
error CoinEngine_TokenAddressAndPriceFeedAddressLengthDontMatch();
53+
54+
///////////////////
55+
// Types
56+
///////////////////
57+
58+
///////////////////
59+
// State Variables
60+
///////////////////
61+
62+
StableCoin private immutable i_sc;
63+
64+
uint256 private constant MIN_HEALTH_FACTOR = 1e18; // 1.0
65+
uint256 private constant PRECISION = 1e18;
66+
uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;
67+
uint256 private constant LIQUIDATION_THRESOLD = 50;
68+
uint256 private constant LIQUIDATION_PRECISION = 100;
69+
70+
/// @dev Mapping of token address to price feed address
71+
mapping(address tokenCollateralAddress => address priceFeed) private s_priceFeeds;
72+
/// @dev Mapping of Amount of token deposited by user
73+
mapping(address user => mapping(address tokenCollateralAddress => uint256 amount)) private s_collateralDeposited;
74+
/// @dev Mapping of Amount of SC token minted by user
75+
mapping(address user => uint256 amount) private s_scMinted;
76+
/// @dev using this to know how many token we are having (assuming we will declare it later externall pass it)
77+
address[] private s_collateralTokens;
78+
79+
///////////////////
80+
// Events
81+
///////////////////
82+
event CollateralDeposited(address indexed user, address indexed tokenCollateralAddress, uint256 amount);
83+
84+
///////////////////
85+
// Modifiers
86+
///////////////////
87+
modifier moreThanZero(uint256 amount) {
88+
if (amount <= 0) {
89+
revert CoinEngine_AmountShouldMoreThanZero();
90+
}
91+
_;
92+
}
93+
94+
modifier isAllowedToken(address token) {
95+
if (s_priceFeeds[token] == address(0)) {
96+
revert CoinEngine_isAllowedToken(token);
97+
}
98+
_;
99+
}
100+
101+
///////////////////
102+
// Functions
103+
///////////////////
104+
105+
constructor(address[] memory tokenAddress, address[] memory priceFeedAddress, address scAddress) {
106+
if (tokenAddress.length != priceFeedAddress.length) {
107+
revert CoinEngine_TokenAddressAndPriceFeedAddressLengthDontMatch();
108+
}
109+
for (uint256 i = 0; i < tokenAddress.length; i++) {
110+
s_priceFeeds[tokenAddress[i]] = priceFeedAddress[i];
111+
s_collateralTokens.push(tokenAddress[i]);
112+
}
113+
i_sc = StableCoin(scAddress);
114+
}
115+
116+
///////////////////
117+
// Public Functions
118+
///////////////////
119+
120+
/// @dev This function is used to deposit collateral in the engine
121+
/// @param tokenCollateralAddress address of the token to be deposited
122+
/// @param amountCollateral amount of the token to be deposited
123+
function depositCollateral(address tokenCollateralAddress, uint256 amountCollateral)
124+
public
125+
nonReentrant
126+
moreThanZero(amountCollateral)
127+
isAllowedToken(tokenCollateralAddress)
128+
{
129+
s_collateralDeposited[msg.sender][tokenCollateralAddress] += amountCollateral;
130+
emit CollateralDeposited(msg.sender, tokenCollateralAddress, amountCollateral);
131+
(bool success) = IERC20(tokenCollateralAddress).transferFrom(msg.sender, address(this), amountCollateral);
132+
if (!success) {
133+
revert CoinEngine_TransferFailed();
134+
}
135+
}
136+
137+
function mintSc(uint256 amount) public nonReentrant moreThanZero(amount) {
138+
s_scMinted[msg.sender] += amount;
139+
_revertIfHealtFactorIsBroken(msg.sender);
140+
}
141+
142+
//////////////////////////////
143+
// Private & Internal View & Pure Functions
144+
//////////////////////////////
145+
function _getValueUSD(address tokenAddress, uint256 amount) private view returns (uint256) {
146+
AggregatorV3Interface dataFeed = AggregatorV3Interface(s_priceFeeds[tokenAddress]);
147+
(, int256 price,,,) = dataFeed.latestRoundData();
148+
// we can try this : price * amount / 1e8
149+
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
150+
}
151+
152+
function _revertIfHealtFactorIsBroken(address user) internal view {
153+
uint256 userHealthFactor = _getUserHealthFactor(user);
154+
if (userHealthFactor < MIN_HEALTH_FACTOR) {
155+
revert CoinEngine_HealthFactorBroken();
156+
}
157+
}
158+
159+
function _getUserHealthFactor(address user) internal view returns (uint256) {
160+
(uint256 totalSCMinted, uint256 collateralValueInUSD) = _getAccountInformation(user);
161+
return _calculateHealthFactor(totalSCMinted, collateralValueInUSD);
162+
}
163+
164+
function _getAccountInformation(address user)
165+
internal
166+
view
167+
returns (uint256 totalSCMinted, uint256 collateralValueInUSD)
168+
{
169+
totalSCMinted = s_scMinted[user];
170+
collateralValueInUSD = _getAccountCollateralValue(user);
171+
}
172+
173+
function _getAccountCollateralValue(address user) internal view returns (uint256 totalCollateralValueInUSD) {
174+
for (uint256 i = 0; i < s_collateralTokens.length; i++) {
175+
address tokenAddress = s_collateralTokens[i];
176+
uint256 amount = s_collateralDeposited[user][tokenAddress];
177+
totalCollateralValueInUSD += _getValueUSD(tokenAddress, amount);
178+
}
179+
return totalCollateralValueInUSD;
180+
}
181+
182+
function _calculateHealthFactor(uint256 totalSCMinted, uint256 collateralValueInUSD)
183+
internal
184+
pure
185+
returns (uint256)
186+
{
187+
// if (totalSCMinted == 0) return type(uint256).max;
188+
uint256 collateralThresold = (collateralValueInUSD * LIQUIDATION_THRESOLD) / LIQUIDATION_PRECISION;
189+
return (collateralThresold * PRECISION) / totalSCMinted;
190+
}
191+
192+
/////////////////////////////////////////////
193+
// External & Public View & Pure Functions
194+
/////////////////////////////////////////////
195+
}

src/StableCoin.sol

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
11
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
22
pragma solidity 0.8;
33

4-
contract StableCoin {
5-
constructor() {}
4+
/**
5+
* @title StableCoin
6+
* @author Vivek Tanna
7+
* It is Exogenous algorithmic pegged stable coin
8+
* This contract ownership transfered to DSCEngine
9+
*
10+
*/
11+
import {ERC20Burnable, ERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
12+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
13+
14+
contract StableCoin is ERC20Burnable, Ownable {
15+
error StableCoin_NotZeroAddress();
16+
error StableCoin_AmountMustBeMoreThanZero();
17+
error StableCoin_BurnAmountShouldExceedsBalance();
18+
19+
constructor() ERC20("StableCoin", "SC") Ownable(msg.sender) {}
20+
21+
function mint(address _to, uint256 _amount) external onlyOwner returns (bool) {
22+
if (_to == address(0)) {
23+
revert StableCoin_NotZeroAddress();
24+
}
25+
if (_amount <= 0) {
26+
revert StableCoin_AmountMustBeMoreThanZero();
27+
}
28+
_mint(_to, _amount);
29+
return true;
30+
}
31+
32+
function burn(uint256 _amount) public override onlyOwner {
33+
uint256 balance = balanceOf(msg.sender);
34+
if (_amount <= 0) {
35+
revert StableCoin_AmountMustBeMoreThanZero();
36+
}
37+
if (balance < _amount) {
38+
revert StableCoin_BurnAmountShouldExceedsBalance();
39+
}
40+
super.burn(_amount);
41+
}
642
}

test/mocks/ERC20Mock.sol

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8;
3+
4+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
6+
contract ERC20Mock is ERC20 {
7+
constructor(string memory name, string memory symbol, address initialAccount, uint256 initialBalance)
8+
payable
9+
ERC20(name, symbol)
10+
{
11+
_mint(initialAccount, initialBalance);
12+
}
13+
14+
function mint(address account, uint256 amount) public {
15+
_mint(account, amount);
16+
}
17+
18+
function burn(address account, uint256 amount) public {
19+
_burn(account, amount);
20+
}
21+
22+
function transferInternal(address from, address to, uint256 value) public {
23+
_transfer(from, to, value);
24+
}
25+
26+
function approveInternal(address owner, address spender, uint256 value) public {
27+
_approve(owner, spender, value);
28+
}
29+
}

0 commit comments

Comments
 (0)