-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSCEngine.sol
397 lines (325 loc) · 14.3 KB
/
SCEngine.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {OracleLib, AggregatorV3Interface} from "./Libraries/OracleLib.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {DecentralizedStableCoin} from "./DecentralizedStableCoin.sol";
contract DSCEngine is ReentrancyGuard {
///////////////////
// Errors
///////////////////
error DSCEngine__TokenAddressesAndPriceFeedAddressesAmountsDontMatch();
error DSCEngine__NeedsMoreThanZero();
error DSCEngine__TokenNotAllowed(address token);
error DSCEngine__TransferFailed();
error DSCEngine__BreaksHealthFactor(uint256 healthFactorValue);
error DSCEngine__MintFailed();
error DSCEngine__HealthFactorOk();
error DSCEngine__HealthFactorNotImproved();
///////////////////
// Types
///////////////////
using OracleLib for AggregatorV3Interface;
///////////////////
// State Variables
///////////////////
DecentralizedStableCoin private immutable i_dsc;
uint256 private constant LIQUIDATION_THRESHOLD = 50; // This means you need to be 200% over-collateralized
uint256 private constant LIQUIDATION_BONUS = 10; // This means you get assets at a 10% discount when liquidating
uint256 private constant LIQUIDATION_PRECISION = 100;
uint256 private constant MIN_HEALTH_FACTOR = 1e18;
uint256 private constant PRECISION = 1e18;
uint256 private constant ADDITIONAL_FEED_PRECISION = 1e10;
uint256 private constant FEED_PRECISION = 1e8;
/// @dev Mapping of token address to price feed address
mapping(address collateralToken => address priceFeed) private s_priceFeeds;
/// @dev Amount of collateral deposited by user
mapping(address user => mapping(address collateralToken => uint256 amount)) private s_collateralDeposited;
/// @dev Amount of DSC minted by user
mapping(address user => uint256 amount) private s_DSCMinted;
/// @dev If we know exactly how many tokens we have, we could make this immutable!
address[] private s_collateralTokens;
///////////////////
// Events
///////////////////
event CollateralDeposited(address indexed user, address indexed token, uint256 indexed amount);
event CollateralRedeemed(address indexed redeemFrom, address indexed redeemTo, address token, uint256 amount); // if redeemFrom != redeemedTo, then it was liquidated
///////////////////
// Modifiers
///////////////////
modifier moreThanZero(uint256 amount) {
if (amount == 0) {
revert DSCEngine__NeedsMoreThanZero();
}
_;
}
modifier isAllowedToken(address token) {
if (s_priceFeeds[token] == address(0)) {
revert DSCEngine__TokenNotAllowed(token);
}
_;
}
///////////////////
// Functions
///////////////////
constructor(address[] memory tokenAddresses, address[] memory priceFeedAddresses, address dscAddress) {
}
///////////////////
// External Functions
///////////////////
/*
* @param tokenCollateralAddress: The ERC20 token address of the collateral you're depositing
* @param amountCollateral: The amount of collateral you're depositing
* @param amountDscToMint: The amount of DSC you want to mint
* @notice This function will deposit your collateral and mint DSC in one transaction
*/
function depositCollateralAndMintDsc(
address tokenCollateralAddress,
uint256 amountCollateral,
uint256 amountDscToMint
) external {
// use depositCollateral
// use mintDsc
}
/*
* @param tokenCollateralAddress: The ERC20 token address of the collateral you're depositing
* @param amountCollateral: The amount of collateral you're depositing
* @param amountDscToBurn: The amount of DSC you want to burn
* @notice This function will withdraw your collateral and burn DSC in one transaction
*/
function redeemCollateralForDsc(address tokenCollateralAddress, uint256 amountCollateral, uint256 amountDscToBurn)
external
moreThanZero(amountCollateral)
isAllowedToken(tokenCollateralAddress)
{
// burn Dsc
// redeem Collateral
// revert if health factor is broken
}
/*
* @param tokenCollateralAddress: The ERC20 token address of the collateral you're redeeming
* @param amountCollateral: The amount of collateral you're redeeming
* @notice This function will redeem your collateral.
* @notice If you have DSC minted, you will not be able to redeem until you burn your DSC
*/
function redeemCollateral(address tokenCollateralAddress, uint256 amountCollateral)
external
moreThanZero(amountCollateral)
nonReentrant
isAllowedToken(tokenCollateralAddress)
{
// redeem collateral (look for a private or internal function)
// revert if health factor is broken
}
/*
* @notice careful! You'll burn your DSC here! Make sure you want to do this...
* @dev you might want to use this if you're nervous you might get liquidated and want to just burn
* you DSC but keep your collateral in.
*/
function burnDsc(uint256 amount) external moreThanZero(amount) {
// burn Dsc (look for a private or internal function)
// revert if health factor is broken
}
/*
* @param collateral: The ERC20 token address of the collateral you're using to make the protocol solvent again.
* This is collateral that you're going to take from the user who is insolvent.
* In return, you have to burn your DSC to pay off their debt, but you don't pay off your own.
* @param user: The user who is insolvent. They have to have a _healthFactor below MIN_HEALTH_FACTOR
* @param debtToCover: The amount of DSC you want to burn to cover the user's debt.
*
* @notice: You can partially liquidate a user.
* @notice: You will get a 10% LIQUIDATION_BONUS for taking the users funds.
* @notice: This function working assumes that the protocol will be roughly 200% overcollateralized in order for this to work.
* @notice: A known bug would be if the protocol was only 100% collateralized, we wouldn't be able to liquidate anyone.
* For example, if the price of the collateral plummeted before anyone could be liquidated.
*/
function liquidate(address collateral, address user, uint256 debtToCover)
external
moreThanZero(debtToCover)
nonReentrant
{
// check health factor (only allow unhealthy positions to be liquidated)
// get the amount of collateral tokens that are equal in value to the debt to cover (DSC)
// uint256 tokenAmountFromDebtCovered = ...
// giving a 10% bonus of the collateral tokens
// uint256 bonusCollateral = ...
// redeem collateral to the liquidator (tokenAmountFromDebtCovered+bonusCollateral)
// burn DSC (to remove bad debt)
// revert if health factor of liquidator is broken
}
///////////////////
// Public Functions
///////////////////
/*
* @param amountDscToMint: The amount of DSC you want to mint
* You can only mint DSC if you hav enough collateral
*/
function mintDsc(uint256 amountDscToMint) public moreThanZero(amountDscToMint) nonReentrant {
// increase our balance (how much Dsc is minted to each user of the protocol
// check the health factor (function should revert if unhealthy)
// mint dsc
// check if mint was successful (uncomment the thing below)
/*
if (minted != true) {
revert DSCEngine__MintFailed();
}
*/
}
/*
* @param tokenCollateralAddress: The ERC20 token address of the collateral you're depositing
* @param amountCollateral: The amount of collateral you're depositing
*/
function depositCollateral(address tokenCollateralAddress, uint256 amountCollateral)
public
moreThanZero(amountCollateral)
nonReentrant
isAllowedToken(tokenCollateralAddress)
{
// increase collateral balance
// emit event that collateral position increased
// transfer collateral to this contract
// if this transfer is not successful revert and throw error
/*
if (!success) {
revert DSCEngine__TransferFailed();
}
*/
}
///////////////////
// Private Functions
///////////////////
function _redeemCollateral(address tokenCollateralAddress, uint256 amountCollateral, address from, address to) private {
// remove collateral balance
// emit event that collateral is redeemed
// transfer collateral to redeemer ("to")
/*
if (!success) {
revert DSCEngine__TransferFailed();
}
*/
}
function _burnDsc(uint256 amountDscToBurn, address onBehalfOf, address dscFrom) private {
// reduce stored balance of onBehalfOf
// transfer from dscFrom
/*
if (!success) {
revert DSCEngine__TransferFailed();
}
*/
// burn
}
//////////////////////////////
// Private & Internal View & Pure Functions
//////////////////////////////
function _getAccountInformation(address user)
private
view
returns (uint256 totalDscMinted, uint256 collateralValueInUsd)
{
totalDscMinted = s_DSCMinted[user];
collateralValueInUsd = getAccountCollateralValue(user);
}
function _healthFactor(address user) private view returns (uint256) {
(uint256 totalDscMinted, uint256 collateralValueInUsd) = _getAccountInformation(user);
return _calculateHealthFactor(totalDscMinted, collateralValueInUsd);
}
function _getUsdValue(address token, uint256 amount) private view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
// 1 ETH = 1000 USD
// The returned value from Chainlink will be 1000 * 1e8
// Most USD pairs have 8 decimals, so we will just pretend they all do
// We want to have everything in terms of WEI, so we add 10 zeros at the end
return ((uint256(price) * ADDITIONAL_FEED_PRECISION) * amount) / PRECISION;
}
function _calculateHealthFactor(uint256 totalDscMinted, uint256 collateralValueInUsd)
internal
pure
returns (uint256)
{
if (totalDscMinted == 0) return type(uint256).max; // => "infinite number aka max health"
uint256 collateralAdjustedForThreshold = (collateralValueInUsd * LIQUIDATION_THRESHOLD) / LIQUIDATION_PRECISION;
return (collateralAdjustedForThreshold * PRECISION) / totalDscMinted;
}
function revertIfHealthFactorIsBroken(address user) internal view {
uint256 userHealthFactor = _healthFactor(user);
if (userHealthFactor < MIN_HEALTH_FACTOR) {
revert DSCEngine__BreaksHealthFactor(userHealthFactor);
}
}
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
// External & Public View & Pure Functions
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
function calculateHealthFactor(uint256 totalDscMinted, uint256 collateralValueInUsd)
external
pure
returns (uint256)
{
return _calculateHealthFactor(totalDscMinted, collateralValueInUsd);
}
function getAccountInformation(address user)
external
view
returns (uint256 totalDscMinted, uint256 collateralValueInUsd)
{
return _getAccountInformation(user);
}
function getUsdValue(
address token,
uint256 amount // in WEI
) external view returns (uint256) {
return _getUsdValue(token, amount);
}
function getCollateralBalanceOfUser(address user, address token) external view returns (uint256) {
return s_collateralDeposited[user][token];
}
function getAccountCollateralValue(address user) public view returns (uint256 totalCollateralValueInUsd) {
for (uint256 index = 0; index < s_collateralTokens.length; index++) {
address token = s_collateralTokens[index];
uint256 amount = s_collateralDeposited[user][token];
totalCollateralValueInUsd += _getUsdValue(token, amount);
}
return totalCollateralValueInUsd;
}
function getTokenAmountFromUsd(address token, uint256 usdAmountInWei) public view returns (uint256) {
AggregatorV3Interface priceFeed = AggregatorV3Interface(s_priceFeeds[token]);
(, int256 price,,,) = priceFeed.staleCheckLatestRoundData();
// $100e18 USD Debt
// 1 ETH = 2000 USD
// The returned value from Chainlink will be 2000 * 1e8
// Most USD pairs have 8 decimals, so we will just pretend they all do
return ((usdAmountInWei * PRECISION) / (uint256(price) * ADDITIONAL_FEED_PRECISION));
}
function getPrecision() external pure returns (uint256) {
return PRECISION;
}
function getAdditionalFeedPrecision() external pure returns (uint256) {
return ADDITIONAL_FEED_PRECISION;
}
function getLiquidationThreshold() external pure returns (uint256) {
return LIQUIDATION_THRESHOLD;
}
function getLiquidationBonus() external pure returns (uint256) {
return LIQUIDATION_BONUS;
}
function getLiquidationPrecision() external pure returns (uint256) {
return LIQUIDATION_PRECISION;
}
function getMinHealthFactor() external pure returns (uint256) {
return MIN_HEALTH_FACTOR;
}
function getCollateralTokens() external view returns (address[] memory) {
return s_collateralTokens;
}
function getDsc() external view returns (address) {
return address(i_dsc);
}
function getCollateralTokenPriceFeed(address token) external view returns (address) {
return s_priceFeeds[token];
}
function getHealthFactor(address user) external view returns (uint256) {
return _healthFactor(user);
}
}