以太坊代币标准ERC20与ERC223的区别

ERC20标准

  ERC-20 标准是在2015年11月份推出的,使用这种规则的代币,表现出一种通用的和可预测的方式。简单地说,任何 ERC-20 代币都能立即兼容以太坊钱包(几乎所有支持以太币的钱包,包括Jaxx、MEW、imToken等,也支持 erc-20的代币),由于交易所已经知道这些代币是如何操作的,它们可以很容易地整合这些代币。这就意味着,在很多情况下,这些代币都是可以立即进行交易的。
ERC20代币标准【官方文档】:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md

  ERC20 是各个代币的标准接口,ERC20 代币仅仅是以太坊代币的子集。为了充分兼容 ERC20,开发者需要将一组特定的函数(接口)集成到他们的智能合约中,以便在高层面能够执行以下操作:

  • 获得代币总供应量
  • 获得账户余额
  • 转让代币
  • 批准花费代币

ERC20 让以太坊区块链上的其他智能合约和去中心化应用之间无缝交互。一些具有部分但非所有ERC20标准功能的代币被认为是部分 ERC20兼容,这还要视其具体缺失的功能而定,但总体是它们仍然很容易与外部交互。

ERC20标准存在的问题

  ERC20标准无法通过接收方合同处理传入的交易,这是该令牌存在的最大问题,也是开发者一直希望改进的地方。ERC20令牌无法将令牌发送给一个与这些令牌不兼容的契约,也正因为这样,部分资金存在丢失的风险。Reddit上的一篇文章指出,由于被发送到“错误”的合同上,大约价值40万美元的ERC20令牌被困,这对整个以太坊生态系统而言是一个巨大的威胁。
场景:将令牌直接发送给令牌的智能合同将导致资金损失,这是因为一个令牌的合同只会跟踪和分配资金。
例如,当您从钱包中向另一个用户发送令牌时,该钱包将调用令牌的合约来更新数据库,所以如果您试图将令牌直接传输到令牌的合约中,那么由于该令牌的合约无法响应,所以金钱就“丢失”了。

ERC223要解决的首要问题

自从引入ERC20令牌标准以来,几乎所有的基于以太坊的令牌都成功的接受了这个新标准。然而其自身的缺点需要及时解决,这便是ERC223令牌诞生的原因。
ERC223令牌标准将向现有的ERC20标准引入一个新功能,以防止意外转移的发生。

ERC223标准

  ERC223允许用户通过一个函数transfer调用,将令牌发送到合约或电子钱包地址,而不需要针对钱包交易或合约交易做不同的处理
 如果接收者是钱包,则ERC223令牌传输将与ERC20传输相同。
 如果接收方是合约,ERC223令牌合约将尝试在接收方合约中调用tokenFallback函数。如果接收方没有tokenFallback函数,合约事务将失败。tokenFallback函数是Ether事务的后备功能模拟,可用于处理传入事务。有一种方法可以将bytes _data附加到类似于连接到Ether事务的_data的令牌事务。它将通过令牌合约,并将通过接收方合同的tokenFallback函数处理。还有一种方法可以在没有数据参数的情况下使用ERC223令牌合约传输函数,或者使用没有传输函数数据的ERC20 ABI。 在这种情况下_data将为空字节数组。

ERC223定义的接口

totalSupply – 获取总量

1
function totalSupply() constant returns (uint256 totalSupply)

name – 得到token的名字

1
function name() constant returns (string _name)

symbol – 得到token的符号

1
function symbol() constant returns (bytes32 _symbol)

decimals – 得到token的小数点后几位

1
function decimals() constant returns (uint8 _decimals)

balanceOf – 得到地址是_owner的账户的余额

1
function balanceOf(address _owner) constant returns (uint256 balance)

transfer(address, uint) – 转移令牌

1
2
3
function transfer(address _to, uint _value) returns (bool)

function transfer(address _to, uint _value, bytes _data) returns (bool)

由于向后兼容性原因,因为ERC20传输函数没有字节参数。
1、如果_to是合约,则此函数必须传输令牌并调_to中的函数tokenFallback(address,uint256,bytes)
2、如果_to(接收方合同)中没有实现tokenFallback函数,则事务必须失败,并且不会发生令牌的传输。
3、如果_to是外部拥有的地址,则必须发送事务,而不尝试在_to中执行tokenFallback。
4、_data可以附加到这个令牌交易中,它将永远保持在块状(需要更多的gas)。 _data可以是空的。

注意:检查_to是合约还是地址的推荐方法是组装_to的代码。 如果_to中没有代码,那么这是一个外部拥有的地址,否则就是一个合约。
重要: 将在接收方合约中调用的令牌备用功能必须命名为tokenFallback,并使用参数address,uint256,bytes。 此函数必须具有0xc0ee0b8a签名。

事件
Transfer – 当token转移的时候触发,合约和token一起工作

1
event Transfer(address indexed _from, address indexed _to, uint256 indexed _value, bytes _data)

tokenFallback – 令牌持有者发送令牌时处理从令牌合同所调用的令牌传输的功能

1
function tokenFallback(address _from, uint _value, bytes _data)

_from是令牌发送者,_value是传入令牌的数量,_data是附加的数据,类似于Ether事务中的数据。 适用于以太交易的回退功能,并且不返回任何内容。

注意:msg.sender将是tokenFallback函数内的令牌合同。 过滤哪些令牌(通过令牌契约地址)发送可能很重要。 令牌发送者(谁发起了代币交易的人)在_from thetokenFallback函数内。

重要: 这个函数必须命名为tokenFallback,并使用参数地址uint256,字节来匹配函数签名0xc0ee0b8a。

示例代码

ERC223_Interface.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pragma solidity ^0.4.9;

/* 新的 ERC23 contract 接口文件 */

contract ERC223 {
uint public totalSupply;
function balanceOf(address who) constant returns (uint);

function name() constant returns (string _name);
function symbol() constant returns (string _symbol);
function decimals() constant returns (uint8 _decimals);
function totalSupply() constant returns (uint256 _supply);

function transfer(address to, uint value) returns (bool ok);
function transfer(address to, uint value, bytes data) returns (bool ok);
function transfer(address to, uint value, bytes data, string custom_fallback) returns (bool ok);
event Transfer(address indexed from, address indexed to, uint value, bytes indexed data);
}

Receiver_Interface.sol

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
pragma solidity ^0.4.9;

/*
* Contract that is working with ERC223 tokens
*/

contract ContractReceiver {

struct TKN {
address sender; //调用合约的人
uint value;
bytes data;
bytes4 sig; //签名
}


function tokenFallback(address _from, uint _value, bytes _data){
TKN memory tkn;
tkn.sender = _from;
tkn.value = _value;
tkn.data = _data;
uint32 u = uint32(_data[3]) + (uint32(_data[2]) << 8) + (uint32(_data[1]) << 16) + (uint32(_data[0]) << 24);
tkn.sig = bytes4(u);

/* tkn变量是Ether交易的msg变量的模拟
* tkn.sender是发起这个令牌交易的人(类似于msg.sender)
* tkn.value发送的令牌数(msg.value的类比)
* tkn.data是令牌交易的数据(类似于msg.data)
* tkn.sig是4字节的功能签名
* 如果令牌事务的数据是一个函数执行
*/
}
}

ERC223_Token.sol

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
129
130
131
132
133
134
135
136
137
138
139
140
141
pragma solidity ^0.4.9;

import "./Receiver_Interface.sol";
import "./ERC223_Interface.sol";

/**
* ERC23 token by Dexaran
*
* https://github.com/Dexaran/ERC23-tokens
* https://github.com/LykkeCity/EthereumApiDotNetCore/blob/master/src/ContractBuilder/contracts/token/SafeMath.sol
*/
contract SafeMath {
uint256 constant public MAX_UINT256 = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;

function safeAdd(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (x > MAX_UINT256 - y) throw;
return x + y;
}

function safeSub(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (x < y) throw;
return x - y;
}

function safeMul(uint256 x, uint256 y) constant internal returns (uint256 z) {
if (y == 0) return 0;
if (x > MAX_UINT256 / y) throw;
return x * y;
}
}


//示例的智能合约代码
contract ERC223Token is ERC223, SafeMath {

mapping(address => uint) balances;

string public name;
string public symbol;
uint8 public decimals;
uint256 public totalSupply;


// 获取token的名称
function name() constant returns (string _name) {
return name;
}
// 获取token的符号
function symbol() constant returns (string _symbol) {
return symbol;
}
// 获取token精确到小数点后的位数
function decimals() constant returns (uint8 _decimals) {
return decimals;
}
// 获取token的发布总量
function totalSupply() constant returns (uint256 _totalSupply) {
return totalSupply;
}


// 当用户或其他合同想要转移资金时调用的功能。
function transfer(address _to, uint _value, bytes _data, string _custom_fallback) returns (bool success) {
//如果to是合约
if(isContract(_to)) {
if (balanceOf(msg.sender) < _value) throw; //如果当前的余额不够就抛出
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);//发送者的余额做减法
balances[_to] = safeAdd(balanceOf(_to), _value); //接收者的余额做加法
ContractReceiver receiver = ContractReceiver(_to); //初始化接收合约,构造函数参数为接收者的合约地址
receiver.call.value(0)(bytes4(sha3(_custom_fallback)), msg.sender, _value, _data);
Transfer(msg.sender, _to, _value, _data);
return true;
}
else {
return transferToAddress(_to, _value, _data);
}
}


// 当用户或其他合同想要转移资金时调用的功能。
function transfer(address _to, uint _value, bytes _data) returns (bool success) {

if(isContract(_to)) {
return transferToContract(_to, _value, _data);
}
else {
return transferToAddress(_to, _value, _data);
}
}

// 类似于ERC20传输的标准功能传输,没有_data。
// 由于向后兼容性原因而增加。
function transfer(address _to, uint _value) returns (bool success) {

//类似于没有_data的ERC20传输的标准功能传输
//由于向后兼容性原因而增加
bytes memory empty;
if(isContract(_to)) {//如果是合约
return transferToContract(_to, _value, empty);
}
else {
return transferToAddress(_to, _value, empty);
}
}

//组装定地址字节码。 如果存在字节码,那么_addr是一个合约。
function isContract(address _addr) private returns (bool is_contract) {
uint length;
assembly {
//检索目标地址上的代码大小,这需要汇编
length := extcodesize(_addr)
}
return (length>0);
}

//当传递目标是一个地址时调用函数
function transferToAddress(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
Transfer(msg.sender, _to, _value, _data);
return true;
}

//当传递目标是一个合约时调用函数
function transferToContract(address _to, uint _value, bytes _data) private returns (bool success) {
if (balanceOf(msg.sender) < _value) throw;
balances[msg.sender] = safeSub(balanceOf(msg.sender), _value);
balances[_to] = safeAdd(balanceOf(_to), _value);
ContractReceiver receiver = ContractReceiver(_to);
receiver.tokenFallback(msg.sender, _value, _data); //必须要调用这个回调
Transfer(msg.sender, _to, _value, _data);
return true;
}


//得到_owner的余额
function balanceOf(address _owner) constant returns (uint balance) {
return balances[_owner];
}
}

JouyPub wechat
欢迎订阅「K叔区块链」 - 专注于区块链技术学习