ERC-20 与 ERC-721

本文是对 ERC 相关标准的分析,包括 ERC-20、ERC-165、ERC-721 等。此文写作时以官方同时期最新文档作为参考,参考的官方文档的状态可能处于 Draft 状态,也可能处于 Accepted 状态,随着以太坊生态社区的发展,官方文档也在不断进行迭代更新,当阅读此文时,官方 ERC 标准可能又进行了小部分更新,因此建议与官方相关文档结合阅读。

相关名词

  • EIPs:以太坊改善建议书(Ethereum Improvement Proposals);
  • ERC:以太坊请求评论(Ethereum request for comment);
  • ERC-20:同质性代币标准(Token Standard);
  • ERC-165:标准接口检测协议(Standard Interface Detection);
  • ERC-721:非同质性代币标准,NFTs(Non-Fungible Token Standard);

EIP

EIPs:

EIPs 描述了 Ethereum 平台的标准,包括核心协议规范、客户端 api 和合约标准。

EIP 状态:

  • Draft:一个开放供考虑的 EIP;
  • Accepted:计划立即采用的一个 EIP,即期望被包含在下一个硬分叉(核心/共识层 EIP);
  • Final:一个在以前的硬分叉中被采用的 EIP(核心/协商一致层EIP);
  • Deferred: 不考虑立即采用的 EIP,以后的硬分叉可能会被重新考虑;

EIP 类型:

EIPs 被分成多个类型,每个类型都有自己的 EIPs 列表。描述影响大多数或所有 Ethereum 实现的任何更改,例如对网络协议的更改、块或事务有效性规则的更改、提出的应用程序标准/约定,或影响使用 Ethereum 应用程序互操作性的任何更改或添加。此外,标准的 EIPs 可以分为以下几类:

  • Core
    改进需要一个协商一致的分支(例如EIP5、EIP101),以及不一定是一致的关键的变更,但是可能与 “核心开发” 的讨论有关(例如,miner/node 策略改变了 EIP86 的 2、3 和 4)。

  • Networking
    包括对 devp2p(EIP8)和 Light Ethereum 子协议的改进,以及对 “whisper” 和 “swarm” 的网络协议规范的改进。

  • Interface
    包括围绕客户端 API/RPC 规范和标准的改进,以及某些语言级别的标准,如方法名称(EIP6)和合约 ABIs。在将 EIP 提交给 EIP 存储库之前,标签 “interface” 与接口 repo 和讨论应该主要发生在哪个存储库中。

  • ERC
    应用程序级标准和约定,包括诸如令牌标准(ERC20)、名称注册表(ERC137)、URI方案(ERC681)、库/包格式(EIP190)和钱包格式(EIP85)等合约标准。

  • Informational
    描述一个 Ethereum 设计问题,或者向 Ethereum 社区提供一般的指导方针或信息,但是没有提出一个新的特性。信息性 EIPs 并不一定代表 Ethereum 社区共识或建议,因此用户和实现者可以自由地忽略信息 EIPs 或听从他们的建议。

  • Meta
    描述一个围绕 Ethereum 的过程,或者提出一个进程的变更(或事件)。流程 EIPs 就像标准跟踪 EIPs,但适用于以太协议本身以外的领域。他们可以提出一个实施方案,但不能提出以太的代码库;他们通常需要社区共识;与信息 EIPs 不同的是,它们不仅仅是建议,而且用户通常不能随意忽略它们。示例包括过程、指导方针、决策过程的更改,以及对以太开发中使用的工具或环境的更改。任何 meta-EIP 也被认为是一个过程 EIP。

推荐阅读

ERC

在 EIP 定义或讨论的问题里,常常会看到它相关的 ERC,也就是在讨论过程中,如果需要征求更多人意见时,就会把它放在 ERC 里。

推荐阅读

EIP 与 ERC 关系

官方一开始会用 EIP 提出建议,结果与细节会定义在 ERC,最后会定稿放在 EIPs 列表中。

ERC-20

ERC-20,同质性代币标准。众所周知,以太坊是一个可以部署智能合约(Smart contract)的分布式平台,可以通过部署智能合约快捷的发布数字货币(Token,也叫代币),目前在以太坊主网上大概有6万+种数字货币,这些数字货币在特定的条件或者特定的项目中是具有一定的价值的,如果众多数字货币采用的标准或者实现方式不统一,那么对于数字货币的流通或者数字货币钱包的开发将是极其困难的,所以推出了一种以太坊代币标准:ERC-20。

遵循 ERC-20 标准的代币是同质性代币,相同数量的代币是可以互换的,简言之,相同数量的代币是相等的。ERC-20标准的制定有利于这些数字货币在不同项目中的流通。ERC-20 当前状态已经处于 Accepted 状态

推荐阅读

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contract ERC20 {
// 必须实现
function name() constant returns (string name)
function symbol() constant returns (string symbol)
function decimals() constant returns (uint8 decimals)

// MUST
function totalSupply() constant returns (uint totalSupply);
function balanceOf(address _owner) constant returns (uint balance);
function transfer(address _to, uint _value) returns (bool success);
function transferFrom(address _from, address _to, uint _value) returns (bool success);
function approve(address _spender, uint _value) returns (bool success);
function allowance(address _owner, address _spender) constant returns (uint remaining);
event Transfer(address indexed _from, address indexed _to, uint _value);
event Approval(address indexed _owner, address indexed _spender, uint _value);
}

注意:
所有的方法必须处理 returns (bool success),不能假设不存在 returns false 的情况。

name

function name() constant returns (string name)

类型:方法
实现:可选
作用:返回Token的完整名字

symbol

function symbol() constant returns (string symbol)

类型:方法
实现:可选
作用:返回Token的简写符号

decimals

function decimals() constant returns (uint8 decimals)

类型:方法
实现:可选
作用:返回Token的最小单位,比如 8,表示最小单位为0.0000001

totalSupply

function totalSupply() constant returns (uint totalSupply)

类型:方法
作用:返回Token的总数量

balanceOf

function balanceOf(address _owner) constant returns (uint balance)

类型:方法
作用:查询 _owner 的账户余额

transfer

function transfer(address _to, uint _value) returns (bool success)

类型:方法
作用:转移 _value 的 Token 数量到账户 _to

transferFrom

function transferFrom(address _from, address _to, uint _value) returns (bool success)

类型:方法
作用:从账户_from转移_value数量的Token到账户_to

approve

function approve(address _spender, uint _value) returns (bool success)

类型:方法
作用:允许账户_spender从自己账户取回_value个Token,可以分多次取回

allowance

function allowance(address _owner, address _spender) constant returns (uint remaining)

类型:方法
作用:返回_spender仍然被允许从_owner提取的金额

Transfer

event Transfer(address indexed _from, address indexed _to, uint _value)

类型:事件
作用:当_value个Token从_from转移到_to账号时候,事件被触发,0个也要触发

Approval

event Approval(address indexed _owner, address indexed _spender, uint _value)

类型:事件
作用:approval方法调用成功触发次事件

ERC-20 示例

常见问题

考虑 gas 消耗,合约安全(特别是值溢出)等问题

测试实例

AHT

AHT 是在以太坊 Ropsten 测试网上部署的一个 ERC-20 Token:

address: 0xe7304a0ec31147e3b771f4bd0d33f68d76049db6
name:any hong token
totalSupply:10000000000
decimals:5
symbol:AHT

合约代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
pragma solidity ^0.4.8;

contract Token {
// token总量,默认会为public变量生成一个getter函数接口,名称为totalSupply().
uint256 public totalSupply;

// 获取账户_owner拥有token的数量
function balanceOf(address _owner) constant returns (uint256 balance);

// 从消息发送者账户中往_to账户转数量为_value的token
function transfer(address _to, uint256 _value) returns (bool success);

// 从账户_from中往账户_to转数量为_value的token,与approve方法配合使用
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);

// 消息发送账户设置账户_spender能从发送账户中转出数量为_value的token
function approve(address _spender, uint256 _value) returns (bool success);

// 获取账户_spender可以从账户_owner中转出token的数量
function allowance(address _owner, address _spender) constant returns (uint256 remaining);

// 发生转账时必须要触发的事件
event Transfer(address indexed _from, address indexed _to, uint256 _value);

// 当函数approve(address _spender, uint256 _value)成功执行时必须触发的事件
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

contract StandardToken is Token {
function transfer(address _to, uint256 _value) returns (bool success) {
// 默认totalSupply不会超过最大值 (2^256 - 1).
// 如果随着时间的推移将会有新的token生成,则可以用下面这句避免溢出的异常
// require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]);
require(balances[msg.sender] >= _value);

// 从消息发送者账户中减去token数量_value
balances[msg.sender] -= _value;

// 往接收账户增加token数量_value
balances[_to] += _value;

//触发转币交易事件
Transfer(msg.sender, _to, _value);

return true;
}

function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
// require(balances[_from] >= _value && allowed[_from][msg.sender] >=
// _value && balances[_to] + _value > balances[_to]);
require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value);

// 接收账户增加token数量_value
balances[_to] += _value;

// 支出账户_from减去token数量_value
balances[_from] -= _value;

// 消息发送者可以从账户_from中转出的数量减少_value
allowed[_from][msg.sender] -= _value;

// 触发转币交易事件
Transfer(_from, _to, _value);
return true;
}

function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}

function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);

return true;
}

function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
// 允许_spender从_owner中转出的token数
return allowed[_owner][_spender];
}

mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}

contract HumanStandardToken is StandardToken {
// 名称: eg anyhong token
string public name;

// 最多的小数位数,
// How many decimals to show.
// ie. There could 1000 base units with 3 decimals.
// Meaning 0.980 SBX = 980 base units.
// It's like comparing 1 wei to 1 ether.
uint8 public decimals;

// token简称: eg AHT
string public symbol;

// 版本
string public version = 'H0.1';

function HumanStandardToken(uint256 _initialAmount, string _tokenName, uint8 _decimalUnits, string _tokenSymbol) {
// 初始token数量给予消息发送者
balances[msg.sender] = _initialAmount;

// 设置初始总量
totalSupply = _initialAmount;

// token名称
name = _tokenName;

// 小数位数
decimals = _decimalUnits;

// token简称
symbol = _tokenSymbol;
}


function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
require(_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData));
return true;
}
}

源码地址:
https://github.com/anyhong/eth-solidity/blob/master/AHT/AHTToken.sol

ERC-165

ERC-165,ERC 标准接口检测协议。此标准提供一个标准的接口来发布和检测智能合约方法的实现情况。此标准的提出是为了解决某些 ERC 标准对于接口实现情况查询、接口版本查询的需求。标准当前的状态处于 当前状态:Draft。 ERC-165协议中标准化了以下几条:

  • 接口关联
  • 合约接口实现内容发布
  • 合约接口实现检测
  • 合约所有接口实现情况检测

接口

推荐阅读官方相关文档:

ERC-721

ERC-721,非同质性代币标准。ERC-721 标准的 Token 最小单位是1,每个 Token 拥有唯一的 Token ID,这意味着每个 Token 都可以代表不同的价值,可以用于虚拟藏品、贷款债权、物理物品估值等等,这使得加密数字资产变得具有现实等同价值。 ERC-721 是在 ERC-20 标准的基础上建立的,并且兼容部分 ERC-20 接口,ERC-721 也依赖于 ERC-165,即是必须遵循接口查询标准。ERC-721 当前处于 Draft 状态。

推荐阅读

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pragma solidity ^0.4.20;

// 基础接口(必须实现)
interface ERC721 {
event Transfer(address indexed _from, address indexed _to, uint256_tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256_tokenId);
event ApprovalForAll(address indexed _owner, address indexed _operator, bool_approved);

function balanceOf(address_owner) externalviewreturns (uint256);
function ownerOf(uint256_tokenId) externalviewreturns (address);
function safeTransferFrom(address_from, address_to, uint256_tokenId, bytesdata) external payable;
function safeTransferFrom(address_from, address_to, uint256_tokenId) external payable;
function transferFrom(address_from, address_to, uint256_tokenId) external payable;
function approve(address_approved, uint256_tokenId) external payable;
function setApprovalForAll(address_operator, bool_approved) external;
function getApproved(uint256_tokenId) externalviewreturns (address);
function isApprovedForAll(address_owner, address_operator) externalviewreturns (bool);
}

// 钱包接口:
interface ERC721TokenReceiver {
function onERC721Received(address_from, uint256_tokenId, bytesdata) externalreturns(bytes4);
}

// 元数据接口(可选):
interface ERC721Metadata/* is ERC721 */ {
function name() externalpurereturns (string_name);
function symbol() externalpurereturns (string_symbol);
function tokenURI(uint256_tokenId) externalviewreturns (string);
}

// 枚举接口(可选):
interface ERC721Enumerable/* is ERC721 */ {
function totalSupply() externalviewreturns (uint256);
function tokenByIndex(uint256_index) externalviewreturns (uint256);
function tokenOfOwnerByIndex(address_owner, uint256_index) externalviewreturns (uint256);
}

// 接口实现查询:
interface ERC165 {
function supportsInterface(bytes4interfaceID) externalviewreturns (bool);
}

示例

常见问题

  • 实现 ERC721Metadata 或 ERC721Enumerable 的合约也应实现 ERC-721 标准接口,ERC-721 还要实现 ERC-165 的标准接口;
  • 如果一个函数在本规范中被显示为 external,那么如果一个合约使用 public 可见性,它将符合规定。作为 Solidity 版本 0.4.20 的解决方法,你可以在继承合约之前编辑此接口以切换到 public;
  • 使用 this.*.selector 被 Solidity 标记为警告,未来版本中 Solidity 不会将其标记为错误。

ERC-20 与 ERC-721 对比

简而言之,ERC-20 每个 token 是相同的,是等值的;而 ERC-721 每个 token 是唯一的,可以表示不同的对象,可以表示不同的价值。

观点

区块链的杀手级应用不是代币(ERC-20),而是它营造的私有经济或者说生态系统,代币只是其中的一个促成因子。然而以太坊的第一款真实的应用是就是代币,它帮助公司集资,而不是创造生态系统。

0%