はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、各ERCで提案されている関数などが、指定された呼び出し元コントラクトでサポートされているか識別するインターフェースを提案している規格であるERC5269についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
5269は現在(2023年10月25日)では「Review」段階です。
他にも様々なERCについてまとめています。
概要
このインターフェースは、Ethereumリソースコントラクト(通常はERCと略されます)を識別および検出するための新しいアプローチを提供します。
一般的に「ERC番号」として知られているもの(ERC721やERC20など)を指し示す「majorERCIdentifier」という名前のフィールドを設けます。
例えば、ERC721は「majorERCIdentifier = 721」と設定されます
同様に、ERC5269は「majorERCIdentifier = 5269」となります。
「majorERCIdentifier」という用語を使うことで、将来的にERCが番号で識別されない可能性や、他の種類の標準を取り入れたい場合にも対応できるようになります。
また、新たなコンセプトとして「minorERCIdentifier」も提案されており、各ERCの作者が独自に定義できます。
例えば、ERC721の作者は「minorERCIdentifier = keccak256("ERC721Metadata")」と定義することができます。
さらに、スマートコントラクトがサポートするERCを宣言できるように、イベントが提案されています。
この仕組みは、将来の変更や新たな標準の導入に柔軟に対応できるようになり、ERCの識別と互換性の向上を意図しています。
動機
このERCは、ERC165に対抗する標準として作成されました。
ERC165については以下を参考にしてください。
以下は、このERCとERC165の主な違いです。
メソッドの存在を宣言する方法
ERC165は、メソッドまたは複数のメソッドの存在を宣言するためにメソッドのシグネチャをハッシュ化します。
そのため、少なくとも1つのメソッドが存在する必要があります。
しかし、このERCでは一部のERC(例: データフォーマットや署名スキームに関連するもの、または "Soul-Bound-ness"(SBT)など)のように、特定のメソッドが必要ない場合でも機能します。
呼び出し元に基づくクエリ機能
ERC165は、呼び出し元に基づいたクエリ機能を提供しません。
一方、このERCに準拠するコントラクトは、指定された呼び出し元に対して特定のERCをサポートするかどうかに応答します。
このERCの存在の動機は以下のとおりです。
ERC番号を使用することは、人間が理解しやすく、ENSなどの名前付きコントラクトとの作業を容易にします。
ERC165識別子の代わりに、ERC番号を特定または指定する方法への関心が高まっています。
例えば、ERC5267は拡張機能をERC番号のリストとして指定しています。
ERC600やERC601はm/purpose'/subpurpose'/ERC'/wallet'パスにERC番号を指定します。
ERC5568はInstruction_idをERC番号として指定することを要求しています。
ERC5568については以下を参考にしてください。
ERC6120はstruct Token { uint eip; ... }を指定し、uint eipはERCを識別するためのERC番号です。
ERC867(停滞中)はerpIdを作成することを提案しており、これはERPに関連するERC番号(例: ERC1234)である可能性があります。
ERC/ERC番号検出インターフェースを持つことで、コントラクト内での機能メソッドまたは全体のインターフェースをERC165識別子のbytes4からそれぞれのERC番号に変換するためのルックアップテーブルの必要性が減少し、ERCの振る舞いを指定する方法が大幅に簡素化されます。
また、異なる呼び出し元アカウントに対してコントラクトが異なる動作をすることがあります。
最も注目すべきユースケースの1つは、Transparent Upgradable Patternを使用する場合で、プロキシコントラクトはAdminアカウントとNon-Adminアカウントが呼び出される時に異なる処理を行うことがあります。
仕様
以下の説明では、「ERC」と「ERC inter-exchangeably」を互換性がある用語として使用します。
これは、ほとんどの場合、この説明がERC規格のスタンダードトラックのERCカテゴリに適用される一方で、ERC番号空間はERC番号空間の一部であるため、時々ERCとして認識されないがクエリの価値がある振る舞いを持つERCに出会う可能性があるからです。
適合するスマートコントラクトは、次のインターフェースを実装する必要があります。
// DRAFTv1
pragma solidity ^0.8.9;
interface IERC5269 {
event OnSupportERC(
address indexed caller, // when emitted with `address(0x0)` means all callers.
uint256 indexed majorERCIdentifier,
bytes32 indexed minorERCIdentifier, // 0 means the entire ERC
bytes32 ercStatus,
bytes extraData
);
/// @dev The core method of ERC Interface Detection
/// @param caller, a `address` value of the address of a caller being queried whether the given ERC is supported.
/// @param majorERCIdentifier, a `uint256` value and SHOULD BE the ERC number being queried. Unless superseded by future ERC, such ERC number SHOULD BE less or equal to (0, 2^32-1]. For a function call to `supportERC`, any value outside of this range is deemed unspecified and open to implementation's choice or for future ERCs to specify.
/// @param minorERCIdentifier, a `bytes32` value reserved for authors of individual ERC to specify. For example the author of [ERC-721](/ERCS/eip-721) MAY specify `keccak256("ERC721Metadata")` or `keccak256("ERC721Metadata.tokenURI")` as `minorERCIdentifier` to be quired for support. Author could also use this minorERCIdentifier to specify different versions, such as ERC-712 has its V1-V4 with different behavior.
/// @param extraData, a `bytes` for [ERC-5750](/ERCS/eip-5750) for future extensions.
/// @return ercStatus, a `bytes32` indicating the status of ERC the contract supports.
/// - For FINAL ERCs, it MUST return `keccak256("FINAL")`.
/// - For non-FINAL ERCs, it SHOULD return `keccak256("DRAFT")`.
/// During ERC procedure, ERC authors are allowed to specify their own
/// ercStatus other than `FINAL` or `DRAFT` at their discretion such as `keccak256("DRAFTv1")`
/// or `keccak256("DRAFT-option1")`and such value of ercStatus MUST be documented in the ERC body
function supportERC(
address caller,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata extraData)
external view returns (bytes32 ercStatus);
}
OnSupportERC
event OnSupportERC(
address indexed caller, // when emitted with `address(0x0)` means all callers.
uint256 indexed majorERCIdentifier,
bytes32 indexed minorERCIdentifier, // 0 means the entire ERC
bytes32 ercStatus,
bytes extraData
);
概要
ERC5269コントラクトがERC_5269_STATUSをサポートしていることを示すために発行されるイベント。
パラメータ
-
caller- 呼び出し元のアドレス。
- イベントを発行した呼び出し元を示します。
-
address(0x0)の場合、すべての呼び出し元を意味します。
-
majorERCIdentifier- メジャーERC識別子。
-
ERC_5269_STATUSのサポートを確認するために使用されます。
-
minorERCIdentifier- マイナーERC識別子。
- 現在は使用されていません。
-
0の場合、ERC全体を示します。
-
ercStatus-
ERC_5269_STATUSのステータスを示すバイト列。 - コントラクトがサポートする
ERC_5269_STATUSのステータスを表します。
-
-
extraData- 追加のデータ。
- 現在は使用されていません。
上記の説明によれば、ERC_5269_STATUSはkeccak256("DRAFTv1")に設定されます。
また、IERC5269のコメントで指定された振る舞いに加えて、以下の要件が適用されます。
- 任意の
minorERCIdentifier=0は、クエリされているERCのメインの振る舞いを指すために予約されています。 - 適合するERCの作成者は、将来の拡張のためにオプションのインターフェース、振る舞い、および値の範囲のリストを宣言することが推奨されます。
- このERCがFINALである場合、この規格に準拠するコントラクトは、
supportERC((任意の呼び出し元), 5269, 0, [])の呼び出しに対してERC_5269_STATUSを返さなければなりません。
現時点では、supportERC((任意の呼び出し元), 5269, 0, [])は必ずERC_5269_STATUSを返す必要があります。
また、以下の要件もあります。
- この規格に準拠するコントラクトは、コントラクトが構築またはアップグレードされた時に、
OnSupportERC(address(0), 5269, 0, ERC_5269_STATUS, [])イベントを発行することが推奨されます。 - この規格に準拠するコントラクトは、関連する値を持つ
OnSupportERCイベントを発行することで、任意のERCのメインの振る舞いまたはサブの振る舞いを簡単に発見できるようにすることができます。
また、この規格に準拠するコントラクトが特定の呼び出し元またはすべての呼び出し元に対してERCまたは特定の振る舞いをサポートするかどうかを変更した場合も、該当するイベントを発行することができます。 -
FINALステータスでないERC-XXXに対して、
supportERC((任意の呼び出し元), xxx, (任意のminor identifier), [])をクエリする場合、keccak256("FINAL")を返してはいけません。
この場合、0を返すことが推奨されますが、ercStatusの他の値も許容されます。
呼び出し元は、keccak256("FINAL")以外の返された値を非FINALとして扱い、0を厳密に「サポートされていない」として扱わなければなりません。 -
supportERC関数はmutability viewである必要があり、EVMのグローバルステートを変更してはいけません。
これにより、ERC5269および関連するインターフェースの実装に関する要件が明確化され、コントラクト間のインタラクションとクエリの仕組みが提供されています。
補足
majorERCIdentifierのデータ型としてuint256を使用する時、他にも代替オプションが考えられます。
具体的には以下の3つです。
- ERC番号のハッシュバージョンを使用する。
- 生の番号を使用する。
- ERC165識別子を使用する。
(1) の利点は、将来のERC番号や命名規則の変更を自動的にサポートできることです。
ただし、デメリットとしては、バックワード互換性がないため、ハッシュ(ERC番号)を見ても通常はどのERC番号かを簡単に推測することができない点が挙げられます。
この提案では、動機づけに提示された理由に基づいて、(2) の生の番号を選択しました。
設計上の決定として、minorERCIdentifierにはbytes32型を使用しています。
代わりに、**(1)**の番号を使用し、すべてのERCの作者にサブの振る舞いの番号を定義することを強制することもできましたが、bytes32を採用してERCの作者には既に仕様書でインターフェース名やメソッド名を考案しているため、サブの振る舞いの文字列名のハッシュを使用するように求めています。
さらに、追加のデータを返り値として追加するか、サポートされているすべてのERCの配列を追加することも考えられましたが、この複雑さがどれだけの価値をもたらし、余分なオーバーヘッドが正当化されるかは不明です。
ERC165と比較して、ERC1967などによって可能になるプロキシパターンの増加に対応するため、アドレスcallerの追加入力を導入しました。
なぜmsg.senderを単に使用しないのかという質問があるかもしれません。
これは、特定の送信者に対してトランザクションやプロキシコントラクトを使用せずにクエリを許可するためです。
ERC1967については以下を参考にしてください。
最後に、ERC/ERCではない他の標準のコレクションをサポートする可能性を考慮して、majorERCIdentifierの入力が2^32以上の値である場合に備えて予約しています。
テスト
describe("ERC5269", function () {
async function deployFixture() {
// ...
}
describe("Deployment", function () {
// ...
it("Should emit proper OnSupportERC events", async function () {
let { txDeployErc721 } = await loadFixture(deployFixture);
let events = txDeployErc721.events?.filter(event => event.event === 'OnSupportERC');
expect(events).to.have.lengthOf(4);
let ev5269 = events!.filter(
(event) => event.args!.majorERCIdentifier.eq(5269));
expect(ev5269).to.have.lengthOf(1);
expect(ev5269[0].args!.caller).to.equal(BigNumber.from(0));
expect(ev5269[0].args!.minorERCIdentifier).to.equal(BigNumber.from(0));
expect(ev5269[0].args!.ercStatus).to.equal(ethers.utils.id("DRAFTv1"));
let ev721 = events!.filter(
(event) => event.args!.majorERCIdentifier.eq(721));
expect(ev721).to.have.lengthOf(3);
expect(ev721[0].args!.caller).to.equal(BigNumber.from(0));
expect(ev721[0].args!.minorERCIdentifier).to.equal(BigNumber.from(0));
expect(ev721[0].args!.ercStatus).to.equal(ethers.utils.id("FINAL"));
expect(ev721[1].args!.caller).to.equal(BigNumber.from(0));
expect(ev721[1].args!.minorERCIdentifier).to.equal(ethers.utils.id("ERC721Metadata"));
expect(ev721[1].args!.ercStatus).to.equal(ethers.utils.id("FINAL"));
// ...
});
it("Should return proper ercStatus value when called supportERC() for declared supported ERC/features", async function () {
let { erc721ForTesting, owner } = await loadFixture(deployFixture);
expect(await erc721ForTesting.supportERC(owner.address, 5269, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("DRAFTv1"));
expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(ethers.utils.id("FINAL"));
expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("ERC721Metadata"), [])).to.equal(ethers.utils.id("FINAL"));
// ...
expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0));
expect(await erc721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0));
});
it("Should return zero as ercStatus value when called supportERC() for non declared ERC/features", async function () {
let { erc721ForTesting, owner } = await loadFixture(deployFixture);
expect(await erc721ForTesting.supportERC(owner.address, 721, ethers.utils.id("WRONG FEATURE"), [])).to.equal(BigNumber.from(0));
expect(await erc721ForTesting.supportERC(owner.address, 9999, ethers.utils.hexZeroPad("0x00", 32), [])).to.equal(BigNumber.from(0));
});
});
});
詳細は以下を確認してください。
参考実装
contract ERC5269 is IERC5269 {
bytes32 constant public ERC_STATUS = keccak256("DRAFTv1");
constructor () {
emit OnSupportERC(address(0x0), 5269, bytes32(0), ERC_STATUS, "");
}
function _supportERC(
address /*caller*/,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata /*extraData*/)
internal virtual view returns (bytes32 ercStatus) {
if (majorERCIdentifier == 5269) {
if (minorERCIdentifier == bytes32(0)) {
return ERC_STATUS;
}
}
return bytes32(0);
}
function supportERC(
address caller,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata extraData)
external virtual view returns (bytes32 ercStatus) {
return _supportERC(caller, majorERCIdentifier, minorERCIdentifier, extraData);
}
}
ERC_STATUS
bytes32 constant public ERC_STATUS = keccak256("DRAFTv1");
概要
ERC5269コントラクトがサポートしているERC_5269_STATUSのステータスを示す定数。
詳細
DRAFTv1という文字列をハッシュ化して得られた固定のバイト列で、ERC5269コントラクトがサポートするERC_5269_STATUSのステータスを識別します。
この値は変更されず、コントラクト内で共通のステータスを表します。
_supportERC
function _supportERC(
address /*caller*/,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata /*extraData*/)
internal virtual view returns (bytes32 ercStatus) {
if (majorERCIdentifier == 5269) {
if (minorERCIdentifier == bytes32(0)) {
return ERC_STATUS;
}
}
return bytes32(0);
}
概要
ERC5269コントラクトがERC_5269_STATUSをサポートしているか確認する関数。
詳細
この内部関数は、指定されたERCインターフェースとメジャーおよびマイナーの識別子を使用して、ERC_5269_STATUSを取得します。
メジャーERC識別子が5269で、マイナーERC識別子が0の場合、ERC_5269_STATUSが返されます。
それ以外の場合、0が返されます。
引数
-
majorERCIdentifier- メジャーERC識別子。
-
ERC_5269_STATUSのサポートを確認するために使用されます。
-
minorERCIdentifier- マイナーERC識別子。
- 現在は使用されていません。
-
extraData- 追加のデータ。
- 現在は使用されていません。
戻り値
-
ercStatus-
ERC_5269_STATUSまたは0が返されます。
-
supportERC
function supportERC(
address caller,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata extraData)
external virtual view returns (bytes32 ercStatus) {
return _supportERC(caller, majorERCIdentifier, minorERCIdentifier, extraData);
}
概要
ERC5269コントラクトがERC_5269_STATUSをサポートしているか確認する関数。
詳細
指定されたERCインターフェースとメジャーおよびマイナーの識別子を使用して、ERC_5269_STATUSを取得します。
内部で _supportERC 関数を呼び出して結果を返します。
引数
-
caller- 呼び出し元のアドレス。
- 現在の関数を呼び出したアカウントです。
-
majorERCIdentifier- メジャーERC識別子。
-
ERC_5269_STATUSのサポートを確認するために使用されます。
-
minorERCIdentifier- マイナーERC識別子。
- 現在は使用されていません。
-
extraData- 追加のデータ。
- 現在は使用されていません。
戻り値
-
ercStatus-
ERC_5269_STATUSまたは0が返されます。
-
詳細は以下を確認してください。
ERC721のコントラクトもこのERCを実装し、検出と発見を容易にしている例を以下に示します。
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "../ERC5269.sol";
contract ERC721ForTesting is ERC721, ERC5269 {
bytes32 constant public ERC_FINAL = keccak256("FINAL");
constructor() ERC721("ERC721ForTesting", "E721FT") ERC5269() {
_mint(msg.sender, 0);
emit OnSupportERC(address(0x0), 721, bytes32(0), ERC_FINAL, "");
emit OnSupportERC(address(0x0), 721, keccak256("ERC721Metadata"), ERC_FINAL, "");
emit OnSupportERC(address(0x0), 721, keccak256("ERC721Enumerable"), ERC_FINAL, "");
}
function supportERC(
address caller,
uint256 majorERCIdentifier,
bytes32 minorERCIdentifier,
bytes calldata extraData)
external
override
view
returns (bytes32 ercStatus) {
if (majorERCIdentifier == 721) {
if (minorERCIdentifier == 0) {
return keccak256("FINAL");
} else if (minorERCIdentifier == keccak256("ERC721Metadata")) {
return keccak256("FINAL");
} else if (minorERCIdentifier == keccak256("ERC721Enumerable")) {
return keccak256("FINAL");
}
}
return super._supportERC(caller, majorERCIdentifier, minorERCIdentifier, extraData);
}
}
詳細は以下を確認してください。
セキュリティ考慮事項
ERC165と同じように、このインターフェースを呼び出す側は、他のスマートコントラクトが特定のERCインターフェースをサポートしていると宣言していても、それが実際には正確にそのインターフェースをサポートしているわけではないことを前提とする必要があります。
あるコントラクトが特定のERCインターフェースをサポートすると宣言していても、そのコントラクトが実際にはそのインターフェースを正確にサポートしていない可能性がある、ということです。
呼び出し元は、コントラクトの実際のサポート状況を確認する責任があります。
引用
Zainan Victor Zhou (@xinbenlv), "ERC-5269: ERC Detection and Discovery [DRAFT]," Ethereum Improvement Proposals, no. 5269, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5269.
最後に
今回は「各ERCで提案されている関数などが、指定された呼び出し元コントラクトでサポートされているか識別するインターフェースを提案している規格であるERC5269」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!