iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
自我挑戰組

Solidity 初學之路系列 第 21

DAY 21 - ERC20、代幣水龍頭

  • 分享至 

  • xImage
  •  

ERC20

ERC20 是以太坊上的代幣標準,來自 2015 年 11 月的 EIP20。它實現了代幣轉帳的基本邏輯:

  • 帳戶餘額 balanceOf()
  • 轉帳 transfer()
  • 授權轉帳 transferFrom()
  • 授權 approve()
  • 代幣總供給 totalSupply()
  • 授權轉帳額度 allowance()
  • 代幣資訊(可選):名稱 name()、代號 symbol()、小數位數 decimals()

IERC20

IERC20 是 ERC20 代幣標準的介面合約,規定了 ERC20 代幣需要實現的函數和事件。有了介面的規範後,就存在所有的 ERC20 代幣都通用的函數名稱、輸入參數與輸出參數。在介面函數中,只需要定義函數名稱,輸入參數,輸出參數,並不關心函數內部如何實現。所以函數就分為內部和外部的內容,一個重點是實現,另一個是對外接口,約定共同資料。這就是為什麼需要 ERC20.solIERC20.sol 兩個檔案實現一個合約。

事件

IERC20 定義了2個事件:Transfer 事件和 Approval 事件,分別在轉帳和授權時被釋放:

/**
 * @dev 釋放條件:當 value 單位的貨幣從帳戶 (from) 轉移到另一個帳戶 (to) 時
 */
event Transfer(address indexed from, address indexed to, uint256 value);

/**
 * @dev 釋放條件:當 value 單位的貨幣從帳戶 (owner) 授權給另一個帳戶 (spender)時
 */
event Approval(address indexed owner, address indexed spender, uint256 value);

函數

IERC20 定義了 6 個函數,提供了轉移代幣的基本功能,並允許代幣獲得批准,以便其他鏈上第三方使用。

  • totalSupply() 回傳代幣總供給。
    function totalSupply() external view returns (uint256);
    
  • balanceOf() 回傳帳戶餘額。
    function balanceOf(address account) external view returns (uint256);
    
  • transfer() 轉賬:從呼叫者帳戶轉帳 amount 單位代幣,如果成功,回傳 true,然後釋放 {Transfer} 事件。
    function transfer(address to, uint256 amount) external returns (bool);
    
  • allowance() 回傳授權額度:回傳 owner 帳戶授權給 spender 帳戶的額度,預設為 0,當 approve()transferFrom() 被呼叫時,allowance會改變。
    function allowance(address owner, address spender) external view returns (uint256);
    
  • approve() 授權:呼叫者帳戶給spender帳戶授權 amount數量代幣,如果成功,回傳 true,然後釋放 {Approval} 事件。
    function approve(address spender, uint256 amount) external returns (bool);
    
  • transferFrom() 授權轉帳:透過授權機制,從 from 帳戶轉帳 amount 數量代幣到 to 帳戶。轉帳的部分會從呼叫者的 allowance 中扣除。如果成功,回傳 true,然後釋放 {Transfer} 事件。
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
    

實現 ERC20

寫一個ERC20,將 IERC20 規定的函數簡單實作。

狀態變數

我們需要狀態變數來記錄帳戶餘額,授權額度和代幣資訊。其中 balanceOfallowancetotalSupplypublic 類型,會自動產生一個同名的 getter 函數,實作 IERC20 規定的 balanceOf()allowance()totalSupply()。而 namesymboldecimals 則對應代幣的名稱、代號和小數位數。
註:用 override 修飾 public 變數,會重寫繼承自父合約的與變數同名的 getter 函數,例如 IERC20 中的 balanceOf() 函數。

mapping(address => uint256) public override balanceOf;

mapping(address => mapping(address => uint256)) public override allowance;

uint256 public override totalSupply;   // 代幣總供給

string public name;   // 名稱
string public symbol;  // 代號

uint8 public decimals = 18; // 小數位數

函數

  • 建構子:初始化代幣名稱、代號。
    constructor(string memory name_, string memory symbol_){
        name = name_;
        symbol = symbol_;
    }
    
  • transfer() 函數:實作 IERC20 中的 transfer 函數:代幣轉帳邏輯。呼叫者扣除amount 數量代幣,接收方增加對應代幣。
    function transfer(address recipient, uint amount) public override returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
    
  • approve() 函數:實作 IERC20 中的 approve 函數:代幣授權邏輯。被授權方 spender 可以支配授權方 amount 數量的代幣。spender 可以是 EOA(Externally Owned Account) 帳戶,也可以是合約帳戶:當你用 uniswap 交易代幣時,你需要將代幣授權給 uniswap 合約。
    function approve(address spender, uint amount) public override returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }
    
  • transferFrom() 函數:實作 IERC20 中的 transferFrom 函數:授權轉帳邏輯。被授權方將授權方 senderamount 數量代幣轉帳給接收方 recipient
    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) public override returns (bool) {
        allowance[sender][msg.sender] -= amount;
        balanceOf[sender] -= amount;
        balanceOf[recipient] += amount;
        emit Transfer(sender, recipient, amount);
        return true;
    }
    
  • mint() 函數:鑄造代幣函數,不在 IERC20 標準中。任何人可以鑄造任意數量的代幣,實際應用中會加權限管理,只有 owner 可以鑄造代幣:
    function mint(uint amount) external {
        balanceOf[msg.sender] += amount;
        totalSupply += amount;
        emit Transfer(address(0), msg.sender, amount);
    }
    
  • burn() 函數:銷毀代幣函數,不在 IERC20 標準中。
    function burn(uint amount) external {
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Transfer(msg.sender, address(0), amount);
    }
    

發行ERC20代幣

有了 ERC20 標準後,在 ETH 鏈上發行代幣變得非常簡單,我們可以發行屬於自己的測試幣:

  1. 在 Remix 上編譯好 ERC20 合約 → 在部署列輸入建構子的參數,name_
    symbol_ 都設為 "WTF",然後點選 transact 鍵進行部署。這樣,我們就創建好了 WTF 代幣。

  2. 運行 mint() 函數來為自己鑄造一些代幣:
    點開 Deployed Contract 中的 ERC20 合約,在 mint 函數那一欄輸入 100 並點選 mint 按鈕,為自己鑄造 100 個 WTF 代幣。

  3. 點開右側的 Debug 按鈕,查看下面具體的 logs,應包含

  • 事件:Transfer
  • 鑄幣地址:0x0000000000000000000000000000000000000000
  • 接收地址:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
  • 代幣金額:100
  1. 利用 balanceOf() 函數來查詢帳戶餘額。輸入我們目前的帳戶,可以看到餘額變成100,鑄造成功。

代幣水龍頭

當人想要免費代幣的時候,就要去代幣水龍頭(Faucut)領,代幣水龍頭就是讓用戶免費領代幣的網站或應用程式。
最早的代幣水龍頭是比特幣(BTC)水龍頭:現在 BTC 一枚要 $30,000,但是在 2010 年,BTC 的價格不到 $0.1,而且持有人很少。為了擴大影響力,比特幣社群的 Gavin Andresen 開發了 BTC 水龍頭,讓別人可以免費領 BTC。

ERC20 水龍頭合約實作

將一些 ERC20 代幣轉到水龍頭合約裡,使用者可以透過合約的 requestToken() 函數來領取 100 單位的代幣,每個地址只能領一次。

狀態變數

  • amountAllowed 設定每次能領取代幣數量(預設為 100,不是一百枚,因為代幣有小數位數)。
  • tokenContract 記錄發放的 ERC20 代幣合約地址。
  • requestedAddress 記錄曾經領過代幣的地址。
uint256 public amountAllowed = 100; // 每次能領取代幣數量
address public tokenContract;   // 記錄發放的 ERC20 代幣合約地址
mapping(address => bool) public requestedAddress;   // 記錄曾經領過代幣的地址

事件

水龍頭合約中定義了 1 個 SendToken 事件,記錄了每次領取代幣的地址和數量,在requestTokens() 函數被呼叫時釋放。

event SendToken(address indexed Receiver, uint256 indexed Amount); 

函數

  • 建構子:初始化 tokenContract 狀態變量,確定發放的 ERC20 代幣地址。
    // 部署時設定 ERC20 代幣合約
    constructor(address _tokenContract) {
        tokenContract = _tokenContract; // set token contract
    }
    
  • requestTokens() 函數,使用者呼叫它可以領取 ERC20 代幣。
function requestTokens() external {
    require(!requestedAddress[msg.sender], "Can't Request Multiple Times!"); // 每個地址只能領一次
    IERC20 token = IERC20(tokenContract); // 創建 IERC20 合約物件
    require(token.balanceOf(address(this)) >= amountAllowed, "Faucet Empty!"); // 水龍頭空了

    token.transfer(msg.sender, amountAllowed); // 發送代幣
    requestedAddress[msg.sender] = true; // 記錄領取地址
    
    emit SendToken(msg.sender, amountAllowed); // 釋放 SendToken 事件
}

上一篇
DAY 20 - Hash、函數選擇器、Try Catch
下一篇
DAY 22 - 空投合約
系列文
Solidity 初學之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言