跳转至主要内容
Change page

ERC-223 代币标准

上次修改时间: @Joe-Chen(opens in a new tab), 2024年6月11日

简介

什么是 ERC-223?

ERC-223 是一种同质化代币标准,与 ERC-20 标准类似。 主要的区别在于,ERC-223 不仅定义了代币应用程序接口,还定义了从发送者向接收者转移代币的逻辑。 它引入了一个交流模型,使代币转账能够能在接收方进行处理。

与 ERC-20 的区别

ERC-223 解决了 ERC-20 存在的一些限制,并在代币合约与可能接收代币的合约之间引入了一种新的交互方法。 有几件事情是 ERC-223 能够做到但 ERC-20 不能做到的:

  • 在接收方处理代币转账:接收者可以检测到 ERC-223 代币的存入。
  • 拒绝不当发送的代币:如果用户向不应该接收代币的合约发送 ERC-223 代币,合约可以拒绝该交易,以避免损失代币。
  • 转账中的元数据:ERC-223 代币可以包含元数据,允许代币交易上附加任意信息。

前提条件

  • 帐户
  • 智能合约
  • 代币标准
  • ERC-20

正文

ERC-223 是一种在智能合约中实现代币应用程序接口的代币标准。 它也为应该接收 ERC-223 代币的合约声明了一个应用程序接口。 不支持 ERC-223 接收者应用程序接口的合约无法接收 ERC-223 代币,防止了用户出错。

实现了以下方法和事件的智能合约可以被称为兼容 ERC-223 的代币合约。 一旦被部署,它将负责追踪在以太坊上创建的代币。

合约能够拥有的函数不止这些,开发者可以将各种代币标准的任何其他功能添加到该合约。 例如,approvetransferFrom 函数不是 ERC-223 标准的一部分,但如果有必要,可以实现这些函数。

来自 EIP-223(opens in a new tab)

方法

ERC-223 代币必须实现以下方法:

1function name() public view returns (string)
2function symbol() public view returns (string)
3function decimals() public view returns (uint8)
4function totalSupply() public view returns (uint256)
5function balanceOf(address _owner) public view returns (uint256 balance)
6function transfer(address _to, uint256 _value) public returns (bool success)
7function transfer(address _to, uint256 _value, bytes calldata _data) public returns (bool success)
复制

应该接收 ERC-223 代币的合约必须实现以下方法:

1function tokenReceived(address _from, uint _value, bytes calldata _data)
复制

如果 ERC-223 代币被发送到没有实现 tokenReceived(..) 函数的合约,那么这笔转账必定会失败,并且代币不会从发送者的余额中移走。

事件

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

示例

ERC-223 代币的应用程序接口与 ERC-20 的相似,因此从用户界面开发的角度上看两者没有区别。 唯一的区别是,ERC-223 代币可能不具有 approve + transferFrom 函数,因为这些函数对该标准来说是可选的。

Solidity 的示例

以下示例说明了基础 ERC-223 代币合约是如何运作的:

1pragma solidity ^0.8.19;
2abstract contract IERC223Recipient {
3 function tokenReceived(address _from, uint _value, bytes memory _data) public virtual;
4}
5contract VeryBasicERC223Token {
6 event Transfer(address indexed from, address indexed to, uint value, bytes data);
7 string private _name;
8 string private _symbol;
9 uint8 private _decimals;
10 uint256 private _totalSupply;
11 mapping(address => uint256) private balances;
12 function name() public view returns (string memory) { return _name; }
13 function symbol() public view returns (string memory) {return _symbol; }
14 function decimals() public view returns (uint8) { return _decimals; }
15 function totalSupply() public view returns (uint256) { return _totalSupply; }
16 function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }
17 function isContract(address account) internal view returns (bool) {
18 uint256 size;
19 assembly { size := extcodesize(account) }
20 return size > 0;
21 }
22 function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success){
23 balances[msg.sender] = balances[msg.sender] - _value;
24 balances[_to] = balances[_to] + _value;
25 if(isContract(_to)) {
26 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
27 }
28 emit Transfer(msg.sender, _to, _value, _data);
29 return true;
30 }
31 function transfer(address _to, uint _value) public returns (bool success){
32 bytes memory _empty = hex"00000000";
33 balances[msg.sender] = balances[msg.sender] - _value;
34 balances[_to] = balances[_to] + _value;
35 if(isContract(_to)) {
36 IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
37 }
38 emit Transfer(msg.sender, _to, _value, _empty);
39 return true;
40 }
41}
显示全部
复制

现在我们希望另一个合约接受 tokenA 存款(假设该 tokenA 是一种 ERC-223 代币)。 该合约必须只接受 tokenA 并拒绝其他代币。 当合约接收 tokenA 时,它必须触发一个 Deposit() 事件并增加 deposits 内部变量的值。

代码如下:

1contract RecipientContract is IERC223Recipient {
2 event Deposit(address whoSentTheTokens);
3 uint256 deposits = 0;
4 address tokenA; // The only token that we want to accept.
5 function tokenReceived(address _from, uint _value, bytes memory _data) public override
6 {
7 // It is important to understand that within this function
8 // msg.sender is the address of a token that is being received,
9 // msg.value is always 0 as the token contract does not own or send Ether in most cases,
10 // _from is the sender of the token transfer,
11 // _value is the amount of tokens that was deposited.
12 require(msg.sender == tokenA);
13 deposits += _value;
14 emit Deposit(_from);
15 }
16}
显示全部
复制

常见问题

如果我们将一些 tokenB 发送到合约会发生什么?

交易会失败,并且不会发生代币的转移。 代币将被退回到发送者的地址。

我们如何向该合约存款?

调用 ERC-223 代币的 transfer(address,uint256)transfer(address,uint256,bytes) 函数,指定 RecipientContract 的地址。

如果我们将 ERC-20 代币转移到该合约会发生什么?

如果 ERC-20 代币被发送到 RecipientContract,这些代币将被转移,但转账不会被识别(不会触发 Deposit() 事件,存款值不会发生改变)。 无法过滤或防止不必要的 ERC-20 存款。

如果我们希望在代币存款完成后执行一些函数呢?

有多种方法可以做到这点。 在此示例中,我们将使用的方法会使 ERC-223 转账与以太币转账相同:

1contract RecipientContract is IERC223Recipient {
2 event Foo();
3 event Bar(uint256 someNumber);
4 address tokenA; // The only token that we want to accept.
5 function tokenReceived(address _from, uint _value, bytes memory _data) public override
6 {
7 require(msg.sender == tokenA);
8 address(this).call(_data); // Handle incoming transaction and perform a subsequent function call.
9 }
10 function foo() public
11 {
12 emit Foo();
13 }
14 function bar(uint256 _someNumber) public
15 {
16 emit Bar(_someNumber);
17 }
18}
显示全部
复制

RecipientContract 收到 ERC-223 代币时,合约会执行一个编码为代币交易参数 _data 的函数,这与以太币交易将函数调用编码为交易 data 相同。 阅读数据字段(opens in a new tab)以获取更多信息。

在上述示例中,ERC-223 代币必须通过 transfer(address,uin256,bytes calldata _data) 函数转移到 RecipientContract 的地址。 如果数据参数将为 0xc2985578foo() 函数的签名),那么在收到代币存款之后,将会调用 foo() 函数并触发事件 Foo()。

也可以将参数编码到代币转账的 data 中,例如我们可以使用数值 12345 作为 _someNumber 来调用 bar() 函数。 在这种情况下,data 必须为 0x0423a13200000000000000000000000000000000000000000000000000000000000004d2,其中 0x0423a132bar(uint256) 函数的签名,00000000000000000000000000000000000000000000000000000000000004d2 是 uint256 类型的 12345。

局限性

虽然 ERC-223 解决了 ERC-20 标准中存在的一些问题,但它也有自己的局限性:

  • 采用与兼容性:ERC-223 目前还未被广泛采用,这可能会限制其与现有工具和平台的兼容性。
  • 向后兼容性:ERC-223 不向后兼容 ERC-20,这意味着现有的 ERC-20 合约和工具在未经修改的情况下无法与 ERC-223 代币一起使用。
  • 燃料成本:与 ERC-20 交易相比,ERC-223 转账中的额外检查与功能可能导致更高的燃料成本。

扩展阅读

本文对你有帮助吗?