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為代幣的數量。
transferFrom
及approve
兩個函式為一個組合,目的是提供用領取的方式來轉移代幣(相對於使用送出方式的transfer
)。會使用領取模式是因為如果代幣接收方是一個合約,則在發生transfer
時合約不會收到通知,即合約沒辦法知道代幣轉到它身上。
也因為這個原因,讓某些應用場景無法實現。例如一個合約C的某個功能需要收取某代幣才能執行,如果合約沒辦法知道transfer
到底發生了沒,就沒辦法知道代幣是否成功移轉給它,也就沒辦法繼續執行它的功能。如果改用領取模式 — 使用者在使用合約C的功能前先批准一定量的代幣給C,合約C執行的時候用transferFrom
領取代幣 ,領取成功就繼續執行它的功能,否則退出執行 — 這讓合約接受代幣做為報酬的方式得以實現。
ERC223
ERC223的目的為修正ERC20遇到的一些問題。移除transferFrom
及approve
讓移轉代幣能統一為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
設為代幣接收者。
提議仍在討論當中,例如將名稱改為Mint
和Burn
。也有人覺得這些函式不需設立為標準,由開發者自己決定是否應用。
ISSUE #662 — 新增一個用線下簽章代替發起交易的驗證標準
此提議原先的目的是讓使用者能用線下簽好的簽章,交由其他人轉送到該函式,該函式驗證完後再以使用者的身份執行代幣移轉。有了此功能就不需要每個代幣轉出者親自送出交易。
後來演變成一個可通用的函式,讓不只是代幣,而是在其他場景也能使用的功能。
- 函式名稱為
provable_X
,X為原本的函式名稱,例如provable_transfer
。如此你會有transfer
和provable_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
都可以依開發者自己的用途去新增,如下面的代幣範例各自都有各自的特殊功能。
應用範例:
- OpenZeppelin:https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts/token
- Consensys:https://github.com/ConsenSys/Tokens
- dapphub:https://github.com/dapphub/ds-token
- MiniMe:https://github.com/Giveth/minime/
加入一些功能例如記下代幣的持有者在其持有代幣數量改變發生的區塊,讓使用者可以非常方便的拷貝一個現有的代幣(其完整的持有分佈)而不影響已存在的代幣。
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