はじめに

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

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

今回は、コントラクトや関数を呼び出すとき、使用したメモリに対してのみガス代を支払う仕組みを提案しているEIP5についてまとめていきます!

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

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

概要

このEIP(Ethereum Improvement Proposal)は、Ethereum Virtual Machine(EVM)内で、文字列やその他の動的サイズ配列を返す関数を呼び出せるようにするための提案です。 現在のシステムでは、EVM内で別のコントラクトや関数を呼び出す時、出力データのサイズを事前に指定する必要があります。 大きなサイズを指定することも可能ですが、実際には書き込まれていないメモリにもガス(Ethereumネットワークでの処理費用)を支払う必要があるため、動的サイズのデータを返すことは非効率的でコストがかかりすぎます。 そのため、現在の方法では実用的ではありません。

このEIPで提案されている解決策は、関数がCALLを終了する時に、実際に書き込まれたメモリに対してのみガスを支払うというものです。 これにより、開発者は必要なデータサイズに応じてメモリを割り当てることができ、使用されないメモリに対するガスの無駄遣いを防ぐことが可能になります。 特に、複雑なデータ構造や大量のデータを扱うコントラクトにとって、この改善は大きなメリットをもたらします。

仕様

EthereumのCALL、CALLCODE、DELEGATECALL(これらをCALL*と総称)のガスとメモリの使用方法に関する仕様変更についてです。 CREATEはメモリに書き込まないので、この変更の影響を受けません。

CALL*に与えられる引数がgas, address, value, input_start, input_size, output_start, output_sizeの場合、オペコード実行の開始時には、input_start + input_sizeに対してのみメモリ拡張に関するガスが支払われます。 しかし、output_start + output_sizeについてはガスの支払いはされません。

呼び出されたコントラクトがサイズnのデータを返した場合、呼び出し元コントラクトのメモリはoutput_start + min(output_size, n)まで拡張されます。 このメモリ拡張にかかるガスは呼び出し元コントラクトに送られ、出力データは(output_start, output_start + min(n, output_size))の範囲に書き込まれます。

この変更により、呼び出し元コントラクトはオペコードの開始時と終了時の両方でガスを使い果たす可能性があります。 呼び出し後、MSIZEオペコードは実際に拡張されたメモリのサイズを返すようになります。

これにより、CALL*のガスとメモリ使用の効率が向上し、開発者は出力データのサイズに合わせてより正確にガス使用量を計算できるようになります。 特に、大規模なデータを扱うコントラクトや、複数のコントラクト間でのデータ交換が頻繁に行われる場合には、この仕様変更が大きな利点をもたらします。

他のコントラクトを呼び出す仕組みについては以下の記事を参考にしてください。

CALL、CALLCODE、DELEGATECALLについては以下の記事を参考にしてください。

動機

この提案は、Ethereumのコントラクト間での呼び出しにおいて、出力データ用のメモリ領域を事前に確保するという実践的な方法に関するものです。 通常、サブルーチンにメモリの任意の領域への書き込みを許可することは危険であるため、呼び出しの出力用に特定のメモリ領域を確保するのが望ましいとされています。 しかし、実際には、呼び出しを行う前にその出力サイズを知ることが難しい場合が多いです。 というのも、データが別のコントラクトのストレージにあり、通常はアクセスできず、そのサイズを知るためにはそのコントラクトへの別の呼び出しが必要になることがあるからです。

加えて、実際には書き込まれていないメモリ領域にガスを支払うのは不必要です。

この提案は、これらの問題を解決することを目指しています。 呼び出し元コントラクトは、自身のメモリ領域の最後に非常に大きな領域を確保することを選ぶことができます。 そして、呼び出されたコントラクトは、戻る(リターンする)ことによってその領域に「書き込む」ことができ、実際に書き込まれたメモリ領域に対してのみ呼び出し元はガスを支払います。

これにより、文字列や動的サイズの配列などの動的データを非常に柔軟に返すことが可能になります。 さらに、返されたデータのサイズを特定することも可能です。 呼び出し元がoutput_startをMSIZE、output_sizeを2**256-1として使用する場合、実際に書き込まれたメモリ領域は(output_start, MSIZE)となります(この場合のMSIZEは呼び出し後に評価されます)。 これは、他のコントラクトを呼び出してその出力をそのまま返す「プロキシ」コントラクトにとって重要です。 つまり、これらのコントラクトは入力と出力の両方をtransferする能力を持ちます。 このためには、呼び出し元が(1)事前に出力のサイズを知る必要がなく、(2)呼び出し後に出力のサイズを決定できることが重要です。

サブルーチン

サブルーチンは、プログラミングにおいてコードの一部を独立した単位として分割する手法の1つです。 これは、特定のタスクや機能を実行するための一連の命令(コード)を含んでいます。 サブルーチンは、異なるプログラムの部分から何度も呼び出されることができるため、コードの再利用性を高め、プログラムの構造をより明確にし、全体の管理を容易にします。

サブルーチンの特徴は以下の通りです。

再利用性 同じコードを何度も書かずに、必要なときにサブルーチンを呼び出すことができます。 これにより、コードの重複を減らし、メンテナンスが容易になります。

構造化 サブルーチンを使うことで、プログラムをより小さく、管理しやすい部分に分割できます。 これにより、プログラムの理解、開発、テストが容易になります。

抽象化 サブルーチンは、プログラムの特定の機能をカプセル化し、それを実行するための詳細を隠します。 この抽象化により、プログラマはより高いレベルでのプログラム設計に集中できます。

サブルーチンは、一般的に「関数」または「メソッド」と呼ばれることが多く、特にオブジェクト指向プログラミングではメソッドという用語がよく使われます。 サブルーチンは引数(入力パラメータ)を取り、戻り値(結果)を返すことができます。 これにより、汎用性が高く、柔軟なコードを書くことが可能になります。

補足

この説明は、Ethereum Virtual Machine(EVM)における問題への対応方法に関するもので、EVMに最小限の変更を加えることを提案しています。 同様の目的を達成するために検討された他の手法には、オペコード自体やその引数の数を変更することが含まれていました。 また、output_sizeが2**256-1に等しい場合にのみガス計算を変更するという案もありました。 しかし、実装における主な課題は、CALLのコードの周りでメモリを2箇所拡張する必要がある点であり、この方法は単純化にはならないと考えられていました。

以前の提案段階では、スタック上に返されたデータのサイズも追加することが検討されていましたが、上述したMSIZEメカニズムが十分であり、より後方互換性が高いと判断されました。

この提案に関するコメントや詳細な議論は、以下のページにて確認することができます。 このページでは、提案の背景や、コミュニティからのフィードバック、提案への様々な視点や意見が交わされています。

互換性

この提案は、コントラクトがガスカウンターやメモリサイズにアクセスできるようにすることで、コントラクトの動作を変更します。 しかし、以下の理由により、既存のコントラクトがこの変更によって影響を受ける可能性は低いとされています。

ガスに関して

この提案では、仮想マシン(VM)は以前よりも多くのガスを支払うことはありません。 通常、コントラクトは、使用するガスが少なくてもその動作が変わらないように設計されています。 もしより多くのガスが使用された場合、サブコールに必要なガスの厳密な見積もりを行ったコントラクトはガス不足になる可能性がありますが、この提案ではコントラクトが呼び出し元に返すガスが増える可能性があります。

メモリサイズに関して

MSIZEオペコードは通常、使用されていない場所にメモリを割り当てるために使用されます。 この提案による意味論の変更は、既存のコントラクトに2つの方法で影響を与える可能性があります。 1つは、割り当てられたメモリ内での重複です。 CALLを使用すると、コントラクトは特定のメモリスライスを割り当てたいと考えるかもしれませんが、呼び出されたコントラクトがその領域に書き込まない場合、MSIZEを使用した後続のメモリ割り当てが、変更前よりも小さくなったこのスライスと重複する可能性があります。 しかし、このようなコントラクトが存在することはあまり考えられません。

もう1つは、メモリアドレスの変更です。 MSIZEを使用してメモリが割り当てられた場合、変更後のオブジェクトのメモリアドレスが異なる可能性があります。 しかし、コントラクトはメモリ内のオブジェクトが再配置可能であるように設計されるべきで、オブジェクトの絶対位置や他のオブジェクトとの相対位置は通常、重要ではありません。 配列のように単一の割り当てで確保されるオブジェクトは、この変更の影響を受けることはありません。

結論として、この提案によるコントラクトの動作への影響は非常に限定的であり、既存のコントラクトが大きな影響を受けることは考えにくいとされています。

実装

この説明は、Ethereum Virtual Machine(EVM)の実装者に対する指示です。 具体的には、呼び出しの終わりまでメモリを拡大する作業を行わないように注意する必要があるとされています。 この作業は、十分なガスがまだ利用可能であることを確認した後にのみ実行すべきです。 Ethereum Improvement Proposal(EIP)のこの変更における典型的な利用例は、出力用に2**256-1バイトのメモリを「予約」することです。

この変更のPythonにおける実装例は、Vitalik Buterinのウェブサイトで公開されています。 古いバージョンの実装はこちらで、新しいバージョンの実装はこちらで確認することができます。

このEIPによる変更の目的は、メモリの管理とガスの消費をより効率的にすることにあります。 EVMの実装者は、これらの変更を適切に取り入れるように指示されています。 特に重要なのは、メモリ拡大を行う時は、まだ十分なガスが残っていることを確認し、慎重に進める必要があるという点です。これにより、EVMの効率と安全性が向上します。

引用

Christian Reitwiessner c@ethdev.com, "EIP-5: Gas Usage for RETURN and CALL*," Ethereum Improvement Proposals, no. 5, November 2015. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5.

最後に

今回は「コントラクトや関数を呼び出すとき、使用したメモリに対してのみガス代を支払う仕組みを提案しているEIP5」についてまとめてきました! いかがだったでしょうか?

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

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