はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、簡単にガス効率良くコントラクトを複製する仕組みを提案している規格であるERC1167についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なERCについてまとめています。
今回の規格はOpcodesについての記述もあるため、Opcodesがわからない方は以下の記事などを参考にしてください。
概要
この規格は、コントラクト機能を効率的かつコスト効率よく「複製」するための仕組みについて提案しています。
この方法では、あるコントラクトが他の特定のコントラクトの機能を使えるようにし、それを標準化することを目的にしています。
具体的には、あるコントラクトが別の特定のアドレス(コントラクト)にある機能を「借りる」ことができます。
この「借りる」という動作はリダイレクトや委譲と呼ばれ、コントラクトが外部の機能を使う時にその機能がどこにあるかを簡単に知ることができます。
この標準化された方法を使うことで、例えばEtherscanのようなツールは、コントラクトがどのように動作するかを把握しやすくなります。
この実装は非常に効率的で、コントラクトが呼び出されると全ての呼び出しとガスをその特定のアドレスのコントラクトに送ります。
その後、結果や戻り値を呼び出し元に返します。
もし借りている機能が何らかの理由で失敗(revert
)した場合でも、その失敗の情報は呼び出し元に正確に伝えられます。
この方法は、あるコントラクトが別のコントラクトの機能を使うことをシンプルにし、その過程で一貫性と予測可能性を保つことができる非常に効果的な手法です。
これにより、複雑なシステムやアプリケーションでも、様々な機能をスムーズに組み合わせて利用することができます。
動機
この標準は、特定のコントラクトの全ての機能を完璧に再現するために使われます。
このプロセスは、他のデータを間違って変更しないように注意しながら(メモリスロットの衝突を避ける)、新しいコピーを作る時の費用(ガスコスト)をできるだけ少なくするなど、問題やガスコストの削減に焦点を当てています。
メモリスロットの衝突
メモリスロットの衝突とは、複数のデータが同じメモリ位置(スロット)に保存されようとするときに起こる問題です。
スマートコントラクトやプログラミングにおいて、同じアドレス空間を共有する場合にバグやセキュリティリスクを引き起こす可能性があります。
例えば、イーサリアムのスマートコントラクトでは、ステート(コントラクトのストレージデータ)はメモリのスロットに保存されます。
コントラクトには、それぞれのステートがどのスロットに格納されるか決まっています。
具体例
オンラインバンキングシステムのスマートコントラクトを作っているとします。
このコントラクトには、ユーザーの残高を追跡するための変数とシステムの重要な設定を保存するための変数があります。
contract Bank {
mapping(address => uint256) public balances;
bool public systemMaintenance = false;
// その他の変数や関数...
}
ここで、balances
はユーザーごとに異なる残高を追跡するためのもので、systemMaintenance
はシステムのメンテナンス状態を示すためのものです。
通常、これらの変数はそれぞれ異なるスロットに保存されます。
しかし、ある状況でこれらの変数が同じスロットに割り当てられてしまうと、balances
を更新するコードが誤ってsystemMaintenance
の値も変更してしまう可能性があります。
このような状況が発生するとセキュリティ問題につながります。
例えば、システムメンテナンスモードが意図せずに有効になり、全ての取引が停止してしまうかもしれません。
このような問題を避けるために、開発者はスマートコントラクトを設計する時に、データの保存場所に注意を払ってテストを徹底する必要があります。
また、既存のコントラクトをクローンする場合には、元のコントラクトのメモリ配置を正確に理解し、衝突が起きないようにすることが重要です。
この標準を使えば、あるコントラクトの機能をまったく同じように使える新しいコントラクトを作ることができます。
この方法は、余計な手間やコストをかけずに効率的に行えるため、開発者がブロックチェーン上でアプリケーションやサービスを簡単に、かつ経済的に構築するのに役立ちます。
仕様
この標準は、あるコントラクトの機能を新しいコントラクトにコピーするための手順を定義しています。
具体的な手順は、特定のバイトコード「363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
」を使用します。
このコードの一部(10
番目から29
番目のバイト)を、コピー元のコントラクトのアドレスに置き換えることで、新しいコントラクトを作成します。
この新しいコントラクトは、元のコントラクトと全く同じ機能を持ちます。
この方法を使うと、新しいコントラクトを作るためのコスト(ガスコスト)を低く抑えつつ、元のコントラクトの機能を正確に複製できます。
このプロセスの詳しい手順や実装例は、GitHubの「optionality/clone-factory」リポジトリで確認できます。
この標準は、ブロックチェーン上で効率的かつ正確にコントラクトを複製するための「レシピ」のようなものと考えることができます。
クローンコントラクトは、既存のコントラクトの機能を複製して新しいコントラクトを作成するためのものです。
ここでは、そのバイトコードと、特定のアドレスをどのように置き換えるかについて説明しています。
バイトコード
「363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3
」は、クローンコントラクトを作成するためのバイトコードです。
このコード内の10
番目から29
番目(両端を含む)のバイト(bebebebebebebebebebebebebebebebebebe
の部分)は、複製したいマスター機能コントラクトのアドレスに置き換えられます。
これにより、クローンコントラクトは特定のマスターコントラクトの機能を複製することができます。
例
例えば、特定のトークンコントラクトの機能を持つ新しいコントラクトを作りたいとします。
そのトークンコントラクトのアドレスが「0x1234567890123456789012345678901234567890
」だとすると、バイトコードの中のbebebebebebebebebebebebebebebebebebe
の部分をこのアドレスで置き換えます。
置き換えた後のバイトコードは、新しいトークンコントラクトをデプロイする時にEthereumネットワークに送信され、その結果としてマスターコントラクトの機能を複製したクローンコントラクトが作成されます。
このように、標準クローンコントラクトのバイトコードは、特定の機能を持つコントラクトを効率的に複製し、新しいコントラクトを作成するための強力なツールです。
この技術を利用することで、開発の効率化、コスト削減、イーサリアムのスマートコントラクトの可能性をさらに広げることができます。
ガス効率向上
-
コードの再利用
- クローンコントラクトは既存のコントラクト(マスターコントラクト)のコードを再利用します。
- 新しいコントラクトを作成する時に、既存のコードを再度ブロックチェーンにアップロードする代わりに、既存のコードを指し示すだけで済むため、デプロイメントに必要なガスが大幅に削減されます。
-
コードの短縮
- クローンコントラクトは、マスターコントラクトへの参照を持つ非常に短いバイトコードで構成されています。
- 短いコードは、ブロックチェーンに保存するデータ量が少なくなるため、それに伴ってガス消費も減少します。
-
バニティアドレス最適化
- マスターコントラクトのアドレスを特定の形式(例えば先頭にゼロバイトが多い)にすることで、クローンコントラクト内のアドレス参照をさらに短くすることができます。
- これにより、コントラクトのサイズがさらに削減され、デプロイメント時のガスコストが減少します。
-
一般的な処理の共有
- クローンコントラクトはマスターコントラクトの機能をそのまま利用するため、新しいコントラクトごとに同じ処理を何度もデプロイする必要がありません。
- この再利用により、ネットワーク全体の負荷とガス消費が減少します。
これらの要因により、クローンコントラクトは特にデプロイメント時にガス効率が良く、同様の機能を持つ独自コントラクトを一からデプロイするよりも、はるかに経済的です。
また、これらの最適化により、ブロックチェーンのリソースを節約し、全体のネットワーク効率も向上します。
補足
この提案は、ブロックチェーン上でコントラクトの機能を効率的かつ安全に複製することを目的としています。
主な目標は以下の通りです。
低コストでのデプロイメント
新しいクローンをブロックチェーン上に作る際の費用をできるだけ抑えます。
これにより、より多くの人が利用しやすくなります。
クローンの初期化のサポート
作成する時に、クローンをすぐに使える状態にすることができます。
これはファクトリー・コントラクト・モデルを使って実現されます。
ファクトリー・コントラクト・モデル
ファクトリー・コントラクト・モデルは、1つの「ファクトリー」と呼ばれるコントラクトを使って、他のコントラクトを生成、管理する方法です。
このモデルは特にイーサリアムのようなブロックチェーンでよく用いられます。
例
オンラインマーケットプレイスを運営しており、各販売者が自分の店を持てるようにしたいと考えています。
各店は独自の在庫、価格設定、販売記録を持つべきでファクトリー・コントラクト・モデルが役立ちます。
-
ファクトリー・コントラクトの作成
- まずは「店舗ファクトリー」というコントラクトを作成します。
- このコントラクトは、新しい店舗コントラクトを生成する機能を持っています。
-
店舗コントラクトのテンプレート
- 各店舗は、在庫、価格、販売記録などを管理する機能を持つ「店舗コントラクト」に基づいています。
- この店舗コントラクトは、ファクトリー・コントラクトによって複製されるテンプレートのようなものです。
-
新しい店舗の作成
- 販売者がマーケットプレイスに参加したいとき、彼らはファクトリー・コントラクトに指示を出して、自分の店舗コントラクトを生成します。
- ファクトリー・コントラクトは新しい店舗コントラクトをブロックチェーン上にデプロイし、その管理権を販売者に渡します。
-
運用と管理
- 販売者は自分の店舗コントラクトを通じて商品を追加したり、価格を設定したり、売り上げを管理したりできます。
- また、ファクトリー・コントラクトはこれら全ての店舗コントラクトを追跡し、必要に応じて追加の機能やアップデートを提供できます。
このように、ファクトリー・コントラクト・モデルを使うと、簡単に多くの独立したコントラクトを生成し、管理することができ、大規模なアプリケーションやサービスを効率的に運営することが可能になります。
シンプルなクローン・バイトコード
コードの調査や理解を容易にするために、クローンのバイトコード(コントラクトの基本的な指示セット)はシンプルに保たれています。
機能の固定と信頼性
このクローンは変更可能な部分を持たず、一度作成されるとその機能は固定されます。
これにより、予測可能で信頼性の高い動作が保証されます。
小さな運用オーバーヘッド
クローンを使用する時の追加コストは最小限に抑えられます。
各操作にわずかなコストが加わるだけです。
リバートメッセージのエラー処理
何かが間違っていた場合に、そのエラーメッセージを適切にユーザーに伝えることができます。
この取り組みはブロックチェーン上でコントラクトを複製する際のハードルを低くし、より多くの人が利用できるようにするとともに、操作がシンプルで予測可能、かつ安全に行えるようにすることを目指しています。
互換性
この規格は、古いシステムを壊すことなく導入されます。
しかし、以前のバージョンのプロキシコントラクト・バイトコードを使っているシステムは、この新しい標準の機能を使用できません。
テスト
テストケースは、コントラクトが様々な条件下で正しく動作するかをチェックするための検証作業です。
以下のような状況をテストします。
-
引数なしでの呼び出し
- 何も引数を渡さずに関数を実行して、正常に動くか確認します。
- これは最も基本的なテストです。
-
引数ありでの呼び出し
- 特定の引数を関数に渡して、それが期待通りに動作するかをチェックします。
- これにより、関数が正しい入力で正しく動くかを確認できます。
-
固定長の戻り値を伴う呼び出し
- 常に同じ長さのデータを返す関数をテストします。
- これは、戻り値が予想されたサイズであるかどうかを確認するのに役立ちます。
-
可変長の戻り値を伴う呼び出し
- 長さが変わる可能性があるデータを返す関数をテストします。
- これは、関数がさまざまな長さのデータを正しく扱えるかを確認するのに重要です。
-
リバート(失敗)と共に呼び出し
- 意図的に失敗させて関数を呼び出し、エラーメッセージなどの正しいペイロードが返されるかをチェックします。
- これは、エラーが発生した際の振る舞いが適切であるかを確認するためです。
これらのテストを行うことで、コントラクトが予期せぬ状況に対しても正確に動作するかを検証し、信頼性と安全性を高めることができます。
実装にこれらのテストケースが含まれているので、開発者はそれを利用してコントラクトの品質を確保できます。
実装
Standard Proxy
| 0x00000000 36 calldatasize cds
| 0x00000001 3d returndatasize 0 cds
| 0x00000002 3d returndatasize 0 0 cds
| 0x00000003 37 calldatacopy
| 0x00000004 3d returndatasize 0
| 0x00000005 3d returndatasize 0 0
| 0x00000006 3d returndatasize 0 0 0
| 0x00000007 36 calldatasize cds 0 0 0
| 0x00000008 3d returndatasize 0 cds 0 0 0
| 0x00000009 73bebebebebe. push20 0xbebebebe 0xbebe 0 cds 0 0 0
| 0x0000001e 5a gas gas 0xbebe 0 cds 0 0 0
| 0x0000001f f4 delegatecall suc 0
| 0x00000020 3d returndatasize rds suc 0
| 0x00000021 82 dup3 0 rds suc 0
| 0x00000022 80 dup1 0 0 rds suc 0
| 0x00000023 3e returndatacopy suc 0
| 0x00000024 90 swap1 0 suc
| 0x00000025 3d returndatasize rds 0 suc
| 0x00000026 91 swap2 suc 0 rds
| 0x00000027 602b push1 0x2b 0x2b suc 0 rds
| ,=< 0x00000029 57 jumpi 0 rds
| | 0x0000002a fd revert
| `-> 0x0000002b 5b jumpdest 0 rds
\ 0x0000002c f3 return
プロキシコントラクトは、他のコントラクトの機能を利用するために間に入るコントラクトです。
Standard Proxyプロキシの仕組み
この規格には、プロキシコントラクトのコードがどのように見えるかが示されています。
このコードは、外部からのデータを受け取り、それを別の特定のアドレスにあるコントラクトに転送する役割を果たします。
これはデリゲートコールを使って行われ、他のコントラクトの機能を呼び出し先のコントラクトで実行します。
もし実行が成功すれば結果が呼び出し元に返されます。
もし失敗すれば、エラーが呼び出し元に伝えられて実行は中止されます。
ガスコストの削減
このコードはガスコストを削減するために特別な技術を使っています。
EIP211に基づき、不要な操作を減らしてガス消費を抑える工夫がされています。
EIP211については以下の記事を参考にしてください。
処理フロー
上記のコードは、Ethereumのスマートコントラクトで使用されるバイトコードの一部です。
これは、プロキシコントラクトが他のコントラクトへの操作を委譲する時の処理を示しています。
各行はEVM(Ethereum Virtual Machine)の命令とその操作を表しています。
以下は、各ステップの説明です。
-
0x00000000 36 calldatasize cds
- 外部から送られてきたデータ(
calldata
)のサイズを取得します。
- 外部から送られてきたデータ(
-
0x00000001 3d returndatasize 0 cds
- 現在のフレームで利用可能な戻り値のデータサイズを
0
としてスタックにプッシュします。
- 現在のフレームで利用可能な戻り値のデータサイズを
-
0x00000002 3d returndatasize 0 0 cds
- もう一度、戻り値のデータサイズ(ここでは
0
)をスタックにプッシュします。
- もう一度、戻り値のデータサイズ(ここでは
-
0x00000003 37 calldatacopy
- 外部からのデータをEVMのメモリにコピーします。
-
0x00000004 3d returndatasize 0
- 新しい戻り値のデータサイズ(
0
)をスタックにプッシュします。
- 新しい戻り値のデータサイズ(
-
0x00000005 3d returndatasize 0 0
- 再び、戻り値のデータサイズ(
0
)をスタックにプッシュします。
- 再び、戻り値のデータサイズ(
-
0x00000006 3d returndatasize 0 0 0
- さらに、戻り値のデータサイズ(
0
)をスタックにプッシュします。
- さらに、戻り値のデータサイズ(
-
0x00000007 36 calldatasize cds 0 0 0
- もう一度、
calldata
のサイズをスタックにプッシュします。
- もう一度、
-
0x00000008 3d returndatasize 0 cds 0 0 0
- 新しい戻り値のデータサイズ(
0
)をスタックにプッシュします。
- 新しい戻り値のデータサイズ(
-
0x00000009 73bebebebebe push20 0xbebebebe 0xbebe 0 cds 0 0 0
-
delegatecall
を送る先のアドレス(ここでは0xbebebebebebe...
)をスタックにプッシュします。
-
-
0x0000001e 5a gas gas 0xbebe 0 cds 0 0 0
- 利用可能なガスの量をスタックにプッシュします。
-
0x0000001f f4 delegatecall suc 0
- スタックの内容を使って、別のコントラクトに操作を委譲する
delegatecall
を実行します。
- スタックの内容を使って、別のコントラクトに操作を委譲する
-
0x00000020 3d returndatasize rds suc 0
-
delegatecall
からの戻り値のサイズをスタックにプッシュします。
-
-
0x00000021 82 dup3 0 rds suc 0
- スタックの3番目の要素をコピーしてプッシュします。
-
0x00000022 80 dup1 0 0 rds suc 0
- スタックの先頭の要素をコピーしてプッシュします。
-
0x00000023 3e returndatacopy suc 0
-
delegatecall
からの戻り値をメモリにコピーします。
-
-
0x00000024 90 swap1 0 suc
- スタックの上位2つの要素の位置を入れ替えます。
-
0x00000025 3d returndatasize rds 0 suc
- 新しい戻り値のデータサイズをスタックにプッシュします。
-
0x00000026 91 swap2 suc 0 rds
- スタックの上から2番目と3番目の要素を入れ替えます。
-
0x00000027 602b push1 0x2b 0x2b suc 0 rds
- ジャンプ先のアドレス(
0x2b
)をスタックにプッシュします。
- ジャンプ先のアドレス(
-
0x00000029 57 jumpi 0 rds
- 条件付きでジャンプします。
- もしスタックの2番目の要素が
0
なら、指定されたアドレス(0x2b
)にジャンプします。
-
0x0000002a fd revert
- もしジャンプが実行されなければ、操作を中止してすべての変更を元に戻します。
-
0x0000002b 5b jumpdest 0 rds
- ジャンプの目的地です。
- ここから次の命令が実行されます。
-
0x0000002c f3 return
- 最終的な結果を呼び出し元に返します。
この一連の命令は、プロキシコントラクトがデリゲートコールを使用して別のコントラクトのコードを実行し、その結果を呼び出し元に返すプロセスを示しています。
Vanity Address Optimization
| 0x00000000 36 calldatasize cds
| 0x00000001 3d returndatasize 0 cds
| 0x00000002 3d returndatasize 0 0 cds
| 0x00000003 37 calldatacopy
| 0x00000004 3d returndatasize 0
| 0x00000005 3d returndatasize 0 0
| 0x00000006 3d returndatasize 0 0 0
| 0x00000007 36 calldatasize cds 0 0 0
| 0x00000008 3d returndatasize 0 cds 0 0 0
| 0x00000009 6fbebebebebe. push16 0xbebebebe 0xbebe 0 cds 0 0 0
| 0x0000001a 5a gas gas 0xbebe 0 cds 0 0 0
| 0x0000001b f4 delegatecall suc 0
| 0x0000001c 3d returndatasize rds suc 0
| 0x0000001d 82 dup3 0 rds suc 0
| 0x0000001e 80 dup1 0 0 rds suc 0
| 0x0000001f 3e returndatacopy suc 0
| 0x00000020 90 swap1 0 suc
| 0x00000021 3d returndatasize rds 0 suc
| 0x00000022 91 swap2 suc 0 rds
| 0x00000023 6027 push1 0x27 0x27 suc 0 rds
| ,=< 0x00000025 57 jumpi 0 rds
| | 0x00000026 fd revert
| `-> 0x00000027 5b jumpdest 0 rds
\ 0x00000028 f3 return
バニティアドレス最適化は、プロキシコントラクトをブロックチェーンに配置する時の効率を上げる方法です。
ここでの「バニティアドレス」とは、特定のパターン(この場合は先頭に0
が並ぶ)を持つアドレスのことを指します。
この方法を使うと、プロキシコントラクトのサイズを小さくしてデプロイメントコストを削減できます。
具体的には、マスターコントラクト(他のコントラクトに機能を委譲するための元となるコントラクト)を、先頭に0
バイトが含まれる特定のアドレスに配置します。
そして、プロキシコントラクトのコード内で、マスターコントラクトのアドレスを指す部分を短縮します。
たとえば、先頭に4つの0
があるアドレスの場合、通常20
バイト必要なアドレス指定を16
バイトに短縮できます。
これにより、プロキシコントラクトの全体のサイズが小さくなり、デプロイメントに必要なガスが少なくなります。
上記のコードブロックは、この最適化を適用した時のプロキシコントラクトの内部動作を示しています。
コントラクトは外部からのデータを受け取り、短縮されたアドレスを使ってマスターコントラクトに操作を委譲し、その結果を呼び出し元に返します。
もし操作が失敗した場合は、エラーメッセージが適切に伝えられます。
この最適化はプロキシコントラクトをより小さく、よりコスト効率良くデプロイするための方法です。
これにより、特に多くのコントラクトをデプロイする必要がある大規模なアプリケーションやサービスで、コストと効率の面で大きなメリットが得られます。
処理フロー
上記のコードは、Ethereumのスマートコントラクトで使用されるバイトコードの一部であり、EVM(Ethereum Virtual Machine)の命令セットを表しています。
この特定のコードは、プロキシコントラクトの一例であり、別のコントラクトへの呼び出しを委譲する機能を持っています。
以下は、各ステップの説明です。
-
0x00000000 36 calldatasize cds
- 外部から送られてきたデータ(
calldata
)のサイズをスタックにプッシュします。
- 外部から送られてきたデータ(
-
0x00000001 3d returndatasize 0 cds
- 現在のコンテキストで利用可能な戻り値データのサイズ(この時点では
0
)をスタックにプッシュします。
- 現在のコンテキストで利用可能な戻り値データのサイズ(この時点では
-
0x00000002 3d returndatasize 0 0 cds
- 再び、戻り値データのサイズ(
0
)をスタックにプッシュします。
- 再び、戻り値データのサイズ(
-
0x00000003 37 calldatacopy
- 外部からのデータ(
calldata
)をEVMのメモリにコピーします。
- 外部からのデータ(
-
0x00000004 3d returndatasize 0
- 新しい戻り値データのサイズ(
0
)をスタックにプッシュします。
- 新しい戻り値データのサイズ(
-
0x00000005 3d returndatasize 0 0
- さらに、戻り値データのサイズ(
0
)をスタックにプッシュします。
- さらに、戻り値データのサイズ(
-
0x00000006 3d returndatasize 0 0 0
- もう一度、戻り値データのサイズ(
0
)をスタックにプッシュします。
- もう一度、戻り値データのサイズ(
-
0x00000007 36 calldatasize cds 0 0 0
-
calldata
のサイズを再度スタックにプッシュします。
-
-
0x00000008 3d returndatasize 0 cds 0 0 0
- 新しい戻り値データのサイズ(
0
)をスタックにプッシュします。
- 新しい戻り値データのサイズ(
-
0x00000009 6fbebebebebe push16 0xbebebebe 0xbebe 0 cds 0 0 0
:- 委譲先コントラクトのアドレス(
0xbebebebe...
)の一部をスタックにプッシュします。
- 委譲先コントラクトのアドレス(
-
0x0000001a 5a gas gas 0xbebe 0 cds 0 0 0
- 現在の利用可能なガス量をスタックにプッシュします。
-
0x0000001b f4 delegatecall suc 0
- 委譲先コントラクトに操作を委譲し、成功したかどうかの結果(成功なら
1
、失敗なら0
)をスタックにプッシュします。
- 委譲先コントラクトに操作を委譲し、成功したかどうかの結果(成功なら
-
0x0000001c 3d returndatasize rds suc 0
-
delegatecall
からの戻り値データのサイズをスタックにプッシュします。
-
-
0x0000001d 82 dup3 0 rds suc 0
- スタックの3番目の要素(この場合
0
)をコピーしてスタックのトップにプッシュします。
- スタックの3番目の要素(この場合
-
0x0000001e 80 dup1 0 0 rds suc 0
- スタックのトップの要素(この場合
0
)をコピーしてスタックのトップにプッシュします。
- スタックのトップの要素(この場合
-
0x0000001f 3e returndatacopy suc 0
-
delegatecall
からの戻り値をメモリにコピーします。
-
-
0x00000020 90 swap1 0 suc
- スタックのトップ2つの要素の位置を入れ替えます。
-
0x00000021 3d returndatasize rds 0 suc
- 新しい戻り値データのサイズをスタックにプッシュします。
-
0x00000022 91 swap2 suc 0 rds
- スタックの2番目と3番目の要素の位置を入れ替えます。
-
0x00000023 6027 push1 0x27 0x27 suc 0 rds
- ジャンプ先のアドレス(
0x27
)をスタックにプッシュします。
- ジャンプ先のアドレス(
-
0x00000025 57 jumpi 0 rds
- 条件付きジャンプを行います。
- もしスタックの2番目の要素(
0
)が0
でなければ、指定されたアドレス(0x27
)にジャンプします。
-
0x00000026 fd revert
- ジャンプが実行されなかった場合(つまり
delegatecall
が失敗した場合)、操作を中止してすべての変更を元に戻します。
- ジャンプが実行されなかった場合(つまり
-
0x00000027 5b jumpdest 0 rds
- ジャンプの目的地です。
- ここから次の命令が実行されます。
-
0x00000028 f3 return
- 最終的な結果を呼び出し元に返します。
このコードは、プロキシコントラクトが別のコントラクトへの呼び出しを委譲するプロセスを実装しており、その過程でメモリの操作、条件付きジャンプ、エラーハンドリングなど複数のステップが含まれています。
Standard Proxyとの比較
両方のコードはEthereumのスマートコントラクト内でデリゲートコールを行うプロキシコントラクトの動作を示しています。
基本的な処理の流れは同じですが、以下の点で異なります。
Standard Proxy
-
0x00000009 73bebebebebe. push20 0xbebebebe
- この命令は
20
バイトのアドレス(0xbebebebebebe...
)をスタックにプッシュしています。 - これはデリゲートコールの目的地アドレスです。
- この命令は
-
0x00000027 602b push1 0x2b
- この命令は1バイトの値(
0x2b
)をスタックにプッシュしています。 - これは後に条件付きジャンプ(
jumpi
)の目的地として使用されます。
- この命令は1バイトの値(
-
0x0000002b 5b jumpdest 0 rds
- この
jumpdest
はアドレス0x2b
に設定されています。
- この
Vanity Address Optimization
-
0x00000009 6fbebebebebe. push16 0xbebebebe
- この命令は
16
バイトのアドレス(0xbebebebe
)をスタックにプッシュしています。 - このアドレスは最初のコードと同じアドレスの末尾
16
バイトですが、長さが異なります。 - これは、バニティアドレス最適化が適用され、先頭の
0
バイトが省略されているためです。
- この命令は
-
0x00000023 6027 push1 0x27
- この命令は1バイトの値(
0x27
)をスタックにプッシュしています。 - この値は最初のコードの
0x2b
よりも小さく、これもバニティアドレス最適化の結果、アドレスが短くなったために必要なジャンプアドレスが変わったことを示しています。
- この命令は1バイトの値(
-
0x00000027 5b jumpdest 0 rds
- この
jumpdest
はアドレス0x27
に設定されています。
- この
異なる点
-
アドレスの長さ
- 最初のコードは
20
バイトのアドレスを使用していますが、2番目のコードはバニティアドレス最適化により16
バイトのアドレスを使用しています。
- 最初のコードは
-
jumpdest
- バニティアドレス最適化の結果、2番目のコードのジャンプデスティネーションアドレスは最初のコードよりも小さくなっています。
これらの違いは、プロキシコントラクトのサイズを削減し、デプロイメント時のガスコストを節約するための最適化を反映しています。
バニティアドレス最適化は、アドレスの長さを短縮し、それに伴い必要なコードを調整することで実現されています。
引用
Peter Murray (@yarrumretep), Nate Welch (@flygoing), Joe Messerman (@JAMesserman), "ERC-1167: Minimal Proxy Contract," Ethereum Improvement Proposals, no. 1167, June 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1167.
最後に
今回は「簡単にガス効率良くコントラクトを複製する仕組みを提案している規格であるERC1167」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!