ブロックチェーンMar 28, 2026
Deep EVM #3:ガスの理解 — コントラクトのコストが決まる理由
OS
Open Soft Team
Engineering Team
ガスは抽象ではない
開発者はしばしばガスを漠然とした「コスト」メトリクスとして扱います。実際には、ガスは正確な公式を持つ精密に定義されたリソース会計システムです。すべてのオペコード、calldataのすべてのバイト、すべてのストレージアクセスには、イエローペーパーとその後のEIPで定義された決定論的なガスコストがあります。
これらのコストを理解することで、ガス最適化は推測からエンジニアリングに変わります。
固有ガス:ベースライン
コントラクトコードが単一のオペコードを実行する前に、トランザクションはすでにガスを消費しています。これが固有ガスです:
intrinsic_gas = 21000 // 基本トランザクションコスト
+ 16 * nonzero_calldata_bytes // 非ゼロバイトあたり
+ 4 * zero_calldata_bytes // ゼロバイトあたり
+ access_list_cost // EIP-2930アクセスリストの場合
+ 32000 (コントラクト作成の場合) // CREATEトランザクションのみ
EIP-2929:アクセスリスト革命
EIP-2929はトランザクションごとのアクセスセットを導入しました。最初にアクセスした時は「コールド」で追加ガスがかかり、以降は「ウォーム」で安価です。
| オペコード | コールド | ウォーム | 節約 |
|---|---|---|---|
| SLOAD | 2100 | 100 | 2000 |
| BALANCE | 2600 | 100 | 2500 |
| CALL | 2600 | 100 | 2500 |
EIP-2930アクセスリスト
アクセスリストを含めることで、アドレスとストレージスロットを「プリウォーム」できます。ストレージキーあたり1900ガスで、コールドSLOADの2100ガスより安価です。
SSTORE:最も複雑なオペコード
SSTOREのガスコストは、元の値、現在の値、新しい値の3つの要因に依存します。
ゼロスロットを非ゼロに設定するコストは20000ガス(新しいステートトライリーフノードの挿入)。既存の非ゼロスロットの変更は2900ガス(ウォーム)。この7倍の差は重要な最適化機会です。
実際に効果のある最適化パターン
1. ストレージ読み取りをメモリにキャッシュ
// 悪い例:3回のSLOAD
function withdraw() external {
require(balances[msg.sender] > 0);
uint256 amount = balances[msg.sender];
balances[msg.sender] = 0;
}
// 良い例:1回のSLOAD
function withdraw() external {
uint256 amount = balances[msg.sender];
require(amount > 0);
balances[msg.sender] = 0;
}
2. ストレージ変数のパッキング
// 3スロット、3回のSLOAD = 6300ガス(コールド)
uint256 price;
uint256 amount;
uint256 timestamp;
// 1スロット、1回のSLOAD = 2100ガス(コールド)
uint128 price;
uint64 amount;
uint64 timestamp;
3. メモリの代わりにCalldataを使用
// 悪い例
function processOrders(Order[] memory orders) external { ... }
// 良い例
function processOrders(Order[] calldata orders) external { ... }
4. Unchecked算術(安全な場合)
for (uint256 i = 0; i < length;) {
// ... ループ本体 ...
unchecked { ++i; }
}
5. カスタムエラーを使用
// 悪い例
require(amount > 0, "Amount must be positive");
// 良い例
error InvalidAmount();
if (amount == 0) revert InvalidAmount();
実例:Uniswap V3スワップのガス内訳
| コンポーネント | ガス |
|---|---|
| 固有(21000 + calldata) | ~21,500 |
| 関数ディスパッチ | ~100 |
| 入力バリデーション | ~200 |
| プール状態読み取り | ~4,200(コールド) |
| 価格計算 | ~5,000 |
| 残高更新(2回のSSTORE) | ~10,000 |
| トークン転送(2回の外部コール) | ~15,000 |
| イベント発行 | ~1,500 |
| 合計 | ~57,500 |
ガスの約60%がストレージ操作と外部コールに費やされます。
まとめ
ガス最適化は算術オペコードのマイクロ最適化ではなく、ストレージアクセスと外部コールの最小化です。最もガスを節約するパターンはアーキテクチャ的なものです:ストレージパッキング、キャッシング、calldataの使用、アクセスリストの最適化。