はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、トークンの所有情報がどのストレージスロットに保存されているかを外部から正確に把握できるようにし、オフチェーンでの取引シミュレーションをより確実に再現するための仕組みを提案しているERC6823についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ERC20、ERC721、ERC1155に準拠したトークンを扱う取引を、オフチェーンでより正確にシミュレーションするための方法について提案しています。 ポイントは、それぞれのトークン規格で必ず利用されている「所有者や残高を管理するための mapping がどのストレージ領域を使っているか」を明確に取得できるようにすることです。

ERC20については以下の記事を参考にしてください。

ERC721については以下の記事を参考にしてください。

Ethereumのコントラクトは、内部状態をEVMのストレージに保持しています。 このストレージは 32 バイト単位で構成されていて、変数は宣言順に割り当てられます。 しかし、mapping はサイズが決まっていないため、他の変数とは異なる特別な方法でデータを保存します。 mapping の各値が保存される場所は、キーと mapping の位置を組み合わせて keccak256 ハッシュを計算した結果で決まります。

ERC規格に準拠したコントラクトでも、mapping がどのストレージ番号をルートとして使っているかは、コントラクトごとに異なります。 つまり実装に依存するため、外部から汎用的に特定することができません。 その結果、オフチェーンのシミュレーションで正しく storage の変化を追えなくなり、実際のコントラクト内部状態と食い違ってしまうことがありました。

そこで、トークン所有者や残高を管理する mapping がどのストレージスロットを使用しているかを標準的に取得できる仕組みがあると、外部ツールは実装を知らなくても確実に状態変化を再現できるようになります。 イベントだけに頼る必要がなくなり、より正確な状態検証が可能になります。

動機

ストレージレイアウトの前提

Ethereumのスマートコントラクトは内部状態をストレージに保存します。 ストレージは「キーと値を組み合わせたデータ構造」で、キーは 32 バイトの位置情報、値は 32 バイトのデータです。 コントラクト内で宣言した変数は、このストレージのどこかに割り当てられます。

変数は宣言の順番で並び、1つの変数が 32 バイト未満なら、可能な限り同じスロットへ詰め込まれます。 この配列は Solidityだけでなく Vyperなど他の言語でも同じルールです。

mapping が特殊扱いされる理由

mapping は「どれだけ要素が増えるかわからない」可変サイズのデータ型です。 そのため、他の変数と違ってストレージ上に連続して配置されません。 mapping 自体には「32 バイトの固定スロット」が割り当てられますが、実際の値は別の位置にハッシュ計算によって保存されます。

値の保存場所は以下のように決まります。

  1. キー k を 32 バイトに変換する
  2. mapping が宣言されたストレージ位置 p を 32 バイトに変換する
  3. [k | p] を連結して keccak256 ハッシュを取る
  4. そのハッシュ結果が値を保存するスロットとなる

ここでの問題は、mapping の位置 p がコントラクトごとに異なり、規格からは判別できないことです。

シミュレーションが難しくなる理由

オフチェーンでトランザクションをシミュレーションする場合、実際のストレージと同じレイアウトで計算しないと正しい結果になりません。 しかし、コントラクトがどのような順番で変数を宣言しているか、mapping がストレージのどこにあるかは、実装を見ない限り分かりません。

そのため、以下のような問題が生じます。

  • ERC20、ERC721、ERC1155であっても、残高や所有者を管理する mapping の実際のストレージ位置が分からない
  • ストレージの変化をオフチェーンで正しく再現できず、外部ツールやサービスは確実に状態を追えない
  • 結果として、イベントを頼りにするしかなくなる

しかし、イベントはあくまで通知目的であり、正しい状態の判断材料として依存するべきではありません。 ストレージこそが唯一の正しい状態を表す場所です。

なぜ標準化された mapping のストレージスロット取得が必要なのか

ストレージレイアウトを実装ごとに解析するのは非現実的であり、ERC規格準拠のコントラクトでも実装の違いで storage 位置が変わってしまいます。 このため、外部ツールは確実なシミュレーションができず、状態検証の精度が下がっていました。

もし、トークン標準側で mapping の基底スロットを取得するための共通の入り口があれば、どんな実装であっても次のことが可能になります。

  • オフチェーンで正しく state を再現
  • イベントだけに依存しない安全な検証
  • 外部サービスが実装を調べなくてもストレージの正しい位置を理解できる
  • シミュレーション精度の向上により、ウォレット・ブロックチェーンエクスプローラ・監査ツールなどの信頼性向上

仕様

トークン標準に追加される機能の概要

ERC20、ERC721、ERC1155を対象に、トークンの所有者や残高を管理する mapping が確保しているストレージスロットを取得できる仕組みを共通仕様として追加する案です。

mapping の値が保存される位置はコントラクトの実装によって変わりますが、共通の関数を導入することで、すべての準拠コントラクトから決定的にこの位置を取り出せるようになります。 これにより、イベントだけに頼らずストレージを直接参照して正確な状態を取得することができるようになり、オフチェーンで高精度のシミュレーションを行うことが可能になります。

外部からの呼び出しに余計なコストがかかることはなく、コントラクトをデプロイする際にも影響は非常に小さく抑えられます。

追加される関数

ERC20、ERC721、ERC1155には、共通の関数である getTokenLocationRoot() を実装します。 この関数は、該当コントラクトで使用している mapping の基底となるストレージスロットを返します。

js
abstract contract ERC20Extension is ERC20 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }
}

abstract contract ERC721Extension is ERC721 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }
}

abstract contract ERC1155Extension is ERC1155 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }
}

ここで <mapping_name> の部分には、実際にトークンの所有情報を保持している mapping の名前が入ります。

この関数を使うことで、オフチェーンのツールは mapping がどの場所をルートとして使用しているかを取得できます。 mapping の仕組み上、すべてのキーの保存先はこの root slot をもとに keccak256 で計算されるため、これが分かればすべてのキーに対するストレージ位置を正確に求めることが可能になります。

実際に得られる効果

外部ツールは getTokenLocationRoot() を使って、以下のようにストレージ内容を正しく検証できます。

  • ERC721の mint(新規発行) 該当トークンIDが保存される位置を計算し、その値が新しい所有者のアドレスであることを確認できます。
  • ERC20の transfer(残高移動) 送信者と受信者のそれぞれの残高が適切に更新されたか、ストレージを計算して直接確認できます。
  • ERC1155の burn 書き換えられるべき位置を求め、その値がゼロアドレスになっているかを確かめられます。

このように、イベントを補助的な情報として扱いながら、ストレージという唯一の正しい情報源を使って状態を判断できるようになります。 また、将来的にはストレージ全体を証明する必要がなく、特定のスロットのみを証明する仕組みに応用できる可能性もあります。

関数名が統一されている理由

getTokenLocationRoot() という関数名は、すべてのトークン標準で同じものを使えるように、あえて一般的な名前が採用されています。 このため、関数シグネチャから導かれる selector(識別子)も共通になり、どのトークンコントラクトでも同じ方法でストレージを読み取れます。

共通の入口があることで、外部ツールは対象のトークン規格を意識せず、同じ手順でストレージ位置を取得できるようになります。

実装者が守るべき要件

要件 内容
MUST コントラクトは必ず getTokenLocationRoot() を実装すること
MUST この関数は、対象 mapping の基底ストレージスロットを返すこと
SHOULD この関数は external pure として宣言することが望ましい

pure とすることで、副作用がなくガスコストが小さくなり、外部からの利用が容易になります。

補足

互換性を損なわない設計

ERC20、ERC721、ERC1155はすでに広く使われている規格であり、既存の実装と互換性を維持することが非常に重要です。 そこで、トークンの仕様に「大きな変更を加える」のではなく、「ごく小さな追加」を行う形が採用されています。

追加されるのは 1 つの関数だけであり、既存の関数やイベントには一切影響を与えません。これにより、古い実装や既存のツールとの間に不具合が生じることを避けられます。すでに運用されているエコシステムを壊さずに、ストレージ情報へのアクセス精度だけを向上させることが目的になっています。

ガスコストを増やさない工夫

スマートコントラクトでは、ユーザーが支払うガスコストが大きな問題になります。トークンの使用場面は非常に多いため、わずかなガス増加でもユーザー負担が積み重なります。

ERC20、ERC721、ERC1155はこれを避けるために、以下のような方針で設計されています。

  • 関数を追加しても、その関数は pure であり、実行時にガスを消費しない
  • コントラクトのデプロイ時にわずかにコストが増えるだけで、ユーザーのトランザクションには影響しない
  • 外部ツールがストレージ情報を参照する際にも追加コストが発生しない

つまり、ユーザーはこれまでと変わらない形でトークンを利用でき、開発者と外部ツールだけが恩恵を受ける仕組みになっています。

最小限で柔軟な実装

追加される仕組みは非常にシンプルで、コントラクトに1つの関数を加えるだけです。 この関数は assembly を使って mapping の基底スロットを返すだけなので処理内容が複雑になることもないです。

このような最小限の構成が採用されている理由は次の通りです。

  • SolidityだけでなくVyperなど、EVM上で利用される多数の言語と直接互換性がある
  • トークン実装ごとの内部構造に依存せず、あらゆる実装に適用できる
  • 拡張性があり、未来のツールや証明システムでも扱いやすい

つまり、単純であることによって柔軟性と互換性が両立し、EVM エコシステム全体がこの仕組みを採用しやすくなっています。

参考実装

js
abstract contract ERC20Extension is ERC20 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }
}

abstract contract ERC721Extension is ERC721 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }
}

abstract contract ERC1155Extension is ERC1155 {
    function getTokenLocationRoot() external pure virtual returns (bytes32 slot) {
        assembly {
            slot := <mapping_name>.slot
        }
    }

引用

qdqd (@qd-qd) qdqdqdqdqd@protonmail.com, "ERC-6823: Token Mapping Slot Retrieval Extension [DRAFT]," Ethereum Improvement Proposals, no. 6823, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6823.

最後に

今回は「トークンの所有情報がどのストレージスロットに保存されているかを外部から正確に把握できるようにし、オフチェーンでの取引シミュレーションをより確実に再現するための仕組みを提案しているERC6823」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!