ERC-223 トークン規格
最終編集者: @milkywebboy(opens in a new tab), 2024年6月11日
はじめに
ERC-223とは何か?
ERC-223は、ファンジブルトークンのための規格であり、ERC-20規格に似ています。 主な違いは、ERC-223がトークンAPIだけでなく、送信者から受信者へのトークン転送のロジックも定義している点です。 ERC-223は、トークン転送を受信者側で処理できる通信モデルを導入しています。
ERC-20との違い
ERC-223は、ERC-20のいくつかの制限を解決し、トークンコントラクトとトークンを受け取る可能性のあるコントラクトとの新しい相互作用方法を導入しています。 ERC-223では可能で、ERC-20ではできないことがいくつかあります。
- 受信者側でのトークン転送処理:受信者は、ERC-223トークンが入金されていることを検出できます。
- 誤って送信されたトークンの拒否:ユーザーがトークンを受け取るべきではないコントラクトにERC-223トークンを送信した場合、そのコントラクトはトランザクションを拒否し、トークンの損失を防ぐことができます。
- 転送時のメタデータ:ERC-223トークンはメタデータを含めることができ、トークントランザクションに任意の情報を添付することが可能です。
前提条件
規格の概要
ERC-223は、スマートコントラクト内でトークンを操作するためのAPIを実装したトークン規格です。 また、ERC-223トークンを受け取ることを想定したコントラクト用のAPIも定義しています。 ERC-223 Receiver APIをサポートしないコントラクトは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トークンのAPIはERC-20トークンのAPIと似ているため、UI開発の観点からは違いはほとんどありません。 ただし例外として、ERC-223では approve
や transferFrom
関数がオプションであるため、ERC-223トークンはこれらを持たない場合があります。
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がERC-223トークンであると仮定し、別のコントラクトがtokenAのデポジットを受け入れるようにしたいとします。 このコントラクトはtokenAのみを受け入れ、他のトークンは拒否する必要があります。 コントラクトがtokenAを受け取ると、 Deposit()
イベントを発生させ、内部の deposits
変数の値を増加させます。
以下がそのコードです:
1contract RecipientContract is IERC223Recipient {2 event Deposit(address whoSentTheTokens);3 uint256 deposits = 0;4 address tokenA; // 受け入れたい唯一のトークン5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 // この関数内で理解するべき重要な点:8 // msg.senderは、受け取っているトークンのアドレスです。9 // msg.valueは常に0です。なぜなら、通常トークンコントラクトはEtherを保有したり送信したりしないためです。10 // _from はトークン転送の送信者です。11 // _value は預け入れられたトークンの量です。12 require(msg.sender == tokenA);13 deposits += _value;14 emit Deposit(_from);15 }16}すべて表示コピー
よくある質問
他のトークン(tokenB)をこのコントラクトに送信した場合、どうなりますか?
トランザクションは失敗し、トークンの転送は行われません。 トークンは送信者のアドレスに返却されます。
どうやってこのコントラクトにデポジットを行うことができますか?
RecipientContract
のアドレスを指定して、ERC-223トークンの transfer(address,uint256)
または transfer(address,uint256,bytes)
関数を呼び出します。
ERC-20トークンをこのコントラクトに送信した場合、どうなりますか?
ERC-20トークンが RecipientContract
に送信されると、トークンは転送されますが、その転送は認識されません( Deposit()
イベントは発行されず、depositsの値も変わりません)。 不要なERC-20トークンのデポジットはフィルタリングや防止ができません。
トークンのデポジット完了後に関数を実行したい場合、どうすればよいですか?
これには複数の方法があります。 この例では、ERC-223の転送をイーサの転送と同じように処理する方法を紹介します:
1contract RecipientContract is IERC223Recipient {2 event Foo();3 event Bar(uint256 someNumber);4 address tokenA; // 受け入れたい唯一のトークン5 function tokenReceived(address _from, uint _value, bytes memory _data) public override6 {7 require(msg.sender == tokenA);8 address(this).call(_data); // トランザクションを処理し、その後の関数呼び出しを実行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
としてエンコードするのと同じです。 詳細については dataフィールド(opens in a new tab) を参照してください。
上記の例では、ERC-223トークンは transfer(address,uin256,bytes calldata _data)
関数を使用して RecipientContract
のアドレスに転送される必要があります。 dataパラメータが 0xc2985578
( foo()
関数のシグネチャ)である場合、トークンデポジットが完了した後にfoo()関数が呼び出され、Foo()イベントが発行されます。
パラメータはトークン転送の data
にエンコードすることもできます。例えば、_someNumber
に 12345 を指定して bar() 関数を呼び出すことができます。 その場合の data
は 0x0423a13200000000000000000000000000000000000000000000000000000000000004d2
となる必要があります。 0x0423a132
は bar(uint256)
関数のシグネチャであり、 00000000000000000000000000000000000000000000000000000000000004d2
は uint256 としての 12345 を表しています。
制約事項
ERC-223は、ERC-20規格で見られるいくつかの問題に対処していますが、独自の制約があります:
- 普及と互換性:ERC-223はまだ広く普及していないため、既存のツールやプラットフォームとの互換性が制限される可能性があります。
- 後方互換性:ERC-223はERC-20と後方互換性がないため、既存のERC-20コントラクトやツールは、修正なしではERC-223トークンと連携できません。
- ガス代:ERC-223の転送には追加のチェックや機能が含まれるため、ERC-20トランザクションに比べてガス代が高くなる可能性があります。