ERC777 代幣及 Address Metadata Registry

NIC Lin
Taipei Ethereum Meetup
10 min readApr 22, 2018

此篇延續上一篇合約介面查詢標準,繼續介紹:
(1) 支援 EIP820 的 ERC777 代幣標準
(2) 類似於 EIP820,一個統一的帳戶 Metadata 查詢合約(Address Metadata Registry

source: http://www.publicdomainfiles.com/show_file.php?id=14027034625449

ERC777

ERC777 最大的特色包含:
(1) 讓代幣發送方和接收方都能夠自訂代幣處理方式,在發送或接收代幣時自動觸發執行,而且使用者不限於要是合約一般的使用者帳戶也能夠使用
(2) 新增 Operator 的角色,讓第三方(例如交易所)能更方便的管理你的代幣。

ERC20的缺點

目前最常見的 ERC20 代幣有兩個主要的缺點:
(1) 如果代幣接收方是一個合約,但不是一個 Proxy 合約,同時也不支援操作代幣的函式(transfer等),則代幣轉給它之後將永遠無法再轉出,等同銷毀。
(2) 代幣要讓第三方來使用、管理,需要經過兩個步驟:approvetransferFrom。使用上不方便,多一次的交易也增加成本。

ERC223的起落

因為上面提到的第一個缺點所造成永久損失的代幣估計高達數百萬美金,曾經氣勢如虹的ERC223代幣的出發點便是要解決這個問題。ERC223規定如果代幣接收方是合約,則它必須支援tokenFallback函式,讓接收方能夠在接收到代幣後做相對應的處理。最重要的是如果接收方不支援tokenFallback函式,則必須終止代幣交易,以此避免將代幣轉給一個不支援代幣操作的合約。

但ERC223遇到的主要問題之一是代幣接收方的Fallback函式(這裏是指合約本身的Fallback函式,不是另外自訂的Fallback函式如tokenFallback等)有可能會導致預期外的結果 — 在代幣交易的過程中,如果接收方不支援(即沒有實作)tokenFallback函式,程式會轉而觸發Fallback函式,這時如果接收方也沒有實作Fallback函式,則呼叫tokenFallback得到的回傳值會是false;但如果接收方的合約裡有實作Fallback函式且能順利執行完的話,則呼叫tokenFallback得到的回傳值會是true,讓發送方誤以為成功執行tokenFallback,造成false positive的結果。

ERC223也因此漸漸消聲匿跡。

ERC777基本介紹和對於ERC20的相容支援

ERC777的函式基本上和ERC20一樣,除了以下幾個地方需要注意。

(1) ERC777規定如果實作decimal函式,則都必須回傳10的18次方。另外ERC777多了一個granularity函式,用途是記載該代幣最小的交易單位(最小值為1)。假設granularity回傳的值是1000,代表所有該代幣的交易數量都要是1000的倍數。

(2) 因為ERC20會有前面提到的送錯造成代幣無法再使用的問題,因此ERC777的發起人希望將代幣發送函式名稱由transfer改成send,藉此來和ERC20做區別(同時也因為send是用來發送ether的語法,改成send可以讓使用者不需分辨是要發送ether還是代幣)。但目前還在討論中,尚未決定是使用transfer或是send(以下還是暫時使用send來代表)。send函式:

function send(address to, uint256 amount) public
function send(address to, uint256 amount, bytes userData) public

第二種的send多了一個參數userData,讓代幣發送方指定任意的訊息。可以用來記錄在log中,或傳進代幣收發雙方註冊的處理函式中來使用。

如果你希望你的ERC777代幣同時也能支援ERC20語法的操作的話,直接新增ERC777沒有但ERC20有的函式即可,兩者是可以兼容的。

ERC777的第一個優點 — 發送、接收代幣時自動執行處理函式

send
第一個步驟是檢查代幣發送方是否有透過EIP820的registry註冊發送代幣的處理函式tokensToSend,如果有則呼叫該函式。
第二個步驟是檢查代幣接收方是否有註冊接收代幣的處理函式tokensReceived,有則呼叫該函式;如果接收方是合約但沒有註冊的話就必須要throw,終止代幣交易
第三個步驟是觸發sent事件,將相關資料寫進log裡。

ERC777透過先查詢registry再呼叫(而不是直接呼叫tokensReceived)的方式,避免如ERC223因為Fallback而誤以為成功執行tokensReceived導致代幣被鎖住的情況。

ERC777的第二個優點 — 可以代為操控代幣的operator角色

如一開始所介紹,如果你需要讓第三方可以操控你的ERC20代幣的話,你必須要先approve對方並指定對方可以操控的代幣數量(allowance),之後再由對方transferFrom領走代幣直到都領完配額為止。這個方式除了不方便、需要兩次交易、增加交易成本之外,還有潛在的風險。因此ERC777改為透過新增operator角色的方式,讓第三方操控你的ERC777代幣。

首先,代幣的擁有者本身一定是operator,擁有者可以一次設定很多人為其operatoroperator可以直接操控擁有者的代幣且沒有數量限制。新增和移除operator的函式為authorizeOperatorrevokeOperatoroperator要透過operatorSend來發送代幣。operatorSendsend基本上一樣,除了會先檢查operator身份外,還有多一個operatorData參數,效用和userData一樣。operatorSend函式:

// from是代幣擁有人;msg.sender是operator
function operatorSend(address from, address to, uint256 amount, bytes userData, bytes operatorData) public

其他需要留意的地方:

(1) 如果要提供mint/burn功能,則需要是獨立的函式,而不是使用send的方式(例如send(from=0xffff…ffff)send(to=0xffff…ffff)的方式)。
(2) 如果你的ERC777要兼容ERC20,在送出代幣後必須要觸發senttransfer兩個event。避免只有支援ERC20的錢包或軟體忽略sent觸發的log而造成狀態不同步。

EIP165 or EIP820?

目前卡住ERC777進度的其中一個原因是社群還未對綁定EIP820的這個要求達成共識,因為EIP820還未被接受。部分的人認為可以改用已經被接受的EIP165來支援tokensToSendtokensReceived,不需要等EIP820。(確認後會再更新)
EIP165和EIP820的介紹可以看前篇

統一的帳戶Metadata查詢合約(Address Metadata Registry)

透過一個registry讓on-chain的合約或off-chain的應用程式、使用者能查詢關於某個帳戶的相關訊息可以有很多用途和幫助 — 例如EIP820,提供註冊代幣收發的處理函式。但要是每個應用都建立自己的registry來提供訊息(像是ERC777仰賴於EIP820的registry),會造成鏈的資料增加、節點的負擔增加。如果應用的資料彼此沒有重疊覆寫或相容的問題,那何不建立一個統一的registry?

在統一的registry中只有簡單的兩個函式:

interface AddressMetadataRegistry {
function provider(address target) view returns(address);
function setProvider(address _provider);
}

帳戶透過呼叫setProvider為自己指定metadata的provider合約;其他人則透過provider查詢某個帳戶的provider合約地址。

而提供資訊的provider合約必須要支援EIP165的supportsInterface函式。

所以要查詢某個帳戶的相關資訊,其流程是
(1)透過AddressMetadataRegistry查詢該帳戶註冊的provider合約地址。
(2)透過呼叫該provider合約的supportInterface函式來確認目標帳戶是否支援特定介面。
(3)使用目標帳戶支援的介面和目標帳戶互動。

通用的授權模式(Generalized Authorization

通用授權是Address Metadata Registry的一個範例應用 — 帳戶透過metadata registry註冊,授權其他帳戶代為執行某個合約上的某個函式。

provider提供三個函式來指定、取消和查詢授權:

//指定
function authoriseCaller(address owner, address caller, address callee, bytes4 func);
//取消
function revokeCaller(address owner, address caller, address callee, bytes4 func);
//授權
function canCall(address owner, address caller, address callee, bytes4 func) view returns(bool);

其他人可以透過canCall來查詢caller是否有權力代替owner去呼叫callee合約裡的func函式。

所以要查詢A帳戶是否有權代替B帳戶去呼叫C合約的D函式,故事經過大概會是:
(1) A帳戶呼叫C合約的D函式,並透過參數說明A帳戶是要以B帳戶的身份執行
(2) C合約透過AddressMetadataRegistry查詢B帳戶註冊的provider合約地址。
(3) 透過呼叫該provider合約的canCall(owner=B, caller=A, callee=C, func=D)來確認A帳戶是否得到授權。
(4) 如果回傳結果為true則繼續執行;否則終止交易。

Reference:

--

--