NIC Lin
Taipei Ethereum Meetup
14 min readSep 27, 2017

--

ERC20 Token使用手冊

由以太坊代幣(Token)的標準 — ERC20 Token來介紹代幣的撰寫及使用方式,和其周邊的附加功能。

ERC20

ERC20 Token的EIP約在兩年前就已經提出,經過歷時長久的社群討論,在九月的時候確定了最終版本

以下是一個標準的ERC20合約所需要具備的函式及行為:

  • 代幣的全名
    function name() constant returns (string name)
    回傳代幣的全名,例如 OutrichTrumpICOToken。注意,此函式目的為增加可讀性,非必要,使用者或合約請勿指望此函式一定會有回傳值。
  • 代幣的縮寫
    function symbol() constant returns (string symbol)
    回傳代幣的縮寫,例如 OTICO,縮寫並無規定一定要為三個字元。同樣請勿指望此函式一定會有回傳值。
  • 代幣的最小單位
    function decimals() constant returns (uint8 decimals)
    回傳使用者看到的最小單位,為一個數值,表示此代幣最多可細分到小數點後幾位數。例如假設數值為3,表示最後使用者看到的擁有代幣數量會是像這樣123.456。看代幣的用途,對應到現實世界中或現有的資產各會有不同的最小單位。沒特別需求可對應到以太幣的單位,設為18。同樣請勿指望此函式一定會有回傳值。
  • 代幣的總量
    function totalSupply() constant returns (uint256 totalSupply)
    回傳代幣的發行總量。
  • 查詢某帳戶的代幣餘額
    function balanceOf(address _owner) constant returns (uint256 balance)
    參數為欲查詢的帳戶地址,回傳值為一正整數。注意,代幣餘額皆會是正整數,要搭配 decimals函式來顯示單位給使用者。
  • 移轉代幣給他人
    function transfer(address _to, uint256 _value) returns (bool success)
    參數1為接收代幣的地址,參數2為數量,回傳值為布林值 — 1表示成功,0表示失敗。
    1. 若成功移轉代幣,則一定要觸發Transfer event(見下一點)。如果送出代幣者的餘額不足,則應該要觸發throw,復原一切更動。
    2. 若是因為產生新代幣而呼叫此函式,則應該要將Transfer事件的_from設為0x0,表示為新產生的代幣。
    3. 若移轉代幣數量為零,仍視為正常的代幣移轉並照常觸發Transfer事件。
  • 移轉代幣觸發事件
    event Transfer(address indexed _from, address indexed _to, uint256 _value)
    參數1為送出代幣的地址,參數2為接受代幣的地址,參數3為代幣的數量。
  • 從A移轉代幣給B
    function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
    參數1為送出代幣的地址,參數2為接受代幣的地址,參數3為數量。回傳值為布林值 — 1表示成功,0表示失敗。
    1. 如果沒有送出代幣者的授權(見下一點)或餘額不足,則應該要觸發throw,復原一切更動。若成功移轉代幣,則一定要觸發Transfer event
    2. 若移轉代幣數量為零,仍視為正常的代幣移轉並照常觸發Transfer事件。
  • 批准自己的代幣移轉
    function approve(address _spender, uint256 _value) returns (bool success)
    參數1為可以領走自己代幣的對象的地址,參數2為數量,回傳值為布林值 — 1表示成功,0表示失敗。透過呼叫這個函式來批准某對象可以藉由transferFrom函式領走自己最多_value數量的代幣。
    1. _value是自訂的,可以設為超過自己的餘額的值。
    2. 批准並不代表將代幣鎖住直到對方領走為止,使用者仍可在對方領走之前先領走代幣,這樣對方在呼叫函式時會因為餘額不足而失敗。若成功批准,則一定要觸發Approval事件。
    3. 為了避免Front Running而導致Double Spend的攻擊,使用者在更改一個批准的數量時,應該要先送出一個數量為零的批准再進行正常的批准。
  • A批准給B的代幣數量(必要
    function allowance(address _owner, address _spender) constant returns (uint256 remaining)
    參數1為代幣擁有者的地址,參數2為可以領走代幣的地址,回傳值為一正整數。
  • 代幣批准觸發事件(必要
    event Approval(address indexed _owner, address indexed _spender, uint256 _value)
    參數1為代幣擁有者的地址,參數2為可以領走代幣的地址,參數3為代幣的數量。

transferFromapprove兩個函式為一個組合,目的是提供用領取的方式來轉移代幣(相對於使用送出方式的transfer)。會使用領取模式是因為如果代幣接收方是一個合約,則在發生transfer時合約不會收到通知,即合約沒辦法知道代幣轉到它身上。

也因為這個原因,讓某些應用場景無法實現。例如一個合約C的某個功能需要收取某代幣才能執行,如果合約沒辦法知道transfer到底發生了沒,就沒辦法知道代幣是否成功移轉給它,也就沒辦法繼續執行它的功能。如果改用領取模式 — 使用者在使用合約C的功能前先批准一定量的代幣給C,合約C執行的時候用transferFrom領取代幣 ,領取成功就繼續執行它的功能,否則退出執行 — 這讓合約接受代幣做為報酬的方式得以實現。

ERC223

ERC223的目的為修正ERC20遇到的一些問題。移除transferFromapprove 讓移轉代幣能統一為transfer的方式,並同時解決接收方為合約時無法知道transfer完成與否的問題。

ERC223大致和ERC20一樣,例如:

  • name()
  • symbol()
  • decimals()
  • balanceOf()
  • totalSupply()

但transfer有兩組:

  • function transfer(address _to, uint256 _value) returns (bool success)
    為了兼容性而留下,介面和ERC20的transfer一樣,。但ERC223要求如果代幣接收者是一個合約的話,在transfer一定要呼叫該合約的tokenFallback函式(見下方),如果該合約沒有tokenFallback函式則視為移轉失敗,還原一切更動。
  • function transfer(address _to, uint256 _value, bytes _data) returns (bool success)
    ERC223自己的transfer,介面多了_data欄位,可以是代幣轉出者的訊息,和比特幣的OP_RETURN類似;或是一個function signature,讓代幣接收合約利用這個signature去呼叫其他函式;亦可以不寫任何東西。如果代幣接收者是一個合約的話,在transfer一定要呼叫該合約的tokenFallback函式,如果該合約沒有tokenFallback函式則視為移轉失敗,還原一切更動。
  • event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes _data)
    Transfer事件介面多了_data欄位。

另外ERC223要求接收代幣者如果是合約,則接受代幣的合約裡一定要有以下函式:

  • function tokenFallback(address _from, uint256 _value, bytes _data)
    代幣接收合約自己在此函式裡面應用處理接收代幣的邏輯。
    1. 此函式的signature為0xc0ee0b8a
    2. 注意,此函式裡的_from參數才是代幣轉出者,不是msg.sender
    3. 如果tokenFallback函式內執行錯誤導致例外的話會連帶transfer一起失敗。
    4. 如果合約沒有tokenFallback函式也沒有一般的fallback函式(沒有名稱的函式)的話,會丟出例外並連帶transfer一起失敗。
    5. 但如果合約沒有tokenFallback函式卻有一般的fallback函式的話,會執行一般的fallback函式(視為執行tokenFallback)。

所以在transfer函式裡必須先檢查對方是否為合約,是的話一定要呼叫代幣接收合約的tokenFallback函式:

Contract tokenReceiverContract {
tokenFallback(address _from, uint256 _value, bytes _data)
}
function transfer(address _to, uint256 _value, bytes _data) returns (bool success) {
...
//檢查_to是否為合約,如果是的話就呼叫tokenFallback函式
uint code_size;
assembly { size := extcodesize(_to) }
if(code_size > 0) {
tokenReceiverContract trc = tokenReceiverContract(_to);
//注意,請不要用.call()等方式來呼叫tokenFallback,否則會導致tokenFallback丟出例外但transfer只會收到一個零當回傳值的情況
trc.tokenFallback(msg.sender, _value, _data)
}
...
//觸發事件
Transfer(msg.sender, _to, _value, _data)
...
}

但目前ERC223還未敲定最終版本,有部分內容還在討論當中。例如:

  • 是否移除decimal
  • symbol回傳值型態改為固定長度的bytes32
  • 是否要為代幣發行/代幣減量而另外新增事件
  • tokenFallback改為其他名稱

此外還有其他提議為ERC20的功能做延伸

ISSUE #677 — 不同transfer名稱的ERC223

在ERC20裡新增一個transferAndCall函式,其行為和ERC223的transfer一樣,唯獨名稱不一樣。目的是希望在大家更廣泛接受ERC223之前,建立一個過渡期用的標準,讓大家能了解並使用ERC223的transfer功能,同時又不致於被名稱所混淆。

  • function transferAndCall(address _to, uint256 _value, bytes _data) returns (bool success)

ISSUE #621 — 新增代幣發行/代幣減量的函式

  • function increaseSupply(uint _value, address _to) returns (bool)
    代幣發行函式, 一樣觸發Transfer事件並將0x0設為代幣轉出者。
  • function decreaseSupply(uint _value, address _from) returns (bool)
    代幣減量函式, 一樣觸發Transfer事件並將0x0設為代幣接收者。

提議仍在討論當中,例如將名稱改為MintBurn。也有人覺得這些函式不需設立為標準,由開發者自己決定是否應用。

ISSUE #662 — 新增一個用線下簽章代替發起交易的驗證標準

此提議原先的目的是讓使用者能用線下簽好的簽章,交由其他人轉送到該函式,該函式驗證完後再以使用者的身份執行代幣移轉。有了此功能就不需要每個代幣轉出者親自送出交易。

後來演變成一個可通用的函式,讓不只是代幣,而是在其他場景也能使用的功能。

  • 函式名稱為provable_X,X為原本的函式名稱,例如provable_transfer。如此你會有transferprovable_transfer,一個用來讓使用者自己呼叫,一個用來讓使用者以線下簽章的方式由其他人呼叫。
  • 使用者要簽的東西為sha3(sha3(params), word, contract_address)
    params為該函式所需的參數的值接起來,word是該函式的function signature,contract_address為此合約的地址。該函式指的是原本的函式而不是provable函式。例如使用者透過provable_transfer來執行transfer,實際要執行的是transfer,所以必須是對transfer簽名。

首先在provable函式裡要對簽章做驗證,驗證過了才去執行實際的函式:

function provable_transfer(bytes32[3] sig, address to, uint value) returns (bool) {
// word: First 4 bytes of keccak-256 hash of "transfer(bytes32[3],address,uint256)"
bytes4 word = 0x5a43675c;
bytes32 msg = sha3(sha3(address(to), uint(value)), bytes4(word), address(this));
address signer = ecrecover(msg, uint8(sig[2]), sig[0], sig[1]);
...
}

其他功能例如加入一個tokenVersion 來識別這是哪一版本的代幣標準。

如果要讓產生的代幣大眾都能用,就必須要符合現有的代幣標準(但並非要符合所有標準,例如ERC20和ERC223即是不相容的兩種標準),但不表示只能應用標準裡面寫的,其他像是Mint或是Burn都可以依開發者自己的用途去新增,如下面的代幣範例各自都有各自的特殊功能。

應用範例:

Reference:
[1]https://github.com/ethereum/EIPs/issues/20
[2]https://github.com/ethereum/EIPs/pull/610
[3]https://medium.com/@jgm.orinoco/understanding-erc-20-token-contracts-a809a7310aa5
[4]https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
[5]https://github.com/ethereum/EIPs/issues/223
[6]https://github.com/ethereum/EIPs/pull/621
[7]https://github.com/ethereum/EIPs/issues/677
[8]https://github.com/ethereum/EIPs/issues/662

--

--