From 9e823471573afc0f3e6043eb19aaaf2ae0761d5c Mon Sep 17 00:00:00 2001 From: Shivendra Singh Date: Fri, 15 Sep 2023 00:01:25 +0530 Subject: [PATCH] covered the stablecoin collapse usecase. Unit test passing, Invariant Test reverting --- src/DSCEngine.sol | 93 +++++++++++++++++++++-------------- test/fuzz/Handler.t.sol | 27 +++++++--- test/unit/DSCEngineTest.t.sol | 37 +++++++++++++- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/src/DSCEngine.sol b/src/DSCEngine.sol index e35c6f4..c037f8a 100644 --- a/src/DSCEngine.sol +++ b/src/DSCEngine.sol @@ -46,6 +46,7 @@ import {DecentralizedStableCoin} from "./DecentralizedStableCoin.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {OracleLib, AggregatorV3Interface} from "./libraries/OracleLib.sol"; +import {console} from "forge-std/console.sol"; contract DSCEngine is ReentrancyGuard { ////////////////////// @@ -61,6 +62,7 @@ contract DSCEngine is ReentrancyGuard { error DSCEngine__HealthFactorOk(address user); error DSCEngine__HealthFactorNotImproved(address user); error DSCEngine__DSCNotCollapsed(uint256 totalDscMinted, uint256 totalCollateralValueOfProtocol); + error DSCEngine__DSCProtocolCollapsed(uint256 totalDscMinted, uint256 _getTotalCollateralValueOfProtocol); /////////////////// //// Types //// @@ -122,6 +124,17 @@ contract DSCEngine is ReentrancyGuard { _; } + modifier checkProtocolCollapsed() { + uint256 totalCollateralValueOfProtocol = _getTotalCollateralValueOfProtocol(); + uint256 totalDSCMinted = i_dsc.totalSupply(); + + if(totalCollateralValueOfProtocol < totalDSCMinted) { + collapseDsc(); + } else { + _; + } + } + /////////////////////////// /// External Functions /// ////////////////////////// @@ -154,7 +167,7 @@ contract DSCEngine is ReentrancyGuard { address tokenCollateralAddress, uint256 amountCollateral, uint256 amountDscToMint - ) external { + ) external checkProtocolCollapsed() { depositCollateral(tokenCollateralAddress, amountCollateral); mintDSC(amountDscToMint); } @@ -227,7 +240,7 @@ contract DSCEngine is ReentrancyGuard { function mintDSC( uint256 amountDscToMint - ) public moreThanZero(amountDscToMint) { + ) public moreThanZero(amountDscToMint) checkProtocolCollapsed(){ s_DSCMinted[msg.sender] += amountDscToMint; // Check if Collateral value is more than DSC value @@ -268,7 +281,7 @@ contract DSCEngine is ReentrancyGuard { address collateralToken, address user, uint256 debtToCover - ) external { + ) external checkProtocolCollapsed(){ uint256 startingHealthFactor = _healthFactor(user); if (startingHealthFactor >= MIN_HEALTH_FACTOR) { @@ -401,6 +414,45 @@ contract DSCEngine is ReentrancyGuard { return totalCollateralValueInUsd; } + + /* + * @title CollapseDSC + * @author Shivendra Singh + * @notice In case of total collateral value being MORE than the DSC Minted, the * protocol should render itself as collapsed. In such an event, the protocol *will burn all minted DSC and redeem all deposited collateral of each and every * user. + */ + + function collapseDsc() public { + + uint256 totalCollateralValueOfProtocol = _getTotalCollateralValueOfProtocol(); + uint256 totalDSCMinted = i_dsc.totalSupply(); + + + if(totalCollateralValueOfProtocol > totalDSCMinted) { + revert DSCEngine__DSCNotCollapsed(totalDSCMinted, totalCollateralValueOfProtocol); + } + + emit DSCProtocolCollapsed(); + console.log('Users in the protocol: ', s_users.length); + + for(uint256 u = 0; u < s_users.length; u++){ + address user = s_users[u]; + uint256 userDscMinted = s_DSCMinted[user]; + + if(userDscMinted > 0) { + _burnDSC(userDscMinted, user); + } + + for(uint256 t = 0 ; t < s_collateralTokens.length; t++) { + address collateralToken = s_collateralTokens[t]; + uint256 collateralTokenAmount = s_collateralDeposited[user][collateralToken]; + + if(collateralTokenAmount > 0 ) { + _redeemCollateral(collateralToken, collateralTokenAmount, user, user); + } + } + } + } + ////////////////////////// /// Internal Functions /// ////////////////////////// @@ -415,6 +467,7 @@ contract DSCEngine is ReentrancyGuard { if (!success) { revert DSCEngine__TransferFailed(); } + i_dsc.burn(amountToBurn); emit DSCBurnt(user, amountToBurn); } @@ -466,40 +519,6 @@ contract DSCEngine is ReentrancyGuard { return (dscMinted, collateralDepositedValue); } - - /* - * @title CollapseDSC - * @author Shivendra Singh - * @notice In case of total collateral value being MORE than the DSC Minted, the * protocol should render itself as collapsed. In such an event, the protocol *will burn all minted DSC and redeem all deposited collateral of each and every * user. - */ - - function _collapseDsc() internal { - uint256 totalCollateralValueOfProtocol = _getTotalCollateralValueOfProtocol(); - uint256 totalDSCMinted = i_dsc.totalSupply(); - - if(totalCollateralValueOfProtocol > totalDSCMinted) { - revert DSCEngine__DSCNotCollapsed(totalDSCMinted, totalCollateralValueOfProtocol); - } - - for(uint256 u = 0; u < s_users.length; u++){ - address user = s_users[u]; - uint256 usersTotalDscMinted = s_DSCMinted[user]; - - _burnDSC(usersTotalDscMinted, user); - - for(uint256 t = 0 ; t < s_collateralTokens.length; t++){ - address collateralToken = s_collateralTokens[t]; - uint256 collateralTokenAmount = s_collateralDeposited[user][collateralToken]; - - if(collateralTokenAmount > 0 ) { - _redeemCollateral(collateralToken, collateralTokenAmount, address(this), user); - } - } - } - - emit DSCProtocolCollapsed(); - } - function _getTotalCollateralValueOfProtocol() internal view returns (uint256) { uint256 totalCollateralValueOfProtocol; diff --git a/test/fuzz/Handler.t.sol b/test/fuzz/Handler.t.sol index 490f14f..0af18f0 100644 --- a/test/fuzz/Handler.t.sol +++ b/test/fuzz/Handler.t.sol @@ -36,7 +36,7 @@ contract Handler is Test { vm.startPrank(msg.sender); ERC20Mock(collateralToken).mint(msg.sender, collateralAmount); - // giving approve to dscEngine to deposit tokens as collateral + // giving approve to dscEngine to deposit/transfer collateral tokens if required ERC20Mock(collateralToken).approve( address(dscEngine), collateralAmount @@ -116,20 +116,35 @@ contract Handler is Test { return; } - vm.prank(sender); + vm.startPrank(sender); dscEngine.mintDSC(mintAmount); + dsc.approve(address(dscEngine), mintAmount); // dsc approval given to dscEngine by the user, incase burning of these tokens required. + vm.stopPrank(); mintDscCalls++; } - /** - * TODO This breaks our system and should be taken care off. + + // TODO This breaks our system and should be taken care off. function updateEthUsdPriceFeed(uint96 newEthUsdPrice) public { + + // Arrange MockV3Aggregator ethUsdPriceFeed = MockV3Aggregator(dscEngine.getCollateralTokenPriceFeed(wEth)); - int256 newPrice = int256(uint256(newEthUsdPrice)); ethUsdPriceFeed.updateAnswer(newPrice); + + + uint256 totalDscMinted = dsc.totalSupply(); + uint256 wEthDeposited = ERC20Mock(wEth).balanceOf(address(dscEngine)); + uint256 wBtcDeposited = ERC20Mock(wBtc).balanceOf(address(dscEngine)); + + uint256 totalCollateralValue = dscEngine.getUsdValue(wEth, wEthDeposited) + dscEngine.getUsdValue(wBtc, wBtcDeposited); + + // Act + if(totalCollateralValue < totalDscMinted){ + dscEngine.collapseDsc(); + } } - */ + //////////////////// /// Helper Func. /// diff --git a/test/unit/DSCEngineTest.t.sol b/test/unit/DSCEngineTest.t.sol index 9345886..8b7a62c 100644 --- a/test/unit/DSCEngineTest.t.sol +++ b/test/unit/DSCEngineTest.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: MIT + // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; @@ -401,7 +401,6 @@ contract DSCEngineTest is Test { function testBurnDsc() public depositedCollateralAndMintedDSC { // Arrange vm.startPrank(user); - dsc.approve(address(dscEngine), mintAmount); dscEngine.burnDSC(mintAmount); vm.stopPrank(); @@ -495,4 +494,38 @@ contract DSCEngineTest is Test { console.log("Liquidator ETH Bal: ", actualEthBalOfLiquidator); assertEq(actualEthBalOfLiquidator, expectedEthBal); } + + /////////////////////////////// + /// Collapse Protocol tests /// + /////////////////////////////// + + function testRevertIfProtocolNotCollapsed() public depositedCollateralAndMintedDSC { + vm.expectRevert(abi.encodeWithSelector( + DSCEngine.DSCEngine__DSCNotCollapsed.selector, + mintAmount, + dscEngine.getUsdValue(weth, COLLATERAL_AMOUNT) + ) + ); + dscEngine.collapseDsc(); + } + + function testProtocolCollapse() public depositedCollateralAndMintedDSC { + // Arrange + // Crash the collateralValue + int256 crashedEthUsdPrice = 8e8; // $8/ETH, collateral value = 10 ether * $8 = 80 < 100 (mintAmount) + MockV3Aggregator ethUsdPriceFeedAggregator = MockV3Aggregator(wethUsdPriceFeed); + ethUsdPriceFeedAggregator.updateAnswer(crashedEthUsdPrice); + + vm.startPrank(user); + dsc.approve(address(dscEngine), mintAmount); + + // Act + // dscEngine.collapseDsc(); + dscEngine.mintDSC(mintAmount); + + // Assert + assertEq(dsc.totalSupply(), 0); + assertEq(dscEngine.getCollateralDeposited(weth), 0); + vm.stopPrank(); + } }