iT邦幫忙

2022 iThome 鐵人賽

DAY 13
0

(Naught Coin)倒楣鬼程式碼

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

 contract NaughtCoin is ERC20 {

  // string public constant name = 'NaughtCoin';
  // string public constant symbol = '0x0';
  // uint public constant decimals = 18;
  uint public timeLock = now + 10 * 365 days;
  uint256 public INITIAL_SUPPLY;
  address public player;

  constructor(address _player) 
  ERC20('NaughtCoin', '0x0')
  public {
    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) override public 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(now > timeLock);
      _;
    } else {
     _;
    }
  } 
} 

通關條件

玩家必須將自己帳戶內的 Token 全數轉出才能通關 (Token 數量 == 0)

先備知識 - ERC20

自從以太坊上的智能合約問世後,區塊鏈不只能做到儲存價值與匯款轉帳,更能以智能合約在鏈上創造各式各樣的應用程式,而今天的主旨 ERC20 便是其中之一,不過,在針對 ERC20 的功能講解之前,還是先來看看甚麼是 ERC20 吧 !

  • EIP (Ethereum Improvement Proposals)
    • 任何人都能提出的改進協議
    • 分為協定改進 & 應用標準
  • ERC (Ethereum Request for Comment)
    • 應用標準
    • 社群推薦大家使用的共識(不強迫)

ERC20 是社群推薦開發者使用的標準,裡面定義了開發者需要為自己的合約完成的函數,和這些函數的名稱,我們甚至可以在 Opzeppelin 的 Github 裡面找到完整的 ERC20 的 Contract code,需要使用時能夠直接複製使用。

而 ERC20 是一個能夠讓開發者在合約裡發行自己的 Token 的合約,主要由一個 mapping 紀錄帳戶內的餘額

mapping(address => uint256) private _balances;

並且在為你的 Token 取名

uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
    _name = name_;
    _symbol = symbol_;
}

如此一來便有了一個欲發行的 Token 的基礎資料,最後再將一個 Token 該有的基本函數完成定義後即可(例如轉帳等等)

function approve(address spender, uint256 amount) public virtual override returns (bool) {
    address owner = _msgSender();
    _approve(owner, spender, amount);
    return true;
}
function _approve(
    address owner,
    address spender,
    uint256 amount
) internal virtual {
    require(owner != address(0), "ERC20: approve from the zero address");
    require(spender != address(0), "ERC20: approve to the zero address");

    _allowances[owner][spender] = amount;
    emit Approval(owner, spender, amount);
}

approve 能夠許可某人存取自己的 Token

function transferFrom(
    address from,
    address to,
    uint256 amount
) public virtual override returns (bool) {
    address spender = _msgSender();
    _spendAllowance(from, spender, amount);
    _transfer(from, to, amount);
    return true;
}

function transfer(address to, uint256 amount) public virtual override returns (bool) {
    address owner = _msgSender();
    _transfer(owner, to, amount);
    return true;
}

transferFrom/transfer 轉移 Token 的函數,可以讓使用者將 Token 轉移到指定的地址

function _mint(address account, uint256 amount) internal virtual {
    require(account != address(0), "ERC20: mint to the zero address");

    _beforeTokenTransfer(address(0), account, amount);

    _totalSupply += amount;
    unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
        _balances[account] += amount;
    }
    emit Transfer(address(0), account, amount);

    _afterTokenTransfer(address(0), account, amount);
}

_mint 能夠製造(發行新的 Token) Token 至指定地址,通常會加上限制(你不會希望每個人都能隨便製造自己的 Token 吧)

function _burn(address account, uint256 amount) internal virtual {
    require(account != address(0), "ERC20: burn from the zero address");

    _beforeTokenTransfer(account, address(0), amount);

    uint256 accountBalance = _balances[account];
    require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
        _balances[account] = accountBalance - amount;
        // Overflow not possible: amount <= accountBalance <= totalSupply.
        _totalSupply -= amount;
    }

    emit Transfer(account, address(0), amount);

    _afterTokenTransfer(account, address(0), amount);
}

_burn 如同字面的意思,將 Token 燒毀(扣除發行總量 + 扣除某人持有量),就結果而言會減少 Token 的總量

以上是部分 ERC20 常使用到的函數,不過既然都介紹了,那倒不如就簡單發行一個吧(?)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract token is ERC20 {

    constructor(string memory name, string memory symbol) ERC20(name, symbol) {}

    function mint(uint amount) public {
        _mint(msg.sender, amount);
    }
    
}

把這份合約扔到測試網上面吧 (◑‿◐)
接著找找最近的一筆交易,找到自己部署的合約地址


找到合約地址後請點擊 Metamask 上的 import token

填上合約地址後點擊 Add Custom Token

這樣就能看到自己發行的 Token 拉 (◉ω◉)
接著我們回到 Remix 執行 mint

就能看到自己的帳戶底下有 Token 囉~
好啦,那希望大家都能順利發行自己的 Token,接著就進入 Level 15 的通關流程囉

程式碼

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';

{...}

function transfer(address _to, uint256 _value) override public 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(now > timeLock);
      _;
    } else {
     _;
    }
  } 

可以看到 transfer 被 lockTokens 這個 modifier 給鎖住了,還鎖了 10 年啊 ( ゚д゚)

不過我們剛剛也看到了 Openzeppelin 的 ERC20 合約裡所實現的函數,發現有兩個 transfer 可以用,所以只要使用另外一個 transferFrom 轉出 Token 就可以囉。

通關

player2 = "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
balance = await contract.balanceOf(player).then(v => v.toString())
await contract.approve(player, balance)
await contract.transferFrom(player, player2, balance)



٩(^ ‿ ^)۶ ٩(^ ‿ ^)۶ ٩(^ ‿ ^)۶ ٩(^ ‿ ^)۶

Reference

https://docs.openzeppelin.com/contracts/4.x/erc20
https://docs.openzeppelin.com/contracts/4.x/api/token/erc20


上一篇
Day 16 - Privacy
下一篇
Day 20 - Preservation
系列文
智能合約漏洞演練 - Ethernaut18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言