
イーサリアムスマートコントラクトのGas最適化におけるトップ10ベストプラクティス
TechFlow厳選深潮セレクト

イーサリアムスマートコントラクトのGas最適化におけるトップ10ベストプラクティス
これらの実践に従うことで、開発者はスマートコントラクトのGas手数料を削減し、取引コストを下げ、より効率的でユーザーフレンドリーなアプリケーションを構築できます。
執筆:Certik
イーサリアムメインネットにおけるGas手数料は、ネットワーク混雑時により顕著になる長年の課題です。ピーク時には、ユーザーが非常に高い取引手数料を支払わざるを得ないことがあります。そのため、スマートコントラクト開発段階でのGas最適化が極めて重要となります。Gas消費の最適化は取引コストを効果的に削減するだけでなく、取引効率も向上させ、ユーザーに経済的かつ効率的なブロックチェーン体験を提供できます。
本稿では、イーサリアム仮想マシン(EVM)のGas手数料メカニズム、Gas最適化に関する主要な概念、およびスマートコントラクト開発時のGas最適化ベストプラクティスについて概説します。これらの内容を通じて、開発者にインスピレーションと実用的な支援を提供するとともに、一般ユーザーがEVMのGas手数料の仕組みをより深く理解できるようにし、共にブロックチェーンエコシステムの課題に取り組んでいきます。
EVMのGas手数料メカニズム概要
EVM互換ネットワークにおいて、「Gas」とは特定の操作を実行するために必要な計算能力を測定する単位です。
下図はEVMの構造レイアウトを示しています。この図では、Gas消費は3つの部分に分けられます。操作の実行、外部メッセージ呼び出し、およびメモリとストレージの読み書きです。

出典:イーサリアム公式サイト[1]
すべてのトランザクション実行には計算リソースが必要であるため、無限ループやサービス拒否(DoS)攻撃を防ぐために一定の手数料が課されます。トランザクション完了に必要な費用は「Gas手数料」と呼ばれます。
EIP-1559(ロンドンハードフォーク)導入以降、Gas手数料は以下の式で計算されます:
Gas手数料 = 使用したGas量 × (基本料金 + 優先料金)
基本料金は燃却され、優先料金は検証者がトランザクションをブロックに含めるインセンティブとして支払われます。トランザクション送信時に高い優先料金を設定することで、次のブロックに含まれる可能性が高まります。これは検証者への「チップ」のようなものです。
1. EVMにおけるGas最適化の理解
Solidityでスマートコントラクトをコンパイルすると、コントラクトは一連の「オペコード(opcodes)」に変換されます。
オペコード(例:コントラクト作成、メッセージ呼び出し、アカウントストレージアクセス、仮想マシン上での操作実行など)にはそれぞれ公認されたGas消費コストがあり、これらはイーサリアムイエローペーパー[2]に記載されています。

複数のEIPによる修正を経て、一部オペコードのGasコストは調整されており、イエローペーパーの記述と差異がある場合があります。最新のオペコードコストについてはこちら[3]をご参照ください。
2. Gas最適化の基本概念
Gas最適化の中心思想は、EVMブロックチェーン上でコスト効率の高い操作を優先し、高コストの操作を避けることです。
EVMにおいて、以下の操作は低コストです:
-
メモリ変数の読み書き
-
定数および不変変数の読み取り
-
ローカル変数の読み書き
-
calldata変数の読み取り(calldata配列や構造体など)
-
内部関数呼び出し
高コストとなる操作は以下の通りです:
-
コントラクトストレージ内の状態変数の読み書き
-
外部関数呼び出し
-
ループ操作
EVM Gas手数料最適化ベストプラクティス
上記の基本概念に基づき、開発者コミュニティ向けにGas最適化のベストプラクティスをまとめました。これらの実践により、開発者はスマートコントラクトのGas消費を削減し、取引コストを下げ、より効率的でユーザーフレンドリーなアプリケーションを構築できます。
1. ストレージ使用の最小化
Solidityにおいて、Storage(ストレージ)は有限リソースであり、そのGas消費はMemory(メモリ)よりもはるかに高くなります。スマートコントラクトがストレージからデータを読み書きするたびに、高額なGasコストが発生します。
イーサリアムイエローペーパーによると、ストレージ操作のコストはメモリ操作の100倍以上です。例えば、OPcodesのmloadおよびmstore命令はわずか3Gas単位の消費ですが、sloadやsstoreなどのストレージ操作は最良の場合でも少なくとも100単位のコストがかかります。

ストレージ使用を制限する方法には以下があります:
-
一時的なデータはメモリに保存する
-
ストレージ変更回数の削減:中間結果をメモリに保持し、すべての計算後に最終結果をストレージ変数に代入する
2. 変数パッキング
スマートコントラクトで使用されるStorage slot(ストレージスロット)の数とデータ表現方法は、Gas手数料に大きな影響を与えます。
Solidityコンパイラはコンパイル時に連続するストレージ変数をパッキングし、32バイトのストレージスロットを変数格納の基本単位とします。変数パッキングとは、複数の変数を合理的に配置して、単一のストレージスロットに収めることです。
左側は非効率な実装で、3つのストレージスロットを使用します。右側はより効率的な実装です。

この微細な調整により、20,000Gas単位(未使用スロットの保存に20,000Gas必要)の節約が可能となり、現在は2つのストレージスロットしか必要ありません。
各ストレージスロットがGasを消費するため、変数パッキングは必要なスロット数を減らすことでGas使用を最適化します。
3. データ型の最適化
変数は複数のデータ型で表現可能ですが、異なるデータ型の操作コストは異なります。適切なデータ型を選択することでGas使用を最適化できます。
例えば、Solidityでは整数はuint8、uint16、uint32などサイズごとに細分化されます。EVMは256ビット単位で操作を行うため、uint8を使用するとEVMはまずそれをuint256に変換する必要があり、この変換は追加のGasを消費します。

図中のコードでuint8とuint256のGasコストを比較できます。UseUint()関数は120,382Gas単位を消費する一方、UseUInt8()関数は166,111Gas単位を消費します。
個別に見ると、ここではuint256の方がuint8より安価です。しかし、前述の変数パッキング最適化を適用すれば状況は変わります。開発者が4つのuint8変数を1つのストレージスロットにパッキングできれば、それらを反復処理する総コストは4つのuint256変数よりも低くなります。これにより、スマートコントラクトは1回のストレージスロット読み書きで、4つのuint8変数をメモリ/ストレージに一度に読み書きできます。
4. 動的変数の代わりに固定サイズ変数を使用
データが32バイト以内に収まる場合、bytesやstringsの代わりにbytes32データ型の使用を推奨します。一般的に、固定サイズ変数は可変サイズ変数よりGas消費が少ないです。バイト長が限定できる場合は、bytes1からbytes32まで可能な限り最小の長さを選択してください。


5. マッピングと配列
Solidityのデータリストは2種類のデータ型で表現可能:配列(Arrays)とマッピング(Mappings)ですが、構文と構造は大きく異なります。
マッピングはほとんどの場合効率が高くコストも低いですが、配列は反復可能でデータ型パッキングが可能です。したがって、データリスト管理時はマッピングを優先し、反復処理が必要な場合やデータ型パッキングでGas消費を最適化できる場合を除いて配列の使用は避けてください。
6. memoryの代わりにcalldataを使用
関数パラメータとして宣言された変数はcalldataまたはmemoryに格納できます。主な違いは、memoryは関数内で変更可能だが、calldataは不変であることです。
原則として、関数パラメータが読み取り専用であれば、memoryではなくcalldataを優先使用してください。これにより、関数calldataからmemoryへの不要なコピー操作を回避できます。
例1:memory使用

memoryキーワードを使用すると、配列の値はABIデコード時にエンコードされたcalldataからmemoryにコピーされます。このコードブロックの実行コストは3,694Gas単位です。
例2:calldata使用

calldataから直接値を読み取る場合、memoryの中間操作をスキップできます。この最適化により、実行コストはわずか2,413Gas単位に低下し、Gas効率が35%向上します。
7. 可能な限りConstant/Immutableキーワードを使用
Constant/Immutable変数はコントラクトのストレージに保存されません。これらの変数はコンパイル時に計算され、コントラクトのバイトコード内に格納されます。そのため、ストレージに比べてアクセスコストが大幅に低く、可能であればConstantまたはImmutableキーワードの使用を推奨します。
8. オーバーフロー/アンダーフローが発生しないことが確実な場合にUncheckedを使用
開発者が算術演算がオーバーフローまたはアンダーフローを引き起こさないと確信できる場合、Solidity v0.8.0で導入されたuncheckedキーワードを使用することで、余分なチェックを回避し、Gasコストを節約できます。
下図では、条件i<lengthにより、変数iがオーバーフローすることはありません。ここでlengthはuint256として定義されており、iの最大値はmax(uint)-1です。したがって、チェックなしコードブロックでのiのインクリメントは安全と見なされ、より少ないGasで済みます。

また、0.8.0以上のバージョンのコンパイラでは、SafeMathライブラリは不要です。コンパイラ自体がオーバーフロー・アンダーフロープロテクションを内蔵しているためです。
9. モディファイアの最適化
モディファイアのコードは変更された関数に埋め込まれ、毎回使用時にコードが複製されます。これによりバイトコードサイズが増大し、Gas消費が上がります。以下はモディファイアのGasコストを最適化する方法です:
最適化前:

最適化後:

この例では、論理を内部関数_checkOwner()に再構成することで、モディファイア内でその内部関数を再利用可能にし、バイトコードサイズを縮小してGasコストを削減できます。
10. ショートサーキット最適化
||および&&演算子では、論理演算がショートサーキット評価されます。つまり、最初の条件だけで論理式の結果が確定する場合、2番目の条件は評価されません。
Gas消費を最適化するには、計算コストの低い条件を前に置くことで、高コストの計算をスキップできる可能性があります。
追加の一般的なアドバイス
1. 不要なコードの削除
コントラクトに未使用の関数や変数がある場合、削除を推奨します。これはコントラクト展開コストを削減し、コントラクトサイズを小さく保つ最も直接的な方法です。
以下は実用的なアドバイスです:
最も効率的なアルゴリズムを使用して計算を行います。コントラクトが何らかの計算結果を直接使用する場合、冗長な計算プロセスは排除すべきです。本質的に、使用されないすべての計算は削除されるべきです。
イーサリアムでは、開発者がストレージスペースを解放することでGas報酬を受け取れます。ある変数が不要になったら、deleteキーワードで削除するか、デフォルト値に設定してください。
ループ最適化:高コストのループ操作を避け、可能な限りループを統合し、繰り返し計算をループ外に移動してください。
2. プリコンパイルコントラクトの使用
プリコンパイルコントラクトは、暗号化やハッシュ操作などの複雑なライブラリ関数を提供します。コードはEVM上で実行されるのではなく、クライアントノードでローカルに実行されるため、必要なGasが少なくなります。プリコンパイルコントラクトを使用することで、スマートコントラクト実行に必要な計算量を減らし、Gasを節約できます。
プリコンパイルコントラクトの例には、楕円曲線デジタル署名アルゴリズム(ECDSA)やSHA2-256ハッシュアルゴリズムがあります。スマートコントラクトでこれらのプリコンパイルコントラクトを使用することで、開発者はGasコストを削減し、アプリケーションの実行効率を向上できます。
イーサリアムネットワークがサポートするプリコンパイルコントラクトの完全なリストについては、こちら[4]をご覧ください。
3. インラインアセンブリコードの使用
インラインアセンブリ(in-line assembly)は、開発者がEVMが直接実行できる低レベルかつ高効率なコードを記述することを可能にし、高コストなSolidityオペコードを回避できます。インラインアセンブリはメモリとストレージの使用をより正確に制御でき、さらにGas費を削減できます。また、Solidityのみでは実現困難な複雑な操作も実行でき、Gas消費の最適化に柔軟性を提供します。
以下はインラインアセンブリでGasを節約するコード例です:

上図から、標準的なケースと比較して、インラインアセンブリ技術を使用した2番目のケースがより高いGas効率を持っていることがわかります。
ただし、インラインアセンブリの使用はリスクを伴い、エラーを起こしやすい可能性があります。したがって、慎重に使用し、経験豊富な開発者のみが操作するべきです。
4. Layer 2ソリューションの使用
Layer 2ソリューションを使用することで、イーサリアムメインネットに保存・計算するデータ量を削減できます。
rollups、サイドチェーン、ステートチャネルなどのLayer 2ソリューションは、取引処理をメインイーサリアムチェーンからオフロードすることで、より迅速かつ安価な取引を実現します。
多数の取引をバンドルすることで、これらのソリューションはオンチェーン取引の数を減らし、Gas手数料を低下させます。Layer 2ソリューションの使用は、イーサリアムのスケーラビリティを向上させ、ネットワークの過負荷による混雑を招くことなく、より多くのユーザーとアプリがネットワークに参加できるようにします。
5. 最適化ツールとライブラリの使用
使用可能な最適化ツールには、solcオプティマイザ、Truffleのビルドオプティマイザ、RemixのSolidityコンパイラなどがあります。

これらのツールはバイトコードサイズの最小化、不要なコードの削除、スマートコントラクト実行に必要な操作回数の削減を支援します。「solmate」などの他のGas最適化ライブラリと組み合わせることで、開発者は効果的にGasコストを削減し、スマートコントラクトの効率を高めることができます。
結論
Gas消費の最適化は、取引コストを最小限に抑え、EVM互換ネットワーク上のスマートコントラクト効率を高める重要なステップです。コスト削減型操作を優先し、ストレージ使用を減らし、インラインアセンブリを利用し、本稿で議論した他のベストプラクティスに従うことで、開発者は効果的にコントラクトのGas消費を削減できます。
ただし、最適化プロセスでは注意深く行動し、セキュリティホールを導入しないよう注意する必要があります。コードの最適化とGas消費の削減において、スマートコントラクト本来の安全性を犠牲にしてはなりません。
[1] : https://ethereum.org/en/developers/docs/gas/
[2] : https://ethereum.github.io/yellowpaper/paper.pdf
[3] : https://www.evm.codes/
[4] : https://www.evm.codes/precompiled
TechFlow公式コミュニティへようこそ
Telegram購読グループ:https://t.me/TechFlowDaily
Twitter公式アカウント:https://x.com/TechFlowPost
Twitter英語アカウント:https://x.com/BlockFlow_News














