Ethernaut - Lvl 5: Token
Tags: ethereum, tutorial, security, smartcontract
Published: 2023-06-04 01:01:03

###### _Requirement: basic smart contract knowledge._

## The challenge 🥊

Hack the following token contract to increase your token balance:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

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

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}
```

### Studying the contract 📝

`Token.sol` is a token contract. 

State variables:
- `balances`: `mapping` to keep tracks of the balance of the holders.
- `totalSupply`: `uint` variable that keeps track of the current supply of tokens.

Methods:
- `constructor`: initializes `totalSupply` and allocates the supply to the deployer.
- `transfer`: method to move tokens if the account owns tokens.
- `balanceOf`: reads the token balance of `_owner`.

Holders can move their tokens via the `transfer` method if they own tokens.

**Hint**: An odometer is the instrument used to measure the distance traveled by vehicles. Odometers are known for being tampered by humans by reducing the reading of the device to increase the selling price of a vehicle.

In this challenge the contract is the odometer and we need to find a way to exploit it to increase our token balance.

### Underflow and overflow 🧮

If you are new to Solidity you may not be aware of the unsafe math operations prone to underflow and overflow before the release of version `v0.8.x`.

Since the release of version `v0.8.x` the compiler by default check all maths operations and revert if any of them will underflow or overflow as a safety mechanism. The token contract uses the compiler version `v0.6.0` that offers no protection against unsafe math operations.

But how does underflow and overflow works? Let's say we have the following contract:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract UnsafeMath {
    function unsafeAdd() external pure returns (uint8) {
        uint8 x = 255;
        return x + 1;
    }

    function unsafeSubtract() external pure returns (uint8) {
        uint8 y;
        return y - 1;
    }
}
```
`unsafeAdd` declares a local variable `x` of type `uint8` and assign to it the value of 255, which is the maximum value this type can take. If we call this function and add 1 to `x` the variable will overflow and will take the minimum value allowed which is zero.

`unsafeSubtract` does the opposite, declares a local variable `y` that is initiliazed as zero, when the function is executed and we subtract 1 from `y` the variable will underflow and instead of taking a value of -1 it will take the maximum value in this type which is 255.

As you can see this is extremely concerning specially if the contract handles money like `Token`. Imagine you have 255 tokens of X token and after receiving a new token your entire balance resets to zero and this mean total lost of funds!

## Breaking the contract 💣🧨

The `transfer` function is the one in charge of updating the balances of the holders after each transaction of tokens, therefore this function is prone to underflow/overflow.

Our current balance is consist of 20 tokens, after learning about unsafe math operations we know that if we cause an underflow our balance will be set to the maximum value of the `uint256` type and that is a **huge** number.

To cause an underflow let's send 21 tokens to an account:

```solidity
await contract.transfer("0x0000000000000000000000000000000000000000", 21)
```
We are transferring 21 tokens to the `address zero`. When the execution reaches the `require` and evaluates the condition `balances[msg.sender] - _value >= 0`, 20 - 21 will underflow and the condition will evaluate `maxUint256 - 21 >= 0` which is `true`.

After sending the transaction, check your balance:

```solidity
// should return:
// 115792089237316195423570985008687907853269984665640564039457584007913129639935
await contract.balanceOf(player).then(bal => bal.toString())
```
Now we own an absurd amount of tokens. Submit the intance fo complete this level.

## Conclusion ✅

Since the release of Solidity `v.0.8.x` the compiler by default checks all math operations and reverts the transaction in the case an underflow/overflow.

This default behavior can be disabled by wrapping the operation inside a `unchecked` block, but be sure the operation will never underflow or overflow.

```solidity

function uncheckedAdd(uint x, uint y) external pure returns (uint) {
    // 22291 gas
    // return x + y;

    // 22103 gas
    unchecked {
        return x + y;
    }
}
```

A benefit of wrapping the math operation inside the `unchecked` block is that the compiler will perform less logic, reducing execution cost.

## Futher reading 🗞📰

- [Solidity Integer Overflow & Underflow](https://neptunemutual.com/blog/solidity-integer-overflow-underflow/)
- [Math - OpenZeppelin Docs](https://docs.openzeppelin.com/contracts/2.x/api/math#:~:text=SafeMath,in%20high%20level%20programming%20languages.)
- [Math.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol)