以太坊虛擬機Opcode執行成本數學推導與量化分析完整指南

本文從量化工程師的視角,深入推導 EVM Opcode 執行成本的數學基礎。我們從計算理論角度分析不同 Opcode 的資源消耗,建立完整的 Gas 成本模型,包括記憶體擴展成本的二次函數特性、儲存操作的層級定價、密碼學操作的複雜度分析、以及密碼學預編譯合約的成本函數。同時提供 Solidity、Python 和 TypeScript 的實作程式碼範例。

以太坊虛擬機Opcode執行成本數學推導與量化分析完整指南

概述

以太坊虛擬機(Ethereum Virtual Machine, EVM)是以太坊智慧合約執行的核心環境,其設計的精巧之處在於透過燃料(Gas)機制實現資源定價與網路安全。以太坊黃皮書(Gavin Wood, 2014)詳細定義了每個操作碼(Opcode)的執行成本,這些成本並非任意設定,而是基於嚴格的計算複雜度分析與真實硬體資源消耗測量。

本文從量化工程師的視角,深入推導 EVM Opcode 執行成本的數學基礎。我們將從計算理論角度分析不同Opcode的資源消耗,建立完整的成本模型,並透過實際測量驗證理論推導的正確性。閱讀本文需要具備計算機結構、演算法複雜度分析、密碼學基礎的先備知識,以及對以太坊智慧合約開發的基本了解。

第一章:EVM Opcode 成本模型的理論基礎

1.1 計算資源消耗的理論框架

EVM Opcode 的執行成本理論基礎建立在計算資源消耗的量化模型上。在真實硬體環境中,每次計算操作都會消耗以下資源:

CPU 週期(CPU Cycles):處理器執行指令所需的時鐘週期數量

記憶體存取(Memory Accesses):讀取和寫入 RAM 的次數與資料量

儲存I/O(Storage I/O):與持久化儲存的資料交換量

網路傳輸(Network Transmission):資料在網路中的傳輸量

以太坊黃皮書將這些資源消耗統一量化為 Gas 單位,這種設計使得不同類型的計算操作可以進行公平比較。

1.2Opcode 成本的層級結構

根據黃皮書的定義,Opcode 成本可以分為以下幾個層級:

class EVMGasCostModel:
    """
    EVM Opcode 成本模型
    成本層級說明
    """
    
    # 第一層:基本運算成本
    BASE_COST = {
        "STOP": 0,
        "ADD": 3,      # 加法
        "MUL": 5,      # 乘法
        "SUB": 3,      # 減法
        "DIV": 5,      # 整數除法(無符號)
        "SDIV": 5,     # 整數除法(有符號)
        "MOD": 5,      # 取模運算(無符號)
        "SMOD": 5,     # 取模運算(有符號)
        "ADDMOD": 8,   # (a + b) % c
        "MULMOD": 8,   # (a * b) % c
    }
    
    # 第二層:位元運算成本
    BITWISE_COST = {
        "AND": 3,
        "OR": 3,
        "XOR": 3,
        "BYTE": 3,
        "SHL": 3,      # 左移
        "SHR": 3,      # 右移
        "SAR": 3,      # 算術右移
    }
    
    # 第三層:比較運算成本
    COMPARISON_COST = {
        "LT": 3,       # 小於
        "GT": 3,       # 大於
        "SLT": 3,      # 有符號小於
        "SGT": 3,      # 有符號大於
        "EQ": 3,       # 等於
        "ISZERO": 3,   # 為零檢查
    }
    
    # 第四層:密碼學操作成本(最昂貴)
    CRYPTO_COST = {
        "KECCAK256": 30 + 6 * (words - 1),  # 依據輸入大小調整
        "SHA256": 60 + 12 * (words - 1),
        "RIPEMD160": 600 + 120 * (words - 1),
    }

print("EVM Opcode 成本層級結構")
print("=" * 60)
print("層級 1 - 基本運算: 3-8 Gas")
print("層級 2 - 位元運算: 3 Gas")
print("層級 3 - 比較運算: 3 Gas")
print("層級 4 - 密碼學: 30-720+ Gas")

1.3 成本函數的數學形式化

讓我們為 EVM Opcode 成本建立完整的數學形式化框架:

定義 1(Opcode 成本函數)

對於任意Opcode op,其執行成本 C(op) 可以表示為:

C(op) = C_base(op) + C_dynamic(op, state)

其中:

定義 2(狀態依賴成本)

某些 Opcode 的執行成本依賴執行時的狀態:

C_dynamic(op, state) = Σ g_i(state_i)

例如,KECCAK256 的成本不僅包含基本成本,還需要計算位元組操作的額外成本。

第二章:記憶體操作成本的數學推導

2.1 記憶體擴展成本的理論基礎

EVM 的記憶體模型基於位元組陣列(Byte Array),支援動態擴展。記憶體操作的成本由兩個部分組成:

固定成本(F):每次記憶體訪問的基礎成本

擴展成本(E):記憶體陣列擴展時增加的邊際成本

根據黃皮書的定義,記憶體成本函數為:

C_memory(bytes) = F + E × M_ext(bytes)

其中 M_ext 是記憶體擴展函數,定義如下:

M_ext(s) = ceil((s + 31) / 32) - ceil(s_prev / 32)

這表示記憶體成本以 32 位元組為一個「字」(Word)進行計算。

2.2 記憶體成本的精確數學推導

讓我們推導記憶體成本的精確數學表達式:

定理 1(記憶體成本函數)

令 s 為記憶體請求的位元組數,則記憶體成本 C_mem(s) 為:

import math

def memory_cost_math(s, F=3, E=3):
    """
    記憶體成本的數學推導
    
    參數:
    - s: 請求的記憶體大小(位元組)
    - F: 固定成本係數(默認 3)
    - E: 擴展成本係數(默認 3)
    
    數學推導:
    令 W = ceil((s + 31) / 32) 為請求的字數
    令 W_prev = ceil((s_prev + 31) / 32) 為之前的字數
    C_mem = F * (W - W_prev) + E * (W^2 - W_prev^2)
    
    簡化後(假設從 0 開始):
    C_mem = 3 * W + 3 * W^2 / 512
    """
    # 請求的字數
    W = (s + 31) // 32 if s % 32 == 0 else s // 32 + 1
    
    # 記憶體成本公式
    # C = F * (W - W_prev) + E * (W^2 - W_prev^2) / 512
    cost = F * W + (E * W * W) // 512
    
    return cost

print("記憶體成本數學推導")
print("=" * 60)
test_sizes = [0, 32, 64, 128, 256, 512, 1024, 2048, 4096]
print(f"{'請求大小':>12} | {'字數 (W)':>10} | {'Gas 成本':>10}")
print("-" * 40)
for size in test_sizes:
    W = math.ceil((size + 31) / 32)
    cost = memory_cost_math(size)
    print(f"{size:>12} | {W:>10} | {cost:>10}")

推論 1(記憶體成本的漸進行為)

當 s → ∞ 時,記憶體成本趨近於:

C_mem(s) ≈ (3/512) × s² / 32² + O(s)
C_mem(s) ≈ (3/1048576) × s² + O(s)

這表明記憶體成本是二次函數,反映了記憶體擴展的邊際成本遞增特性。

2.3 MLOAD/MSTORE 操作的成本模型

MLOAD 和 MSTORE 是 EVM 中最常見的記憶體操作,其成本由以下公式給出:

def mlod_mstore_cost(s, s_prev=0):
    """
    MLOAD/MSTORE 操作成本模型
    
    公式:
    C_mload = G_verylow + C_memory(s) - C_memory(s_prev)
    
    其中:
    - G_verylow = 3 (基本操作成本)
    - C_memory 為記憶體成本函數
    """
    G_verylow = 3
    
    # 記憶體成本變化
    delta_mem = memory_cost_math(s, F=3, E=3) - memory_cost_math(s_prev, F=3, E=3)
    
    return G_verylow + delta_mem

print("MLOAD/MSTORE 成本分析")
print("=" * 60)
print(f"{'記憶體位置':>15} | {'前次位置':>15} | {'Gas 成本':>10}")
print("-" * 50)
test_cases = [
    (0, 0),
    (32, 0),
    (32, 32),
    (64, 0),
    (64, 32),
    (64, 64),
]
for s, s_prev in test_cases:
    cost = mlod_mstore_cost(s, s_prev)
    print(f"{s:>15} | {s_prev:>15} | {cost:>10}")

數值分析

從上表可以看出:

第三章:儲存操作成本的數學模型

3.1 SSTORE 操作的成本結構

SSTORE 是 EVM 中最昂貴的操作之一,因為它涉及與持久化儲存的資料交換。SSTORE 的成本結構如下:

定義(SSTORE 成本函數)

def sstore_cost(current_value, new_value, original_value, gas_refund):
    """
    SSTORE 操作成本模型
    
    根據 EIP-2200(Istanbul)定義:
    
    成本:
    - 20000 gas: 當 current_value = 0
    - 2900 gas: 當 current_value ≠ 0 且 new_value = 0(觸發清除退款)
    - 2900 gas: 當 current_value ≠ 0 且 new_value ≠ 0
    - 800 gas: 當 current_value = new_value
    
    退款(上限 15000 gas):
    - 若 current_value = 0 且 new_value ≠ 0: 無退款(已被扣20000)
    - 若 current_value ≠ 0 且 new_value = 0: 退款 15000 gas(淨成本 1400)
    - 若 current_value ≠ 0 → new_value ≠ 0: 退款 15000 gas(但只扣2900)
    """
    
    if current_value == 0 and new_value != 0:
        # 從零到非零:支付 20000 gas
        cost = 20000
        refund = 0
    elif current_value != 0 and new_value == 0:
        # 從非零到零:支付 2900 gas,退款 15000 gas
        cost = 2900
        refund = 15000
    elif current_value != 0 and new_value != 0:
        # 從非零到非零:支付 2900 gas
        cost = 2900
        refund = 0
    else:
        # current_value == new_value: 支付 800 gas
        cost = 800
        refund = 0
    
    net_cost = cost - refund if refund > 0 else cost
    return cost, refund, net_cost

print("SSTORE 成本結構分析")
print("=" * 80)
print(f"{'當前值':>15} | {'新值':>15} | {'原始值':>15} | {'成本':>8} | {'退款':>8} | {'淨成本':>8}")
print("-" * 95)
test_cases = [
    (0, 0, 0, "零→零"),
    (0, 1, 0, "零→非零"),
    (1, 0, 0, "非零→零"),
    (1, 2, 0, "非零→非零"),
    (1, 1, 0, "相同值"),
    (0, 0, 1, "零→零(歷史非零)"),
]
for cv, nv, ov, desc in test_cases:
    cost, refund, net = sstore_cost(cv, nv, ov, 0)
    print(f"{cv:>15} | {nv:>15} | {ov:>15} | {cost:>8} | {refund:>8} | {net:>8} | {desc}")

3.2 儲存清除退款的數學推導

SSTORE 操作的退款機制是 EVM 設計中最複雜的部分之一。讓我們數學推導退款機制的邊界條件:

定理 2(儲存清除退款的邊界條件)

令:

則淨成本 C_net 為:

C_net = C_sstore_nonzero_to_zero - min(R_sstore, R_max)
C_net = 2900 - 15000 = -12100 (理論上)

但實際上退款不能使淨成本為負,因此:

C_net = max(2900 - R_max, 2900 - R_sstore)
C_net = max(-12100, -12100) = 0 (理論下界)

實際上,淨成本存在下限,定義為:

C_net_min = C_sstore_nonzero_to_zero - R_max
           = 2900 - 15000
           = -12100

但由於交易必須支付基礎成本,實際淨成本 >= 5000

讓我們驗證這個推論:

def verify_storage_refund_math():
    """
    驗證儲存清除退款的數學推導
    """
    
    # 參數定義
    R_MAX = 15000  # 最大退款上限
    R_SSTORE = 15000  # SSTORE 清除退款
    C_SSTORE_NZ_Z = 2900  # 非零到零成本
    C_BASE_MIN = 5000  # 交易基礎成本下限
    
    print("儲存清除退款數學驗證")
    print("=" * 60)
    
    # 理論計算
    theoretical_net = C_SSTORE_NZ_Z - R_SSTORE
    print(f"理論淨成本: {theoretical_net} gas")
    print(f"最大退款上限: {R_MAX} gas")
    print(f"SSTORE 非零到零成本: {C_SSTORE_NZ_Z} gas")
    
    # 實際計算(考慮邊界條件)
    # 退款不能使淨成本低於基礎成本
    
    print()
    print("邊界條件分析:")
    print(f"1. 如果只清除一個存儲槽:")
    print(f"   成本: {C_SSTORE_NZ_Z} gas")
    print(f"   退款: {R_MAX} gas")
    print(f"   淨成本: {C_SSTORE_NZ_Z - R_MAX} gas")
    print()
    print(f"2. 如果清除多個存儲槽:")
    print(f"   總成本: n × {C_SSTORE_NZ_Z} gas")
    print(f"   總退款: min(n × {R_SSTORE}, {R_MAX}) gas")
    print(f"   單槽平均淨成本: max({C_SSTORE_NZ_Z - R_MAX}, 0) gas")
    
    return theoretical_net

verify_storage_refund_math()

3.3 SLOAD 操作的層級成本結構

SLOAD 操作的成本結構反映了狀態讀取的不同層級:

def sload_cost(warmth, cold_access_cost=2100, warm_access_cost=100):
    """
    SLOAD 操作成本模型
    
    根據 EIP-2929(Berlin)定義:
    - Cold access: 2100 gas (首次訪問某個存儲槽)
    - Warm access: 100 gas (在交易中已訪問過)
    
    數學表達式:
    C_sload = C_cold_access × I(cold) + C_warm_access × I(warm)
    
    其中 I(condition) 為指示函數
    """
    return cold_access_cost if not warmth else warm_access_cost

print("SLOAD 成本結構")
print("=" * 60)
print(f"{'訪問類型':>20} | {'Gas 成本':>12}")
print("-" * 40)
print(f"{'Cold Access':>20} | {2100:>12}")
print(f"{'Warm Access':>20} | {100:>12}")
print(f"節省比例: {(2100 - 100) / 2100 * 100:.1f}%")

第四章:密碼學操作成本的量化分析

4.1 Keccak-256 雜湊函數的成本模型

Keccak-256 是以太坊的核心密碼學原語,用於多種場景。其成本模型是 EVM 中最複雜的之一。

定理 3(Keccak-256 成本函數)

令輸入位元組數為 k,則 Keccak-256 的 Gas 成本 C_keccak(k) 為:

def keccak256_cost(k):
    """
    Keccak-256 成本函數
    
    根據黃皮書定義:
    C_keccak256(k) = 30 + 6 × (⌈k/1360⌉ - 1)
    
    其中 1360 是 Keccak-256 的吞吐量(每 1360 個輸入位元組需要一個額外循環)
    
    數學推導:
    - 基本成本: 30 gas (處理第一個區塊)
    - 每額外區塊: 6 gas
    """
    # 計算所需的處理循環數
    # Keccak-256 對每 1360 個輸入位元組需要一個完整的壓縮函數調用
    blocks = (k + 1360 - 1) // 1360 if k > 0 else 1
    
    # 成本公式
    cost = 30 + 6 * (blocks - 1)
    
    return cost, blocks

print("Keccak-256 成本函數分析")
print("=" * 60)
print(f"{'輸入大小':>12} | {'區塊數':>10} | {'Gas 成本':>10}")
print("-" * 40)
test_sizes = [0, 1, 32, 64, 1360, 1361, 2720, 2721, 13600, 27200]
for size in test_sizes:
    cost, blocks = keccak256_cost(size)
    print(f"{size:>12} | {blocks:>10} | {cost:>10}")

數值分析

從上表可以觀察:

4.2 橢圓曲線運算成本

ECADD(橢圓曲線加法)和 ECMUL(橢圓曲線乘法)是以太坊中用於簽名驗證的核心操作。這些操作的 Gas 成本反映了真實硬體執行的複雜度:

def ec_operation_cost(operation, mode="high"):
    """
    橢圓曲線運算成本模型
    
    根據黃皮書定義:
    
    ECADD (加法):
    - High-quality gas meter: 500
    - Mid-quality gas meter: 150
    
    ECMUL (乘法):
    - High-quality gas meter: 40000
    - Mid-quality gas meter: 12000
    
    成本比值分析:
    ECMUL / ECADD ≈ 80 (反映乘法比加法複雜 80 倍)
    """
    
    if operation == "ECADD":
        return {"high": 500, "mid": 150}[mode]
    elif operation == "ECMUL":
        return {"high": 40000, "mid": 12000}[mode]
    else:
        raise ValueError(f"Unknown operation: {operation}")

def precompile_cost(precompile_address):
    """
    預編譯合約成本映射
    """
    costs = {
        # 橢圓曲線運算
        0x01: ("ecRecover", 3000),
        0x02: ("sha256", 60 + 12 * (message_size - 1)),
        0x03: ("ripemd160", 600 + 120 * (message_size - 1)),
        0x04: ("identity", 15 + 3 * (message_size - 1)),
        
        # 橢圓曲線
        0x06: ("ecAdd", 500),
        0x07: ("ecMul", 40000),
        0x08: ("ecPairing", 100000 + 80000 * (pair_count - 1)),
    }
    return costs.get(precompile_address, (None, 0))

print("預編譯合約 Gas 成本")
print("=" * 60)
print(f"{'地址':>10} | {'名稱':>20} | {'Gas 成本':>12}")
print("-" * 50)
precompiles = [
    (0x01, "ecRecover", "可變"),
    (0x02, "SHA256", "60 + 12n"),
    (0x03, "RIPEMD160", "600 + 120n"),
    (0x04, "identity", "15 + 3n"),
    (0x05, "modexp", "可變"),
    (0x06, "ecAdd", "500"),
    (0x07, "ecMul", "40000"),
    (0x08, "ecPairing", "100000 + 80000n"),
    (0x09, "bn128Add", "500"),
    (0x0a, "bn128Mul", "40000"),
    (0x0b, "bn128Pairing", "100000 + 80000n"),
]
for addr, name, cost_str in precompiles:
    print(f"0x{addr:02x}:     | {name:>20} | {cost_str:>12}")

4.3 橢圓曲線配對成本函數

EVM 中的橢圓曲線配對(Pairing)操作用於零知識證明驗證和某些進階密碼學應用。其成本函數為:

定理 4(Pairing 成本函數)

令配對的點對數量為 n,則 Pairing 操作的 Gas 成本為:

C_pairing(n) = 100000 + 80000 × (n - 1)
def pairing_cost(n):
    """
    橢圓曲線配對成本函數
    
    C(n) = 100000 + 80000 × (n - 1)
    
    數學分析:
    - 固定成本: 100000 gas (處理第一對)
    - 邊際成本: 80000 gas (每增加一對)
    
    經濟解釋:
    - 邊際成本 ≈ 固定成本的 80%
    - 反映配對運算的固定開銷與線性擴展部分
    """
    if n < 1:
        raise ValueError("Number of pairs must be at least 1")
    
    return 100000 + 80000 * (n - 1)

print("橢圓曲線配對成本函數")
print("=" * 60)
print(f"{'配對數量':>10} | {'Gas 成本':>15} | {'邊際成本':>12}")
print("-" * 50)
prev_cost = 0
for n in range(1, 11):
    cost = pairing_cost(n)
    marginal = cost - prev_cost
    print(f"{n:>10} | {cost:>15,} | {marginal:>12,}")
    prev_cost = cost

第五章:合約調用成本的數學模型

5.1 CALL 系列操作的層級成本結構

CALL、CALLCODE、DELEGATECALL 和 STATICCALL 是 EVM 中用於合約間通訊的核心操作。這些操作的 Gas 成本結構如下:

def call_cost(call_type, gas_sent, call_value, is_new_account, is_precompile=False):
    """
    調用操作成本模型
    
    CALL 操作的成本組成:
    1. 基礎成本 G_call
    2. 價值轉移成本 G_callValue (如果 call_value > 0)
    3. 新帳戶成本 G_newaccount (如果目標是新帳戶)
    
    數學表達式:
    C_call = G_call
           + G_callValue × I(call_value > 0)
           + G_newaccount × I(is_new_account)
           + gas_sent
    
    其中 I(condition) 為指示函數
    """
    
    # 基礎成本
    G_call = 700  # 基本調用成本
    G_callvalue = 9000  # 價值轉移額外成本
    G_newaccount = 25000  # 新帳戶創建成本
    
    cost = G_call
    
    # 價值轉移
    if call_value > 0:
        cost += G_callvalue
    
    # 新帳戶
    if is_new_account and not is_precompile:
        cost += G_newaccount
    
    # 轉帳的 Gas
    cost += gas_sent
    
    return cost

print("CALL 操作成本結構分析")
print("=" * 80)
print(f"{'調用類型':>15} | {'轉移價值':>15} | {'新帳戶':>10} | {'Gas':>12}")
print("-" * 60)
test_cases = [
    ("CALL", 0, False),
    ("CALL", 1e18, False),
    ("CALL", 0, True),
    ("CALL", 1e18, True),
]
for call_type, value, is_new in test_cases:
    cost = call_cost(call_type, 0, value, is_new)
    print(f"{call_type:>15} | {value:>15.0f} | {str(is_new):>10} | {cost:>12,}")

5.2 CREATE 系列操作的 Gas 成本函數

CREATE 和 CREATE2 操作用於創建新合約。讓我們推導其成本函數:

定理 5(CREATE 操作成本函數)

def create_cost(code_length, init_code_gas):
    """
    CREATE 操作成本模型
    
    根據 EIP-170 和後續升級定義:
    
    C_create = G_create + G_codedeposit × code_length
             + init_code_gas
    
    其中:
    - G_create = 32000 (合約創建基礎成本)
    - G_codedeposit = 200 (每單位代碼長度的存款成本)
    - init_code_gas = 初始化代碼執行的 Gas
    """
    
    G_create = 32000  # 合約創建基礎成本
    G_codedeposit = 200  # 每位元組代碼存款成本
    
    # EIP-170: 最大合約代碼大小為 24576 位元組
    max_code_size = 24576
    
    if code_length > max_code_size:
        raise ValueError(f"Code size exceeds maximum: {code_length} > {max_code_size}")
    
    cost = G_create + G_codedeposit * code_length + init_code_gas
    
    return cost

def create2_cost(code_length, init_code_gas, salt):
    """
    CREATE2 操作成本模型
    
    CREATE2 相比 CREATE 增加了:
    - Keccak-256 雜湊計算成本(用於 salt 和代碼的雜湊)
    
    C_create2 = G_create + G_codedeposit × code_length
              + G_keccak256 × 2 + init_code_gas
    """
    
    G_create = 32000
    G_codedeposit = 200
    G_keccak256_per_word = 6  # 每個 32 位元組字的 Keccak 成本
    G_keccak256_base = 30
    
    # CREATE2 的 salt 和代碼需要 Keccak-256
    # 輸入 = 32 (salt) + len(code) (代碼)
    words = (32 + code_length + 31) // 32
    keccak_cost = G_keccak256_base + G_keccak256_per_word * (words - 1)
    
    cost = G_create + G_codedeposit * code_length + keccak_cost + init_code_gas
    
    return cost

print("CREATE/CREATE2 成本函數")
print("=" * 70)
print(f"{'合約大小':>12} | {'初始化Gas':>12} | {'CREATE':>12} | {'CREATE2':>12}")
print("-" * 60)
test_cases = [(100, 50000), (1000, 100000), (10000, 500000), (24000, 1000000)]
for code_len, init_gas in test_cases:
    c_cost = create_cost(code_len, init_gas)
    c2_cost = create2_cost(code_len, init_gas, 0)
    print(f"{code_len:>12} | {init_gas:>12,} | {c_cost:>12,} | {c2_cost:>12,}")

5.3 委託調用的成本差異分析

DELEGATECALL 和 STATICCALL 是兩種特殊調用類型,其成本結構與普通 CALL 有所不同:

def special_call_cost(call_type):
    """
    特殊調用類型的成本模型
    
    比較分析:
    - CALL: 700 gas (基礎) + 可能的轉帳成本
    - CALLCODE: 700 gas (等同於 CALL)
    - DELEGATECALL: 700 gas (基礎,EIP-7 後引入)
    - STATICCALL: 700 gas (基礎,EIP-214 後引入)
    
    關鍵差異:
    1. STATICCALL 不能修改狀態
    2. DELEGATECALL 保持 msg.sender 和 msg.value
    3. CALLCODE 執行目標代碼但使用調用者存儲
    """
    
    base_costs = {
        "CALL": 700,
        "CALLCODE": 700,
        "DELEGATECALL": 700,
        "STATICCALL": 700,
    }
    
    return base_costs.get(call_type, 0)

print("特殊調用類型成本比較")
print("=" * 50)
for call_type in ["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"]:
    cost = special_call_cost(call_type)
    print(f"{call_type:>15}: {cost} gas")

第六章:完整 Gas 成本的計算框架

6.1 交易 Gas 成本的數學模型

以太坊交易的 Gas 成本由以下部分組成:

def transaction_gas_cost(tx_type="legacy", tx_data=None):
    """
    交易 Gas 成本模型
    
    成本組成:
    1. 基礎交易成本 G_transaction
    2. 數據成本 G_data (根據數據內容)
    3. 調用成本 G_call (如果交易呼叫合約)
    
    數學表達式:
    C_tx = G_transaction
          + Σ G_zerobyte × Z(tx_data[i])
          + Σ G_nonzerobyte × NZ(tx_data[i])
          + C_call
          
    其中:
    - Z(byte) = 1 如果 byte = 0, 否則 0
    - NZ(byte) = 1 如果 byte ≠ 0, 否則 0
    """
    
    G_transaction = 21000  # 基礎交易成本(ETH 轉帳)
    G_zerobyte = 4  # 每個零位元組的成本
    G_nonzerobyte = 16  # 每個非零位元組的成本(EIP-2028 後從 68 降至 16)
    G_calldatazero = 4  # calldata 零位元組
    G_calldatanonzero = 16  # calldata 非零位元組(EIP-2028 後)
    
    if tx_data is None:
        return G_transaction
    
    # 計算數據成本
    zero_count = sum(1 for b in tx_data if b == 0)
    nonzero_count = len(tx_data) - zero_count
    
    data_cost = G_zerobyte * zero_count + G_nonzerobyte * nonzero_count
    
    total_cost = G_transaction + data_cost
    
    return total_cost

print("交易 Gas 成本分析")
print("=" * 70)
test_data = [
    b"",  # 空數據
    b"\x00\x00\x00",  # 全部為零
    b"\x01\x02\x03",  # 全部非零
    b"\x00\x01\x00\x02",  # 混合
    bytes(range(256)),  # 完整位元組範圍
]
for data in test_data:
    cost = transaction_gas_cost(tx_data=data)
    zero = sum(1 for b in data if b == 0)
    nonzero = len(data) - zero
    print(f"數據長度: {len(data):>4} | 零: {zero:>3} | 非零: {nonzero:>3} | 成本: {cost:,} gas")

6.2 EIP-1559 之後的費用結構

EIP-1559 引入了一種新的費用結構,其數學模型如下:

def eip1559_fee_calculation(
    base_fee_per_gas,
    priority_fee_per_gas,
    max_fee_per_gas,
    gas_used,
    gas_limit
):
    """
    EIP-1559 費用計算模型
    
    核心公式:
    
    1. Base Fee (基礎費用):
       base_fee_per_gas 根據網路利用率自動調整
       
    2. Priority Fee (優先費用):
       priority_fee = min(priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)
       
    3. Effective Gas Price (有效 Gas 價格):
       effective_gas_price = base_fee_per_gas + priority_fee
       
    4. Total Fee (總費用):
       total_fee = effective_gas_price × gas_used
       
    5. Burned Amount (燃燒金額):
       burned = base_fee_per_gas × gas_used
       
    6. Miner/Validator Tip (礦工/驗證者小費):
       tip = priority_fee × gas_used
    """
    
    # 優先費用上限
    priority_fee = min(
        priority_fee_per_gas,
        max_fee_per_gas - base_fee_per_gas
    )
    
    # 有效 Gas 價格
    effective_gas_price = base_fee_per_gas + priority_fee
    
    # 總費用
    total_fee = effective_gas_price * gas_used
    
    # 燃燒金額
    burned = base_fee_per_gas * gas_used
    
    #礦工/驗證者收益
    validator_tip = priority_fee * gas_used
    
    return {
        "priority_fee": priority_fee,
        "effective_gas_price": effective_gas_price,
        "total_fee": total_fee,
        "burned": burned,
        "validator_tip": validator_tip,
    }

print("EIP-1559 費用結構數學模型")
print("=" * 70)
print("場景: 標準轉帳交易")
print("-" * 70)

params = {
    "base_fee_per_gas": 30,  # Gwei
    "priority_fee_per_gas": 2,  # Gwei
    "max_fee_per_gas": 100,  # Gwei
    "gas_used": 21000,  # 標準 ETH 轉帳
    "gas_limit": 21000,
}

result = eip1559_fee_calculation(**params)
print(f"Base Fee: {params['base_fee_per_gas']} Gwei")
print(f"Priority Fee: {result['priority_fee']} Gwei")
print(f"Effective Gas Price: {result['effective_gas_price']} Gwei")
print(f"Gas Used: {params['gas_used']:,}")
print()
print(f"Total Fee: {result['total_fee']:,} Gwei = {result['total_fee']/1e9:.6f} ETH")
print(f"Burned: {result['burned']:,} Gwei = {result['burned']/1e9:.6f} ETH")
print(f"Validator Tip: {result['validator_tip']:,} Gwei = {result['validator_tip']/1e9:.6f} ETH")

6.3 完整 Gas 成本計算器

將所有成本模型整合為一個完整的 Gas 計算器:

class EVMGasCalculator:
    """
    完整 EVM Gas 成本計算器
    """
    
    # 操作成本表
    OPCODE_COSTS = {
        # 基本運算
        "STOP": 0,
        "ADD": 3, "MUL": 5, "SUB": 3,
        "DIV": 5, "SDIV": 5, "MOD": 5, "SMOD": 5,
        "ADDMOD": 8, "MULMOD": 8,
        "EXP": 10,  # 基礎成本
        
        # 位元運算
        "AND": 3, "OR": 3, "XOR": 3, "NOT": 3,
        "BYTE": 3, "SHL": 3, "SHR": 3, "SAR": 3,
        
        # 比較
        "LT": 3, "GT": 3, "SLT": 3, "SGT": 3,
        "EQ": 3, "ISZERO": 3,
        
        # 堆疊操作
        "PUSH1": 3, "PUSH2": 3, "PUSH3": 3, "PUSH4": 3,
        "PUSH5": 3, "PUSH6": 3, "PUSH7": 3, "PUSH8": 3,
        "PUSH9": 3, "PUSH10": 3, "PUSH11": 3, "PUSH12": 3,
        "PUSH13": 3, "PUSH14": 3, "PUSH15": 3, "PUSH16": 3,
        "PUSH17": 3, "PUSH18": 3, "PUSH19": 3, "PUSH20": 3,
        "PUSH21": 3, "PUSH22": 3, "PUSH23": 3, "PUSH24": 3,
        "PUSH25": 3, "PUSH26": 3, "PUSH27": 3, "PUSH28": 3,
        "PUSH29": 3, "PUSH30": 3, "PUSH31": 3, "PUSH32": 3,
        "POP": 2, "DUP1": 3, "DUP2": 3, "DUP3": 3, "DUP4": 3,
        "DUP5": 3, "DUP6": 3, "DUP7": 3, "DUP8": 3,
        "DUP9": 3, "DUP10": 3, "DUP11": 3, "DUP12": 3,
        "DUP13": 3, "DUP14": 3, "DUP15": 3, "DUP16": 3,
        "SWAP1": 3, "SWAP2": 3, "SWAP3": 3, "SWAP4": 3,
        "SWAP5": 3, "SWAP6": 3, "SWAP7": 3, "SWAP8": 3,
        "SWAP9": 3, "SWAP10": 3, "SWAP11": 3, "SWAP12": 3,
        "SWAP13": 3, "SWAP14": 3, "SWAP15": 3, "SWAP16": 3,
        
        # 記憶體操作
        "MLOAD": 3,
        "MSTORE": 3,
        "MSTORE8": 3,
        "MSIZE": 3,
        "GAS": 2,
        
        # 控制流
        "JUMP": 8,
        "JUMPI": 10,
        "JUMPDEST": 1,
        "PC": 2,
        "RJUMPI": 10,  # EIP-4200
        "RJUMPV": 10,  # EIP-4200
        
        # 日誌
        "LOG0": 375, "LOG1": 375, "LOG2": 375, "LOG3": 375, "LOG4": 375,
    }
    
    def __init__(self):
        self.reset()
    
    def reset(self):
        self.total_gas = 0
        self.memory_gas = 0
        self.storage_gas = 0
        self.stack_gas = 0
        self.op_count = {}
    
    def add_opcode(self, opcode, *args):
        """添加一個操作並計算其成本"""
        base_cost = self.OPCODE_COSTS.get(opcode, 0)
        dynamic_cost = self._calculate_dynamic_cost(opcode, *args)
        total_cost = base_cost + dynamic_cost
        
        self.total_gas += total_cost
        self.op_count[opcode] = self.op_count.get(opcode, 0) + 1
        
        return total_cost
    
    def _calculate_dynamic_cost(self, opcode, *args):
        """計算動態成本"""
        if opcode == "EXP":
            if len(args) > 0:
                # 指數大小成本
                byte_size = (args[0].bit_length() + 7) // 8
                return 50 * byte_size
        elif opcode in ["MLOAD", "MSTORE"]:
            if len(args) > 0:
                offset = args[0]
                # 記憶體擴展成本
                new_words = (offset + 31) // 32
                # 這裡應該計算邊際成本
                return 0
        return 0
    
    def estimate_deployment_gas(self, bytecode):
        """
        估算合約部署的 Gas
        """
        # 部署成本 = G_create + G_codedeposit × len(code)
        G_create = 32000
        G_codedeposit = 200
        
        deploy_gas = G_create + G_codedeposit * len(bytecode)
        
        # 添加執行初始化代碼的成本(粗略估計)
        # 實際需要完整的 bytecode 分析
        exec_gas = len(bytecode) * 3  # 粗估
        
        return deploy_gas + exec_gas

# 使用示例
calc = EVMGasCalculator()

# 添加一些操作
calc.add_opcode("PUSH1", 1)
calc.add_opcode("PUSH1", 2)
calc.add_opcode("ADD")
calc.add_opcode("PUSH1", 0)
calc.add_opcode("MSTORE")
calc.add_opcode("STOP")

print("EVM Gas 成本計算器示例")
print("=" * 60)
print(f"操作統計:")
for op, count in calc.op_count.items():
    base = calc.OPCODE_COSTS.get(op, 0)
    print(f"  {op}: {count} × {base} = {count * base} gas")
print(f"\n總 Gas 消耗: {calc.total_gas}")

第七章:實際應用與優化策略

7.1 智慧合約 Gas 優化的數學原理

理解 Opcode 成本模型後,我們可以推導出智慧合約優化的數學原則:

def optimize_analysis():
    """
    Gas 優化的數學分析
    
    核心原則:
    1. 減少存儲操作次數
    2. 批量處理以攤銷固定成本
    3. 使用位元運算替代算術運算
    4. 優化記憶體佈局
    """
    
    print("Gas 優化數學分析")
    print("=" * 70)
    
    # 原則 1: 存儲 vs 記憶體
    print("\n1. 存儲 vs 記憶體成本比較")
    sstore_nz = 20000  # 寫入新存儲槽
    sstore_z = 2900   # 寫入現有存儲槽
    mstore = 3 + 3    # 基本記憶體操作
    print(f"   SSTORE (新值): {sstore_nz} gas")
    print(f"   SSTORE (現有值): {sstore_z} gas")
    print(f"   MSTORE: ~{mstore} gas")
    print(f"   存儲比記憶體貴: {sstore_nz / mstore:.0f}x - {sstore_z / mstore:.0f}x")
    
    # 原則 2: 批量處理效益
    print("\n2. 批量處理效益分析")
    fixed_overhead = 21000  # 交易基礎成本
    per_operation = 5000    # 每個操作的成本(平均值)
    
    n_operations = [1, 10, 100, 1000]
    print(f"   固定開銷: {fixed_overhead} gas")
    print(f"   每操作成本: {per_operation} gas")
    print()
    for n in n_operations:
        total = fixed_overhead + per_operation * n
        avg = total / n
        saving = (fixed_overhead + per_operation) / total * 100
        print(f"   n={n:>4}: 總成本 = {total:>8,} gas, 平均 = {avg:.1f} gas, 節省 = {saving:.1f}%")
    
    # 原則 3: 迴圈展開
    print("\n3. 迴圈展開效益")
    loop_overhead = 8  # JUMPI 成本
    loop_body = 10    # 迴圈體成本
    
    def loop_cost(n, body_cost, overhead=loop_overhead):
        """迴圈成本"""
        return overhead + n * (body_cost + overhead)
    
    def unrolled_cost(n, body_cost):
        """展開後成本"""
        return n * body_cost
    
    n = 10
    print(f"   n = {n} 次操作")
    print(f"   迴圈: {loop_cost(n, loop_body)} gas")
    print(f"   展開: {unrolled_cost(n, loop_body)} gas")
    print(f"   差異: {loop_cost(n, loop_body) - unrolled_cost(n, loop_body)} gas")

optimize_analysis()

7.2 不同資料結構的 Gas 成本比較

def data_structure_cost_comparison():
    """
    資料結構 Gas 成本比較分析
    
    分析不同資料結構的 Gas 消耗特性
    """
    
    print("資料結構 Gas 成本比較")
    print("=" * 80)
    
    # 存儲映射 (Mapping)
    print("\n1. 存儲映射 (Mapping)")
    print("   存取模式: key → value")
    sload_warm = 100   # 熱存儲讀取
    sstore_write = 2900  # 存儲寫入
    print(f"   SLOAD (熱): {sload_warm} gas")
    print(f"   SSTORE: {sstore_write} gas")
    print(f"   讀寫比: 1:{sstore_write/sload_warm}")
    
    # 動態數組
    print("\n2. 動態數組")
    print("   存取需要:")
    print(f"   - Slot 讀取: {sload_warm} gas")
    print(f"   - Length 讀取: {sload_warm} gas")
    print(f"   - Keccak-256 (計算位置): 30 + 6 × (words-1) gas")
    print(f"   - 總存取: ~{sload_warm * 2 + 30} gas")
    
    # 結構體
    print("\n3. 結構體 (Struct)")
    print("   存取模式: slot_base + offset → value")
    print(f"   - Base Slot: {sload_warm} gas")
    print(f"   - Offset Slot: {sload_warm} gas")
    print(f"   - 總存取: ~{sload_warm * 2} gas")
    print(f"   結論: 結構體比動態數組省一個 Keccak-256 計算")
    
    # 固定大小數組
    print("\n4. 固定大小數組")
    print("   存取模式: keccak256(slot) + offset → value")
    print(f"   - Keccak-256: 30 + 6 × (words-1) gas")
    print(f"   - Slot 讀取: {sload_warm} gas")
    print(f"   結論: 固定數組存取成本恆定")

data_structure_cost_comparison()

結論

本文從數學推導的角度全面分析了 EVM Opcode 的執行成本。我們可以看到,EVM 的 Gas 機制是一個精心設計的資源定價系統:

理論基礎的嚴謹性:每個 Opcode 的成本都基於計算資源消耗的真實測量,記憶體成本呈二次函數增長反映了真實硬體的特性。

經濟激勵的一致性:成本函數的設計確保了礦工/驗證者的收益與網路資源消耗成正比,同時防止了各種 DoS 攻擊。

實際應用的可操作性:透過理解這些成本模型,智慧合約開發者可以編寫更高效的程式碼,節省用戶的 Gas 費用。

理解 EVM 成本模型不僅是區塊鏈工程師的必備技能,也是評估智慧合約效率和安全性的重要工具。

參考資料

  1. Wood, G. (2014). Ethereum: A Secure Decentralised generalised Transaction Ledger (Yellow Paper). Ethereum Research.
  2. Buterin, V. (2014). Ethereum Whitepaper. ethereum.org.
  3. EIP-150: Gas Cost Changes for IO-heavy Operations.
  4. EIP-1559: Fee market change for ETH 1.0 chain.
  5. EIP-2028: Transaction data gas cost reduction.
  6. EIP-2200: Structured Definitions for Gas Cost changes.
  7. EIP-2929: Gas cost increases for state access operations.
  8. Buten, R. (2022). EVM Deep Dive: Understanding the Opcode Costs. Ethereum Foundation Blog.

附錄:完整 Opcode 成本表

Opcode名稱基礎成本 (Gas)備註
0x00STOP0停止執行
0x01ADD3加法
0x02MUL5乘法
0x03SUB3減法
0x04DIV5整數除法
0x05SDIV5有符號除法
0x06MOD5取模
0x07SMOD5有符號取模
0x08ADDMOD8加法後取模
0x09MULMOD8乘法後取模
0x0AEXP10+指數運算
0x10LT3小於
0x11GT3大於
0x14EQ3等於
0x15ISZERO3為零
0x20KECCAK25630+Keccak-256 雜湊
0x31BALANCE100/2100餘額查詢
0x35CALLDATALOAD3讀取調用數據
0x36CALLDATASIZE2調用數據大小
0x37CALLDATACOPY3+拷貝調用數據
0x51MLOAD3+記憶體讀取
0x52MSTORE3+記憶體寫入
0x54SLOAD100/2100存儲讀取
0x55SSTORE100-20000存儲寫入
0xF1CALL700+合約調用
0xF2CALLCODE700+代碼調用
0xF4DELEGATECALL700+委託調用
0xFASTATICCALL700+靜態調用
0xF0CREATE32000+合約創建
0xF5CREATE232000+CREATE2 創建

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!