理解ERC-20代币合约

原文: Understanding ERC-20 token contracts

cover

本周早些时候ERC-20代币(ERC-20 token)接口的定义正式确定,成为了一个正式的改进提案。本文探讨一下代币(Token),并解释一下ERC-20的特性和函数,让大家理解到底什么是代币合约(token contract)以及开发者怎么使用它。

ERC-20的出现是想为以太坊中的代币合约提供一整套常用的功能和接口,事实证明它成功了。ERC-20有许多好处,包括允许钱包为大量不同种类的代币提供代币余额功能,并且使交易所只需要通过代币合约的地址就能上线更多的代币。创建一个兼容ERC-20代币的益处是现在很多代币合约都是兼容ERC-20的。

什么是代币合约

关于代币合约到底是什么仍有很多困惑。本质上,一个代币合约就是一个智能合约,其中包含了账户地址与其余额的映射关系。余额代表一个由合约创建者定义的值:一个代币合约可能会使用余额来表示物理物体,其他货币的值,或者此余额持有者的声誉。通常情况下余额的单位叫做一个代币。

erc-20-001

地址和代币余额的列表

当代币从一个账户转移到另一个账户,代币合约会更新这两个账户的余额。比如,从0x2299…3ab7转移10个代币到0x1f59…3492将会产生如下的余额变动:

erc-20-002

从0x2299…3ab7转移10个代币到0x1f59…3492;红色的表示变动

在代币合约允许的前提下,代币的总体发行量可以通过两种方式进行变更。代币的总体发行量可以通过生成新的代币来增加。比如,为0x4ba5…ae22增加100个代币将会使余额发生如下变化:

erc-20-003

为0x4ba5…ae22增加100个代币;红色的表示变动

可以通过销毁现存的代币来减少代币的总体发行量。比如,0x4919…413d销毁50个代币将会使余额发生如下变化:

erc-20-004

销毁0x4919…413d的50个代币;红色的表示变动

一种销毁代币的可选方法是把代币转移到一个没有人知道其私钥的地址中,通常指的是0这个地址。这个与销毁代币有同样的效果,使这些代币不可用,但是并没有减少代币的总量。比如,0x93f1…1b09以这种形式销毁了50个代币,将会使余额发生如下变更:

erc-20-005

0x93f1…1b09向死亡地址转移了50个代币;红色的表示变动

简单的代币合约会在一个地址到余额的映射关系中存储以上信息。当更复杂的场景出现的时候,比如分红,那么会需要更加强大的其他数据结构。先不管实现的细节,不管怎么样,代币余额在外界看来应该跟以上的图表一样。

ERC-20代币合约的定义

ERC-20代币合约是由合约地址和代币的总体发行量来定义的,除此之外还有一些可选的内容也需要提供,以便给用户提供更多细节。这些内容是代币的名称(name),符号(symbol),以及小数的位数(decimals)。每一项都会在后面有详细的说明。

在探讨细节之前,理解有一点非常重要,那就是在以太坊世界中不存在一个代币合约的中央注册结构,所以特定代币的名字或者符号的唯一性是没有保证的。一旦你创建了一个代币合约,你应该向一些常用的网站比如Etherscan、MyEtherWallet和CoinMarketCap申请被收录,并且一定要遵循链接中的规则,以提高你提交的成功率。

代币合约的名字(name)指的是用来标识代币合约的比较长的名字,比如“My token”。对于名字的长度没有限制,但是比较长的名字有可能会被一些钱包应用截断,所以最好还是保持名字简短。

代币合约的符号(symbol)是用来标识代币合约用的符号,比如“MYT”。它大致相当股票代码,尽管对于长度没有限制,但基本上是3或4个字符。

小数的位数(decimals)经常容易造成困惑,但是配以恰当的解释非常容易理解。decimals指的是一个代币能被多少所除,从0(不可整除)到18(几乎连续),如果需要还可以更大。从技术上来讲,decimals的值是当显示代币数值的时候小数位数字的数量。decimals存在的原因是因为以太坊并不会处理小数,所有的数字都会以整数的形式处理。考虑一下如下两个例子:

第一个例子是LicenseToken,一个用来记录某个软件产品license分配情况的代币合约;持有一个LicenseToken的用户可以使用该软件。用户没有理由持有分数形式的license,所以代币创建者设置decimals0。LicenseToken的一些持有者以图形的形式表示如下。

erc-20-006

LicenseToken的代币持有人

这里你可以看到总共有100个license,大部分被一个账户所持有。当用户购买一个license,一个代币将会从持有者的账户转移到购买者的账户。license校验器可以检查是否特定的账户拥有一个license代币,并采取相应的行动。

第二个例子我们来看看GoldToken,用来表示黄金所有权的代币合约。合约创建者希望可以用一个单位来表示1Kg黄金,也想让用户以克(最小)的粒度来持有和交易黄金。因为以太坊不支持小数,所以一个代币只能表示1克黄金,并通过把decimals设置为3(因为1千克等于10³克,代币创建者想把1千克以1个代币的形式展示),这样就可以用1000克来对外表示Kg这个单位。GoldToken的一些持有者以图形的形式表示如下。 erc-20-007

GoldToken的代币持有人

可以看到这里总共有50Kg的黄金(一个代币表示1g * 50000个代币)。当decimals被设置为3的时候,用户看到的会变成如下所示:

erc-20-008

以用户的视角看GoldToken持有者

可以看到把decimals设置为3,从字面的意义上看意味着当显示GoldToken余额的时候,余额小数点后面应该有三位小数。

decimals经常被称为认为人性化因素(humanising factor),因为它允许代币合约去决定如何向用户展示代币余额。GoldToken内部不会处理小数点,并且从来不会在计算过程中使用decimals,因为一切都是以克为单位,但是却允许用户使用黄金常见的单位(Kg)而不是合约中使用的单位(g)。

在以上GoldToken中所展示的关于可分割性的概念让代币合约可以表示非常细粒度的十进制数值,并且decimals被设置为18的代币具有近乎连续的数值。

综上所述,当选择合适的decimals数值的时候应该遵循以下规则:

理解代币创建的时候decimals的作用非常重要。内部代币的数量应该为展示给用户的代币数量乘以10^decimals。就像GoldToken所示,代币创建者想要创建能够表示50Kg黄金的代币,但是由于decimals的值为3,他们必须在内部创建50000个代币(50 * 10³)。

totalsupply是用来定义ERC20代币合约的最后一项,并且是唯一的一个强制参数。尽管在ERC-20规范中没有明确定义,但totalsupply非常简单:totalsupply等于所有余额的总和。totalsupply已经在以上的例子中见过了,所以在这就没有必要继续讨论了。

ERC-20代币合约的函数

ERC-20代币合约拥有许多函数,可以让用户查询账户的余额,以及在特定情况下把余额从一个账户转移到另一个账户。这些函数将会在下面介绍。

balanceOf()函数能够提供某个地址所拥有的代币数量。注意任何人都可以查询任何地址的余额,因为区块链上的所有数据都是公开的。

这里有两种方法可以把代币从一个地址转移到另一个地址。transfer()函数可以直接把一定数量的代币从消息发送者转移到另一个地址。注意这里不会做收款人地址校验,所以发送方要自己确认收款人的地址是正确的。

尽管transfer()对于把代币从一个用户转移到另一个用户没什么问题,但是当用代币来支付智能合约中的某个功能的时候就爱莫能助了。这是因为当智能合约运行的时候,它无法获知到底是哪个地址进行了资金转移,所以并不能确保调用合约的用户已经支付过相应的费用了。

设想一下有一个名为Doer的合约部署在链上。Doer拥有一个名为doSomething()的函数,需要10个Do代币才能执行。Joe想要调用doSomething(),并且 他的账户拥有50个Do代币。Joe该如何向Doer付款才能成功的运行doSomething()呢?

approve()transferFrom()是两个函数,通过这两个函数能够使以上场景通过两步处理的方式来实现。第一步代币持有者允许某一个地址(通常是一个智能合约)可以最多转移一定数量的代币,叫做限额(allowance)。代币持有者使用approve()来提供此信息。

erc-20-009

代币持有者的代币转移的限额

上面例子中的第二行显示地址为0x1f59…3492的Joe允许地址为0xd8f0…c028的Doer最多能够从Joe的账户中转移走25个代币。

一旦限额(allowance)创建完成,智能合约就可以从用户的限额中转移走所允许数量的代币,作为合约执行的一部分。继续以上的例子,Joe现在可以调用doSomething(),然后doSomething()可以使用transferFrom()从Joe的账户中转走10个Do代币,然后继续自己的工作。如果Joe的账户中没有10个代币或者限额小于10个代币,doSomething()将会执行失败。

allowance()函数提供了从一个地址转移到另一个地址所允许的代币数量。注意任何人都可以查询任何账户地址的限额,因为区块链上的所有数据都是公开的。限额都是 “soft”的,意味着不管是个别的还是累积的限额都可能超过一个账户地址的余额。在以上的表格中,举个例子,持有者0x2299…3ab7已经允许最多能够转移500个代币,但是他们的余额很显然只有90个代币。任何使用allowance()的合约在计算可用代币数量的时候必须要额外考虑持有者的余额。

ERC-20代币合约中的事件

ERC-20定义了两个事件,当合约执行一些相关的操作的时候必须要触发。第一个事件是Transfer(),这个事件将会把代币从一个地址向另一个地址的转移细节广播出来。第二个事件是Approval(),这个事件将会把代币从一个地址允许转移到另一个地址的细节广播出去。这些事件可以在不需要拉取区块链数据的情况下用来跟踪特定地址中余额和限额的变更情况。

增发(Minting)代币将会触发Transfer()事件,地址0作为事件的源地址。

代币销毁的时候不会触发任何事件。因此,ERC-20代币合约销毁代币的常用做法是把代币转移到地址0,而不是真的销毁。

ERC-20之后

ERC-20提供了很好的基础来构建代币合约,但是也有缺陷。ERC-223提案提供了一些额外的特性和安全措施,但是与ERC-20不兼容。如今创建代币合约还是应该要继续遵循ERC-20的规范,但是开发者应该跟踪ERC-223提案的进度,并贡献一些力量。

这个系列其他文章

《Ethereum smart service payment with tokens》:如何用ERC-20代币来支付智能合约提供的服务。

(全文完)
comments powered by Disqus
Powered by Github  &&  Jekyll
Fork me on GitHub