ERC-223 代幣標準
頁面最後更新時間: 2025年9月6日
介紹
什麼是 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(..) 函數的合約,該傳送則必定會失效,並且代幣不會從發送者的餘額中移動。
Events
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 // 在該函數中理解這一點很重要8 // msg.sender 是正在被接收的代幣地址,9 // 由於代幣合約在大多數情況下不擁有或發送以太幣,msg.value 始終為 0,10 // _from 是代幣傳送的發送者,11 // _value 是存入的代幣數量。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 相同。 閲讀 數據欄位 以獲取更多資訊。
在上述範例中,ERC-223 必須被傳送到具有 transfer(address,uin256,bytes calldata _data) 函數的 RecipientContract 地址。 如果數據參數為 0xc2985578 (foo() 函數的簽名),則在代幣存款被接收之後,foo() 函數將被調用,並且 Foo() 事件將會釋出。
參數也可以編碼在代幣傳送的「data」中,例如我們可以使用「_someNumber」的 12345 值來呼叫 bar() 函數。 在這種情況下,data 必須為0x0423a13200000000000000000000000000000000000000000000000000000000000004d2,其中 0x0423a132 是 bar(uint256) 函數的簽名,00000000000000000000000000000000000000000000000000000000000004d2 是 12345 作爲 uint256。
限制
雖然 ERC-223 解決了 ERC-20 標準中的一些問題,但它也有自己的限制:
- 採用與兼容性: ERC-223 目前還未被廣泛採用,這可能會限制其與現存工具和平台的兼容性。
- 向後兼容性: ERC-223 不向後兼容 ERC-20,這意味著現存的 ERC-20 合約和工具無法在未經修改的情況下與 ERC-223 代幣一起使用。
- 燃料成本: 與 ERC-20 的交易相比,ERC-223 中的額外檢查與功能可能會導致更高的燃料成本。