ERC20
是以太坊上的代幣標準,來自 2015 年 11 月的 EIP20。它實現了代幣轉帳的基本邏輯:
balanceOf()
transfer()
transferFrom()
approve()
totalSupply()
allowance()
name()
、代號 symbol()
、小數位數 decimals()
IERC20 是 ERC20 代幣標準的介面合約,規定了 ERC20 代幣需要實現的函數和事件。有了介面的規範後,就存在所有的 ERC20 代幣都通用的函數名稱、輸入參數與輸出參數。在介面函數中,只需要定義函數名稱,輸入參數,輸出參數,並不關心函數內部如何實現。所以函數就分為內部和外部的內容,一個重點是實現,另一個是對外接口,約定共同資料。這就是為什麼需要 ERC20.sol
和 IERC20.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,將 IERC20 規定的函數簡單實作。
我們需要狀態變數來記錄帳戶餘額,授權額度和代幣資訊。其中 balanceOf
、 allowance
和 totalSupply
為 public
類型,會自動產生一個同名的 getter 函數,實作 IERC20 規定的 balanceOf()
、allowance()
和 totalSupply()
。而 name
、symbol
、decimals
則對應代幣的名稱、代號和小數位數。
註:用 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
函數:授權轉帳邏輯。被授權方將授權方 sender
的 amount
數量代幣轉帳給接收方 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 標準後,在 ETH 鏈上發行代幣變得非常簡單,我們可以發行屬於自己的測試幣:
在 Remix 上編譯好 ERC20 合約 → 在部署列輸入建構子的參數,name_
和 symbol_
都設為 "WTF",然後點選 transact 鍵進行部署。這樣,我們就創建好了 WTF 代幣。
運行 mint()
函數來為自己鑄造一些代幣:
點開 Deployed Contract 中的 ERC20 合約,在 mint 函數那一欄輸入 100 並點選 mint 按鈕,為自己鑄造 100 個 WTF 代幣。
點開右側的 Debug 按鈕,查看下面具體的 logs,應包含
當人想要免費代幣的時候,就要去代幣水龍頭(Faucut)領,代幣水龍頭就是讓用戶免費領代幣的網站或應用程式。
最早的代幣水龍頭是比特幣(BTC)水龍頭:現在 BTC 一枚要 $30,000,但是在 2010 年,BTC 的價格不到 $0.1,而且持有人很少。為了擴大影響力,比特幣社群的 Gavin Andresen 開發了 BTC 水龍頭,讓別人可以免費領 BTC。
將一些 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 事件
}