ERC-777是一种新的代币合约标准,解决了 ERC-20 的一些安全问题,可以让合约创建者、代币持有者和受众在无需更改代币合约的情况下扩展其功能。ERC-777 从 ERC-20 和 ERC-223 等标准处汲取了很多想法,并在此基础上发展成为新一代标准,为开发者和用户提供了很多强大的特性。
本文主要介绍了代币合约,并讲解了 ERC-777 代币合约的特性、功能和用途。请注意,本文不涉及 ERC-20 。如果你想了解关于 ERC-20 代币合约的信息,可以看看另一篇文章(编者注:中译本见文末超链接《理解ERC-20 token合约》)。
什么是代币合约?
代币合约指的是一个包含了一组账户地址及其对应余额的智能合约,如下图所示。余额表示由合约创建者定义的一种价值:代币合约可以使用余额来表示实物、币值或持币者的声誉。一个单位的余额就是我们通常所说的代币。
需要注意的是,一个最终用户可能会拥有任意多个地址。造成这种情况的原因有很多,例如,用户想把自己所持有的代币分散到不同的逻辑账户(储蓄、税务、开支等)中,或使用不同的账户来代表不同的来源(ICO、投资、服务费支付等)。
每当代币从一个账户转移到另一个账户,代币合约就会更新这两个账户的余额。例如,从 0x2299…3ab7 向 0x1f59…3492 转移 10 个代币之后,余额更新情况如下表所示:
可以通过铸成新的代币来增加其总供应量(通常是代币合约所有者的功能)。例如,在 0x4ba5..ae22 中铸成 100 个代币,则余额的更新情况如下表所示:
代币的总供给量可以通过销毁已有代币来减少(如果合约允许的话,任何代币持有者都可以销毁代币)。例如,0x4919…413d 销毁了 50 个代币之后,余额的变化情况如下表所示:
简单的代币合约会将上述信息保存在地址与余额的映射表中。如果是在更复杂的场景下,如分红等,通常会另外采用更加强大的结构。然而,无论具体的实现细节如何,代币余额情况始终如上文的图表所示。
ERC-777 代币合约的运营者
ERC-777 代币合约引入了 运营者 的概念。运营者是代表代币持有者进行操作的第三方,可以将代币从持有者的地址转移出来。请注意,由于运营者拥有很大权力,应该谨慎添加。
每个地址都包含一个经授权的运营者列表,如上表所示:
如上表所示,代币持有地址 0x1f59…3492 拥有两个运营者,其他两个地址各自拥有一个运营者。当然了,没有运营者的地址也是有效的。
使用运营者的一个简单例子是,用户在多个地址代币上都有代币,必须分别管理这些地址。在一般情况下,在将代币从一个地址发送到另一个地址之前,要先确保发送者的地址内有一笔 ETH ,足以用来支付 gas 费用。因此,在将 ETH 从一个账户发送到另一个账户之时,发送者需要先完成几个交易,如下图所示:
如上图所示,地址 0x93f1…1b09 先向地址 0x1f59…3492 发送 ETH ,等待该交易完成后,地址 0x1f59…3492 向 0x4ba5…ae22 发送代币。如此繁多的步骤既降低了用户体验,又加大了网络负载量。
有了运营者之后,只要一个账户里有 ETH ,其他账户里有其他代币,即可由持有 ETH 的账户进行代币转账。接着上一个例子往下看,如果把 0x93f1…1b09 作为 0x1f59…3492 的运营者,那么由 0x1f59…3492 向 0x4ba5…ae22 发送代币的过程就可以简化为:
这就大大降低了用户的负担。此外,这可以让用户在通过一个运营者账户控制 ETH 资金的同时,确保其代币分散于多个持币账户之中。
运营者也有可能是合约的形式(即 “代币运营者合约”),而且在代币合约创建的时候就可以为所有持币者预先定义好代币运营者合约。这样一来,运营者可以为所有持币用户提供服务,同时其功能又被限制在智能合约的功能范围内,代币合约就可以不费吹灰之力地为持有者提供更多功能。后文将详细阐述代币合约运营者拥有哪些权力。
ERC-777 代币合约的定义
每个部署到以太坊上的 ERC-777 代币合约都会被分配一个地址,即代币地址。这个代币合约将会包含一些定义合约操作的参数。
首先要理解的是,由于代币合约缺少一个中心化的注册表,无法保证名称或符号具有唯一性。这样一来,获得并保有唯一身份的最佳方法就是公开你的代币合约。一旦你创建了一个代币合约,就应该把它添加到一些常用网站上,如 Etherscan、MyEtherWallet、MyCrypto 和 CoinMarketCap 等等,不过要确保遵守每个网站的要求,这样你的提交得到接受的可能性才最大。
代币合约的 name 就是用来指代合约本身的长名称,例如 “My token”。名称的长度并没有限制,但是一些钱包应用可能会将过长的名称截短,因此要将名称的长度控制在较短的范围内。
代币合约的 symbol 就是用来指代合约本身的短符号,例如 “MYT”。这个符号跟股票代码差不多,虽然没有长度限制,但是通常都在 3 至 4 个字符左右。
Solidity(以太坊主要使用的编程语言)不支持小数,但是可分割性对于代币来说是一个常见需求。ERC-777 采用的解决方案是,所有代币的内部所示数额均使用其实际数额的 1018 整数倍来表示。例如,终端用户看到的 1.2345 代币其实在内部是用 1.2345×1018 来表示的。这样一来,即使一个代币被分割成了 0.000000000000000001 ,在内部也依旧是以整数表示的,如下表所示:
一些代币合约的创建者可能不想让他们的代币分割得这么细。例如,有一名用户创建了一个软件许可证代币合约,他可能不想看见一个完整的许可证被分割的情况。又或者,有一名用户创建了一个黄金代币合约,用 1 token 代表 1 Kg 黄金,他可能想将转账金额限制在 0.01 Kg 及以上。
代币合约的 granularity(粒度) 是代币内部所示数额的最小可分割单位。紧接着上面的例子,许可证代币的粒度应当是 1018 (因为 1018/ 1018 = 1),而黄金代币的 粒度应当是 1016(因为 1016/1018 = 0.01)。
可以预期的是,绝大多数代币合约的粒度都是 1 ,也就是说,这个代币可以被分割成 1/1018 ,或是 0.000000000000000001 。根据上面给出的例子,如果对代币的可分割性有具体要求,可以选择不同的粒度。
不妨来探究一下 ERC-777 的粒度和 ERC-20 的小数之间的区别。虽然二者的都可以实现代币的可分割性,ERC-20 是基于具体的值来移动小数点的位置,而 ERC-777 的小数点位置是固定的。这样一来,ERC-777 代币的值就更容易在用户界面上显示,因为小数点的位置始终是固定的,只是去掉了后面一连串的 0 。
ERC-777 代币合约的功能
ERC-777 代币合约具有很多功能,可以让用户查找账户余额,并且在不同条件下将代币从一个账户转到另一个账户上。这些函数的详情如下。
totalSupply()函数说明了所有地址持有的代币总量。如果有新的代币被铸造出来,这个值就会增加,如果已有的代币被销毁,这个值就会减少。
balanceOf() 函数说明了特定地址所持有的代币数量。要注意的是,任何人都可以询问任意地址的余额,因为区块链上的所有数据都是公开的。
send()函数将一定数量的代币从信息发送方的地址转到另一个地址上。相比于 ERC-20 代币,ERC-777 代币的发送功能更完善,详情见后文。
burn() 函数会销毁信息发送方所持有的一部分代币。相比于 ERC-20 代币,ERC-777 代币的销毁功能更完善,详情见后文。
authorizeOperator() 函数允许消息发送方将自己的代币授权给另一个地址(译者注:即给运营者授权)。
revokeOperator() 函数可以将撤销现有运营者控制消息发送方代币的权限。
isOperatorFor() 说明了某个地址是否是某个代币持有者的运营者。
只要发送方拥有某个账户的运营者权限,就可以通过 operatorSend() 函数将一定数量的代币从那个账户发送至另一个账户。
defaultOperators()函数提供了一个代币运营者合约列表,列表中的运营者均已得到所有代币的全部权限;关于这个功能,可以参见下文的 “代币运营者合约” 一节。
ERC-777 代币合约所涉及的事件
ERC-777 定义了一些事件,可以用来追踪一个代币合约的个体和整体信息。
一旦有新的代币被铸造出来,就会触发Minted() 事件。该事件包含了新铸代币的数量,及其目标地址的信息。
一旦现有代币被销毁,就会触发 Burned() 事件。该事件包含了被销毁代币的数量及源地址的信息。
一旦有代币从一个地址转移到另一个地址,就会触发 Sent() 事件。该事件包含了被转移代币的数量,以及持有者地址和接收者地址的信息。
除了上述几个事件之外,ERC-777 代币标准还包含了两个管理型事件。一旦用户为己方地址添加了一个运营者,就会触发 AuthorizedOperator() 事件。一旦用户将己方地址的某个运营者移除,就会触发 RevokedOperator() 事件。要注意的是,这些事件不会包含关于代币数量和所有权变化的信息。
详解 ERC-777 代币合约的发送功能
将 ERC-777 代币从一个地址发送到另一个地址需要经过几个步骤来完成。在这一流程,ERC-777 代币标准在功能性和安全性上都展现出了优越之处。
常见的代币发送流程如下图所示:
具体步骤如下:
· 验证:确保输入参数是有效的,需验证该地址是否有足额代币可用来发送,以及所发送数额是否是该代币粒度的倍数
· 授权:确保发送方有权发送代币,发送方必须是这些代币的持有者或是拥有对应地址权限的运营者
· 发送:执行代币转账,更新代币合约上每个地址的持币信息
· 日志:发送包含所有操作细节的事件
ERC-777 在上述步骤的基础上又新增了两个步骤,如下图所示:
可以看到,ERC-777 在常见流程中新增了 tokensToSend() 和 tokensReceived() 这两个步骤。
tokensToSend() 的调用放在了验证交易信息以及完成授权之后,但是在更新合约地址的持币信息之前。
tokensReceived() 的调用放在了更新合约地址的持币信息之后。
乍一看,新增的步骤似乎没有让整个流程变得很复杂。但是, tokensToSend() 和 tokensReceived() 的强大之处在于,它们不是由合约地址定义的,而是分别位于代币发送方和接收方的合约内。由此一来,发送方和接收方就有权决定是否要达成交易,还可以实现更高级的功能。
tokensToSend() 允许持币者以 “在代币离开该账户之前” 的形式提供条件和操作。
tokensReceived() 允许代币接收方以 “代币何时到达该账户。..” 的形式提供条件和操作。
tokensToSend() 的目的
想象一个场景。假设有一家公司的首席财务官制定了多种货币资金的转出规则。这个首席财务官允许财务经理在遵守公司规则的情况下使用资金,同时保留对规则以及资金的控制权,就如下图所示:
如果这笔资金采用的是 ERC-777 代币的形式,就可轻松实现上图中的设置。需要执行以下步骤:
1. 首席财务官制定的规则被编码进了一个代币控制合约内,且该合约被应用于该公司的持币地址
2. 首席财务官授权财务经理成为公司持币地址的运营者
3. 财务经理使用 operatorSend() 发送资金
首席财务官可以制定哪些规则?几乎所有规则都可以被编码进智能合约内,下面举了几个例子:
· 只允许运营者花费一定量的资金(代币)
· 对运营者设置 每日/每周/每月 的支出限额
· 运营者只能向一组经过授权的收款方发送资金
· 只有提供了对首席财务官批准过的发票的引用,运营者才可以花费这笔资金
· 等等
要留意的一点是,既可以针对公司所持有的不同种类的 ERC-777 代币制定多套规则,也可以对多种 ERC-777 代币实行同一套规则。这样一来,首席财务官就可以制定合乎自己公司情况的规则了,财务经理也只能遵守这些规则。
同一个地址也可以拥有多个运营者。因此,如果财务经理有代理人的话,只要其代理人也遵守同样的规则(或其他规则),就有权访问这笔资金。
tokensToSend() 旨在通过对交易制定规则来控制一个或多个账户内资金的转出。说白了,tokensToSend() 就是让用户把 “在代币离开我的账户之前。..” 这句话补充完整。这些规则是在代币控制合约中定义的。同一个代币控制合约可用于多个 ERC-777 代币合约,以及多个账户之间,从而保证各账户之间规则的统一。
tokensReceived() 的目的
与 tokensToSend() 类似,tokensReceived() 会收到代币已转入该账户的通知。接着上文的例子,该公司有一个会计部门负责付款。每次收到付款,都需要核对是否与发票相符,并且分配给公司内部的部门。收到这笔资金之后,会计部门需要执行以下步骤:
· 如果收到的资金带有发票参考号,则将其与发票进行核对,并记入相应部门的贷方
· 如果收到的资金来自一个已知的发送方,则直接记入相应部门的贷方
· 除上述情况之外,将资金存入持币账户并进行调查
就 tokensToSend() 而言,上述过程只是一个实例,它实际上可以描述一切规则。例如,提早付款可享折扣(推迟付款需额外收费),只接受哪几种货币,等等。这只需要一个步骤:
1.该部门的流程被编程进了一个代币控制合约,且该合约被应用于该公司的收款地址
通常来说,tokensReceived() 之所以没有 tokensToSend() 那么复杂,是因为两个原因。第一,tokensReceived() 通常只包含一个参与者(收款地址),而 tokensToSend() 会涉及运营者。第二,相比收款来说,用户通常更关注付款。尽管如此,tokensReceived() 是一个非常强大的功能,可以帮助像交易所之类的大型组织来管理已收到的资金。
tokensReceived() 旨在通过对交易制定规则来控制进入一个或多个账户的资金。说白了,tokensReceived() 就是让用户把 “当代币进入我的账户之时。..” 这句话补充完整。这些规则都是在代币控制合约中定义的。同一个代币控制合约可用于多个 ERC-777 代币合约,以及多个账户之间,从而保证各账户之间规则的统一。
对 tokensToSend() 和 tokensReceived() 的要求
tokensToSend() 是可选项;如果不选的话,就会按照常见流程发送代币。tokensReceived() 也是可选的,除非收款账户是合约,在这种情况下就是强制的。强制所有收到代币的合约执行 tokensReceived() ,就可以确保代币只会被发送到主动说明可以处理这这些代币的合约处。这是 ERC-223 的主要目标。除此之外, ERC-777 还实施了其他保障措施,就是强制收款方登记是否能够接收 ERC-777 代币和 ERC-1820 代币。
代币运营者合约
如上文所述,代币运营者合约就是在 ERC-777 代币合约上调用 operatorSend() 的合约。这类合约的强大之处就在于,它们能够在不需要改变 ERC-777 代币合约本身的情况下扩展 ERC-777 的功能。
当持币者想要把代币发送到另一个地址之时,他会直接在该代币合约上调用 send() ,如下图所示:
但是,任何用户也都可以通过调用代币运营者合约来发送代币。通过该合约,任何用户都可以代表持有者发送代币,如下图所示:
在创建代币合约之时,就可为所有持有者都启用代币运营者合约(即 默认运营者 ),或是在有需要的情况下,为个别持有者启用该合约。
代币持有者合约可以为代币持有者提供额外的功能。例如,批量发送代币是一大常见需求,但是没有在 ERC-777 标准中注明。在部署 ERC-777 代币合约之前,有可能会新增批量发送的功能,但是这样会为代币合约引入自定义属性,因此更容易出现错误。
另一种解决方案是,编写一个可实现批量发送的独立代币运营者合约,并单独进行部署。这个代币运营者合约可以接受来自持币者的交易,并根据交易中所记录的将哪种代币发送给哪些收款方的细节,反复调用 operatorSend() 来发送这些代币。
为了实现批量转账的功能,在部署一个标准 ERC-777 代币合约的同时会指定一个批量发送运营者合约作为默认的运营者。现在,任何持币者都可以在代币运营者合约上调用 send() 函数,仅通过一个交易就可以将多种代币从自己的账户上发送出去。如果代币合约没有注明将批量发送运营者合约作为默认的运营者合约,则持币者可以针对账户进行自定义配置。
请注意,由上图可见,代币运营者合约只有一个send() 函数,但是复杂的代币运营者合约可以有多个 send() 函数。例如,一个批量发送代币运营者合约可能具备以下功能,即,向多个收款方发送相同数量的代币,向多个收款方发送不同数量的代币,等等。
除了上述例子中提到的功能之外,调用代币运营者合约的用户也可以是除持币者之外的人。让外人代替持币者发送代币,这种做法可能听起来很危险,但实际上在很多场景下都非常有用。将持币者和要求转账的用户分开,就可以实现更多功能,例如:
· 在获得持币者授权的情况下发送代币(“免手续费转账”)
· 通过发送代币来换取其他代币(例如,ICO、分布式交易)
· 一旦满足特定条件,立即发送代币(基于绩效的奖励、根据时间锁定的代币)
简言之,代币运营者合约可以通过修改规则来规定代币在何时可以从一个账户转移到另一个账户。这是一个非常强大的功能,需要用户充分信任代币运营者合约。可以设想的是,以太坊主网上将会部署一些知名的代币运营者合约,用来实现特定的功能。代币合约创建者和个人持币者通过选择自己想要的代币运营者合约就可以扩展功能,从而提高代币转账的效率和安全性。
在下一篇文章中,我们将更深入地探究代币运营者合约。
代币运营者合约和代币控制合约之间的差别
乍一看,代币运营者合约跟代币控制合约中的 tokensToSend() 差异不大,其实二者还是有一些差别的。
代币运营者合约是可选的;任何持币者都可以忽视这个功能,直接调用 send() 。而代币控制合约是强制性的,不能被忽视。
任何人都可以调用代币运营者合约。代币控制合约是作为 send() 和 operatorSend() 操作的一部分调用的,因此只能由持币者(或该持币者的运营者)调用。
一般而言,代币运营者合约侧重于扩展代币合约的功能。代币控制合约则侧重于控制来自账户的代币流。
下表汇总了代币运营者合约和代币控制合约之间的不同点:
与 ERC-20 的兼容性
敏锐的读者可能已经注意到了,虽然 ERC-20 和 ERC-777 提供的功能差不多,但是二者对这些功能的命名都不尽相同;ERC-20 使用的名称是transfer()/approve()/transferFrom() ,而 ERC-777 使用的名称是send()/operatorSend() 。这就意味着,同一个代币合约有可能提供相同的 ERC-20 和 ERC-777 功能。ERC-777 标准中详细说明了具体的操作方式和触发事件。
ERC-777 实现
ERC-777 带有一个参考实现,其中还包括了一个可兼容 ERC-20 标准的版本。代币运营者合约和代币控制合约的样本可单独获得。
责任编辑;zl
评论
查看更多