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、挙動を分けて検証します。

同じ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_ir、evmVersion、metadata、solc更新も別の軸なので、設定名だけで優劣を決めず、同じ入力とtestで差分を測ります。
ビルド結果を決めるもの
再現に必要なのは.solだけではありません。最低限、次の7項目を一つの記録にします。
- source内容とhash
- exactなsolc version
- Standard JSONのoptimizer、
viaIR、evmVersion、metadata設定 - import remappingと依存packageのversion
- library addressとlink時点
- constructor arguments、immutable、対象chain
- 期待する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が変わらないことも確認しました。
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が不要になることは別です。
移行時は次の順で確認します。
- legacyとvia-IRを同じsolc、target EVM、optimizer runsでビルドする
- 正常系のreturn、event、storage差を比較する
- revert type、custom error selector、rollback後stateを比較する
- bytecode size、deploy gas、頻出entry pointのgasを別々に測る
- inheritance初期化、inline assembly、memory safety、library境界をreviewする
- 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に成功しています。
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_version、optimizer、optimizer_runs、via_ir、evm_version、bytecode_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を分離します。
確認した一次情報
- Solidity 0.8.36 release確認日: 2026/09/01
- Solidity Using the Compiler確認日: 2026/09/01
- Solidity Optimizer internals確認日: 2026/09/01
- Solidity IR-based Codegen Changes確認日: 2026/09/01
- Solidity Contract Metadata確認日: 2026/09/01
- Solidity List of Known Bugs確認日: 2026/09/01
- EIP-3855 PUSH0 instruction確認日: 2026/09/01
- Foundry Solidity compiler configuration確認日: 2026/09/01
- Foundry v1.8.0確認日: 2026/09/01



