はじめに

初めまして。 『DApps開発入門』という本や色々記事を書いているかるでねです。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!

今回はSolidityのバージョン0.8.29での変更点をまとめていきます。

以下の公式のリリース記事をもとにまとめていきます。

変更点

  • EOFのサポート
  • カスタムストレージレイアウトのサポート
  • ethdebug の初期サポート

EOFのサポート

Solidity 0.8.29で、新しいEVM Object Format(EOF)の実験的なサポートがされています。 ただし、EOFを使うにはEVMバージョンが「Osaka」を指定する必要があり、このEVMバージョンはまだメインネットやテストネットには導入されていません。

EVM Object Format (EOF)とは?

EOF(EVM Object Format)は、Ethereum Virtual Machine(EVM)の新しいバイナリフォーマットです。 従来のEVMバイトコードよりもセキュリティやパフォーマンスが向上することを目的としています。

なぜEOFが必要なのか?

従来のEVMバイトコード(非EOF)には、いくつかの問題があります。

  • ガス効率が悪い
    • 最適化されていないオペコードが多く、無駄な計算が発生しやすい。
  • コードの解析が難しい
    • EVMは事前にコードサイズがわからず、デバッグやセキュリティ分析がしにくい。
  • 安全性の向上が難しい
    • バイトコードの自由度が高すぎて、セキュリティ的に問題のあるコードも簡単に書けてしまう。

EOFでは、これらの課題を解決するために、構造化されたフォーマットを導入しています。

EOFの特徴

明確なバイナリ構造

EOFでは、以下のようにコードがいくつかのセクションに分かれています。

  • ヘッダー
    • EOFコードであることを示す情報。
  • コードセクション
    • EVMで実行されるプログラム。
  • データセクション
    • ストレージや定数値などのデータ。

これにより、EVMがコードを解析しやすくなり、安全性とパフォーマンスの向上が期待できます。

無駄なオペコードを削除

EOFでは、以下のような従来のEVMバイトコードで問題になっていた一部のオペコードが削除されました。

  • GAS や GASLEFT()
    • EOFではガス量を直接取得できない。
  • <address>.code
    • EOFではコントラクトのコードを取得できない。

これは、EVMの動作をより安全で予測可能にするための変更です。

コントラクトアドレスの決定方法が変更

EOFでは、コントラクトをデプロイするときに salt を使います。

従来のEVMでは、アカウントの nonce を使ってアドレスを決定しますが、EOFでは、同じ salt を使うと同じアドレスにデプロイされます。 デフォルトで salt = 0 ですが、将来的には明示的に salt を指定が必須になる可能性があります。

EOF対応のコンパイラはまだ開発途中で一部の機能が未実装ですが、Solidityのコードをコンパイルするには十分な状態です。 今後、RJUMPV や EXCHANGE などの新しいオペコードを活用することで、さらに効率の良いコードを生成できるようになる予定です。

ただし、EOFでは従来の機能のいくつかが廃止されているため、既存のコードをそのままコンパイルできない場合があります。 例えば、gasleft() や <address>.code のようなガスやコードの参照ができなくなります。 また、new 演算子によるコントラクト作成のルールも変更され、デフォルトで salt を指定する必要があります(デフォルト値は0)。

EOF対応のコンパイルは、IR(中間表現)を経由する必要があり、最適化オプションも有効にする必要があります。 現状では低レベルの最適化がまだ実装されていないため、コードサイズが増えることがあります。 この部分は今後のバージョンで改善予定です。

試してみる場合は、以下のようにオプションを指定してください。

bash
solc --experimental-eof-version 1 --evm-version osaka

または、Standard JSONの設定で以下のように指定してください。

json
"settings": {
  "eofVersion": 1,
  "evmVersion": "osaka"
}

カスタムストレージレイアウトのサポート

Solidity 0.8.29では、コントラクトのストレージ変数を任意の位置に配置できる新しい構文が導入されました。

この機能は以前から実装の要望があったものの、さまざまなユースケースや競合要件があり実装が遅れていました。 しかし、Pectraアップグレードで EIP7702(EOAにコードを設定する提案) が導入されることになり、Account Abstractionに注目が再度向けられているため優先的に対応することにしたようです。

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

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

現時点では、機能は制限されており、指定できるのは「決まった数値(リテラル)」だけであり、変数や計算式を使って動的に変更することはできません。 さらに、この設定は継承している全てのコントラクトに適用されるため、親コントラクトと子コントラクトで異なるストレージ位置を指定することはできません。

使い方としては、以下のようにコントラクト全体のストレージの開始位置を layout atで指定しています(2**255 - 42)。 これにより、uint x のストレージスロットは 2**255 - 42 から始まります。 また、このコントラクトを継承した全てのコントラクトでもxは同じ位置を使うことになります。

solidity
contract C layout at 2**255 - 42 {
    uint x;
}

今後のアップデートでは、定数の使用や、ERC7201(ネームスペース付きストレージレイアウト) のようなレイアウトのサポートが追加される予定です。

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

最終的にはより自由度の高い設定が可能になる可能性がありますが、まずは必要な機能から順次追加する方針とのことです。

詳細については、カスタムストレージレイアウトのドキュメントを参照してください。

カスタムストレージレイアウトのドキュメント

ドキュメントの内容を簡単にまとめます。

カスタムストレージレイアウトとは?

Solidity 0.8.29では、コントラクトのストレージ変数の開始位置を自由に指定できる 機能が追加されました。
これまでは、ストレージの開始位置はデフォルトで0番目のスロットでしたが、新機能を使うと好きなスロットから開始できるようになりました。

使い方

カスタムストレージレイアウトを指定するには、layout at <開始スロット> という構文を使います。
例えば、以下のコードでは、ストレージの開始位置を 0xAAAA + 0x11 に設定しています。

solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.29;

contract C layout at 0xAAAA + 0x11 {
    uint[3] x; // x[0] -> 0xAABB, x[1] -> 0xAABC, x[2] -> 0xAABD
}

この場合、配列 x の3つの要素は 0xAABB から 0xAABD までのスロットを使います。

ルールと制約

  • layout at はコントラクトの定義時に記述する

    • contract C layout at <スロット> のように書きます。
    • 継承 (is) を使う場合は layout at を前後どちらに書いてもOK。
  • 指定できるのは整数リテラルのみ

    • layout at に使える値は、コンパイル時に計算可能な整数リテラル のみです。
    • 変数や関数を使って動的に指定することはできません。
  • 継承ツリー全体に適用される

    • layout at を指定したコントラクトを継承すると、その子コントラクトのストレージも同じ開始位置を基準に配置される 。
    • つまり、親コントラクトと子コントラクトで異なる開始位置を指定することはできない。
  • ストレージの「折り返し」はできない

    • 256ビットのストレージ範囲を超えるとエラー になる。
    • 例えば、開始位置を大きすぎる値にすると、変数がEVMのストレージ範囲を超えてしまいコンパイルエラーになる。
  • abstract・interface・libraryでは使えない

    • layout at はコントラクトにのみ適用可能。
  • 一時的なストレージ変数には影響しない

    • memory や calldata に保存される変数(例:関数の引数や一時変数)は layout at の影響を受けない。

注意点

  • EVMのアドレス空間の末端付近のスロットは避けるべき
    • 将来のアップグレードで問題が起きる可能性があるため、開始スロットを極端に大きくしない方が安全。
  • layout と at は今後予約語になる予定
    • まだ正式なキーワードではないが、将来的に予約語になるため変数名などに使わない方が良い。

ethdebug の初期サポート

Solidity 0.8.29では、新しいデバッグフォーマット「ethdebug」の実験的なサポートも追加されています。

ethdebug はスマートコントラクト向けのデバッグ情報フォーマットで現在仕様は開発中です。ethdebug の開発状況を知りたい場合は、GitHubのethdebugリポジトリ や Matrixチャンネル をフォローすることで最新情報を取得できます。 また、毎月第2・第4木曜日のベルリン時間17:00に定例ミーティングが開催されています。

現時点では、ethdebug に対応するコンパイラの初期バージョンがリリースされており、命令の情報やソースコードの範囲 を出力できます。 ただし、最適化なしの IR(中間表現)経由のコンパイルのみ対応しており多くの機能がまだ実装されていません。

試してみる場合は、以下のオプションをつけてコンパイルしてください。

bash
solc --ethdebug --via-ir

または、Standard JSON の設定で以下のように指定してください。

json
"settings": {
  "viaIR": true,
  "outputSelection": {
    "*": {
      "evm.bytecode.ethdebug": ["*"],
      "evm.deployedBytecode.ethdebug": ["*"]
    }
  }
}

ワイルドカード * では ethdebug の出力は有効にならないので上記のように明示的に設定する必要があります。

今後のアップデートでさらに機能が追加される予定のようです。

ethdebug

Githubの内容を簡単にまとめます。

ethdebugフォーマットとは?

Ethereum上のスマートコントラクトのデバッグをしやすくするための標準フォーマットを設計するプロジェクトです。

従来のプログラムと同じように、スマートコントラクトのデバッグデータを統一フォーマットで扱えるようにすることで、開発者・監査人・ユーザーがEVMの挙動をより理解しやすくすることを目的としています。

なぜethdebugが必要なのか?

  • EVMの動作は観察できるが、人間には理解しにくい

Ethereumのスマートコントラクトは、実行の透明性が確保されているという強みがあります。 しかし、EVMの動作を直接見ても、それが元のSolidityコードのどの部分と対応しているのか分かりにくいという問題があります。

  • 従来のデバッグフォーマット(例: DWARF)はEVMには適用できない

これまでのプログラミング環境では、DWARFなどのデバッグフォーマットがあり、コンパイラが「人間が書いたコード」と「機械が実行するコード」の対応関係を記録できます。 しかし、EVMにはこのような標準的なデバッグフォーマットがないため、開発者が低レベルなバイトコードを直接解析するしかありません。

  • EVMのデバッグは専門家にしかできない

EVMの命令を直接観察・再生することは可能ですが、それを理解してバグを見つけるのは専門家でないと難しいです。
そのため、ethdebugフォーマットを導入することで、EVMの動作を高レベルなコードと対応付ける仕組みを提供し、開発・監査の負担を軽減します。

ethdebugの目的

スマートコントラクト用の標準的なデバッグフォーマットを作ることです。

このフォーマットが普及すれば、コンパイラ・エディタ・デバッガが連携しやすくなり、EVMのデバッグがより簡単になることが期待されています。

リポジトリの内容

このプロジェクトのリポジトリには、ethdebugフォーマットの設計・実装に関連するコンポーネントが含まれています。

  • schemas/(スキーマ定義)

    • ethdebugフォーマットを正式に定義するJSONスキーマ(YAML形式)
  • NPMパッケージ(ツール群)

    • @ethdebug/format
      • TypeScript/JavaScriptでスキーマを使えるようにするパッケージ(型安全な読み込みをサポート)
    • @ethdebug/pointers
      • EVMの実行状態を参照し、ethdebugフォーマットのポインタを解決するツール
    • @ethdebug/format-web(未公開)
      • Docusaurusを使ったドキュメントサイトの素材

Solidity 0.8.29の変更点(フルチェンジログ)

新機能

  • コントラクトのストレージ位置を自由に指定可能に
    • layout at を使って、ストレージ変数の開始位置を指定できるように。

コンパイラの新機能

  • エラーメッセージの改善

    • これまでコード生成時に発生したエラーの詳細な位置が分からない場合があったが、エラーの発生元(コントラクトの場所)を表示するように。
  • EVM: Osakaバージョンのサポート

    • 新しいEVMバージョン「Osaka」に対応(EOFを試すためのEVM)。
  • EVMアセンブリのオペコードベースの最適化を有効化可能に

    • EVMバイトコードの効率をさらに向上させることが可能に。
  • EOF(EVM Object Format)の実験的バックエンド

    • EOFを使ったコンパイルが可能に(ただし、IR経由かつ最適化有効時のみ)。
  • SMTChecker(形式検証ツール)の改善

    • block.blobbasefee と blobhash のサポートを追加(EIP4844の一部)。
    • --model-checker-print-query を --model-checker-solvers smtlib2 なしで使えるように。
    • Z3(SMTソルバー)がビルド時ではなく実行時の依存ライブラリにた。
  • Yulパーサー(低レベルIR)

    • 予約語と名前がかぶっていてもコンパイルエラーにならないように変更(警告扱いに)。

バグ修正

  • CLI(コマンドライン)

    • StackTooDeep(変数のネストが深すぎるエラー)が適切なエラーとして表示されるように。
  • エラーメッセージの改善

    • ソースファイルの名前が空の場合でもエラーの位置を正しく表示。
  • コンパイラ内部エラーの修正

    • IR ASTの出力を要求したときに発生するエラーを修正(特にinterfaceやabstract関連)。
  • メタデータ関連の修正

    • カスタムクリーンアップ処理が適用されない問題を修正(最適化設定がデフォルトのとき)。
  • SMTCheckerのバグ修正(多数)

    • オーバーフロー計算時のエラーを修正。
    • 異なるコントラクト間での安全性チェックの不具合を修正。
    • クロスコントラクトのgetter呼び出しでの解析エラーを修正。
    • 文字列リテラルを fixed bytes に変換するときの問題を修正。
    • 不要な括弧がある外部呼び出しの解析ミスを修正。
    • 固定サイズバイト配列を文字列リテラルで初期化するときのエラーを修正。
    • 配列のストア・セレクト操作を含む不変条件の解析エラーを修正。
    • ecrecover のプリコンパイル呼び出しで文字列リテラルのエンコードミスを修正。
  • JSONインターフェース

    • generatedSources や sourceMap が明示的にリクエストされていないのに内部で生成されていた問題を修正。
  • 型チェックの修正

    • ストレージサイズ計算時に誤って一時変数(transient storage variables)を含めるバグを修正。
    • これにより、不必要なコンパイルエラーが発生する問題が解決。
  • Yul関連の修正

    • コード生成時のエラーを適切に報告。
    • デバッグ時にオプション値を誤って参照する問題を修正(Yul最適化のバグ修正)。

ビルドシステムの変更

  • Linuxのリリースビルドが完全に静的リンクに

    • これにより、glibc に依存しなくなった。
  • C++17 → C++20に移行

    • コンパイラのコードベースがC++20をターゲットにするよう変更。
  • Solc-JS(JavaScript版のSolidityコンパイラ)

    • Node.js v12以上が必須に(古いバージョンでは動作しなくなる)。

最後に

今回はSolidityのバージョン0.8.29の変更点をまとめました。

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください!