ERC-223 代幣標準
簡介
什麼是 ERC-223?
ERC-223 是同質化代幣的標準,類似於 ERC-20 標準。主要差異在於 ERC-223 不僅定義了代幣 API,還定義了從發送者到接收者的代幣轉帳邏輯。它引入了一種通訊模型,允許在接收者端處理代幣轉帳。
與 ERC-20 的差異
ERC-223 解決了 ERC-20 的一些限制,並引入了代幣合約與可能接收代幣的合約之間互動的新方法。有幾件事是 ERC-223 可以做到,但 ERC-20 做不到的:
- 接收者端的代幣轉帳處理:接收者可以偵測到 ERC-223 代幣正在被存入。
- 拒絕不當發送的代幣:如果使用者將 ERC-223 代幣發送到不應該接收代幣的合約,該合約可以拒絕交易,從而防止代幣遺失。
- 轉帳中的中繼資料:ERC-223 代幣可以包含中繼資料,允許將任意資訊附加到代幣交易中。
先決條件
內文
ERC-223 是一種代幣標準,為智能合約內的代幣實作了 API。它還為應該接收 ERC-223 代幣的合約宣告了 API。不支援 ERC-223 接收者 API 的合約無法接收 ERC-223 代幣,從而防止使用者錯誤。
如果智能合約實作了以下方法與事件,則可以稱為相容於 ERC-223 的代幣合約。一旦部署,它將負責追蹤在以太坊上建立的代幣。
該合約不強制僅具有這些函式,開發人員可以將不同代幣標準的任何其他功能新增至此合約中。例如,approve 與 transferFrom 函式不是 ERC-223 標準的一部分,但如果需要,可以實作這些函式。
來自 EIP-223 (opens in a new tab):
方法
ERC-223 代幣必須實作以下方法:
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transfer(address _to, uint256 _value, bytes calldata _data) public returns (bool success)
應該接收 ERC-223 代幣的合約必須實作以下方法:
function tokenReceived(address _from, uint _value, bytes calldata _data)
如果將 ERC-223 代幣發送到未實作 tokenReceived(..) 函式的合約,則轉帳必須失敗,且代幣不得從發送者的餘額中移出。
事件
event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes calldata _data)
範例
ERC-223 代幣的 API 類似於 ERC-20,因此從使用者介面 (UI) 開發的角度來看沒有差異。唯一的例外是 ERC-223 代幣可能沒有 approve + transferFrom 函式,因為這些在此標準中是選用的。
Solidity 範例
以下範例說明了基本的 ERC-223 代幣合約如何運作:
pragma solidity ^0.8.19;
abstract contract IERC223Recipient {
function tokenReceived(address _from, uint _value, bytes memory _data) public virtual;
}
contract VeryBasicERC223Token {
event Transfer(address indexed from, address indexed to, uint value, bytes data);
string private _name;
string private _symbol;
uint8 private _decimals;
uint256 private _totalSupply;
mapping(address => uint256) private balances;
function name() public view returns (string memory) { return _name; }
function symbol() public view returns (string memory) {return _symbol; }
function decimals() public view returns (uint8) { return _decimals; }
function totalSupply() public view returns (uint256) { return _totalSupply; }
function balanceOf(address _owner) public view returns (uint256) { return balances[_owner]; }
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly { size := extcodesize(account) }
return size > 0;
}
function transfer(address _to, uint _value, bytes calldata _data) public returns (bool success){
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
}
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
function transfer(address _to, uint _value) public returns (bool success){
bytes memory _empty = hex"00000000";
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
if(isContract(_to)) {
IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
}
emit Transfer(msg.sender, _to, _value, _empty);
return true;
}
}
現在我們希望另一個合約接受 tokenA 的存款,假設 tokenA 是 ERC-223 代幣。該合約必須僅接受 tokenA 並拒絕任何其他代幣。當合約接收到 tokenA 時,它必須觸發 Deposit() 事件並增加內部 deposits 變數的值。
程式碼如下:
contract RecipientContract is IERC223Recipient {
event Deposit(address whoSentTheTokens);
uint256 deposits = 0;
address tokenA; // 我們唯一想要接受的代幣。
function tokenReceived(address _from, uint _value, bytes memory _data) public override
{
// 重要的是要了解在此函式中
// msg.sender 是正在接收的代幣地址,
// msg.value 總是為 0,因為在大多數情況下代幣合約並不擁有或發送以太幣,
// _from 是代幣轉帳的發送者,
// _value 是已存入的代幣數量。
require(msg.sender == tokenA);
deposits += _value;
emit Deposit(_from);
}
}
常見問題
如果我們發送一些 tokenB 到合約會發生什麼事?
交易將會失敗,且代幣轉帳不會發生。代幣將退還至發送者的地址。
我們如何向此合約存款?
呼叫 ERC-223 代幣的 transfer(address,uint256) 或 transfer(address,uint256,bytes) 函式,並指定 RecipientContract 的地址。
如果我們將 ERC-20 代幣轉帳到此合約會發生什麼事?
如果將 ERC-20 代幣發送到 RecipientContract,代幣將被轉帳,但該轉帳不會被識別(不會觸發 Deposit() 事件,且存款值不會改變)。無法過濾或防止不必要的 ERC-20 存款。
如果我們想在代幣存款完成後執行某些函式怎麼辦?
有多種方法可以做到這一點。在此範例中,我們將遵循使 ERC-223 轉帳與以太幣轉帳相同的方法:
contract RecipientContract is IERC223Recipient {
event Foo();
event Bar(uint256 someNumber);
address tokenA; // 我們唯一想要接受的代幣。
function tokenReceived(address _from, uint _value, bytes memory _data) public override
{
require(msg.sender == tokenA);
address(this).call(_data); // 處理傳入的交易並執行後續的函式呼叫。
}
function foo() public
{
emit Foo();
}
function bar(uint256 _someNumber) public
{
emit Bar(_someNumber);
}
}
當 RecipientContract 接收到 ERC-223 代幣時,合約將執行編碼為代幣交易 _data 參數的函式,這與以太幣交易將函式呼叫編碼為交易 data 的方式相同。閱讀資料欄位以了解更多資訊。
在上述範例中,必須使用 transfer(address,uin256,bytes calldata _data) 函式將 ERC-223 代幣轉帳到 RecipientContract 的地址。如果資料參數為 0xc2985578(foo() 函式的簽章),則在收到代幣存款後將呼叫 foo() 函式,並觸發 Foo() 事件。
參數也可以編碼在代幣轉帳的 data 中,例如我們可以呼叫 bar() 函式,並將 _someNumber 的值設為 12345。在這種情況下,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 轉帳中的額外檢查與功能可能會導致更高的燃料成本。
延伸閱讀
頁面最後更新: 2025年9月6日