3MIKAN
仮想通貨直コン

Solidityビルド設定入門:optimizer・via-IR・EVM versionとbytecodeを比較する

同じSolidity sourceをoptimizer runs、via-IR、target EVM、metadata、solc versionの7条件でビルドし、bytecode、size、deploy gas、runtime gas、挙動を分けて検証します。

3MIKANのブランドキャラクターが同じ設計図をlegacyとIRのビルド機構へ通し、異なるbytecode列とtarget EVM gateを比較する記事画像

同じSolidity sourceでも、compiler versionとビルド設定が違えば、creation bytecode、runtime bytecode、metadata、contract size、gasは変わります。sourceだけが同じでも、block explorerのverified bytecodeや再現ビルドが一致するとは限りません。

この記事では2026-09-01時点のSolidity最新安定版0.8.36と比較用0.8.35、Foundry 1.8.0を固定し、一つのcontractを7条件でビルドしました。すべてローカルAnvil上の計測で、外部RPC、wallet、個人鍵、public transaction、実資産は使いません。

結論を先に置くと、optimizer_runsはoptimizerを何回繰り返すかではありません。想定する実行回数に応じて、deploy時のcode sizeと利用中の実行costのどちらを重く見るかを伝える値です。via_irevmVersion、metadata、solc更新も別の軸なので、設定名だけで優劣を決めず、同じ入力とtestで差分を測ります。

ビルド結果を決めるもの

再現に必要なのは.solだけではありません。最低限、次の7項目を一つの記録にします。

  1. source内容とhash
  2. exactなsolc version
  3. Standard JSONのoptimizer、viaIRevmVersion、metadata設定
  4. import remappingと依存packageのversion
  5. library addressとlink時点
  6. constructor arguments、immutable、対象chain
  7. 期待するcreation / runtime bytecode hashと挙動test

compilerのStandard JSON interfaceは自動ビルド向けの正本です。CLIやFoundryの設定も、最終的には同じ境界をcompiler inputへ渡します。

{
  "optimizer": { "enabled": true, "runs": 200 },
  "viaIR": false,
  "evmVersion": "prague",
  "metadata": { "bytecodeHash": "ipfs", "appendCBOR": true }
}

今回の検証用contractは、pureな反復計算、結果をstorageへ書く正常系、custom errorへ戻る失敗系を持ちます。同じ入力execute(17, 32, 1)を各ビルドへ送り、出力、成功状態、revert selectorが変わらないことも確認しました。

3MIKANのキャラクターが一つの設計図から設定の異なる3台のビルド機構へ入力し、長さと構成が異なるbytecode tile列を比較する図
図は同一sourceから異なる出力が生まれる関係の補助表現です。正確なsettings、byte数、hash、gasは下の表と公開JSONを正本にします。

7条件のbytecode・size・gasを比較する

gasは二つに分けました。deploy gasは新しいcontractを作るtransaction receiptのgasUsed、runtime gasは初回のexecute(17,32,1)でstorageを0から更新したreceiptのgasUsedです。base feeとgas priceは0に固定し、通貨換算はしません。

ビルド条件 creation bytes runtime bytes metadata bytes deploy gas runtime gas
legacy / runs 1 / Prague 624 596 53 182,147 47,261
legacy / runs 200 / Prague 624 596 53 182,147 47,261
legacy / runs 10,000 / Prague 724 696 53 202,439 47,261
via-IR / runs 200 / Prague 443 417 53 143,417 47,008
legacy / runs 200 / Paris 648 617 53 186,453 47,268
legacy / runs 200 / metadata hashなし 583 555 12 173,283 47,261
solc 0.8.35 / legacy / runs 200 624 596 53 182,147 47,261

この小さなcontractでは、runs 1と200のfunctional runtime部分は同じでした。設定値を含むmetadata hashが変わるためraw bytecode hashは異なります。runs 10,000ではfunctional runtimeが543 bytesから643 bytesへ増えましたが、今回選んだruntime操作のgasは47,261のままです。

つまり「runsを上げれば常にruntime gasが下がる」とは言えません。optimizerはdispatch、constant、inlineなどの候補を、想定利用回数とのtrade-offで選びます。実際の効果はcontractとentry pointごとに測ります。

via-IRビルドは今回、runtime 417 bytes、deploy 143,417 gas、runtime 47,008 gasでした。legacyより小さい結果ですが、この1例をすべてのcontractへ一般化はできません。IR-based pipelineはSolidityをYul IRへ変換してから最適化するため、出力経路そのものが異なります。

optimizer runsは反復回数ではない

Solidity公式はoptimizerを、code generation、Yul IR、opcodeの3層に分けています。runsは各opcodeがcontractの生涯で何回ほど実行されるかという期待値で、optimizer passの回数ではありません。

低い値は一般に短いcodeと安いdeploymentを重く見ます。高い値は繰り返し利用時の実行costを重く見て、inlineやconstantの表現が長くなる場合があります。ただし「runs 1なら必ず最小」「runs 10,000ならすべてのfunctionが安い」という保証ではありません。

比較するときは、少なくとも次を分離します。

観測対象 何を測るか 混ぜないもの
creation bytecode constructorを実行してruntimeを返すcode ABI-encoded constructor arguments
runtime bytecode deploy後にaddressへ保存されたcode creation code、transaction input
functional runtime CBOR metadata trailerを除いた部分 metadata hashだけの差
deploy gas creation transaction全体 runtime call gas、fee換算
runtime gas 固定entry point・固定state・固定input 別function、warm / cold条件の違い

contract sizeが小さくても、よく使うrouteが安いとは限りません。逆にdeploy gasが増えても、長期に高頻度で使うcontractでは総costが下がる可能性があります。想定transaction数を置いて損益分岐を計算し、upgrade後も同じbenchmarkを回します。

via-IRはbytecodeだけの切替ではない

legacy pipelineとIR-based pipelineには小さなsemantic differenceがあるため、Solidity公式は移行時の注意点を別ページにまとめています。今回の正常系とcustom error失敗系は、両pipelineで同じ結果とselectorになりました。それでも「同じ挙動は自動的に保証された」とは扱いません。

一方、17引数を一つの式へ束ねたstack-pressure用contractは、legacy pipelineでStack too deepになり、via-IRではcreation 275 bytes、runtime 250 bytesとしてビルドできました。via-IRがcompile failureを解消する場合があることと、既存contractのsemantic regressionが不要になることは別です。

移行時は次の順で確認します。

  1. legacyとvia-IRを同じsolc、target EVM、optimizer runsでビルドする
  2. 正常系のreturn、event、storage差を比較する
  3. revert type、custom error selector、rollback後stateを比較する
  4. bytecode size、deploy gas、頻出entry pointのgasを別々に測る
  5. inheritance初期化、inline assembly、memory safety、library境界をreviewする
  6. compilerのknown bugsを対象versionとsettingsで照合する

Stack too deepが消えたことだけで採用を決めず、差分testとreviewの開始点にします。

target EVMが違うとopcodeと実行可否が変わる

evmVersionは「どのchain名へdeployするか」ではなく、compilerが利用してよいopcodeとgas modelを決めるtargetです。Solidity公式も、実行環境と一致しないtargetは失敗や予期しない挙動につながると警告しています。

今回、Paris targetのcreation / runtimeにはPUSH0が0件でした。Prague targetのlegacyビルドにはcreation 32件、runtime 28件のPUSH0が含まれます。PUSH0はShanghaiで導入されたため、Prague targetで生成されたcreation bytecodeをParis Anvilへ送るとdeploymentでrevertしました。Paris targetビルドは同じParis Anvilでdeployとruntime callに成功しています。

3MIKANのキャラクターが旧targetと新targetの2台の実行gateを点検し、新しい小型tileを含むbytecode列が旧gateへ入らない境界を確認する図
画像は互換性の概念図です。Paris / Prague、PUSH0件数、deployment結果は本文と公開JSONを正本にします。

private chain、L2、fork前block、古いlocal nodeでは、network名だけを見ず実際のhardfork supportを確認します。EVM・hardfork差分の追跡方法ではopcode、precompile、gas、system contractを層に分けています。

metadataを除けば同じ、raw bytecodeは違う場合がある

Solidityは通常、runtime末尾へCBOR metadataを付けます。今回のbytecodeHash: "ipfs"では53 bytes、bytecodeHash: "none"でもappendCBOR: trueのため12 bytesのtrailerが残りました。後者のfunctional runtimeは前者と完全一致していますが、raw runtime hashと総byte数は異なります。

verified sourceを再現するときは、次の三つを同じ意味にしません。

  • raw bytecodeが完全一致する
  • metadataを分けたfunctional bytecodeが一致する
  • source codeが見た目として同じである

library linking、immutable、constructor argumentsも比較位置が違います。library addressはcompiler inputへ入れてlinkするのが原則で、後からbytecodeを書き換えるとmetadataは更新されません。verified sourceとbytecode再現でcreation、runtime、metadata、library、immutableを分解しています。

compiler upgradeはhash差と挙動差を別に見る

同じsettingsでsolc 0.8.35から0.8.36へ上げると、今回のraw runtime hashは変わりました。一方、metadataを除いたfunctional runtime hash、size、deploy gas、runtime gas、正常系の出力は同じでした。

これは「更新しても常にfunctional codeが同じ」という意味ではありません。今回の差はversionを含むmetadataへ閉じましたが、compiler releaseにはoptimizer、codegen、bugfixが含まれます。upgrade reviewでは次を固定します。

差分 判定方法
compiler identity full versionと配布packageを固定する
input source hashとStandard JSONを保存する
output creation / runtime / functional / metadata hashを分ける
behavior 正常、revert、event、storage、return dataを比較する
gas 同じhardfork、初期state、sender、inputでreceiptを比較する
known bug old / new両versionの条件へ該当するか確認する

proxy upgradeではcompiler差に加えてstorage layoutとauthorityが関わります。ERC-7201 namespaced storageのupgrade差分を使い、raw slotとlayoutも同じreviewへ入れます。

再現ビルドmanifestを作る

今回の公開検証JSONには、source SHA-256、2つのcompiler full version、7条件のStandard JSON settings、creation / runtime / functional / metadata hash、byte数、PUSH0件数、deploy / runtime gas、正常系出力、custom error selectorを保存しました。

各条件は同じprocessで2回ビルドし、creationとruntimeのhashが一致することを確認しています。再現確認は「手元でビルドできた」で終えず、期待hashと比較して初めてpassにします。

1. source hashを照合する
2. exact compiler packageを選ぶ
3. 保存したStandard JSON settingsでビルドする
4. creation / runtime / functional / metadata hashを計算する
5. 期待hashと比較する
6. 固定inputの正常系・failure系testを実行する
7. target hardfork上でgasとopcode互換性を測る

Foundryではprofileごとにsolc_versionoptimizeroptimizer_runsvia_irevm_versionbytecode_hashを明記できます。環境変数や自動compiler検出に任せた値も、release artifactへ残すmanifestでは明示値へ展開します。

ビルド設定変更のreview checklist

  • source commitとsource hashを固定した
  • solcのfull versionと取得元を固定した
  • Standard JSON inputまたは等価な全settingsを保存した
  • optimizer enabledとrunsを別項目で記録した
  • legacy / via-IRの正常系とfailure系を比較した
  • target EVMとdeployment先hardforkを照合した
  • creation、runtime、functional、metadataを分けてhash化した
  • library address、remapping、dependency、constructor argumentsを固定した
  • deploy gasと主要entry pointのruntime gasを分けた
  • 同じstate、sender、input、hardforkでgasを比較した
  • compiler upgrade前後でreturn、revert、event、storageを比較した
  • 対象versionとsettingsをknown bugsへ照合した
  • 期待bytecode hashを使う再ビルド検証を自動化した
  • public RPC、wallet、実資産を使わない最小再現を先に作った

Foundry invariant testの設計を使えば、unit testにないstate sequenceもcompiler変更前後で比較できます。ただしfuzzやinvariantのpassも、入力空間全体の証明やaudit完了ではありません。

再現ビルドは、source hash、solc version、Standard JSON settings、library addresses、target EVM、期待bytecode hash、挙動testを一つの記録に固定します。設定変更のreviewでは「gasが下がった」「Stack too deepが消えた」だけで終えず、何が変わり、何が同じだったかをcreation、runtime、metadata、挙動の各層で説明できる状態を完成とします。

この記事にtoken購入、特定protocolの利用、wallet接続、署名、transaction送信、実資産操作を促す導線はありません。将来compiler tooling、verification、audit、testing platform、developer educationの広告を掲載する場合も、広告表記と再現結果、security judgmentを分離します。

確認した一次情報