ERC1400:支持证券增发,交易,相关法律文件存储的证券类 Token 标准
"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成员的关系,可见下图:
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
通过指定一套标准的接口,加速发行和管理以太坊上的证券,通过该接口所有相关方可以查询和操作证券型代币。
证券型代币与其他代币用例存在重大差异,链上与链下参与者的交互更加复杂,有着相当严格的监管审查。
证券型代币应该能够对标任何资产类别,在任何司法管辖区内发型和管理,并遵守相关的监管限制。
将证券的发行,交易和生命周期事件迁移到公共账本上需要采用标准的方式对证券,其所有权及其在链上的属性进行建模。
经证券型代币生态中各方讨论后,编写了如下的要求:
必须有一个标准的接口来查询一笔转账交易是否成功,失败的话则返回原因。
必须能够强制转账以应对法律诉讼或资金回收。
必须发布用于发行和赎回的标准事件。
必须能将一些数据附加到代币持有者余额的子集上,例如特殊股东权利或是转账限制的数据。
必须能根据离线数据,链上数据和转账的参数在转账时修改元数据。
可能需要将签名数据传递到转账交易中,以便于在链上验证。
不应该限制可以代表司法管辖区内的资产类别范围。
应该兼容ERC20和ERC777标准。
1\. `ERC-1400: Security Token Standard`
2\. `ERC-w: Semi-Fungible Token`
3\. `ERC-w-1: Tranche metadata schema`
4\. `ERC-x: Permissioned Token Transfers (canSend() and status codes)`
5\. `ERC-y: Token Metadata (set/getDocument)`
6\. `ERC-z: Optional Security Token features`
7\. `Forced transfers`
8\. `Permanent end to Issuance`
9\. `Trading Halts`
10\. `Batched transfers`
1400 Security Token Standard (证券型代币标准)
1404 Simple Restricted Token Standard (简单的受限代币标准)
有许多类型的证券虽然代表相同的标的资产,但需要有与之相关的差异化数据。
这些额外的元数据隐含地使这些证券不可置换,但实际上这些数据通常应用于证券的一个子集而不是单个证券。将令牌持有者的余额划分为各个部分的能力,每个部分具有单独的元数据,在 Partially-Fungible Token 部分中进行了说明。
例如,代币持有人的余额可以分为两部分:在首次发行期间发行的代币和通过二级市场交易得到的代币。
证券代币合同可以引用该元数据去应用额外的逻辑来决定转账是否有效,并确定一旦转账到接收方账户中,这些代币所关联的原数据。
这些条件可能与正在转账的证券代币关联的元数据有关(即它们是否受制于锁定期),证券发送方和接收方的身份(即他们是否通过了KYC流程,是否是经认可的,或是发行方的附属公司)或者是与具体转账无关的原因,而是设定在代币的级别上(即代币合同强制执行投资人数量的上限,或任何单一投资人持有的百分比上限)。
对于ERC20/ERC777代币而言, balanceOf
和 allowance
方法提供了一个在执行转账之前检查转账是否可能成功的方法,转账在链上或是链下都可以执行。
对于标的证券的代币而言,这个标准引入了一个函数 canSend
,当失败的原因更加复杂的时候,它提供了一种更通用的方法来实现这一目的。还引入了一个用于整个转账行为的函数(即包括同转账一起发送的任意数据和证券的接收方)
为了提供同true或false相比更加丰富的信息,会返回一个字节返回码。这使得我们能够说明转账失败的原因,或者至少是失败原因的类别。查询文档的能力和对转账成功的预期会包含在 Security Token 部分中说明。
一个 Partially-Fungible Token 允许将元数据关联到代币持有者余额当中的一部分上面。这些部分的余额称为 tranches (tranche,实际上是一个法语单词意为“切片”或“部分”。在投资界,用来描述可以被分割成并卖给投资者的小额证券。),并由 bytes32_tranche
键来建立索引,该键可以同链上或链下的元数据相关联。
代币转账始终具有关联的源tranche和目标tranche,以及通常的转账数量和发送方/接收方的地址。
为了提供对ERC777的兼容性,实现需要决定在执行ERC777发送函数时要使用哪一个tranche。
这个函数返回在此情况下使用的tranches。例如,一个证券型代币可以返回 byte32("unrestricted")
tranche, 或者使用一小组可能的tranches的简单实现,可以返回与代币持有者相关联的所有tranches。
返回值可以为空,这意味着没有默认的tranche(因此ERC777的发送函数将抛出错误),或者返回多个tranches,在这种情况下,ERC777的发送函数应该按照顺序循环遍历这些tranches,直到指定数量的代币已经成功转账。
1\. `function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]);`
允许为指定的地址设置默认的tranches,将会在ERC777的发送函数执行时使用到这些设置。
这个函数可以供所有代币持有人自行调用,或仅限于那些实现了某些必要行为的持有人。
1\. `function setDefaultTranche(bytes32[] _tranches) external;`
除了有查询所有tranches下总的代币余额的 balanceOf
,也要有查询某个特定tranche下代币余额的函数。
1\. `function balanceOfByTranche(bytes32 _tranche, address _tokenHolder) external view returns (uint256);`
通过拓展ERC777标准并提供一个默认的tranche(通过 getDefaultTranches
函数获取),就可以发送代币了(从默认的tranches)。要从一个特定的tranche来发送代币的话,可以使用 sendByTranche
函数。
例如,一个有权限的代币可以使用tranche元数据来强制执行如下的转账限制:
_tranche
值
与 _tranche
值相关联的任何额外数据(例如,可能与 _tranche
关联的锁定时间戳)
与代币的发送方或接收方相关联的任何细节(例如,他们的身份信息已经创建)
转账的代币数量(比如,它是否遵守任意按天或者基于其他时间周期的转账数量限制)
_data
参数允许调用者提供与转账相关的任何附加的授权或详细信息(例如,来自一个被允许对转账行为进行授权的授权实体的签名数据)
其它的用例包括通过把先前的持有者与目标tranches关联起来,跟踪代币的出处。
如果转账代币因某种原因失败,这个函数必须抛出错误。
当从特定的tranche上转账代币时,了解这些代币的链上的(即不仅是通过一个响应的事件)目标tranche是有用的。目标tranche将会由这个函数的实施来决定,并依据用例而有所不同。
这个函数在转账成功时必须触发一个 SentByTranche
事件。
1\. `function sendByTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (bytes32);`
2\. `function sendByTranches(bytes32[] _tranches, address[] _tos, uint256[] _amounts, bytes _data) external returns (bytes32);`
允许代币持有者赎回(销毁)代币。
必须从代币总量和代币持有人账户里扣除已销毁或已赎回的代币。销毁代币应该像发送代币一样,受到相同条件的限制。每次此函数被调用时,必须触发 RedeemedByTranche
事件。
1\. `function redeemByTranche(bytes32 _tranche, uint256 _amount, bytes _data) external;`
代币持有者可以把他们的余额拆分成多个部分(tranches) —— 此函数将返回与特定代币持有者地址关联的所有tranches。
1\. `function tranchesOf(address _tokenHolder) external view returns (bytes32[]);`
操作者可以获得以下授权:
所有的代币持有人和tranches( defaultOperators
继承自ERC777标准)
一个特定tranche的所有代币持有人( defaultOperatorsByTranche
)
一个特定代币持有人( isOperatorFor
继承自ERC777标准)的所有tranches(当前的和以后的)
一个特定代币持有人的特定的一个tranche( isOperatorForTranche
)
此函数返回默认的由所有代币持有人和一个特定tranche授权的操作者集合。
1\. `function defaultOperatorsByTranche(bytes32 _tranche) external view returns (address[]);`
允许一个代币持有人去为他的某个指定tranche的代币设置一个操作人。
每次调用此函数时,必须触发 AuthorizedOperatorByTranche
事件
1\. `function authorizeOperatorByTranche(bytes32 _tranche, address _operator) external;`
允许一个代币持有人撤销其某个特定tranche的代币的操作人。
注意 —— 操作人可能会通过 defaultOperatorsByTranche
或 defaultOperators
保留对该代币持有人和tranche的授权。
每次调用此函数时,必须触发 RevokedOperatorByTranche
事件。
1\. `function revokeOperatorByTranche(bytes32 _tranche, address _operator) external;`
返回某个特定地址是否是给定代币持有人和tranche的操作人。
如果地址是上述任何类别下的操作人,则应该返回TRUE。
1\. `function isOperatorForTranche(bytes32 _tranche, address _operator, address _tokenHolder) external view returns (bool);`
允许操作人代表代币持有人发送证券型代币。
此函数应该返回接收方的 bytes32 _tranche
。
如果是在链下生成的话,接收方的 bytes32 _tranche
可以在 bytes _data
中定义。
如果此函数被一个缺少由 isOperatorForTranche
定义的合适授权的地址调用的情况下,必须回退。
此函数在成功发送代币后必须触发一个 SentByTranche
事件。
1\. `function operatorSendByTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (bytes32);`
2\. `function operatorSendByTranches(bytes32[] _tranches, address[] _froms, address[] _tos, uint256[] _amounts, bytes _data, bytes _operatorData) external returns (bytes32[]);`
允许一个操作人代表代币持有人销毁或赎回代币。
必须从代币供应总量和代币持有人账户中扣除被销毁或赎回的数量。销毁代币应该同发送代币一样,收到相同的条件限制。每次调用此函数时必须触发 RedeemedByTranche
事件。
1\. `function operatorRedeemByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _operatorData) external;`
1\. `/// @title ERC-PFT Fungible Token Metadata Standard`
2\. `/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec`
4\. `interface IERCPFT is IERC777 {`
6\. `function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]);`
7\. `function setDefaultTranche(bytes32[] _tranches) external;`
8\. `function balanceOfByTranche(bytes32 _tranche, address _tokenHolder) external view returns (uint256);`
9\. `function sendByTranche(bytes32 _tranche, address _to, uint256 _amount, bytes _data) external returns (bytes32);`
10\. `function sendByTranches(bytes32[] _tranches, address[] _tos, uint256[] _amounts, bytes _data) external returns (bytes32[]);`
11\. `function operatorSendByTranche(bytes32 _tranche, address _from, address _to, uint256 _amount, bytes _data, bytes _operatorData) external returns (bytes32);`
12\. `function operatorSendByTranches(bytes32[] _tranches, address[] _froms, address[] _tos, uint256[] _amounts, bytes _data, bytes _operatorData) external returns (bytes32[]);`
13\. `function tranchesOf(address _tokenHolder) external view returns (bytes32[]);`
14\. `function defaultOperatorsByTranche(bytes32 _tranche) external view returns (address[]);`
15\. `function authorizeOperatorByTranche(bytes32 _tranche, address _operator) external;`
16\. `function revokeOperatorByTranche(bytes32 _tranche, address _operator) external;`
17\. `function isOperatorForTranche(bytes32 _tranche, address _operator, address _tokenHolder) external view returns (bool);`
18\. `function redeemByTranche(bytes32 _tranche, uint256 _amount, bytes _data) external;`
19\. `function operatorRedeemByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _operatorData) external;`
21\. `event SentByTranche(`
22\. `bytes32 indexed fromTranche,`
23\. `bytes32 toTranche,`
24\. `address indexed operator,`
25\. `address indexed from,`
26\. `address indexed to,`
27\. `uint256 amount,`
28\. `bytes data,`
29\. `bytes operatorData`
30\. `);`
31\. `event AuthorizedOperatorByTranche(bytes32 indexed tranche, address indexed operator, address indexed tokenHolder);`
32\. `event RevokedOperatorByTranche(bytes32 indexed tranche, address indexed operator, address indexed tokenHolder);`
33\. `event RedeemedByTranche(bytes32 indexed tranche, address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData);`
34\. `event IssuedByTranche(bytes32 indexed tranche, address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData);`
35\. `}`
为了保持向后兼容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()
触发。
这些函数用于管理与代币关联的一个文档库。这些文件可以是法律文件或者是其他参考资料。
文档与短名称(表示为一个 bytes32
)相关联,并且可以选择有一个同其链上相关联的文档内容的哈希值。
1\. `function getDocument(bytes32 _name) external view returns (string, bytes32);`
2\. `function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external;`
证券转让可能由于多种原因失败,例如:
代币发送方或接收方的身份信息。
对转账的特定代币的限制(即与转让代币相关tranche的限制)
对代币总体状态相关的限制(即投资人总数)
该标准提供了一个链上函数,用于决定转账是否能成功,并返回指示转账失败原因的详细原因。
这些规则可以由智能合约还有链上数据来定义,或者依赖可以表示对转账进行授权的 sendByTranche
函数(例如,声明转账有效性的转账代理的签名信息)返回的部分 _data
。
此函数将会返一个遵循ERC-1066标准的ESC (Ethereum Status Code,以太坊状态码),还有一个可以用于定义程序指定原因码及额外详细信息(例如,执行发送操作的转账限制无效)的 bytes32
参数
此函数也会以与 sendByTranche
类似的方式返回所转账代币的目标tranche
1\. `function canSend(address _from, address _to, bytes32 _tranche, uint256 _amount, bytes _data) external view returns (byte, bytes32, bytes32);`
一个证券型代币发行人可以指定此代币的发行已结束(即,不能铸造或发行新的代币)
如果代币在 issuable()
返回的是false,今后只能返回false。
1\. `function issuable() external view returns (bool);`
此函数在增长代币供应总量时必须被调用。
在调用时,此函数必须触发 IssuedByTranche
事件。
1\. `function issueByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _data) external;`
1\. `/// @title ERC-ST Fungible Token Metadata Standard`
2\. `/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec`
4\. `interface IERCST is IERCPFT {`
5\. `function getDocument(bytes32 _name) external view returns (string, bytes32);`
6\. `function setDocument(bytes32 _name, string _uri, bytes32 _documentHash) external;`
7\. `function issuable() external view returns (bool);`
8\. `function canSend(address _from, address _to, bytes32 _tranche, uint256 _amount, bytes _data) external view returns (byte, bytes32, bytes32);`
9\. `function issueByTranche(bytes32 _tranche, address _tokenHolder, uint256 _amount, bytes _data) external;`
10\. `}`
可能是法规要求发行人或受信任的第三方保留代表投资人转账代币的权利。因此,ERC-ST规范取代ERC-PFT,因为不得允许代币持有人撤销默认的操作人。
相比实用型代币,证券型代币的转账可能因为多种原因而失败,而实用型代币通常仅要求发送方有足够的余额。
这些条件可能与被转账的证券型代币的元数据有关(即它们是否受制于锁定周期),代币发送方和接收方的身份及资格(即他们是否已经通过了KYC,是否是可信任的或是发行方的附属机构),或者出于与特定转账无关的原因,而是出于监管下证券层面的原因(即证券强制执行投资者的最大数量和单一投资者持有百分比的最高上限)。
对于实用型代币(ERC20/ERC777标准的), balanceOf
和 allowance
函数提供了一种方式用以在执行转账操作前检查转账是否可能成功,可以在链上或链下执行。
该标准引入了一个函数 canSend
,它提供了一种更通用的方法来查询发送代币能否成功。它接收一组参数,参数可能包括签名数据,并返回关于交易成功或失败的原因字节码。
注意——调用 canSend
的结果可能会根据链上状态(包括区块高度或时间戳)和链下预言机发生改变。因此,在作为不修改任何状态的视图函数被调用之后,它不能保证将来的转账一定会成功。
在许多司法管辖区,一方能否接收和发送证券型代币依赖于该方身份的特征。例如,在一方有资格购买或出售特定的证券之前,绝大多数的司法管辖区都要求具有某种程度的 KYC/AML 流程。另外,一方可能被分类到投资人资格类别(比如,合格的投资人和购买者),并且他们的公民身份也可以告知与其证券相关联的限制。
多种身份标准(比如ERC725,Civic,uPort)可用于捕获某一方的身份数据,以及集中管理的其他方法(例如,维护一个经KYC批准的地址白名单)。这些身份标准的共同之处是以太坊地址(可以是一方的钱包地址或者身份合同),因此 canSend
函数可以使用证券型代币的发送方和接收方的地址作为确定是否符合资格要求身份的代理。
除此之外,该标准并未强制要求任何特定的身份识别方法。
为了改善代币持有人的体验, 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
确定一个证券型代币能否被发送的规则是可以自动执行的(例如,对证券投资人的最大数量限制),或者需要一些链下的输入(比如,经纪人对交易的明确批准)。为了方便线下的情况, sendByTranche
和 canSend
函数接收一个额外的 _data
参数,该参数可以由批准方签名并用于验证传输。
此规范超出了本标准的范围,具体的实现也是特定的。
本文转载自 《ERC1400提案中文版,关于ERC的新成员,你想要知道的都在这里了》 ,翻译版权属于原作者
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!