Published on

Ethernaut - Naught Coin - Solution

Authors

Ethernaut - Naught Coin - Solution

Contract

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract NaughtCoin is ERC20 {

  // string public constant name = 'NaughtCoin';
  // string public constant symbol = '0x0';
  // uint public constant decimals = 18;
  uint 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) override public 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 {
     _;
    }
  }
}

Solution

The goal is to transfer all tokens from our player account to another address. At first sight, this looks like an impossible thing to do because the lockTokens modifier prevents the player from executing transfer during the first 10 years after contract deployment.

However, notice that the ERC-20 interface exposes two functions that facilitate token transfers: transfer and transferFrom.

In the above contract, transfer is overridden and protected by lockTokens. transferFrom, however, is not overridden and, in particular, not protected by the lockTokens modifier!

Therefore, we can solve this challenge using the following steps: First, we use our player account to approve a second account we control for the entire INITIAL_SUPPLY. The quickest way to do this is through the Ethernaut console.

contract.approve("<your second account address>", "1000000000000000000000000")

Subsequently, we can call transferFrom from our second account, transferring the tokens from player's account to our second account. The following line shows how to do this using Cast.

cast send --private-key <private key of your second account> --rpc-url $SEP_RPC_URL <your level instance> "transferFrom(address,address,uint256)" <your player address> <address of your second account> 1000000000000000000000000