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-223 是一种在智能合约中实现代币应用程序接口的代币标准。 它也为应该接收 ERC-223 代币的合约声明了一个应用程序接口。 不支持 ERC-223 接收者应用程序接口的合约无法接收 ERC-223 代币,防止了用户出错。
实现了以下方法和事件的智能合约可以被称为兼容 ERC-223 的代币合约。 一旦被部署,它将负责追踪在以太坊上创建的代币。
合约能够拥有的函数不止这些,开发者可以将各种代币标准的任何其他功能添加到该合约。 例如,approve
和 transferFrom
函数不是 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 override6 {7 // It is important to understand that within this function8 // 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 override6 {7 require(msg.sender == tokenA);8 address(this).call(_data); // Handle incoming transaction and perform a subsequent function call.9 }10 function foo() public11 {12 emit Foo();13 }14 function bar(uint256 _someNumber) public15 {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
的地址。 如果数据参数将为 0xc2985578
(foo()
函数的签名),那么在收到代币存款之后,将会调用 foo() 函数并触发事件 Foo()。
也可以将参数编码到代币转账的 data
中,例如我们可以使用数值 12345 作为 _someNumber
来调用 bar() 函数。 在这种情况下,data
必须为 0x0423a13200000000000000000000000000000000000000000000000000000000000004d2
,其中 0x0423a132
是 bar(uint256)
函数的签名,00000000000000000000000000000000000000000000000000000000000004d2
是 uint256 类型的 12345。
局限性
虽然 ERC-223 解决了 ERC-20 标准中存在的一些问题,但它也有自己的局限性:
- 采用与兼容性:ERC-223 目前还未被广泛采用,这可能会限制其与现有工具和平台的兼容性。
- 向后兼容性:ERC-223 不向后兼容 ERC-20,这意味着现有的 ERC-20 合约和工具在未经修改的情况下无法与 ERC-223 代币一起使用。
- 燃料成本:与 ERC-20 交易相比,ERC-223 转账中的额外检查与功能可能导致更高的燃料成本。