以太坊 EVM 規格從零推導:Yellow Paper 形式化定義的直覺化解讀

本文從以太坊黃皮書(Yellow Paper)的形式化定義出發,從第一性原理推導 EVM 的狀態轉換函數。我們不依賴任何第三方解讀,而是直接解讀 Yellow Paper 的符號系統,幫助讀者建立對 EVM 最底層運作的嚴格理解。這是給那些想真正搞懂以太坊,而不只停留在「使用」層面的開發者的文章。


title: 以太坊 EVM 規格從零推導:Yellow Paper 形式化定義的直覺化解讀

summary: 本文從以太坊黃皮書(Yellow Paper)的形式化定義出發,從第一性原理推導 EVM 的狀態轉換函數。我們不依賴任何第三方解讀,而是直接解讀 Yellow Paper 的符號系統,幫助讀者建立對 EVM 最底層運作的嚴格理解。這是給那些想真正搞懂以太坊,而不只停留在「使用」層面的開發者的文章。

tags:

difficulty: advanced

date: 2026-04-01

parent: null

status: published

references:

url: https://ethereum.github.io/yellowpaper/paper.pdf

desc: 以太坊黃皮書,正式規格定義

tier: tier1

url: https://github.com/ethereum/yellowpaper

desc: Yellow Paper 原始 TeX 源代碼

tier: tier1

url: https://www.evm.codes/

desc: 完整的 EVM 操作碼參考

tier: tier2

disclaimer: 本網站內容僅供教育與資訊目的,不構成任何技術或投資建議。

datacutoffdate: 2026-04-01

knowledge_path: technical/evm/yellow-paper-analysis


以太坊 EVM 規格從零推導:Yellow Paper 形式化定義的直覺化解讀

說實話,第一次打開以太坊黃皮書(Yellow Paper)的時候,我是有點絕望的。滿頁的希臘字母、奇怪的數學符號、感覺像是研究生作業的推導過程......這玩意真的是給人類看的嗎?

但後來我慢慢意識到,這份文件是以太坊最嚴格的技術規格。它不是一本教程,也不是一篇部落格文章——它是一份「合約」,定義了以太坊虛擬機應該如何運作。任何偏離黃皮書的實現,原則上都不能稱為「以太坊」。

這篇文章的目的,是把黃皮書中最核心的 EVM 規格,用人話解釋一遍。我會保持數學推導的嚴謹性,但會用大量的直覺解釋和具體例子來幫助理解。目標讀者是那些不滿足於「會用 Solidity」,而想真正搞懂 EVM 底層運作的開發者。

為什麼要讀黃皮書?

我知道很多人會問這個問題。Solidity 編譯器會幫你處理所有事情,你真的需要知道 EVM 的狀態轉換函數長什麼樣子嗎?

我的答案是:需要,而且非常需要。

第一,調試和優化。當你的合約消耗過多 Gas、當你遇到奇怪的 revert、當你想理解某個安全漏洞的原理......這些問題都需要對 EVM 有深入理解。

第二,寫出更高效的代碼。了解狀態轉換函數的代價模型,你才能真正優化你的合約。比如,你知道為什麼 memory 操作比 storage 操作便宜幾個數量級嗎?這不是因為硬體差別,而是因為黃皮書裡的 Gas 計算公式就是這麼定義的。

第三,安全審計。如果你是安全審計員,不懂黃皮書就像不懂交通規則就上路的司機。你可能可以開車,但遇到事故的時候,你根本不知道是誰的錯。

第四,規格本身的魅力。以太坊黃皮書是密碼經濟學和分散式系統設計的精華。讀懂它,你對區塊鏈的理解會提升一個層次。

符號系統:黃皮書的語言

在開始之前,讓我們先熟悉黃皮書使用的符號系統:

基本集合:

σ    - 世界狀態(World State)
     一個從地址到帳戶狀態的映射
     σ: Address → AccountState

B    - 區塊(Block)
     包含區塊頭和交易列表

Π    - 區塊層面的世界狀態(Block-level World State)
     在處理區塊開始前的世界狀態

Ω    - 區塊報酬函數(Block Reward Function)
     定義礦工/驗證者獲得的獎勵

Υ    - 交易報酬函數(Transaction Receipt Function)
     定義交易執行後的 receipt

Λ    - 交易執行函數(Transaction Execution Function)
     定義如何執行一筆交易

Φ    - 狀態轉換函數(State Transition Function)
     定義如何從一個狀態轉換到下一個狀態

基本類型:

μ    - 機器狀態(Machine State)
     EVM 的運行時狀態,包含:
       - g: Gas 剩餘量
       - pc: 程序計數器
       - m: Memory
       - i: 指令
       - s: Stack

H    - 區塊頭(Block Header)
     包含所有區塊元數據

Γ    - 環境資訊(Environment Information)
     包含交易發送者、接收者、金額等

世界狀態:一切開始的地方

黃皮書的第一個核心定義是世界狀態(World State):

定義 1:世界狀態

世界狀態 σ 是從地址到帳戶狀態的映射:
  σ: Address → AccountState

其中:
  Address = B_{20}  // 20 字節的地址空間
  AccountState = (nonce, balance, storageRoot, codeHash)

對於 EOA(外部擁有帳戶):
  AccountState = (n, b, ∅, ∅)
  
其中 n 是 nonce(交易計數),b 是餘額

對於合約帳戶:
  AccountState = (n, b, s, c)
  
其中 s 是 storage root,c 是合約代碼的 hash

這個定義看起來很抽象,讓我們用一個具體例子來理解:

現實世界的例子:

假設以太坊世界狀態包含以下帳戶:

地址 0x1234...:{
  nonce: 5,
  balance: 100 ETH,
  storageRoot: ∅,
  codeHash: ∅
}
這是一個 EOA,有 5 筆交易歷史,100 ETH 餘額

地址 0xabcd...:{
  nonce: 1,
  balance: 0 ETH,
  storageRoot: 0xdef...,
  codeHash: 0x789...
}
這是一個合約,有 1 筆交易歷史,無餘額

交易執行:狀態轉換函數

黃皮書的核心是定義狀態轉換函數 Φ。這個函數描述了如何從一個世界狀態轉換到另一個:

定義 2:狀態轉換函數

Φ(σ, T) ≡ σ'

其中:
  σ  是執行前的世界狀態
  T  是待執行的交易
  σ' 是執行後的世界狀態

狀態轉換函數的實現:
  Φ(σ, T) ≡ Α(Ι(σ, T), T)

其中:
  Ι 是交易初始化函數
  Α 是交易應用函數

這個定義有兩層嵌套:

  1. 初始化Ι(σ, T) 計算交易的初始環境,包括驗證簽名、計算費用等
  2. 應用Α(σ', T) 實際執行交易,更新世界狀態

初始化函數

讓我們更詳細地看看初始化函數:

定義 3:交易初始化

Ι(σ, T) ≡ (σ', g', ...)

初始化函數負責:
  1. 驗證交易簽名
  2. 扣除交易費用(gas × gasPrice)
  3. 計算新地址(如果是合約創建)
  4. 初始化執行環境

黃皮書的符號看起來嚇人,但核心思想很簡單:在執行任何代碼之前,先做一堆準備工作

Gas 計算

Gas 是以太坊最獨特的設計。黃皮書定義了 Gas 的消耗模型:

定義 4:Gas 消耗

Gas 的計算分為三部分:

1. 基本費用(Intrinsic Gas)
   G_0(T) = G_txdata + G_txcreate + G_transaction

   其中:
     G_transaction = 21000(基本交易)
     G_txdata = 每 byte 4 或 16 gas(取決於是否為零 byte)
     G_txcreate = 32000(合約創建)

2. 操作費用(Operation Gas)
   根據執行的 opcode 累加
   具體數值見黃皮書附錄 G

3. 動態費用(Dynamic Gas)
   根據 Memory 擴展、Storage 操作等動態計算

讓我們用一個具體例子:

例子:執行一筆 ETH 轉帳

交易內容:
  - 從 EOA A 轉 1 ETH 到 EOA B
  - Gas Price = 30 Gwei

Gas 消耗計算:
  G_0 = G_transaction = 21000 gas
  
  總費用 = 21000 × 30 Gwei = 0.00063 ETH
  
  扣除後的狀態:
    σ[A].balance -= 1 + 0.00063 ETH
    σ[B].balance += 1 ETH

EVM 執行:形式化定義

現在讓我們深入到 EVM 的核心——狀態轉換函數 Λ:

定義 5:EVM 狀態轉換

Λ(σ, g, T) ≡ (σ', g', A, o)

其中:
  σ, σ'  : 世界狀態(輸入/輸出)
  g, g'  : 剩餘/消耗的 Gas
  A      : 執行後的狀態(成功/失敗)
  o      : 輸出數據(return data)

這個函數描述了 EVM 如何執行一段字節碼

機器狀態

黃皮書定義了 EVM 的機器狀態 μ:

定義 6:機器狀態

機器狀態 μ = (g, pc, m, i, s)

其中:
  g  : Gas 剩餘量
  pc : 程序計數器(program counter)
  m  : Memory 內容
  i  : 當前指令(instruction)
  s  : Stack 內容
  
初始狀態:
  μ_0 = (T.g, 0, {}, 0, [])
  
其中 T.g 是交易提供的 Gas

操作語義

每個 opcode 的語義在黃皮書中都有形式化定義。讓我們看幾個關鍵的:

定義 7:STOP

W_{STOP}(μ, σ) ≡ (σ, μ.g, A, ∅)

解釋:
  - 不修改世界狀態
  - 不消耗額外 Gas
  - 執行成功(A = ∅ 表示成功)
  - 無輸出
  - 這是「正常」終止的方式

定義 8:ADD

W_{ADD}(μ, σ) ≡ (σ, μ.g - G_{mid}, μ', A, ∅)

其中:
  G_{mid} = 3 gas
  μ'.pc = μ.pc + 1
  μ'.s = [μ.s[0] + μ.s[1]] // 棧頂兩個元素相加
  其他狀態不變

解釋:
  - 消耗 3 gas
  - 將棧頂兩個元素相加,結果壓入棧
  - pc + 1(移動到下一條指令)

定義 9:SSTORE

W_{SSTORE}(μ, σ) ≡ (σ', μ.g - G_{sstore}, μ', A, ∅)

其中:
  G_{sstore} = 20000 gas(寫入新 slot)
  或者 = 5000 gas(修改已有 slot)
  或者 = 2900 gas(如果值為零)
  
  σ' = σ with σ[μ.s[1]] = μ.s[0]
  // 將棧第二個元素作為 key
  // 棧頂元素作為 value
  // 寫入合約的 storage

解釋:
  - Storage 寫入是最昂貴的操作之一
  - 這是為什麼要避免頻繁 Storage 操作的底層原因

執行循環:EVM 的心跳

黃皮書用 Big-Step 語義定義了完整的執行循環:

定義 10:執行循環

loop(μ, σ, T) ≡
  if μ.g < C(μ.i) then
    // Gas 不足,拋出异常
    (σ, μ.g, A_OOOGAS, ∅)
  else if μ.i = STOP then
    // 正常終止
    (σ, μ.g, ∅, [])
  else if μ.i = REVERT then
    // REVERT
    (σ, μ.g, A_REVERT, μ.μ_s)
  else
    // 繼續執行下一條指令
    let (σ', μ', A, o) = W(μ, σ)
    loop(μ', σ', T)

這個定義揭示了 EVM 執行的一個關鍵特性:它是一個循環,直到 Gas 耗盡或正常終止

異常處理

黃皮書定義了幾種異常類型:

異常類型:

A_REVERT  - REVERT 操作,狀態回滾,剩餘 Gas 不退還
A_BADJUMP - 無效的跳轉目標
A_STACKOVERFLOW - Stack 溢出(> 1024 元素)
A_STACKUNDERFLOW - Stack 下溢(操作數不足)
A_INVALID  - 執行了無效的操作碼
A_OOOGAS   - Gas 不足
A_DATAACCESS - 嘗試訪問無效的 Memory 或 Storage

一個重要的細節:當拋出異常時,除了 Gas 消耗之外,所有的狀態修改都會被撤銷。這是 EVM 原子性保證的基礎。

從形式化定義到實際執行

現在讓我們把這些形式化定義翻譯成實際的執行流程:

步驟 1:交易驗證
  1. 檢查交易的 nonce 是否正確
  2. 驗證簽名
  3. 檢查帳戶餘額是否足夠(value + gas × gasPrice)

步驟 2:Gas 預扣
  1. 計算 intrinsic gas
  2. 從帳戶預扣 maxGas × gasPrice
  3. 設置 μ.g = maxGas

步驟 3:執行
  如果是消息調用:
    1. 轉帳(如果 value > 0)
    2. 執行目標地址的代碼
    3. 返回執行結果
  
  如果是合約創建:
    1. 計算新地址
    2. 創建空合約
    3. 執行初始化代碼
    4. 返回代碼和地址

步驟 4:Gas 結算
  1. 退還剩餘的 Gas × gasPrice 給發送者
  2. 支付礦工/驗證者(小費部分)
  3. 燒毀基礎費用(EIP-1559)

步驟 5:狀態提交
  如果執行成功:
    1. 提交所有的 Storage 修改
    2. 更新帳戶餘額
    3. 創建新合約(如有)
  
  如果執行失敗:
    1. 回滾所有 Storage 修改
    2. 保留 Gas 消耗
    3. 帳戶狀態恢復到執行前(除了費用)

Memory 模型的形式化

黃皮書對 Memory 的定義非常精確:

定義 11:Memory 狀態

Memory 狀態 m 是從字節位置到字節值的偏函數:
  m: N → B
  其中 m 的有效範圍為 [0, m.size)

Memory 擴展代價:

  C_{mem}(n) = G_{memory} × n + n² / 512

其中:
  G_{memory} = 3 gas(每 word 的基本代價)
  n = words(即 32 字節為 1 word)
  n²/512 = Memory 擴展的二次代價

這個二次代價模型是以太坊 Gas 設計的精髓之一。讓我們推導一下:

例子:Memory 擴展成本

假設我們要擴展到 100 words(3200 字節):

C_mem(100) = 3 × 100 + 100² / 512
           = 300 + 10000 / 512
           = 300 + 19.5
           = 319.5 gas

相比之下,擴展到 200 words:
C_mem(200) = 3 × 200 + 200² / 512
           = 600 + 40000 / 512
           = 600 + 78.125
           = 678.125 gas

擴展到 400 words:
C_mem(400) = 3 × 400 + 400² / 512
           = 1200 + 160000 / 512
           = 1200 + 312.5
           = 1512.5 gas

注意:代價不是線性增長的!
100 → 200 words: +358 gas
200 → 400 words: +834 gas(幾乎是 2.3 倍)

這個設計的意義:防止攻擊者透過大規模 Memory 操作來拖慢網路

Storage 模型的形式化

Storage 是 EVM 三層儲存架構中最複雜的一層。黃皮書的定義是:

定義 12:Storage 狀態

Storage 狀態是合約帳戶的一個子狀態:
  σ[a].storage: N → N
  即一個從 slot(256 位)到 value(256 位)的映射

Storage 操作代價(Deneb升級後):
  
  SSTORE(新 slot)= 20000 gas
  SSTORE(修改 existing slot)= 2900 gas  
  SSTORE(設為零)= 100 gas(補貼後)
  SLOAD = 100 gas(warm)/ 2100 gas(cold)

SLOAD 的特殊之處

黃皮書對 SLOAD 的定義有一個重要的優化:warm storage access

定義 13:Warm Storage Access

如果一個 storage slot 在當前交易中被「接觸」過,
它就會被標記為「warm」,後續的讀取會更便宜。

Warm slot 判定規則:
  - 在同一筆交易中已經被 SLOAD 過
  - 在同一筆交易中已經被 SSTORE 過
  - 或者它是 msg.sender 或地址(特殊 slot)

Warm SLOAD = 100 gas
Cold SLOAD = 2100 gas

差異高達 20 倍!

這個設計是為了:懲罰那些「冷啟動」的 Storage 訪問。因為從磁碟加載 Storage trie 節點是非常昂貴的操作。

Call 指令的形式化

黃瓜書對 Call 指令的定義是整份文件中最複雜的部分之一:

定義 14:CALL 語義

W_CALL(μ, σ) ≡ 
  let (σ', μ', A, o) = Λ(σ, g'', T)
  where:
    g'' = min(μ.g - G_call, μ.s[2])
    T = (sender: μ.s[0], 
         recipient: μ.s[1], 
         value: μ.s[2], 
         data: μ.m[μ.s[4]:μ.s[4]+μ.s[5]],
         gas: g'')

這個定義很抽象,讓我用人話解釋:

CALL 指令做了什麼:

1. 從 Stack 取參數:
   - μ.s[0]: 目標地址
   - μ.s[1]: 轉帳金額
   - μ.s[2]: 提供的 Gas
   - μ.s[3]: 內存起始位置(calldata)
   - μ.s[4]: calldata 長度

2. 計算 Gas:
   - 消耗基本 Call 費用(G_call)
   - 剩餘的 Gas 減去提供的 Gas
   
3. 創建子執行框架:
   - 子框架有自己的 Stack、Memory、Gas
   - 但共享 Storage 和 World State(這是代理合約的基礎)

4. 執行目標合約:
   - Λ(σ, g'', T) 就是我們之前定義的狀態轉換函數

5. 返回結果:
   - o: 子執行的輸出數據
   - A: 子執行的異常狀態

特殊 Call 變體

黃皮書定義了幾種 Call 的變體:

DELEGATECALL:
  - 在調用者的上下文執行目標代碼
  - Storage 是調用者的
  - msg.sender 和 msg.value 不變

STATICCALL:
  - 執行期間禁止 Storage 修改
  - 如果執行了 SStore,拋出异常
  - 用於 view 函數的鏈上驗證

CREATE:
  - 創建新合約
  - 返回新合約地址
  - 執行初始化代碼
  
CREATE2:
  - 使用 Salt 和 bytecode hash 計算地址
  - 確保地址可預測

從理論到實踐:調試 EVM

了解了黃皮書的形式化定義之後,我們可以用這些知識來調試 EVM 問題:

常見 EVM 問題的根源分析:

1. 「Out of Gas」異常
   原因:操作消耗的 Gas 超過了可用余量
   解決:優化代碼減少 Gas 消耗,或增加 Gas limit

2. 「Stack Underflow」異常
   原因:POP 指令時 Stack 為空,或指令需要 n 個元素但 Stack 只有 < n 個
   解決:檢查 Stack 操作序列

3. 「Invalid Jump」異常
   原因:JUMPDEST 指令之前沒有有效的 jump
   解決:動態跳轉目標必須是 JUMPDEST

4. 「Revert」異常
   原因:合約代碼執行了 REVERT 或 REQUIRE 失敗
   解決:檢查 require 條件是否合理

5. 「Static Call Violation」
   原因:在 STATICCALL 期間嘗試修改 Storage
   解決:確保 view/ pure 函數不修改狀態

黃皮書的局限性

最後,我想客觀地說說黃皮書的一些局限性:

1. 升級延遲
   黃皮書不是實時更新的。EIP 通過後,
   通常需要幾個月甚至幾年才能在黃皮書中體現。
   例如:EIP-1559 在 2021 年實施,但 Yellow Paper 
   直到 2022 年才更新。

2. 實現差異
   黃皮書定義的是「規格」,但不同客戶端的實現可能略有差異。
   例如:某些 edge case 的處理可能不一致。
   這也是為什麼形式化驗證很重要的原因。

3. 遺漏的優化
   黃皮書不考慮性能優化。
   例如:evmone 的 JIT 編譯器在某些操作上比解釋器快 100 倍,
   但這在黃皮書中是完全不可見的。

4. Layer 2 的不適用性
   黃皮書只定義了 Layer 1 的 EVM。
   Optimism、Arbitrum、zkSync 等 Layer 2 
   的實現與黃皮書有顯著差異。

結語:規格是契約,執行是義務

寫到這裡,我意識到這篇文章已經涵蓋了黃皮書 EVM 部分的核心內容。當然,這只是冰山一角——完整的黃皮書還包括共識機制、區塊結構、獎勵系統等更多內容。

但我相信,學習黃皮書最重要的是建立一種思維方式:不再滿足於「我知道怎麼用」,而是追求「我知道它為什麼這樣設計」。

這種思維方式會讓你成為更好的工程師。不是因為你記住了多少 opcode,而是因為你學會了從第一性原理思考複雜系統。

如果你對深入研究黃皮書感興趣,我建議:

  1. 先從黃皮書的 GitHub repo 開始,那裡有最新的版本
  2. 配合 evm.codes 這個網站,一邊讀一邊實際操作
  3. 讀 Geth 的 vm package 原始碼,看看形式化定義如何變成可執行代碼
  4. 關注以太坊魔術師論壇(ethresear.ch),那裡有最新的規格討論

規格不是束之高閣的文件。它們是活的契約,而我們每一個區塊鏈開發者,都是這個契約的執行者。

祝你在 EVM 的世界裡玩得開心。


參考資源

三級可信來源標準

本網站內容僅供教育與資訊目的,不構成任何技術或投資建議。

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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