前言
"ERC1400"是新提案的证券型代币的标准,新标准主要是把token的互换性(fungible)结合证券相关的业务场景,设计了一套通用接口。
标准制定了代币持有人的余额分离成多个分片(tranche)的能力。tranche是一种以债务为基础的投资结构。这些证券将具有不同期限的,投资风险或高或低的tranche组合成一个整体,以达到降低投资者的风险,提供长期投资的目的。例如,你可能有一笔贷款转付证券,其中包括5到30年期的高风险和低风险的转付证券,基于ERC1400标准的代币将支持这种投资方式。
我们先了解ERC777标准,ERC777是ERC20的加强版,旨在加强用户的控制权限,具体有:
- 随交易发送可以附带描述数据,以供某些业务场景使用
- 设置一些转账限制,如黑名单
- 支持一些高级交易
无论是ERC20还是ERC777,每个单位的token都是相同的,并无附加属性,属于fungible token(同质化代币/可互换代币)。ERC721标准的代币,每个token均有不同的ID,不同ID可以有不同的解释,属于no-fungible token(非同质化代币,不可互换代币)
ERC1410标准的代币属于Partially-Fungible Token (部分可互换代币),将 ERC20/ERC777 中不存在解释属性的余额,附加额外的信息,从而划分成不同的部分,就可以做一些操作上的限制(例如:某些操作只限定于指定tranche的token,某些操作优先消耗指定tranche的token)
ERC1400则是继承ERC1410标准,增加了证券相关业务会使用到的函数:证券增发,相关法律文件存储等。
先前一些证券型代币的合约大多是在ERC20标准的基础上,通过维护一个或多个以太坊地址集合,对这部分地址做出了划分:是否通过kyc,是否处于锁定期等,来进行转账上的限制。这些都是在地址层面做出不同的解释。而ERC1400对token本身做了不同解释,以适用于更复杂的证券相关业务场景。
ERC1400则是继承ERC1410, 加上了证券相关功能会使用的函数(发行证券,法律文件等)。
Security Token Standard
- eip: ERC1400
- 标题: Standard for Security Tokens
- 作者: Adam Dossa (@adamdossa), Pablo Ruiz (@pabloruiz55), Fabian Vogelsteller (@frozeman), Stephane Gosselin (@thegostep)
- 状态: 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
操作者可以获得以下授权:
- 所有的代币持有人和tranches(
defaultOperators
继承自ERC777标准) - 一个特定tranche的所有代币持有人(
defaultOperatorsByTranche
) - 一个特定代币持有人(
isOperatorFor
继承自ERC777标准)的所有tranches(当前的和以后的) - 一个特定代币持有人的特定的一个tranche(
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
/// @title ERC-PFT Fungible Token Metadata Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec
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
/// @title ERC-ST Fungible Token Metadata Standard
/// @dev See https://github.com/SecurityTokenStandard/EIP-Spec
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
参数,该参数可以由批准方签名并用于验证传输。
此规范超出了本标准的范围,具体的实现也是特定的。