# EIP-4750 EOF 函式功能

# 注意事項

本文並沒有經過作者外的其他審查者審核,因此若內容有誤,請到 issue 區提出問題,我會儘速修改,謝謝。

# 前言

想像你在寫一個程式,就像組裝樂高積木一樣。以前我們只能用一整條長長的指令來完成所有工作,但現在我們可以把程式分成很多小塊,每一塊都是一個「函式」(功能),就像把不同的樂高積木組合起來。

引入這樣的特性,代表我們從過往沒有結構的傳統 EVM 程式碼,開始轉變成具備高度結構化的程式碼。透過這樣的改變能讓我們的程式碼更容易進行分析、最佳化、與理解。

# 這個改進想要解決什麼問題?

以前的程式就像一條長長的跑道,要跳來跳去很麻煩:

  • 程式只能用「動態跳躍」,因此跑者無法在最初的時候知道跑道的真正軌跡,需要到正在跑的當下才會得知下一個目的地在哪(像隨便跳到某個地方)
  • 這樣很混亂,不只人類在分析與計算有難度,且電腦也很難在最佳化上有很好的做法
  • 更多的是我們還需要動態驗證許多的指令,以確保在執行期間不會出現問題,這樣將導致程式碼執行的成本被迫增加

# 新的解決方案

現在我們引入了兩個新的指令:

  1. CALLF(呼叫函式)- 就像打電話給朋友請他幫忙
  2. RETF(回傳結果)- 朋友做完事情後回電話告訴你結果

# 如何運作?

# 型態區段(Type Section)

每個函式都有一個標籤,告訴我們:

  • 需要多少東西作為輸入(像食譜需要多少材料)
  • 會產生多少東西作為輸出(像食譜會做出多少個餅乾)
  • 最多會用到多少空間(像廚房檯面要多大)
每個函式的資訊包含:
- 輸入數量:0-255
- 輸出數量:0-255  
- 最大堆疊增加:0-65535

# 函式資料結構

┌─────────────┬─────────────┬────────────────────┐
│  輸入數量   │  輸出數量   │ 最大堆疊增加量     │
│ (0~255)     │ (0~255)     │ (0~65535)          │
└─────────────┴─────────────┴────────────────────┘

# 新的執行狀態:回傳堆疊(Return Stack)

EVM 現在有第二個堆疊:「回傳堆疊」,用來儲存函式呼叫的返回資訊。

# 回傳堆疊結構

  • 每次 CALLF 會壓入一個回傳項目:

    (code_section_index = 呼叫前的區段,
     offset = CALLF 結束後的 PC)
    
  • 若回傳堆疊超過 1024 項目會中止

  • 呼叫時也會檢查 operand stack 是否會超過 1024 的限制(加上 callee 的最大堆疊增量)

  • RETF 從堆疊中取出該項,跳回呼叫處

    最大容量:1024 個項目
    

# 回傳堆疊操作示意

初始時:
Return Stack: []

CALLF:
Return Stack: [(MainSection, offset=CALLF+2)]

CALLF(巢狀):
Return Stack: [(MainSection, offset=CALLF+2), (Func1Section, offset=CALLF+2)]

RETF:
Return Stack: [(MainSection, offset=CALLF+2)]
→ 回到 Func1

RETF:
Return Stack: []
→ 回到 Main

# CALLF 指令(呼叫函式)

CALLF(target_section_index: u16)
  1. 有一個 16-bit 無號整數參數(big-endian 編碼),代表要呼叫的目標程式區段。

  2. 根據 EIP-5450 驗證,保證 operand stack 有足夠輸入供目標函式使用。

  3. 如果 operand stack 的目前高度 + type[target_section_index].max_stack_increase 超過 1024,會異常中止。

  4. 若回傳堆疊已滿(1024 個項目),則異常中止。

  5. 花費 5 單位 gas。

  6. 不操作 operand stack。

  7. 壓入 return stack:

    (code_section_index = 呼叫前所在區段,
     offset = CALLF 指令後下一個指令位置)
    
  8. current_section_index = target_section_index 並將 PC 設為 0,開始執行新函式。

# RETF 指令(回傳結果)

RETF
  1. 無參數。
  2. 根據驗證,保證 operand stack 有正確數量的輸出值。
  3. 花費 3 單位 gas。
  4. 不操作 operand stack。
  5. 從回傳堆疊取出物件 (section, offset),跳回該位置繼續執行。

根據驗證規則,0 號主程式區段不允許使用 RETF,其主因是在合法程式中執行 RETF 時,回傳堆疊必不為空,但在執行之初, 0 號主程式區段只能是空的回傳堆疊。

# CALLF / RETF 執行流程圖

┌────────────┐
│ 執行主程式 │
└─────┬──────┘
      │
      ▼
┌────────────┐
│  執行 CALLF │───┐
└─────┬──────┘   │
      │ 寫入返回位置
      ▼         │
┌────────────┐  │
│  進入函式區 │◀─┘
└─────┬──────┘
      │
      ▼
┌────────────┐
│ 執行 RETF   │────┐
└─────┬──────┘    │
      │ 讀取返回位置
      ▼            │
┌────────────┐   ◀─┘
│ 回主程式繼續 │
└────────────┘

# 🧪 巢狀呼叫堆疊流程

(0) Main Function
  ↓ CALLF(1)
(1) Function 1
  ↓ CALLF(2)
(2) Function 2
  ↓ RETF → 回到 Function 1
(1) Function 1
  ↓ RETF → 回到 Main Function
堆疊變化:
Step 1: []
Step 2: [(0, Main_PC)]
Step 3: [(0, Main_PC), (1, Func1_PC)]
Step 4: [(0, Main_PC)]
Step 5: []

# 程式碼驗證規則

為了確保程式碼正確,我們有這些規則:

  1. 不能呼叫不存在的函式
  2. 跳躍指令不能跳到函式外面
  3. 每個函式都要能從主程式到達,不可有死碼
  4. 禁止使用舊的動態跳躍指令
  5. 每個函式的返回堆疊必須在執行 RETF 前不為空

# 被禁用的指令

以下指令在新版本中不能使用:

  • JUMPJUMPI
  • JUMPDEST(轉為 NOP 無操作指令)
  • PC

# 執行過程

  1. 程式從第 0 程式區段開始執行
  2. 回傳堆疊初始為空
  3. CALLF 外,不需檢查堆疊高度限制

# 為什麼這樣設計?

#JUMPDEST 變成 NOP

因為 JUMPDEST 原本只是跳躍標記,現在已不再需要:

  1. 可作為效能測試點
  2. 幫助程式碼對齊
  3. 作為佔位符指令使用

# 不再需要掃描 JUMPDEST

以前需掃描所有可能跳躍目標以進行驗證。現在使用靜態結構與相對位址後,這項工作不再必要,提升驗證與執行效率。

# 相容性

  • 僅適用於 EOFv1 格式的合約
  • 舊有合約完全不受影響

# 花費成本

  • CALLF:5 Gas
  • RETF:3 Gas

# 總結

EIP-4750 把以往「一條到底」的 EVM 程式,轉換成可以分段、結構化、具可讀性的函式程式風格。搭配回傳堆疊(return stack)的設計,不但使程式更容易分析與最佳化,就像從一堆線變成一組有邏輯的樂高積木,EVM 也即將迎來新的程式架構時代!

Last Updated: 2025/5/21 下午3:18:25