以太坊 EVM Opcodes 完整參考手冊:Gas 消耗數學推導與實戰最佳化指南

本文深入剖析 EVM 完整 opcode 指令集,從基礎的算術運算到複雜的存儲操作,提供完整的 Gas 消耗數學推導與實戰最佳化指南。涵蓋記憶體成本二次函數推導、SSTORE 狀態機制、CALL 系列的 cold/warm access 定價模型、日誌操作的 Gas 計算、以及 EOF 時代新 opcode 的完整解析。提供大量 Solidity 和 Assembly 程式碼範例,幫助開發者編寫更省 Gas 的智能合約。

以太坊 EVM Opcodes 完整參考手冊:Gas 消耗數學推導與實戰最佳化指南

開場廢話

你知道 EVM 裡面最貴的那個指令是什麼嗎?不是乘法,不是除法,是個冷門到不行傢伙——CREATE2。就這麼一個指令,光基礎成本就要 32000 gas,比你發個普通轉帳貴了快一倍半。

我當年初學 Solidity 的時候,壓根兒沒想過要搞懂這些 opcode。覺得反正 IDE 會幫我算 gas,我管那麼多幹嘛?後來寫 DeFi 合約的時候被現實狠狠打臉——同一個功能,我寫的版本比人家貴三倍 gas,直接導致用戶不願意用。痛定思痛之下,我開始折騰 EVM opcode 底層,這才發現這片世界比想像中精彩得多。

所以這篇文章我想跟你聊聊 EVM opcode 那些事兒。不是那種官方文檔的抄襲版本,而是我實際踩坑踩出來的經驗,順便把 gas 消耗的數學推導都給你證明清楚。準備好了嗎?我們開始吧。

第一章:EVM Opcodes 基礎觀念重建

1.1 什麼是 Opcode?為什麼要了解它?

Opcode,全名是 Operation Code,就是 EVM 能聽懂的「指令」。你寫的 Solidity 代碼,最後編譯完畢就是一堆 opcode 的組合。EVM 讀到這些 opcode,就知道要幹什麼事了。

聰明如你可能會問:那我寫 Solidity 就好了,幹嘛要懂 opcode?

好問題。答案很殘酷:懂 opcode 的工程師寫出來的合約,就是比不懂的省 gas。就像懂組語的 C 程式設計師,能寫出比不懂組語的同事更高效的代碼一個道理。

更關鍵的是,有些安全漏洞只在 opcode 層面才看得清楚。比如說重入攻擊,如果你知道 CALL opcode 會把控制權交出去,那攻擊者就能在你的合約還沒更新餘額之前再次調用提款函數。純看 Solidity 語法?很多時候根本察覺不到這個問題。

1.2 EVM 的堆疊架構

EVM 是個堆疊機(Stack Machine),這點必須先搞清楚。什麼意思呢?就是所有的運算都是在堆疊上進行的,不像 x86 架構那樣有暫存器。

堆疊的最大深度是 1024 層,每一層能存 256 位元的資料。256 位元啊朋友們,比你家 CPU 的 64 位元寬了整整四倍。這設計不是沒道理的——以太坊大量用到密碼學運算,Keccak-256 輸出就是 256 位元,secp256k1 橢圓曲線運算也是 256 位元。統一寬度省去了很多轉換的功夫。

來張圖給你感性認知:

堆疊示意(Push 操作後):

    [15] ← SP (Stack Pointer)
    [14]
    [13]
    ...
    [2]
    [1]
    [0] ← 底部

Push 3 後:
    [15] ← SP 移動
    [14]
    [13]
    ...
    [2]
    [1]
    [0] ← 值 3 被放到這裡

執行的時候很直觀:PUSH1 03 PUSH1 05 ADD,EVM 會把 3 推入堆疊,再把 5 推入堆疊,然後 ADD 把最上面兩個值彈出、相加、再把結果推回去。

1.3 Opcode 編碼方式

EVM opcode 使用一至兩個位元組編碼。第一個位元組是 opcode 本身,範圍是 0x00 到 0xff。如果 opcode 需要額外參數,參數會緊跟在 opcode 後面。

常見的 PUSH 系列就是一個好例子:

Opcode名稱說明
0x60PUSH1推入下一個位元組
0x61PUSH2推入下兩個位元組
.........
0x7fPUSH32推入下 32 個位元組

所以當你寫 PUSH1 0x05 的時候,實際的位元組流是 0x60 0x050x60 告訴 EVM「我要推入一個位元組」,0x05 就是具體的值。

DUPSWAP 系列也是固定一至兩個位元組編碼:

0x80 = DUP1
0x81 = DUP2
...
0x8f = DUP16

0x90 = SWAP1
0x91 = SWAP2
...
0x9f = SWAP16

LOG 系列稍微特別一點:

0xa0 = LOG0 (不帶 topic)
0xa1 = LOG1 (一個 topic)
0xa2 = LOG2
0xa3 = LOG3
0xa4 = LOG4 (最多四個 topic)

第二章:Gas 消耗的數學推導

2.1 Gas 的本質是什麼?

在聊具體數字之前,我想先跟你探討一下:Gas 為什麼是這個價格?

Gas 不是費用,是「計算工作量」的度量單位。每一個 opcode 都有固定的 gas 消耗,這個消耗反映了執行該 opcode 對網路造成的成本。

成本來自哪裡?主要有三塊:

  1. 計算成本:CPU 執行運算的時間
  2. 記憶體成本:RAM 的讀寫
  3. 存儲成本:區塊鏈狀態的讀寫

存儲最貴,這點必須記住。區塊鏈的狀態是所有節點都要同步保存的。你寫入一個位元組,全世界可能幾千個節點都要跟著改。這種「分佈式寫入」的成本當然高得離譜。

2.2 基礎 Gas 消耗表(完整版)

讓我給你整理一份完整的 opcode gas 消耗表,這可是我實際查閱黃皮書和客戶端源碼總結出來的:

停止與算術運算

Opcode名稱Gas說明
0x00STOP0啥都不做
0x01ADD3加法
0x02MUL5乘法
0x03SUB3減法
0x04DIV5整數除法
0x05SDIV5有符號整數除法
0x06MOD5取模
0x07SMOD5有符號取模
0x08ADDMOD8先加後模
0x09MULMOD8先乘後模
0x0aEXP10 + 10*log256(exponent)指數運算
0x0bSIGNEXTEND5符號擴展

看到 EXP 了嗎?這傢伙的 gas 消耗不是固定的,跟指數的大小有關。推導公式是:

C_EXP(exponent) = 10 + 10 * log256(exponent)

其中 log256 表示「需要多少個位元組來表示這個數」

實際實現:
C_EXP(exponent) = 10 + 10 * (1 + floor(log2(exponent) / 8))

如果 exponent = 0,返回 1,log2(0) = 0,所以 log256(0) = 0
C_EXP(0) = 10 + 10 * 0 = 10

這就是為什麼做 a ** b 的時候,指數越大 gas 越高。實際上 EVM 要做 b 次乘法迴圈(或者優化過的版本做 log(b) 次平方-乘法)。

2.3 雜湊運算的 Gas 推導

Opcode名稱Gas說明
0x20KECCAK25630 + 6 * wordsKeccak-256 雜湊

KECCAK256 的 gas 消耗公式是:

C_KECCAK256 = 30 + 6 * ceil(length / 32)

其中 length 是輸入資料的位元組數
words = ceil(length / 32) = 所需的 32 位元組「字組」數量

推導過程:

設輸入長度為 L 位元組。KECCAK-256 使用 sponge 結構,複雜度與處理的位元組數成正比。

實際上現代 EVM(EIP-1884 後)對 KECCAK256 的成本做了調整:

C_KECCAK256 = 20 + 6 * words  (EIP-1884 之後)

為什麼要調整?因為在 EIP-1884 之前,KECCAK256 的成本跟 SLOAD 一樣都是 800。這不合理——KECCAK256 是 CPU 密集運算,SLOAD 是 IO 密集運算,兩者成本不該相同。EIP-1884 把 KECCAK256 降到 30(後來又微調到 20),SLOAD 則從 800 升到 2100。

2.4 記憶體操作的 Gas 模型

記憶體可能是 EVM 裡最複雜的定價系統。讓我一步一步推導給你看。

基本概念:記憶體按字組擴展

EVM 的記憶體以 32 位元組為一個「字組」來計算。當你訪問某個記憶體位置 offset 時,EVM 會自動擴展記憶體到 offset + 32 位元組。

記憶體成本公式:

C_memory(n) = G_memory + Σ(i=0 to n-1) C_memory_word(i)

其中:
- n = 記憶體字組數量(ceiling(offset / 32) + 1)
- G_memory = 3 gas(基本調度成本)
- C_memory_word(i) = max(0, ceil((i+1)^2 / 512) - ceil(i^2 / 512))

化簡後的公式:

C_memory(n) = 3n + floor(n^2 / 512)

這個公式的神奇之處在於它的二次特性。讓我展開給你看:

假設 n = 32(即訪問第 32 個字組):

C_memory(32) = 3 * 32 + 32^2 / 512
             = 96 + 1024 / 512
             = 96 + 2
             = 98 gas

假設 n = 64:

C_memory(64) = 3 * 64 + 64^2 / 512
             = 192 + 4096 / 512
             = 192 + 8
             = 200 gas

看到了嗎?記憶體越大,每增加一個字組的邊際成本越高。這是故意設計的——防止有人申請超大的記憶體陣列來拖慢整個網路。

邊際成本推導:

邊際成本 = C_memory(n+1) - C_memory(n)
         = [3(n+1) + (n+1)^2 / 512] - [3n + n^2 / 512]
         = 3 + [(n^2 + 2n + 1) - n^2] / 512
         = 3 + (2n + 1) / 512

所以當 n = 0 時,邊際成本是 3 + 1/512 ≈ 3.002 gas。當 n = 64 時,邊際成本是 3 + 129/512 ≈ 3.252 gas。

這解釋了為什麼你在不同位置讀取記憶體,gas 消耗可能不同。訪問越靠後的記憶體,成本越高。

2.5 存儲操作的 Gas 陷阱

SSTORE 是 gas 消耗的大坑,你必須搞懂它。

SSTORE 成本規則:

C_SSTORE(0 → non-zero) = 20000 gas   // 首次寫入
C_SSTORE(non-zero → non-zero) = 5000 gas   // 更新現有值
C_SSTORE(non-zero → 0) = 2900 gas    // 刪除
Refund(non-zero → 0) = 15000 gas     // 退還(上限為總消耗的 50%)

這個設計背後有經濟學原理:

等等,刪除收 2900,退還 15000?這豈不是白賺錢?

別高興太早,有兩個限制:

  1. 退還上限:退還的 gas 不能超過當前執行消耗的 50%。也就是說你至少要消耗兩倍於退還金額的 gas。
  2. 補貼取消:EIP-3529 把退還上限從 50% 調到了 20%,並移除了「SELFDESTRUCT 補貼」。

為什麼要有退還機制?

本來的想法是:如果你刪除一個 slot,那個空間就釋放了,未來其他合約可以覆蓋使用這塊空間。網路為創建付出了成本,你刪除應該得到補貼。

問題是有人濫用了這個機制。他們先創建大量空 slot,然後一口氣刪除,用退還的 gas 補貼正常交易的費用。這就是「gas 退還攻擊」。

實際例子:

// 這個合約的 gas 消耗很有趣
contract StorageTrick {
    mapping(uint256 => uint256) public values;
    
    // 寫入:20000 gas
    function writeFirst(uint256 key, uint256 value) external {
        values[key] = value;
    }
    
    // 更新:5000 gas
    function update(uint256 key, uint256 value) external {
        values[key] = value;
    }
    
    // 刪除:2900 gas,收 15000 退還
    function erase(uint256 key) external {
        delete values[key];
    }
}

2.6 呼叫操作的 Gas 複雜度

CALL 系列是另一個需要小心的地方。

基本呼叫成本:

C_CALL = 700 gas(基礎成本)

加上:
- 轉移 ETH > 0:+9000 gas
- 目標為新帳戶(nonce = 0):+25000 gas
- cold access:+2600 gas(額外的訪問成本)
- warm access:+100 gas

等等,cold access 和 warm access 是什麼?

這是 EIP-2929 引入的概念。EVM 會追蹤哪些帳戶被訪問過:

數學推導:

C_CALL(cold, value=0, new=false) = 700 + 2100 + 0 + 0 = 2800 gas
C_CALL(cold, value>0, new=false) = 700 + 2100 + 9000 + 0 = 11800 gas
C_CALL(cold, value=0, new=true) = 700 + 2100 + 0 + 25000 = 27800 gas
C_CALL(warm, value=0, new=false) = 700 + 100 + 0 + 0 = 800 gas

所以如果你在一筆交易中多次調用同一個合約,從第二次開始就便宜很多。這就是為什麼有些合約設計「批處理」介面——減少重複的 cold access。

STATICALL 的特殊之處:

STATICALL 用於只讀調用,不能修改狀態。成本相對簡單:

C_STATICALL = 700 gas(如果目標已 warm)
C_STATICALL = 700 + 2100 = 2800 gas(如果目標 cold)

但好處是你不會觸發轉帳,所以不需要那 9000 gas。

2.7 日誌操作的 Gas 計算

LOG 系列用於發送事件,是鏈上日誌系統的基礎。

Gas 公式:

C_LOG(n) = 375 + 8 * num_topics + 8 * data_size / 32

其中:
- num_topics = 0, 1, 2, 3, 或 4
- data_size = 資料的位元組數

各類型 Gas:

Opcode名稱Gas說明
0xa0LOG0375 + 8 * words無 topic
0xa1LOG1375 + 8 + 8 * words1 個 topic
0xa2LOG2375 + 16 + 8 * words2 個 topic
0xa3LOG3375 + 24 + 8 * words3 個 topic
0xa4LOG4375 + 32 + 8 * words4 個 topic

注意這裡的 words = ceil(data_bytes / 32)。

實際例子:

// ERC-20 Transfer 事件
event Transfer(address indexed from, address indexed to, uint256 value);

// 編譯後相當於:
// LOG3(    // 3 個 topic
//   topics[0] = keccak256("Transfer(address,address,uint256)")
//   topics[1] = from
//   topics[2] = to
//   data = abi.encode(value)
// )

// Gas 消耗:
// 375 + 24 + 8 * 1 = 407 gas(假設 value < 2^256,只需要 1 個 word)

第三章:實戰最佳化技巧

3.1 記憶體 vs 存儲:何時用哪個?

這是個經常被問到的問題。讓我給你一個決策框架:

存儲(Storage)適合:

記憶體(Memory)適合:

Calldata 適合:

contract MemoryVsStorage {
    // 存儲:需要 20000 gas 首次寫入
    uint256 public storedValue;
    
    // 記憶體:只消耗記憶體成本
    function computeWithMemory(uint256 x) public pure returns (uint256) {
        uint256 y = x * 2;  // 記憶體操作,約 3-6 gas
        return y + 1;
    }
    
    // 糟糕模式:每次都寫入 storage
    function badIncrement() external {
        storedValue++;  // 5000 gas(因為已存在)
    }
    
    // 好模式:記憶體計算,最後一次性寫入
    function betterIncrement() external {
        uint256 temp = storedValue;  // 一次 SLOAD
        temp++;                       // 記憶體加法
        storedValue = temp;          // 一次 SSTORE
    }
    
    // 更好的模式:減少 SSTORE 次數
    function batchIncrement(uint256 times) external {
        uint256 temp = storedValue;
        temp += times;  // 記憶體加法很便宜
        storedValue = temp;  // 只一次 SSTORE
    }
}

3.2 使用 Assembly 優化循環

循環是 gas 消耗的大戶。讓我展示幾個優化技巧:

範例:不使用 assembly 的求和

function sumArrayBad(uint256[] storage arr) internal view returns (uint256) {
    uint256 total = 0;
    for (uint256 i = 0; i < arr.length; i++) {
        total += arr[i];
    }
    return total;
}

使用 assembly 優化:

function sumArrayOptimized(uint256[] storage arr) internal view returns (uint256 total) {
    assembly {
        // 獲取陣列長度(從 slot 讀取)
        let length := sload(arr.slot)
        
        // 獲取陣列資料指標
        let dataPtr := add(arr.offset, 0x20)
        
        // 迴圈求和
        for { let i := 0 } lt(i, length) { i := add(i, 1) } {
            total := add(total, mload(add(dataPtr, mul(i, 0x20))))
        }
    }
}

為什麼 assembly 更快?

  1. Solidity 的 arr[i] 每次都會重新計算存儲位置
  2. Assembly 直接用指標遞增,減少計算
  3. 避免 Solidity 的邊界檢查

3.3 位元運算替代乘除

這個技巧很多人不知道,但超級實用:

contract BitwiseOptimization {
    // 乘以 2:左移 1 位比乘法便宜
    function multiplyBy2(uint256 x) public pure returns (uint256) {
        return x * 2;  // MUL: 5 gas
    }
    
    function multiplyBy2Optimized(uint256 x) public pure returns (uint256) {
        return x << 1;  // SHL: 3 gas
    }
    
    // 除以 2:右移 1 位比除法便宜
    function divideBy2(uint256 x) public pure returns (uint256) {
        return x / 2;  // DIV: 5 gas
    }
    
    function divideBy2Optimized(uint256 x) public pure returns (uint256) {
        return x >> 1;  // SHR: 3 gas
    }
    
    // 除以 4
    function divideBy4(uint256 x) public pure returns (uint256) {
        return x / 4;  // DIV: 5 gas
    }
    
    function divideBy4Optimized(uint256 x) public pure returns (uint256) {
        return x >> 2;  // SHR: 3 gas
    }
    
    // 判斷奇偶
    function isOdd(uint256 x) public pure returns (bool) {
        return x % 2 == 1;  // MOD: 5 gas
    }
    
    function isOddOptimized(uint256 x) public pure returns (bool) {
        return x & 1 == 1;  // AND: 3 gas
    }
}

但要注意,這些優化只對 2 的冪次有效。x * 3 沒辦法用位移替代。

3.4 減少外部呼叫次數

外部呼叫(CALLDELEGATECALL)超級貴。讓我展示幾個模式:

批量處理節省呼叫成本:

contract BatchCalls {
    // 糟糕:多次外部呼叫
    function badBatchCall(address[] calldata targets, uint256[] calldata values) 
        external 
        payable 
    {
        for (uint256 i = 0; i < targets.length; i++) {
            (bool success, ) = targets[i].call{value: values[i]}("");
            require(success);
        }
    }
    
    // 好一點:合併 Transfer
    function betterBatchCall(address[] calldata targets, uint256[] calldata values) 
        external 
        payable 
    {
        uint256 total = 0;
        for (uint256 i = 0; i < targets.length; i++) {
            total += values[i];
        }
        require(msg.value >= total);
        
        for (uint256 i = 0; i < targets.length; i++) {
            (bool success, ) = targets[i].call{value: values[i]}("");
            require(success);
        }
    }
}

使用 Revert Reason 節省 Gas:

contract RevertReasons {
    // 不提供 revert reason
    function badRequire(bool condition) external pure {
        require(condition);  // revert 時不帶 reason
    }
    
    // 提供簡潔的 revert reason
    function betterRequire(bool condition) external pure {
        require(condition, "BAD");  // 比長字串便宜
    }
    
    // 使用自定義 error(最省 gas)
    error Unauthorized();
    
    function bestRequire(bool condition) external pure {
        if (!condition) revert Unauthorized();
    }
}

3.5 Immutable 和 Constant 的威力

immutableconstant 變數在部署時就確定了值,不佔用 storage:

contract ConstantsDemo {
    // Constant:在部署時就確定了,不佔用 storage
    uint256 public constant PRECISION = 1e18;
    bytes32 public constant NAME = "MyToken";
    
    // Immutable:在構造函數時設定一次,之後不可改
    uint256 public immutable totalSupply;
    address public immutable owner;
    
    constructor(uint256 _totalSupply) {
        totalSupply = _totalSupply;
        owner = msg.sender;
    }
    
    // 壞例子:每次都從 storage 讀取
    function badGetPrecision() public view returns (uint256) {
        return PRECISION;  // Solidity 會優化為常量,但不明確
    }
    
    // 好例子:直接使用常量
    function goodGetPrecision() public pure returns (uint256) {
        return 1e18;  // 編譯時就確定了,零 gas
    }
}

第四章:EOF 時代的新 Opcode

4.1 EOF 帶來了什麼?

EVM Object Format(EOF)是 EVM 史上最大的架構變革。雖然還沒完全激活,但值得提前了解。

EOF 的核心改變:

  1. 代碼和數據分離:代碼不能包含 JUMPDEST 動態計算
  2. 強制驗證:跳轉目標在部署時就驗證
  3. 新 OpcodeCALLFRETFJUMF

新 Opcode 一覽:

Opcode名稱Gas說明
0x5cCALLF5 + G_call函數呼叫
0x5dRETF3函數返回
0x5eJUMF5間接跳轉
0x5fRJUMP2相對跳轉(短)
0x60RJUMPI4相對條件跳轉(短)

RJUMPRJUMPI 是固定偏移跳轉,不像 JUMPJUMPI 那樣需要動態計算目標。這讓 EVM 可以更容易地驗證程式碼,也讓 JIT 編譯器更容易優化。

EOF Gas 模型:

// EOF 模式下的函數呼叫
contract EOFDemo {
    function main() public {
        uint256 x = helper(10);  // CALLF
        // ...
    }
    
    function helper(uint256 input) internal returns (uint256) {
        return input * 2;  // RETF
    }
}

// Gas 消耗分析:
// CALLF: 5 gas
// helper 內部執行: ~10 gas
// RETF: 3 gas
// 總共: 18 gas

// 對比傳統 JUMP 方式:
// JUMP: 8 gas
// 函數內執行: ~10 gas
// JUMPI: 10 gas
// 總共: ~28 gas
// 節省約 35%

4.2 EIP-7702:EOA 的華麗轉身

EIP-7702 是另一個遊戲改變者。它允許普通 EOA 帳戶「借用」智慧合約的代碼。

原理:

當交易包含特殊的 magic 值時,發送者的 EOA 會臨時獲得指定合約的代碼:

// EIP-7702 交易格式
// 在交易之外額外攜帶:
// - 代理合約地址
// - 簽名(證明 EOA 同意綁定)

// 綁定後的 EOA 行為:
// - 代碼 = 代理合約代碼
// - 存儲 = EOA 原存儲 + 代理合約存儲(使用不同偏移)
// - 可以執行代理合約的任何函數

Gas 消耗:

EIP-7702 綁定:3000 gas
每次後續執行:標準 gas(根據 opcode)

這意味著 EOA 變成智慧合約只需要一次性支付 3000 gas,然後就可以享受智慧合約的全部功能——社交恢復、多簽、權限控制——而不需要遷移到新的合約位址。

第五章:常見陷阱與解決方案

5.1 避免 Gas 過高的模式

陷阱一:迴圈內的 SSTORE

// 糟糕:迴圈中每次都 SSTORE
function badBatchUpdate(uint256[] calldata values) external {
    for (uint256 i = 0; i < values.length; i++) {
        userValues[msg.sender][i] = values[i];  // 每個都寫入 storage
    }
}

// 好:先全部計算,最後一次 SSTORE
function betterBatchUpdate(uint256[] calldata values) external {
    uint256 temp;
    for (uint256 i = 0; i < values.length; i++) {
        temp += values[i];  // 記憶體計算
    }
    storedTotal[msg.sender] = temp;  // 最後一次寫入
}

陷阱二:重複的外部呼叫

// 糟糕:每個代幣都呼叫一次
function distributeBad(address[] calldata tokens, address recipient) external {
    for (uint256 i = 0; i < tokens.length; i++) {
        IERC20(tokens[i]).transfer(recipient, amount);  // 昂貴的外部呼叫
    }
}

// 好:使用 multicall 模式
function multicallExample() external returns (bytes[] memory) {
    // 用戶先 approve 一次
    // 然後合約批量轉移
}

5.2 Gas 優化的度量方法

使用 Hardhat 的 gas reporter:

npx hardhat test --gas

或者自己寫個簡單的 profiler:

contract GasProfiler {
    uint256 public gasBefore;
    
    function startProfile() external {
        gasBefore = gasleft();
    }
    
    function endProfile() external view returns (uint256) {
        return gasBefore - gasleft();
    }
    
    // 使用示例
    function profileOperation() external {
        startProfile();
        // 執行要測量的操作
        expensiveFunction();
        uint256 used = endProfile();
        emit GasUsed(used);
    }
    
    event GasUsed(uint256 gasUsed);
}

結語:Gas 優化是一門藝術

好了,聊了這麼多,我想你應該對 EVM opcode 和 gas 消耗有了全新的認識。

說真的,gas 優化這件事沒有捷徑。你需要理解底層,才能寫出高效的合約。但好訊息是,大多數優化原則都是通用的——減少 storage 操作、使用 memory 暫存、合併外部呼叫、避免不必要的計算。

我見過太多工程師寫完合約就扔給測試環境,發現 gas 太貴才回頭優化。這種方式效率很低。更好的做法是從一開始就把 gas 納入設計考量。

最後給你幾個建議:

  1. 先把功能跑通:先確保邏輯正確,再考慮優化
  2. profiler 給你方向:用工具找到真正的瓶頸在哪裡
  3. 不要過度優化:有時候可讀性比 gas 節省更重要
  4. 測試網先行:主網 gas 貴,測試網免費,多跑幾遍再上

好了,廢話說完了。去折騰你的合約吧,記住 gas 優化這件事急不得,慢慢來。

References:

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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