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

JouyPub

ERC20标准

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

  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 -- 获取总量

function totalSupply() constant returns (uint256 totalSupply)

name -- 得到token的名字

function name() constant returns (string _name)

symbol -- 得到token的符号

function symbol() constant returns (bytes32 _symbol)

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

function decimals() constant returns (uint8 _decimals)

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

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

transfer(address, uint) -- 转移令牌

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一起工作

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

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

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

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

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

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

示例代码

ERC223_Interface.sol

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

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

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];
  }
}



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

博客地址:http://www.jouypub.com
简书主页:https://www.jianshu.com/u/756c9c8ae984
segmentfault主页:https://segmentfault.com/blog/jouypub
腾讯云主页:https://cloud.tencent.com/developer/column/72548
阅读 1.4k

jouypub.com
聊聊区块链,大数据,后端开发,前端开发

聊聊区块链、大数据、应用开发

522 声望
159 粉丝
0 条评论

聊聊区块链、大数据、应用开发

522 声望
159 粉丝
宣传栏