Deep EVM #5:Yul入門 — Solidityの秘密のアセンブリ言語
Engineering Team
Yulとは何か、なぜ存在するのか
YulはSolidityチームが設計した中間言語で、EVMバイトコードにコンパイルされます。Solidityと生のオペコードの間に位置し、変数、関数、if/switch/for構文、スコープブロックを持つ読みやすい構造化構文を通じて、すべてのEVMオペコードに直接アクセスできます。
Yulが存在するのは、Solidityのコンパイラが保守的なバイトコードを生成するからです。オーバーフローチェック、ABIエンコーディング、メモリ管理 — アプリケーションコントラクトには便利ですが、MEVボットやDEXルーターなどのガスクリティカルなコードには許容できないオーバーヘッドです。
Solidity内でassembly { ... }と書くと、それがYulです。
Yulの構文基礎
変数
let x := 42
let y := add(x, 1) // y = 43
関数
function safeAdd(a, b) -> result {
result := add(a, b)
if lt(result, a) {
revert(0, 0) // オーバーフロー
}
}
制御フロー
Yulのifにはelseがありません。代わりにswitchを使用:
switch selector
case 0xa9059cbb { /* transfer */ }
case 0x70a08231 { /* balanceOf */ }
default { revert(0, 0) }
Solidityでのインラインアセンブリ
Yulの最も一般的な使い方は、Solidityのassembly { }ブロック内です。Solidity変数はブロック内でアクセス可能です。
Yulを使うべき場合
使うべき場合:
- ガスがクリティカル — MEVボット、DEXアグリゲータルーター
- ビット演算が必要 — データのパック/アンパック
- メモリレイアウトが重要 — 外部コール用calldataの構築
- Solidityが最適でないコードを生成 — メモリ間コピーなど
- 一時ストレージが必要 — Solidityがネイティブサポートを追加する前のTLOAD/TSTORE
使うべきでない場合:
- 可読性がガスより重要 — ほとんどのアプリケーションコントラクト
- EVM知識に自信がない — Yulにはセーフティネットがない
- ガス節約が無視できる — 500,000ガスの関数で200ガスの節約は価値がない
実践例:効率的なMulticall
ガス効率の良いmulticall関数の実世界の例を示します。
実践例:Uniswap V3のSlot0読み取り
MEVボットにとって、プール状態の素早い読み取りは不可欠です。Yulでは最小限のメモリ使用でstaticcallを実行し、ABIデコーディングオーバーヘッドなしで結果が即座にスタック変数として利用可能になります。
Yul vs Huff
| 機能 | Yul | Huff |
|---|---|---|
| スタック管理 | 自動(コンパイラ) | 手動 |
| 変数名 | あり | なし |
| 関数 | あり | マクロ |
| 制御フロー | if/switch/for | JUMPI/JUMPDEST |
| コードサイズ制御 | 限定的 | 完全 |
| ガスオーバーヘッド | 小(変数管理) | ゼロ |
ほとんどのガス最適化作業では、Yulが大幅に優れた可読性で80-90%の節約を提供します。
よくある落とし穴
- メモリはゼロ初期化されない — 書き込み前のメモリ読み取りはゴミデータ
- リターンデータの誤解 — 次のCALL後にreturn dataは無効
- 大きな関数でのスタック深度超過 — 小さな関数に分割するか、メモリを使用
- ダーティな上位ビットの未処理 — calldataからのアドレス読み取り時にマスクが必要
まとめ
YulはSolidityの安全性とEVMの生のパワーの間の橋渡しです。オペコードレベルの制御を、変数名、関数、構造化制御フローで実現します。MEV開発者にとって、Yulをマスターすることがガス効率で利益を出すボットと競争に負けるボットの違いです。