はじめに

初めまして。 CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です! スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。

代表的なゲームはクリプトスペルズというブロックチェーンゲームです。

今回は、EIP170を拡張し、初期化コード(initcode)に対して最大サイズ制限を設け、initcodeの32バイトごとに2ガスの支払いを課す提案をしているEIP3860についてまとめていきます!

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

他にも様々なEIPについてまとめています。

概要

EIP170は、Ethereumのスマートコントラクトのコードサイズに上限を設ける提案です。 ここで言及されている拡張は、この上限を初期化コード(initcode)にも適用し、その上限を「2倍のMAX_CODE_SIZE」として設定しています。 具体的には、スマートコントラクトの本体コードの最大サイズが24,576バイト(MAX_CODE_SIZE)であるため、初期化コードの最大サイズはこれの2倍の49,152バイト(MAX_INITCODE_SIZE)となります。

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

初期化コードとは、スマートコントラクトがブロックチェーン上にデプロイされる時に一度だけ実行されるコードのことで、コントラクトのバイトコードを生成したり、コントラクトの状態を初期化するために使用されます。 この制限を設けることで、ブロックチェーンの効率性やセキュリティが向上します。

さらに、初期化コードの各32バイトチャンクに対して2ガス支払うことで、ジャンプ宛先(jumpdest)分析のコストを表現しています。 jumpdestとは、スマートコントラクトのコード内でジャンプ(分岐)する先が有効かどうかをチェックするプロセスです。 このガスの支払いは、デプロイ時のコストをより正確に反映させ、不要に大きなコードをデプロイすることの抑制にも繋がります。

最後に、このサイズ制限により、EVM(Ethereum Virtual Machine)のコードサイズ、コードオフセット(プログラムカウンタPC)、ジャンプオフセットが16ビット値に収まるという利点があります。 これは、EVMの実装や最適化において扱いやすいサイズであり、処理の効率化に貢献します。

この拡張はスマートコントラクトの初期化コードにサイズ上限を設定し、そのデプロイにかかるコストをより適切に反映させることで、Ethereumの効率性とセキュリティを向上させることを目的としています。

動機

Ethereumのスマートコントラクトを作成する時には、実行前に初期化コード(initcode)に対してジャンプ宛先(jumpdest)分析を行う必要があります。 この分析作業は、initcode`のサイズに比例して増加します。 しかし、現在この作業にはガスが計測されておらず、サイズに対するプロトコルによる上限も設定されていません。

現在、以下の3つのコストが発生します。

  • 初期化コード(initcode)に対するコスト(いわゆるcalldataコスト)
    • 値がゼロのバイトについては4ガス、それ以外の場合は16ガスがかかります。
  • デプロイされたコードの結果に対するコスト
    • バイトあたり200ガスがかかります。
  • CREATE2の場合のアドレス計算(コードのハッシュ化)コスト
    • 単語あたり6ガスがかかります。

これらのコストのうち、初期化コードに関連するのは第1のコストのみであり、それもスマートコントラクト作成トランザクションの場合に限ります。 CREATE/CREATE2の場合、このようなコストは発生せず、比較的安価にinitcodeのバリエーションをプログラム的に生成することが可能です。 過去には、2017年にgeth 1.6.5で修正された脆弱性を利用して悪意のあるinitcodeを作成することが可能でした。

さらに、サイズ制限がないため、一部のEVM提案について長期にわたる議論が行われ、設計に影響を与えたり機能の遅延やキャンセルを引き起こすことがありました。

この提案は主に以下の3つの理由によって動機付けられています。

  • 将来のリスクを最小限に抑えるために、initcodeにかかるコストを公正に(特に長さに比例して)課すこと。
  • 将来的に拡張可能なコストシステムを持つこと。
  • コードサイズ、コードオフセット(PC)、ジャンプオフセットが16ビットに収まる明示的な制限により、EVMエンジンを単純化すること。

この提案は初期化コードに対する公平な課金を実現し、将来にわたって拡張可能なコストシステムを確立すること、そしてEVMの実装を単純化することを目的としています。 これにより、Ethereumネットワークの効率性と安全性が向上することが期待されます。

仕様

このテキストは、Ethereumスマートコントラクトの初期化コード(initcode)に関するパラメータとルールを定義しています。主なポイントを明確にするために、まずパラメータを表形式で示し、その後ルールについて簡単に説明します。

パラメータ

定数 値
INITCODE_WORD_COST 2
MAX_INITCODE_SIZE 2 * MAX_CODE_SIZE

ここで、MAX_CODE_SIZEはEIP170によって24,576と定義されています。 これは、スマートコントラクトのコードサイズに設定された上限値です。

ルール

  • トランザクションデータ(initcode)の長さがMAX_INITCODE_SIZEを超える場合、そのトランザクションは無効です。

    • これは、トランザクションが内部ガスコスト要件を満たさない場合に無効と見なされるのと同様です。
  • コントラクト作成トランザクションの場合、トランザクションデータコストの計算式にinitcode_cost(initcode)を加えます。

    • ここで、initcode_cost(initcode)はINITCODE_WORD_COST * ceil(len(initcode) / 32)で計算されます。
    • これはトランザクションの内部コストに含まれ、initcodeのコストをカバーするだけのガスがないトランザクションは無効です。
  • CREATEまたはCREATE2命令で使用されるinitcodeの長さがMAX_INITCODE_SIZEを超える場合、命令の実行は例外的に中断されます(ガス不足のように)。

  • CREATEおよびCREATE2命令に対しては、initcode_cost(initcode)に等しい追加のガスコストを課します。

    • このコストは、結果として得られるコントラクトアドレスの計算とinitcodeの実行の前、またはその時に差し引かれます(これは、CREATE2で適用されるハッシュコストと同時またはその前に意味します)。

これらのルールは、Ethereum上でのスマートコントラクトのデプロイメントをより公平で効率的にするために設計されています。 initcodeに対する明確なサイズ制限とコスト支払いにより、ネットワークのセキュリティとパフォーマンスを向上させることが目指されています。

補足

一定のガス代

Ethereumのスマートコントラクトの初期化コード(initcode)のガスコスト定数INITCODE_WORD_COSTを選択するためのパフォーマンスベンチマークに関するものです。 ベンチマークの基準としては、4.0 GHzのx86_64 CPU上での70 Mgas/sのガスリミットターゲットに合わせたgeth 1.10.9におけるKECCAK256ハッシュのパフォーマンスが使用されています。

各EVM実装のベンチマーク結果を以下の表にまとめました。

EVM実装 バージョン MB/s B/CPUサイクル CPUサイクル/B 1Bあたりのコスト 32Bあたりのコスト
geth/KECCAK256 1.10.9 357 1.8 0.6 0.2 6.0
geth 1.10.9 1091 5.5 0.2 0.1 2.0
evmone/Baseline 0.8.2 727 3.7 0.3 0.1 2.9
evmone/Advanced 0.8.2 155 0.8 1.3 0.4 13.8
  • MB/s
    • 秒間に処理できるメガバイト数。
  • B/CPUサイクル
    • CPUサイクルあたりに処理できるバイト数。
  • CPUサイクル/B
    • 1バイトを処理するのに必要なCPUサイクル数。
  • 1Bあたりのコスト
    • 1バイトを処理するのにかかるガスコスト。
  • 32Bあたりのコスト
    • 32バイトを処理するのにかかるガスコスト。

このベンチマークからは、各EVM実装におけるパフォーマンスの差異が明確に示されています。 例えば、gethのバージョン1.10.9は非常に高いMB/sの値を示しており、1Bあたりのコストも非常に低くなっています。 一方で、evmone/Advancedのバージョン0.8.2では、MB/sの値が低く、1Bあたりのコストが高くなっています。

INITCODE_WORD_COSTの値を決定する時には、これらのベンチマークの結果を考慮し、異なる実装間で最悪のケースのパフォーマンスを基に選択されます。 これにより、Ethereumネットワーク上でのトランザクションの効率性と公平性が保証されます。

ガスコストの選定基準

Gethの実装とKECCAK256のパフォーマンスを比較した結果、単語(32バイトチャンク)あたり2ガスのコストが選択されました。 これは、1バイトあたりのコストが0.0625ガスであることを意味します。 EVMでは分数のガスコストが許可されていないため、単語ごとに課金することでこれを近似します。

さらに、単語ごとのガス計算は、EIP1014のCREATE2のハッシュコスト計算と互換性があります。 そのため、CREATEとCREATE2の実装は同じものを使用できてコスト定数のみが異なります。  活性化前はCREATEが0、CREATE2が6、活性化後はCREATEが2、CREATE2が6 + 2です。

initcodeのサイズ制限の理由

上限が設定されている場合、検索パラメータが大幅に削減されるため、最悪のシナリオを推定して作成するのが容易になります。 これにより、バイトあたりのガスをより楽観的に選択することが可能になります。

上限がない場合、未知のリスクを考慮してコストを高く設定する必要があります。 既存のほとんどのinitcodeが提案された制限を超えないことを考慮すると、過度に保守的なコストでコントラクトをペナルティすることは不要と思われます。

initcodeのサイズ制限の影響

新しいコントラクトが作成される時、ほとんどの場合(場合によっては全ての場合)で、結果として得られるランタイムコードはinitcode自体からコピーされます。 基本的なケースでは、2倍のMAX_CODE_SIZEの制限により、ランタイムコードにMAX_CODE_SIZE、コントラクトのコンストラクタコードにもう1つのMAX_CODE_SIZEを許可します。 しかし、単一のcreateトランザクションで複数のコントラクトがデプロイされる場合には、この制限が実際的な意味を持つ可能性があります。

createトランザクションのinitcodeコスト

createトランザクションデータのinitcodeコスト(バイトあたり0.0625ガス)は、トランザクションデータコスト(バイトあたり4または16ガス)と比較して無視できます。 それにもかかわらず、一貫性を保つため、そしてより重要なのは将来の互換性のために、この仕様に含めることにしました。

initcodeの制限違反の報告方法

CREATE/CREATE2におけるinitcodeサイズ制限の違反は、実行の例外的な中断をもたらすと規定しました。 これにより、スタックアンダーフロー、メモリ拡張、静的コール違反、initcodeハッシュコスト、およびこのEIPによって導入されるinitcodeコストを含む、早期のガス不足チェックのグループに分類されます。 これらは、後の「コール深度と残高」チェックより先に実行されます。 この選択により、チェックの順序に一貫性がもたらされ、実装の複雑さが低減されます(ガス不足のチェックは任意の順序で実行できます)。

Ethereumのスマートコントラクト実行時のチェック(検証プロセス)の順序について説明しています。 特に、Ethereum Virtual Machine (EVM) がトランザクションやスマートコントラクトのコールを処理する時に行う様々なチェックの中で、「軽い」チェック(コール深度と残高のチェックなど)があり、これらは他のより厳格なチェックよりも先に実行されます。

「軽い」チェックとは

  • コール深度チェック
    • Ethereumには、スマートコントラクトのコールチェーンの最大深度があり、これを超えるとトランザクションは失敗します。
    • これは、無限のループや過度に深いコールスタックによるリソースの枯渇を防ぐための措置です。
  • 残高チェック
    • トランザクションが実行される前に、アカウントがトランザクションコストを支払うのに十分な残高を持っているかどうかがチェックされます。
    • 不十分な場合、トランザクションは失敗します。

これらのチェックは比較的「軽い」ものと見なされ、実行の早い段階で行われます。これに対して、ガス不足やメモリ拡張のような他のチェックはより複雑で、実行により多くの計算リソースを必要とすることがあります。

チェックの順序と実装の複雑さ

チェックを一定の順序で行うことにより、EVMの実装とトランザクションの処理が簡素化されます。 このアプローチでは、各トランザクションを効率的に検証し、無効なトランザクションを早期に排除することができます。 このようにして、不必要な計算リソースの消費を避け、ネットワークの全体的な効率を向上させることができます。

さらに、一定の順序でチェックを行うことで、EVMの実装者はより一貫したアプローチを取ることができ、エラーハンドリングや異常検出の複雑さを軽減することができます。 これは、Ethereumネットワークの安定性と信頼性を高める上で重要な要素です。

互換性

提案されたEthereum改善提案(EIP)が「ネットワークアップグレード」を必要とすることについて述べています。 ネットワークアップグレードとは、Ethereumの合意形成ルールに変更を加えることを意味します。 これは、Ethereumネットワーク上でのトランザクションやスマートコントラクトの実行方法に影響を与える可能性がある重要な変更です。

ネットワークアップグレードの意味

合意形成ルールの変更

Ethereumネットワークは、トランザクションが有効かどうか、ブロックが正しいかどうかを判断するために、一連のルール(合意形成ルール)に従います。 このEIPによるルールの変更は、新しいトランザクションやコントラクトのデプロイ方法に影響を及ぼすため、全ノードが新しいルールに同意し、それに従う必要があります。

アップグレードの必要性

このような変更を実施するには、ネットワーク上の全ノード(ユーザー、マイナー、開発者など)がソフトウェアを更新して新しいルールに対応する必要があります。 これがネットワークアップグレードです。

既存のコントラクトへの影響

既存のコントラクトの安全性

このEIPは既にデプロイされているコントラクトには影響を与えません。 つまり、既存のスマートコントラクトはこの変更の後も引き続き正常に機能します。

一部のトランザクションへの影響

提案されたサイズ制限を超えるinitcodeを含むトランザクションは、ブロックに含まれる可能性がありますが、実行時に「例外的な中断」を引き起こします。 これは、そのようなトランザクションがネットワークの新しいルールに違反しているため、正常に完了せず、エラーとして扱われることを意味します。

テスト

これらのテストケースは、Ethereumのスマートコントラクトの作成トランザクションやCREATE/CREATE2命令に関連しています。 特に、initcode(スマートコントラクトを初期化するためのコード)の処理とそのガスコストに焦点を当てています。

1. initcodeコストをカバーできるガスリミットを持つ作成トランザクション

このテストケースでは、トランザクションに割り当てられたガスリミットが、initcodeの実行に必要なコストをカバーするのに十分であるかを確認します。 initcodeコストとは、スマートコントラクトの初期化コードを実行するために必要なガスのことです。 このテストは、ガスリミットが適切に設定された場合にトランザクションが成功することを保証するために重要です。

2. initcodeコストを除く固有のコストをカバーできるガスリミットを持つ作成トランザクション

ここでは、トランザクションのガスリミットが、initcodeのコストを除く、トランザクションの実行に必要なその他の全てのコスト(例えば、データ送信コストやコントラクト作成コスト)をカバーできるかどうかをテストします。 このケースは、initcodeのコストが高くトランザクションの失敗を引き起こす可能性がある状況をシミュレートします。

3. len(initcode)がMAX_INITCODE_SIZEに等しいCREATE/CREATE2/作成トランザクション

このテストでは、initcodeの長さが許容される最大サイズ(MAX_INITCODE_SIZE)に正確に等しい場合のトランザクションの挙動を評価します。 目的は、システムがこのサイズの限界で正しく動作し、トランザクションが成功することを確認することです。

4. len(initcode)がMAX_INITCODE_SIZE+1に等しいCREATE/CREATE2/作成トランザクション

ここでは、initcodeの長さが許容される最大サイズを1バイト超える場合のトランザクションをテストします。 このケースは、サイズ制限を超えたinitcodeを含むトランザクションがどのように扱われるか、つまり例外的に中断されるかどうかを確認するためのものです。

これらのテストケースは、Ethereumネットワークがinitcodeのサイズとコストに関連する様々なシナリオを正しく処理できるかどうかを検証するために重要です。 これにより、ネットワークの安定性と予測可能性が向上します。

セキュリティ

クライアント実装への影響

このEIPにより、ジャンプ宛先(jumpdest)分析に基づく攻撃が問題となりにくくなります。 ジャンプ宛先分析とは、スマートコントラクトのコード内でのジャンプ(分岐)ポイントを特定するためのプロセスです。 このプロセスは、攻撃者が意図的に巨大なinitcodeを提供することで、クライアントのパフォーマンスを低下させる攻撃に利用される可能性があります。 このEIPによってinitcodeのサイズに上限が設定され、initcodeの各32バイトチャンクに対してガスを課すことで、この種の攻撃が実行されるコストが大幅に増加します。 結果として、Ethereumクライアントの堅牢性が向上します。

レイヤー2への影響

レイヤー2では、以前は存在しなかった失敗モードが導入されます。 たとえば、複数レベルのコントラクト階層をデプロイするファクトリコントラクトが存在する可能性があり、最初のコントラクトのinitcodeに複数のコントラクトのコードが含まれている場合があります。 このEIPの著者は、このようなコントラクトが存在することを認識していませんが、新しいサイズ制限により、これらのコントラクトのデプロイメントが影響を受ける可能性があります。

攻撃コストの増加

ロンドンアップグレード時点での30Mガスリミットでは、合計約1.3GBのinitcodeのjumpdestをトリガーすることが可能です。 このEIPにより、そのような攻撃を行うためのコストは約80Mガス増加します。 これにより、大量のinitcodeを使った攻撃の実行コストが増加し、そのような攻撃が行われる可能性が減少します。

このEIPはEthereumクライアントの堅牢性を向上させる一方で、特定の複雑なコントラクトデプロイメントパターンに対する新たな制約を導入する可能性があります。 また、大規模なinitcodeを用いた攻撃のコストが増加するため、ネットワークのセキュリティが向上すると期待されます。

引用

Martin Holst Swende (@holiman), Paweł Bylica (@chfast), Alex Beregszaszi (@axic), Andrei Maiboroda (@gumb0), "EIP-3860: Limit and meter initcode," Ethereum Improvement Proposals, no. 3860, July 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3860.

最後に

今回は「EIP170を拡張し、初期化コード(initcode)に対して最大サイズ制限を設け、initcodeの32バイトごとに2ガスの支払いを課す提案をしているEIP3860」についてまとめてきました! いかがだったでしょうか?

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

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