ERC1400提案中文版,關於ERC的新成員,你想要知道的都在這裡了
9月11日,開發者Stephane Gosselin在GitHub提交了一份新提議,公佈了一項新的證券型Token -- “ERC1400”。新Token提案主打監管功能,目的是方便使用者以合法合規方式在以太坊網路發行證券。
目前,ERC1400還處於起草階段,未來是否會獲得社群通過並投入使用還尚未可知。
臨界 (Hashgard) 團隊對ERC1400的提案進行了翻譯,為所有的開發和技術人員,提供更多支援和助力!
- 01 -
關於ERC1400
"ERC1400"是新提案的證券型代幣的標準,新標準主要是把 Token 的互換性(fungible)結合證券相關的業務場景,設計了一套通用介面。
標準制定了 Token 持有人的餘額分離成多個分片(tranche)的能力。tranche 是一種以債務為基礎的投資結構。這些證券將具有不同期限的,投資風險或高或低的 tranche 組合成一個整體,以達到降低投資者的風險,提供長期投資的目的。例如,你可能有一筆貸款轉付證券,其中包括5到30年期的高風險和低風險的轉付證券,基於 ERC1400 標準的 Token 將支援這種投資方式。
我們先了解 ERC777 標準,ERC777 是 ERC20 的加強版,旨在加強使用者的控制權限,具體有:
1. 隨交易傳送可以附帶描述資料,以供某些業務場景使用
2. 設定一些轉賬限制,如黑名單
3. 支援一些高階交易
無論是 ERC20 還是 ERC777,每個單位的 Token 都是相同的,並無附加屬性,屬於 fungible token(同質化代幣/可互換 Token)。ERC721標準的 Token,每個 Token 均有不同的ID,不同ID可以有不同的解釋,屬於 no-fungible token(非同質化 Token,不可互換 Token)
ERC1410標準的 Token 屬於Partially-Fungible Token (部分可互換 Token ),將 ERC20/ERC777 中不存在解釋屬性的餘額,附加額外的資訊,從而劃分成不同的部分,就可以做一些操作上的限制(例如:某些操作只限定於指定 tranche 的 Token,某些操作優先消耗指定tranche的 Token)
ERC1400 則是繼承 ERC1410 標準,增加了證券相關業務會使用到的函式:證券增發,相關法律檔案儲存等。
先前一些證券型 Token 的合約大多是在 ERC20 標準的基礎上,通過維護一個或多個以太坊地址集合,對這部分地址做出了劃分:是否通過kyc,是否處於鎖定期等,來進行轉賬上的限制。這些都是在地址層面做出不同的解釋。而 ERC1400 對 Token 本身做了不同解釋,以適用於更復雜的證券相關業務場景。
關於ERC20,以及其它ERC成員的關係,可見下圖:
- 02 -
ERC1400 提案翻譯
Security Token Standard
- eip: ERC1400
- 標題: Standard for Security Tokens
- 作者: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep)
- 譯者: Jason(來自臨界Hashgard團隊)
- 狀態: Draft
- 型別: Standards Track
- 範疇: ERC
- 創建於: 2018-09-09
- 需要: ERC-777, ERC-1066
Motivation
通過指定一套標準的介面,加速發行和管理以太坊上的證券,通過該介面所有相關方可以查詢和操作證券型代幣。
證券型代幣與其他代幣用例存在重大差異,鏈上與鏈下參與者的互動更加複雜,有著相當嚴格的監管審查。
證券型代幣應該能夠對標任何資產類別,在任何司法管轄區內髮型和管理,並遵守相關的監管限制。
Requirements
將證券的發行,交易和生命週期事件遷移到公共賬本上需要採用標準的方式對證券,其所有權及其在鏈上的屬性進行建模。
經證券型代幣生態中各方討論後,編寫了如下的要求:
- 必須有一個標準的介面來查詢一筆轉賬交易是否成功,失敗的話則返回原因。
- 必須能夠強制轉賬以應對法律訴訟或資金回收。
- 必須釋出用於發行和贖回的標準事件。
- 必須能將一些資料附加到代幣持有者餘額的子集上,例如特殊股東權利或是轉賬限制的資料。
- 必須能根據離線資料,鏈上資料和轉賬的引數在轉賬時修改元資料。
- 可能需要將簽名資料傳遞到轉賬交易中,以便於在鏈上驗證。
- 不應該限制可以代表司法管轄區內的資產類別範圍。
- 應該相容ERC20和ERC777標準。
Structure
ERC-1400: Security Token Standard ERC-w: Semi-Fungible Token ERC-w-1: Tranche metadata schema ERC-x: Permissioned Token Transfers (canSend() and status codes) ERC-y: Token Metadata (set/getDocument) ERC-z: Optional Security Token features Forced transfers Permanent end to Issuance Trading Halts Batched transfers
Semi-Fungible Token
- #1410 Partially-Fungible Token (部分可互換代幣)
Permissioned Token Transfers
- #1400 Security Token Standard (證券型代幣標準)
- #1404 Simple Restricted Token Standard (簡單的受限代幣標準)
Abstract
有許多型別的證券雖然代表相同的標的資產,但需要有與之相關的差異化資料。
這些額外的元資料隱含地使這些證券不可置換,但實際上這些資料通常應用於證券的一個子集而不是單個證券。將令牌持有者的餘額劃分為各個部分的能力,每個部分具有單獨的元資料,在 Partially-Fungible Token 部分中進行了說明。
例如,代幣持有人的餘額可以分為兩部分:在首次發行期間發行的代幣和通過二級市場交易得到的代幣。
證券代幣合同可以引用該元資料去應用額外的邏輯來決定轉賬是否有效,並確定一旦轉賬到接收方賬戶中,這些代幣所關聯的原資料。
這些條件可能與正在轉賬的證券代幣關聯的元資料有關(即它們是否受制於鎖定期),證券傳送方和接收方的身份(即他們是否通過了KYC流程,是否是經認可的,或是發行方的附屬公司)或者是與具體轉賬無關的原因,而是設定在代幣的級別上(即代幣合同強制執行投資人數量的上限,或任何單一投資人持有的百分比上限)。
對於ERC20/ERC777代幣而言, balanceOf
和 allowance
方法提供了一個在執行轉賬之前檢查轉賬是否可能成功的方法,轉賬在鏈上或是鏈下都可以執行。
對於標的證券的代幣而言,這個標準引入了一個函式 canSend
,當失敗的原因更加複雜的時候,它提供了一種更通用的方法來實現這一目的。還引入了一個用於整個轉賬行為的函式(即包括同轉賬一起傳送的任意資料和證券的接收方)
為了提供同true或false相比更加豐富的資訊,會返回一個位元組返回碼。這使得我們能夠說明轉賬失敗的原因,或者至少是失敗原因的類別。查詢文件的能力和對轉賬成功的預期會包含在 Security Token 部分中說明。
#1410
- Partially-Fungible Token
一個 Partially-Fungible Token 允許將元資料關聯到代幣持有者餘額當中的一部分上面。這些部分的餘額稱為 tranches (tranche,實際上是一個法語單詞意為“切片”或“部分”。在投資界,用來描述可以被分割成並賣給投資者的小額證券。),並由 bytes32_tranche
鍵來建立索引,該鍵可以同鏈上或鏈下的元資料相關聯。
Sending Tokens
代幣轉賬始終具有關聯的源tranche和目標tranche,以及通常的轉賬數量和傳送方/接收方的地址。
getDefaultTranches
為了提供對ERC777的相容性,實現需要決定在執行ERC777傳送函式時要使用哪一個tranche。
這個函式返回在此情況下使用的tranches。例如,一個證券型代幣可以返回 byte32("unrestricted")
tranche, 或者使用一小組可能的tranches的簡單實現,可以返回與代幣持有者相關聯的所有tranches。
返回值可以為空,這意味著沒有預設的tranche(因此ERC777的傳送函式將丟擲錯誤),或者返回多個tranches,在這種情況下,ERC777的傳送函式應該按照順序迴圈遍歷這些tranches,直到指定數量的代幣已經成功轉賬。
function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]);
setDefaultTranches
允許為指定的地址設定預設的tranches,將會在ERC777的傳送函式執行時使用到這些設定。
這個函式可以供所有代幣持有人自行呼叫,或僅限於那些實現了某些必要行為的持有人。
function setDefaultTranche(bytes32[] _tranches) external;
balanceOfByTranche
除了有查詢所有tranches下總的代幣餘額的 balanceOf
,也要有查詢某個特定tranche下代幣餘額的函式。
function balanceOfByTranche(bytes32 _tranche, address _tokenHolder) external view returns (uint256);
sendByTranche
通過拓展ERC777標準並提供一個預設的tranche(通過 getDefaultTranches
函式獲取),就可以傳送代幣了(從預設的tranches)。要從一個特定的tranche來發送代幣的話,可以使用 sendByTranche
函式。
例如,一個有許可權的代幣可以使用tranche元資料來強制執行如下的轉賬限制:
-
_tranche
值 - 與
_tranche
值相關聯的任何額外資料(例如,可能與_tranche
關聯的鎖定時間戳) - 與代幣的傳送方或接收方相關聯的任何細節(例如,他們的身份資訊已經建立)
- 轉賬的代幣數量(比如,它是否遵守任意按天或者基於其他時間週期的轉賬數量限制)
-
_data
引數允許呼叫者提供與轉賬相關的任何附加的授權或詳細資訊(例如,來自一個被允許對轉賬行為進行授權的授權實體的簽名資料)
其它的用例包括通過把先前的持有者與目標tranches關聯起來,跟蹤代幣的出處。
如果轉賬代幣因某種原因失敗,這個函式必須丟擲錯誤。
當從特定的tranche上轉賬代幣時,瞭解這些代幣的鏈上的(即不僅是通過一個響應的事件)目標tranche是有用的。目標tranche將會由這個函式的實施來決定,並依據用例而有所不同。
這個函式在轉賬成功時必須觸發一個 SentByTranche
事件。
function sendByTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (bytes32); function sendByTranches(bytes32[] _tranches, address[] _tos, uint256[] _amounts, bytes _data) external returns (bytes32);
redeemByTranche
允許代幣持有者贖回(銷燬)代幣。
必須從代幣總量和代幣持有人賬戶里扣除已銷燬或已贖回的代幣。銷燬代幣應該像傳送代幣一樣,受到相同條件的限制。每次此函式被呼叫時,必須觸發 RedeemedByTranche
事件。
function redeemByTranche(bytes32 _tranche, uint256 _amount, bytes _data) external;
tranchesOf
代幣持有者可以把他們的餘額拆分成多個部分(tranches) —— 此函式將返回與特定代幣持有者地址關聯的所有tranches。
function tranchesOf(address _tokenHolder) external view returns (bytes32[]);
Operators
操作者可以獲得以下授權:
defaultOperators defaultOperatorsByTranche isOperatorFor isOperatorForTranche
defaultOperatorsByTranche
此函式返回預設的由所有代幣持有人和一個特定tranche授權的操作者集合。
function defaultOperatorsByTranche(bytes32 _tranche) external view returns (address[]);
authorizeOperatorByTranche
允許一個代幣持有人去為他的某個指定tranche的代幣設定一個操作人。
每次呼叫此函式時,必須觸發 AuthorizedOperatorByTranche
事件
function authorizeOperatorByTranche(bytes32 _tranche, address _operator) external;
revokeOperatorByTranche
允許一個代幣持有人撤銷其某個特定tranche的代幣的操作人。
注意 —— 操作人可能會通過 defaultOperatorsByTranche
或 defaultOperators
保留對該代幣持有人和tranche的授權。
每次呼叫此函式時,必須觸發 RevokedOperatorByTranche
事件。
function revokeOperatorByTranche(bytes32 _tranche, address _operator) external;
isOperatorForTranche
返回某個特定地址是否是給定代幣持有人和tranche的操作人。
如果地址是上述任何類別下的操作人,則應該返回TRUE。
function isOperatorForTranche(bytes32 _tranche, address _operator, address _tokenHolder) external view returns (bool);
operatorSendByTranche
允許操作人代表代幣持有人傳送證券型代幣。
此函式應該返回接收方的 bytes32 _tranche
。
如果是在鏈下生成的話,接收方的 bytes32 _tranche
可以在 bytes _data
中定義。
如果此函式被一個缺少由 isOperatorForTranche
定義的合適授權的地址呼叫的情況下,必須回退。
此函式在成功傳送代幣後必須觸發一個 SentByTranche
事件。
function operatorSendByTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (bytes32); function operatorSendByTranches(bytes32[] _tranches, address[] _froms, address[] _tos, uint256[] _amounts, bytes _data, bytes _operatorData) external returns (bytes32[]);
operatorRedeemByTranche
允許一個操作人代表代幣持有人銷燬或贖回代幣。
必須從代幣供應總量和代幣持有人賬戶中扣除被銷燬或贖回的數量。銷燬代幣應該同傳送代幣一樣,收到相同的條件限制。每次呼叫此函式時必須觸發 RedeemedByTranche
事件。
-
function operatorRedeemByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _operatorData) external;
Interface
interface IERCPFT is IERC777 {
function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]); function setDefaultTranche(bytes32[] _tranches) external; function balanceOfByTranche(bytes32 _tranche, address _tokenHolder) external view returns (uint256); function sendByTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (bytes32); function sendByTranches(bytes32[] _tranches, address[] _tos, uint256[] _amounts, bytes _data) external returns (bytes32[]); function operatorSendByTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (bytes32); function operatorSendByTranches(bytes32[] _tranches, address[] _froms, address[] _tos, uint256[] _amounts, bytes _data, bytes _operatorData) external returns (bytes32[]); function tranchesOf(address _tokenHolder) external view returns (bytes32[]); function defaultOperatorsByTranche(bytes32 _tranche) external view returns (address[]); function authorizeOperatorByTranche(bytes32 _tranche, address _operator) external; function revokeOperatorByTranche(bytes32 _tranche, address _operator) external; function isOperatorForTranche(bytes32 _tranche, address _operator, address _tokenHolder) external view returns (bool); function redeemByTranche(bytes32 _tranche, uint256 _amount, bytes _data) external; function operatorRedeemByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _operatorData) external;
event SentByTranche( bytes32 indexed fromTranche, bytes32 toTranche, address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData ); event AuthorizedOperatorByTranche(bytes32 indexed tranche, address indexed operator, address indexed tokenHolder); event RevokedOperatorByTranche(bytes32 indexed tranche, address indexed operator, address indexed tokenHolder); event RedeemedByTranche(bytes32 indexed tranche, address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData); event IssuedByTranche(bytes32 indexed tranche, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData); }
Notes
ERC20 / ERC777 Backwards Compatibility (向後相容ERC20/ERC777標準)
為了保持向後相容ERC20/ERC777(以及其它可互換代幣標準),必須去定義在執行一個 transfer
/ send
操作時(即未指定tranche時),應該使用哪一個或者哪幾個tranche。
如果函式實現保證每個代幣持有人儘可能的tranches的數量,則迭代代幣持有人的所有tranches(通過 tranchesOf
)是合理的。
代幣建立者必須為所有的代幣持有人指定一個或多個預設的供ERC20/ERC777標準下函式使用的tranche。每個代幣持有人或者其所有tranches的代幣餘額的操作者,可以更改代幣持有人的預設tranche。代幣持有人去更改他們的預設tranche的能力允許他們更改顯示在還未相容ERC_PFT的ERC20/ERC777錢包中的tranche。
以下是對ERC777標準函式的實現描述:
-
send()
必須使用getDefaultTranche()
來獲取預設的tranche。 -
opratorSend()
必須使用getDefaultTranche()
獲取預設的tranche。 -
REDEEM()
必須使用getDefaultTranche()
來獲取預設的tranche。 -
opratorRedeem()
必須使用getDefaultTranche()
獲取預設的tranche。 -
balanceOf()
必須計算指定代幣持有人的所有tranche的餘額總量。 -
totalSupply()
必須返回被該合約跟蹤的所有代幣總量。 -
defaultOperators()
必須查詢可以操作所有地址和tranches的操作人列表。 -
authorizeOperator()
必須對msg.sender
的所有tranches授權一個操作人。 -
revokeOperator()
必須撤銷先前對msg.sender
的所有tranches授權的操作人。 -
isOperatorFor()
必須查詢_operator
是否是_tokenHolder
所有tranches的一個操作人。 - 事件
Minted()
和IssuedByTranche()
必須在代幣供應總量有任何增長時觸發。 - 事件
Burned()
和RedeemedByTranche()
必須在代幣供應總量有任何減少時觸發。 - 事件
AuthorizedOperator()
必須由authorizeOperator()
觸發。 - 事件
RevokedOperator()
必須由revokeOperator()
觸發。
#1400
- Security Token
Methods
getDocument / setDocument
這些函式用於管理與代幣關聯的一個文件庫。這些檔案可以是法律檔案或者是其他參考資料。
文件與短名稱(表示為一個 bytes32
)相關聯,並且可以選擇有一個同其鏈上相關聯的文件內容的雜湊值。
function getDocument(bytes32 _name) external view returns (string, bytes32); function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external;
canSend
證券轉讓可能由於多種原因失敗,例如:
- 代幣傳送方或接收方的身份資訊。
- 對轉賬的特定代幣的限制(即與轉讓代幣相關tranche的限制)
- 對代幣總體狀態相關的限制(即投資人總數)
該標準提供了一個鏈上函式,用於決定轉賬是否能成功,並返回指示轉賬失敗原因的詳細原因。
這些規則可以由智慧合約還有鏈上資料來定義,或者依賴可以表示對轉賬進行授權的 sendByTranche
函式(例如,宣告轉賬有效性的轉賬代理的簽名信息)返回的部分 _data
。
此函式將會返一個遵循ERC-1066標準的ESC (Ethereum Status Code,以太坊狀態碼),還有一個可以用於定義程式指定原因碼及額外詳細資訊(例如,執行傳送操作的轉賬限制無效)的 bytes32
引數
此函式也會以與 sendByTranche
類似的方式返回所轉賬代幣的目標tranche
function canSend(address _from, address _to, bytes32 _tranche, uint256 _amount, bytes _data) external view returns (byte, bytes32, bytes32);
issuable
一個證券型代幣發行人可以指定此代幣的發行已結束(即,不能鑄造或發行新的代幣)
如果代幣在 issuable()
返回的是false,今後只能返回false。
function issuable() external view returns (bool);
issueByTranche
此函式在增長代幣供應總量時必須被呼叫。
在呼叫時,此函式必須觸發 IssuedByTranche
事件。
function issueByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _data) external;
Interface
interface IERCST is IERCPFT { function getDocument(bytes32 _name) external view returns (string, bytes32); function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external; function issuable() external view returns (bool); function canSend(address _from, address _to, bytes32 _tranche, uint256 _amount, bytes _data) external view returns (byte, bytes32, bytes32); function issueByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _data) external; }
Notes
Forced Transfers
可能是法規要求發行人或受信任的第三方保留代表投資人轉賬代幣的權利。因此,ERC-ST規範取代ERC-PFT,因為不得允許代幣持有人撤銷預設的操作人。
Restricted Transfers
相比實用型代幣,證券型代幣的轉賬可能因為多種原因而失敗,而實用型代幣通常僅要求傳送方有足夠的餘額。
這些條件可能與被轉賬的證券型代幣的元資料有關(即它們是否受制於鎖定週期),代幣傳送方和接收方的身份及資格(即他們是否已經通過了KYC,是否是可信任的或是發行方的附屬機構),或者出於與特定轉賬無關的原因,而是出於監管下證券層面的原因(即證券強制執行投資者的最大數量和單一投資者持有百分比的最高上限)。
對於實用型代幣(ERC20/ERC777標準的), balanceOf
和 allowance
函式提供了一種方式用以在執行轉賬操作前檢查轉賬是否可能成功,可以在鏈上或鏈下執行。
該標準引入了一個函式 canSend
,它提供了一種更通用的方法來查詢傳送代幣能否成功。它接收一組引數,引數可能包括簽名資料,並返回關於交易成功或失敗的原因位元組碼。
注意——呼叫 canSend
的結果可能會根據鏈上狀態(包括區塊高度或時間戳)和鏈下預言機發生改變。因此,在作為不修改任何狀態的檢視函式被呼叫之後,它不能保證將來的轉賬一定會成功。
Identity
在許多司法管轄區,一方能否接收和傳送證券型代幣依賴於該方身份的特徵。例如,在一方有資格購買或出售特定的證券之前,絕大多數的司法管轄區都要求具有某種程度的 KYC/AML 流程。另外,一方可能被分類到投資人資格類別(比如,合格的投資人和購買者),並且他們的公民身份也可以告知與其證券相關聯的限制。
多種身份標準(比如ERC725,Civic,uPort)可用於捕獲某一方的身份資料,以及集中管理的其他方法(例如,維護一個經KYC批准的地址白名單)。這些身份標準的共同之處是以太坊地址(可以是一方的錢包地址或者身份合同),因此 canSend
函式可以使用證券型代幣的傳送方和接收方的地址作為確定是否符合資格要求身份的代理。
除此之外,該標準並未強制要求任何特定的身份識別方法。
Reason Codes
為了改善代幣持有人的體驗, canSend
必須依據下面指定的ERC-1066標準應用程式特定狀態程式碼返回成功或失敗的原因位元組碼。具體的實現還可以將任意資料以 bytes32
的形式返回,以提供原因碼以外的額外的資訊
Code
Reason
0xA0 |
Transfer Verified - Unrestricted |
0xA1 |
Transfer Verified - On-Chain approval for restricted token |
0xA2 |
Transfer Verified - Off-Chain approval for restricted token |
0xA3 |
Transfer Blocked - Sender lockup period not ended |
0xA4 |
Transfer Blocked - Sender balance insufficient |
0xA5 |
Transfer Blocked - Sender not eligible |
0xA6 |
Transfer Blocked - Receiver not eligible |
0xA7 |
Transfer Blocked - Identity restriction |
0xA8 |
Transfer Blocked - Token restriction |
0xA9 |
Transfer Blocked - Token granularity |
On-chain vs. Off-chain Transfer Restrictions
確定一個證券型代幣能否被髮送的規則是可以自動執行的(例如,對證券投資人的最大數量限制),或者需要一些鏈下的輸入(比如,經紀人對交易的明確批准)。為了方便線下的情況, sendByTranche
和 canSend
函式接收一個額外的 _data
引數,該引數可以由批准方簽名並用於驗證傳輸。
此規範超出了本標準的範圍,具體的實現也是特定的。