“现在两根金条放这儿,你告诉我哪一根是高尚的,哪一根是龌龊的”
——《潜伏》谢若林
STO:币圈乱象终结者
随着数字货币和ICO市场的逐渐冷却,以监管合规为特点的证券型通证发行(Security Token Offering,证券型通证发行,简称STO)开始获得广泛关注。
STO是将证券的发行、交易以及其他生命周期中的事件放到区块链账本上管理的方式。既然是证券,就必须符合证券法的监管。证券型通证在现实中必须有某种金融资产和权益作为内在价值支撑,例如公司股权、债权、黄金、房地产投资信托、可转债的转换权等。
技术方面,9月14日,开发者Stephane Gosselin在以太坊改进方案(EIP)中提出了一种以太坊上实现STO的技术方案ERC1410,称为“部分可替代通证标准(Partially Fungible Token Standard)”,是一种兼容ERC20和ERC777标准的证券型通证发行标准。
笔者将从代码层面,为您剖析ERC1410提案的实现细节,帮助开发者深入理解以太坊ERC1410实现可监管证券型通证的细节。
从Fungible定义说起
要搞清什么是“部分可替代通证标准(Partially Fungible Token Standard)”,就需要从Fungile的定义讲起。
Fungible一般被翻译为“可替代的”或“可互换的”,根据Wikipedia上的定义:
在经济学中,Fungibility指的是物品可以根据特定单位进行互换的属性。 比方说,黄金可以根据重量单位进行互换,纯度100%的1公斤黄金可以和任意同纯度的1公斤黄金互换,而不管它是硬币、铸锭还是其他的状态。其他可互换的物品包括轻质原油、公司股票、债券、贵金属以及货币等等。 可替代性仅指每单位物品与另一同类物品每单位之间的等价性,而不是指一种物品与另一种物品的交换,比如以物换物。
在以太坊上,所有的ERC20 Token都是“可替代通证”(Fungible Token,简称FT),同一通证内两个相等单位的Token并无差别。Bitcoin与Ether也是可替代的,我的一个Ether跟你的一个Ether并无差别。
与可替代通证相反的叫做“不可替代通证”(Non-Fungible Token,简称NFT)。不可替代通证中的每一个Token都是独一无二的。比如一部2002年的诺基亚无法与一部2018年的智能手机相互替换,因为它们在各自的功能、型号、外观等属性上千差万别,无法通过一种可互换的基础单位进行替代。在以太坊上,ERC721标准即为不可替代通证的标准,ERC721标准中主要刻画了不同属物品的所有权转移。在2017年底流行的CryptoKitties(加密猫游戏)就是基于ERC721实现的。
Partially Fungible Token是什么
如果把可替代通证与不可替代通证看做两端,部分可替代通证(Partially Fungible Token,简称PFT)就是兼具了可替代性和不可替代性2种属性的通证形态。
部分可替代通证是如何做到兼具可替代性和不可替代性的呢?
ERC1410引入一个新的名词叫作“Tranche”,在证券行业中通常翻译为“分级”(例如AAA级、AA级、次级),目的是将通证持有者的账户内Token分成不同的tranches份额,比如用户Alice拥有100个Token,其中tranche1是20个,tranche2是30个,tranche3是50个。不同的tranche具有不同的属性(元数据,metadata),基于这些元数据,对tranche进行不同的控制。比如tranche1只能用作投票,tranche2只能用于股权确认,tranche3只能用于流通交易。从而在更小的业务粒度上管理账户的数字资产。
不支持tranche的通证账户:
支持tranche的通证账户:
ERC1410技术细节剖析
ERC1410提案的核心部分是tranche,所有提供的操作接口都围绕tranche展开。每个tranche用一个key(类型为bytes32)表示,每个账户地址下可以有多个tranches。
与tranche有关的查询接口
查询某个账户下指定 tranche 的余额:
function balanceOfByTranche(bytes32 _tranche,address _tokenHolder) external view returns (uint256);
查询某个账户下所有的tranches:
function tranchesOf(address _tokenHolder) external view returns (bytes32[]);
与tranche有关的转账接口
举个例子,Alice想要用自己的账户给Bob的账户转账,那么她需要考虑如下问题:
针对问题1,可以指定一个tranche,或默认的1个或多个tranches(默认tranches将在后文讲述);针对问题2,由业务逻辑决定;针对问题3,需要由接口指定。
sendByTranche:
function sendByTranche(bytes32 _tranche,address _to,uint256_amount,bytes _data) external returns (bytes32);
上述接口表示从调用者的指定tranche转指定金额到目的账户。
sendByTranches:
function sendByTranches(bytes32[] _tranches,address[] _tos,uint256[] _amounts,bytes _data) external returns (bytes32);
上述接口是sendByTranche接口的升级版本,从指定的多个tranches中,往多个目标地址进行转账。
sendByTranche、sendByTranches接口均为交易发起者(msg.sender)对自有账户的操作。
ERC1410基于 ERC777继承了交易员(operator)的相关概念,允许某个交易员代表某个账户持有者基于tranche进行转账。operatorSendByTranche和operatorSendByTranches就是该类型接口。
operatorSendByTranche:
function operatorSendByTranche(bytes32 _tranche,address _from,address _to,uint256 _amount,bytes _data,bytes _operatorData) external returns (bytes32);
operatorSendByTranches:
function operatorSendByTranches(bytes32[] _tranches,address _from,address _to,uint256[] _amounts,bytes _data,bytes _operatorData) external returns (bytes32[]);
默认tranche管理
setDefaultTranches:
为了保证与ERC777、ERC20的兼容性,当调用send时,需要决定从哪个或哪几个tranches中转出。为解决这个问题,可以指定某一个或某几个tranches为默认。
function setDefaultTranche(bytes32[] _tranches) external;
设置默认tranches,这样在调用send时,将从default tranches中转账。
getDefaultTranches:
function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]);
获得某个账户的默认tranches。如果返回值为空,则调用send会抛出异常。如果返回多个,则可以按照某种策略进行转出。
交易员(Operator)相关接口
交易员可以被授权操作:
defaultOperatorsByTranche:
function defaultOperatorsByTranche(bytes32 _tranche) external view returns (address[]);
返回具有所有账户的某个特定tranche的默认操作员列表。
authorizeOperatorByTranche:
function authorizeOperatorByTranche(bytes32 _tranche,address _operator) external;
消息发送者授权给某个交易员某个特定tranche的操作权。每次被调用,必须emit AuthorizedOperatorByTranche event。
revokeOperatorByTranche:
function revokeOperatorByTranche(bytes32 _tranche,address _operator) external;
消息发送者撤销某个交易员对某个特定tranche的操作权。每次被调用,必须emit RevokedOperatorByTranche event。
(注: 此Operator仍有可能通过defaultOperatorsByTranche或defaultOperators拥有对此tranche的操作权。)
isOperatorForTranche:
function isOperatorForTranche(bytes32 _tranche,address _operator,address _tokenHolder) external view returns (bool);
查询_operator是否是某个账户特定tranche的操作员。
兼容性
为了使新标准与ERC20/ERC777(另一个NFT标准)兼容,需要定义在transfer/send操作时,哪些tranches会被用到。ERC1410中扩展的getDefaultTranches/setDefaultTranche规则将会被用到。
function getDefaultTranches(address _tokenHolder) external view returns (bytes32[]);
function setDefaultTranche(bytes32[] _tranches) external;
token的创建者必须为所有的通证持有人定义默认的tranche(s)(使用tranchesOf),以供ERC20/ERC777相关函数使用。而每个单独的账户所有者也可以针对性的修改其默认tranche(s)。
以下行为是在实施ERC777函数中的注意点:
应用场景
ERC1410带来的部分可替换通证能力,为证券通证化带来了可能。通过关联元数据,还能带来丰富的功能逻辑和更灵活的权限控制。
证券型通证发行
游戏点卡控制
游戏内的道具需要用点卡购买,为吸引玩家,玩家存入10个点数(Token)赠送2个点数(此刻玩家一共获得12个点数)。但从细粒度来看,这12个点数分为两个 tranches:10个是玩家自有的,可提现但需要优先消费;2个是系统赠送的,不可提现可后消费。在智能合约内就可以实现对不同tranches的不同控制逻辑。
总结
STO的兴起,意味着区块链通证主动进入证券法的监管范围。ERC1410结合可替代通证和证券业务的特点,在技术层面将通证分割成不同的tranches(份额),不同tranches拥有不同的业务逻辑,从而赋予智能合约对通证的细粒度控制能力。
虽然ERC1410目前还处于草案阶段,需要在社区进行广泛听证、讨论后才能正式实施,但从可监管的角度来看,ERC1410依旧是证券通证化的一个良好开端,笔者团队也将持续跟进,为读者解读标准的后续进展。
参考链接
ERC 1400: Security Token Standard #1411
https://github.com/ethereum/EIPs/issues/1411
ERC 1410: Partially Fungible Token Standard #1410
https://github.com/ethereum/EIPs/issues/1410
Security Token Standard
https://github.com/SecurityTokenStandard/EIP-Spec
作者简介:Codefine好码安全团队专注于智能合约安全审计和全生命周期管理, 已为全球多家交易所、钱包、公链做过智能合约安全审计和开发管理。团队通过独有的多维审计引擎,持续为合作伙伴提供正确、安全、可用的智能合约基础设施。