メインコンテンツへスキップ
ブロックチェーン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はトランザクションごとのアクセスセットを導入しました。最初にアクセスした時は「コールド」で追加ガスがかかり、以降は「ウォーム」で安価です。

オペコードコールドウォーム節約
SLOAD21001002000
BALANCE26001002500
CALL26001002500

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の使用、アクセスリストの最適化。