Solidity 0.8.22での変更点まとめ
はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
今回はSolidityのバージョン0.8.22での変更点をまとめていきます。
以下の公式のリリース記事をもとにまとめていきます。
変更点
Solidityのバージョン0.8.22では以下の項目が変更されました。
- チェックされないループインクリメント。
- Yulオプティマイザの調整。
- EVMアセンブリのJSONインポートサポート。
- ファイルレベルでのイベント定義。
チェックされないループインクリメント
これまでは、ガスコスト効率化の観点から、unchecked
ブロックでforループのカウントインクリメント部分を囲い、オーバーフローチェックを無視する方法が使用されていました。
for (uint i = 0; i < array.length;) {
acc += array[i];
unchecked { i++; } // i gets incremented without overflow checks -- less gas used
}
ただし、このコードは冗長だったため、Solidity0.8.22では特定の条件下でコードをシンプルに保ちながらガスコストを削減できるようになりました。
特定の条件とは以下になります。
- ループ条件が
i < ...
の形式であること。-
i
はループ内で使われるカウンター変数です。
-
- ループ条件の右側(比較される値)の型が、カウンター変数iの型に自動的に変換できること。
- カウンターの型が比較の前に変わらないこと。
- ループのカウンター変数
i
が、Solidityで用意されている整数型であること。 - ループのカウンターを増やす式が、
i++
または++i
であること。 - ループ条件やループの中で、カウンター変数
i
が変更されないこと。
以下のコードだと最適化が適用されます。
for (uint i = 0; i < array.length; ++i) {
acc += array[i]; // i はループ本体で変更されません
}
一方、以下の場合はi
の比較前にuint16
に変換されるため、最適化が適用されません。
for (uint8 i = 0; i < uint16(1000); ++i) {
// ループ本体
}
また以下のような条件や設定もあります。
- 最適化が有効になるのは
<
演算子だけです。- 他の演算子、例えば
<=
などは対象外です。
- 他の演算子、例えば
- 開発者が独自に定義した
<
演算子は最適化の対象になりません。- Solidityで元々用意されている
<
演算子だけが対象です。
- Solidityで元々用意されている
- この最適化は、他の最適化設定が無効になっていても自動的に有効になります。
- この最適化を無効にしたい場合は、
settings.optimizer.details.simpleCounterForLoopUncheckedIncrement
をfalse
に設定することで設定できます。
Yulオプティマイザの調整
Solidityのバージョン0.8.20で導入されたPUSH0
オペコードは、0
をスタックにプッシュするための命令です。
PUSH0
は他のオペコードに比べてガスコストが低いため、効率的にゼロを扱うために利用されています。
Solidityのバージョン0.8.24では、Yulオプティマイザ内で使用される最適化ステップの一つであるRematerialiserで、ゼロリテラル(値が0
という定数)を変数参照として保持するのではなく常に再生成するように拡張されました。
これにより以下のメリットがあります。
- ゼロリテラルをスタックにプッシュする時に、ガスコストの高いDUP命令を使わずにPUSH0を使えるようになる。
- 全体としてガスコストが削減される。
これまでの方法では、ゼロリテラルを一度変数として保存してその変数を参照していましたが、DUP
命令が使用されるためガスコストが高くなっていました。
新しく実装された方法では、ゼロリテラルが常に再生成されるためPUSH0
オペコードが使用され、ガスコストが低く抑えられます。
また、最適化ステップのデフォルトに以下の2つのステップが追加されました。
-
Rematerialiser
- ゼロリテラルを常に再生成するステップ。
-
UnusedPruner
- 使われていない変数を削除するステップ。
EVMアセンブリのJSONインポートサポート
実験的な機能としてEVMアセンブリのインポートをサポートを追加しました。
これにより、外部ツールを使用してバイトコードが生成される直前に最適化を行うことが可能になります。
この新機能は、EVMアセンブリをインポートできるようにするものです。
これにより、バイトコードが生成される直前に外部ツールで最適化を行うことができます。
以下のような手順での使用が想定されています。
- コンパイラが生成したEVMアセンブリをエクスポート。
- 外部ツールを使用して、エクスポートしたアセンブリコードを修正。
- 修正されたアセンブリコードを再インポートし、通常のコンパイルプロセスを再開。
ただ、この機能はまだ実験段階にあり、現時点では本番環境での使用は推奨されていません。
ファイルレベルでのイベント定義
イベント定義をファイルレベルで行えるようになりました。
これにより、コードの整理がしやすくなり、イベントを無理にライブラリ内に定義する必要がなくなります。
今回のアップデートで、イベントをコントラクトの外やファイル全体のスコープで定義できるようになりました。
これにより、以下のようにコードの整理がしやすくなり、イベントの定義を共通の場所にまとめることができます。
event E(); // ファイルレベルでのイベント定義
contract D {
function f() public {
emit E(); // ファイルレベルで定義されたイベントの発行
}
}
また、Solidityのバージョン0.8.21では、他のコントラクトやインターフェースで定義されたイベントを発行しようとすると、NatSpecドキュメント生成時にエラーが発生するバグがありました。
このバグが修正され、外部イベントを正しく発行できるようになりました。
interface I {
event ForeignEvent(); // インターフェースで定義されたイベント
}
contract C {
event ForeignEvent(); // コントラクトで定義されたイベント
}
contract D {
function f() public {
emit I.ForeignEvent(); // インターフェースで定義されたイベントの発行
emit C.ForeignEvent(); // コントラクトで定義されたイベントの発行
emit E(); // ファイルレベルで定義されたイベントの発行
}
}
その他の変更
その他の細かい修正については以下の記事を参考にしてください。
最後に
今回はSolidityのバージョン0.8.22の変更点をまとめました。
以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!
Discussion