|
| 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 | +} |
0 commit comments