- Published on
Capture the Ether (RareSkills Repo) - Token Whale - Solution
- Authors
- Name
- Marco Besier, Ph.D.
- @marcobesier
Capture the Ether (RareSkills Repo) - Token Whale - Solution
Contract
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
contract TokenWhale {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
event Transfer(address indexed from, address indexed to, uint256 value);
constructor(address _player) {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000;
}
function _transfer(address to, uint256 value) internal {
unchecked {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
}
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
}
Solution
This challenge is conveniently solved via Remix. To increase the player
's token balance to 1M (or more), we'll use two different accounts and perform the following sequence of function calls:
- Set
_player
to an EOA you control, e.g., the first of Remix's default accounts. This will giveplayer
an account balance of 1000. - Next, approve a second EOA you control, e.g., the second of Remix's default accounts, with
value = 1
. Ensure to make this function call fromplayer
's account. - Now, call
transferFrom(player, player, 1)
from the second EOA. At first sight, one would assume that this wouldn't change anything since we're just sendingvalue = 1
from theplayer
to theplayer
. However, by taking a closer look at the implementation of_transfer
, we see that this call will underflow the second EOA's balance (and add 1 to theplayer
's balance). In other words, after this call,player
has a balance of 1001 while the second EOA has a balance of2**256 - 1
! - Lastly, we can use
transfer
to transfervalue = 998999
(or more) from the second EOA toplayer
, leavingplayer
with a balance of 1000000 (or more).
Note that the to
argument in the above call to transferFrom
can actually be chosen arbitrarily. The important point in the above sequence is not that we increase the player
's balance from 1000 to 1001, but that we underflow the balance of our second EOA so that this account has enough tokens to bump up player
's balance to 1M or more.