2

The following code is from a Stablecoin project.

To gain more context, please refer to this Github repo.

I've added a new function to the DSCEngine.sol name collapseDsc() which is responsible for returning the collateral deposited by all users of the protocol, in case of a dramatic collapse of the underlying asset (wEth, wBtc) values.

DSCEngine.sol

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);
                } 
            } 
        }
    } 

I have the following Fuzz tests:

Minting DSC: This will mint the DSC and approve it to the DSCEngine at the same time.

Handler.t.sol

 function mintDsc(uint256 mintAmount, uint256 userWithCollateralSeed) public {
        if(usersWithDepositedCollateral.length == 0){
            return;
        } 
        // Arrange
        address sender = _getRandomUserWithCollateral(userWithCollateralSeed); 
        
        // mintAmount should not be 0 and should not be more than (half the collateral value deposited - dscMinted)
        (uint256 dscMinted, uint256 collateralDepositedValue) = dscEngine
            .getAccountInformation(sender);

        int256 maxMintAmount = (int256(collateralDepositedValue) / 2) - int256(dscMinted);
        if (maxMintAmount < 0) {
            // this can happen if the collateral value drops after a token crash
            return;
        }

        mintAmount = bound(mintAmount, 0, uint256(maxMintAmount));
        if (mintAmount == 0) {
            // can be 0 if no collateral is deposited
            return;
        }

        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++;
    }

The second is the test which crashes the value of the underlying asset and triggers the collapseDsc() function.

Handler.t.sol


 // TODO This breaks our system and should be taken care of.
    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();
        }
    }

Now on running this test, I get a revert. Here are the error logs:

[234902] Handler::updateEthUsdPriceFeed(4463) 
    ├─ [2613] DSCEngine::getCollateralTokenPriceFeed(ERC20Mock: [0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76]) [staticcall]
    │   └─ ← MockV3Aggregator: [0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496]
    ├─ [79404] MockV3Aggregator::updateAnswer(4463) 
    │   └─ ← ()
    ├─ [2349] DecentralizedStableCoin::totalSupply() [staticcall]
    │   └─ ← 36867680678869884643073056819756867246485946828 [3.686e46]
    ├─ [2585] ERC20Mock::balanceOf(DSCEngine: [0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512]) [staticcall]
    │   └─ ← 147181787746094153296977941449 [1.471e29]
    ├─ [2585] ERC20Mock::balanceOf(DSCEngine: [0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512]) [staticcall]
    │   └─ ← 118501289238336304 [1.185e17]
    ├─ [18666] DSCEngine::getUsdValue(ERC20Mock: [0x50EEf481cae4250d252Ae577A09bF514f224C6C4], 118501289238336304 [1.185e17]) [staticcall]
    │   ├─ [12650] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [8993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   └─ ← 118501289238336304000 [1.185e20]
    ├─ [3666] DSCEngine::getUsdValue(ERC20Mock: [0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76], 147181787746094153296977941449 [1.471e29]) [staticcall]
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   └─ ← 6568723187108182061644125 [6.568e24]
    ├─ [92888] DSCEngine::collapseDsc() 
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 6, 4463, 1, 1, 6
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000116f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006
    │   ├─ [2150] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   ├─ [349] DecentralizedStableCoin::totalSupply() [staticcall]
    │   │   └─ ← 36867680678869884643073056819756867246485946828 [3.686e46]
    │   ├─ emit DSCProtocolCollapsed()
    │   ├─ [0] console::9710a9d0(000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000017557365727320696e207468652070726f746f636f6c3a20000000000000000000) [staticcall]
    │   │   └─ ← ()
    │   ├─ [3016] DecentralizedStableCoin::transferFrom(0x8499B29da102AD9F9C76c0380A190408a49e2326, DSCEngine: [0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512], 59250644619166520499 [5.925e19]) 
    │   │   └─ ← "ERC20: insufficient allowance"
    │   └─ ← "ERC20: insufficient allowance"
    └─ ← "ERC20: insufficient allowance"

  [75427] InvariantsTest::invariant_protocolMustAlwaysHaveMoreCollateralValueThanMintedDSCValue() 
    ├─ [2349] DecentralizedStableCoin::totalSupply() [staticcall]
    │   └─ ← 36867680678869884643073056819756867246485946828 [3.686e46]
    ├─ [2585] ERC20Mock::balanceOf(DSCEngine: [0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512]) [staticcall]
    │   └─ ← 147181787746094153296977941449 [1.471e29]
    ├─ [2585] ERC20Mock::balanceOf(DSCEngine: [0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512]) [staticcall]
    │   └─ ← 118501289238336304 [1.185e17]
    ├─ [18666] DSCEngine::getUsdValue(ERC20Mock: [0x50EEf481cae4250d252Ae577A09bF514f224C6C4], 118501289238336304 [1.185e17]) [staticcall]
    │   ├─ [12650] OracleLib::2ba1c96a(000000000000000000000000bb2180ebd78ce97360503434ed37fcf4a1df61c3) [delegatecall]
    │   │   ├─ [8993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 1, 100000000000, 1, 1, 1
    │   │   └─ ← 0x0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000174876e800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
    │   └─ ← 118501289238336304000 [1.185e20]
    ├─ [16166] DSCEngine::getUsdValue(ERC20Mock: [0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76], 147181787746094153296977941449 [1.471e29]) [staticcall]
    │   ├─ [12650] OracleLib::2ba1c96a(000000000000000000000000a8452ec99ce0c64f20701db7dd3abdb607c00496) [delegatecall]
    │   │   ├─ [8993] MockV3Aggregator::latestRoundData() [staticcall]
    │   │   │   └─ ← 5, 93067110251944013583873118, 1, 1, 5
    │   │   └─ ← 0x00000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000004cfbba27f6840d86cbf45e000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000005
    │   └─ ← 136977836672439669664473841260116313382165854880 [1.369e47]
    ├─ [0] console::9710a9d0(000000000000000000000000000000000000000000000000000000000000004000000000000000000000000006753437ea323983eb5b8b193a9445be01d841cc0000000000000000000000000000000000000000000000000000000000000012546f74616c2044534320537570706c793a200000000000000000000000000000) [staticcall]
    │   └─ ← ()
    ├─ [0] console::9710a9d0(000000000000000000000000000000000000000000000000000000000000004000000000000000000000000017fe4c72fda38f95a91c31d6fd5a12f4a5a9ae200000000000000000000000000000000000000000000000000000000000000018546f74616c20436f6c6c61746572616c2076616c75653a200000000000000000) [staticcall]
    │   └─ ← ()
    ├─ [2308] Handler::mintDscCalls() [staticcall]
    │   └─ ← 3
    ├─ [0] console::9710a9d0(00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000c4d696e742063616c6c733a200000000000000000000000000000000000000000) [staticcall]
    │   └─ ← ()
    └─ ← ()

I don't understand why I'm getting a ERC20: insufficient allowance error when the user is allowing the DSCEngine the DSCs they mint, right after the minting in the mintDsc(..) test.

Source code: https://github.com/alfheimrShiven/defi-stablecoin

Github discussion thread: https://github.com/Cyfrin/foundry-full-course-f23/discussions/871#discussioncomment-7012541

Any help would be appreciated. Thanks!

1 Answer 1

0

The bug was due to the assumption that the approve(...) function adds up the allowance approved to the spender, in case called multiple times, but that isn't the case. Only the latest mintAmount allowance holds true, not the cumulative value! Hence instead of giving the allowance each time the DSC is minted, I have written a modifier that should give the allowance to all DSC minted by the user at once.

Refer this commit

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.