# EIP-4750 EOF 函式功能
# 注意事項
本文並沒有經過作者外的其他審查者審核,因此若內容有誤,請到 issue 區提出問題,我會儘速修改,謝謝。
# 前言
想像你在寫一個程式,就像組裝樂高積木一樣。以前我們只能用一整條長長的指令來完成所有工作,但現在我們可以把程式分成很多小塊,每一塊都是一個「函式」(功能),就像把不同的樂高積木組合起來。
引入這樣的特性,代表我們從過往沒有結構的傳統 EVM 程式碼,開始轉變成具備高度結構化的程式碼。透過這樣的改變能讓我們的程式碼更容易進行分析、最佳化、與理解。
# 這個改進想要解決什麼問題?
以前的程式就像一條長長的跑道,要跳來跳去很麻煩:
- 程式只能用「動態跳躍」,因此跑者無法在最初的時候知道跑道的真正軌跡,需要到正在跑的當下才會得知下一個目的地在哪(像隨便跳到某個地方)
- 這樣很混亂,不只人類在分析與計算有難度,且電腦也很難在最佳化上有很好的做法
- 更多的是我們還需要動態驗證許多的指令,以確保在執行期間不會出現問題,這樣將導致程式碼執行的成本被迫增加
# 新的解決方案
現在我們引入了兩個新的指令:
CALLF
(呼叫函式)- 就像打電話給朋友請他幫忙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)
有一個 16-bit 無號整數參數(big-endian 編碼),代表要呼叫的目標程式區段。
根據 EIP-5450 驗證,保證 operand stack 有足夠輸入供目標函式使用。
如果
operand stack
的目前高度 +type[target_section_index].max_stack_increase
超過 1024,會異常中止。若回傳堆疊已滿(1024 個項目),則異常中止。
花費 5 單位 gas。
不操作 operand stack。
壓入 return stack:
(code_section_index = 呼叫前所在區段, offset = CALLF 指令後下一個指令位置)
將
current_section_index = target_section_index
並將 PC 設為 0,開始執行新函式。
# RETF
指令(回傳結果)
RETF
- 無參數。
- 根據驗證,保證 operand stack 有正確數量的輸出值。
- 花費 3 單位 gas。
- 不操作 operand stack。
- 從回傳堆疊取出物件
(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: []
# 程式碼驗證規則
為了確保程式碼正確,我們有這些規則:
- 不能呼叫不存在的函式
- 跳躍指令不能跳到函式外面
- 每個函式都要能從主程式到達,不可有死碼
- 禁止使用舊的動態跳躍指令
- 每個函式的返回堆疊必須在執行 RETF 前不為空
# 被禁用的指令
以下指令在新版本中不能使用:
JUMP
和JUMPI
JUMPDEST
(轉為NOP
無操作指令)PC
# 執行過程
- 程式從第 0 程式區段開始執行
- 回傳堆疊初始為空
- 除
CALLF
外,不需檢查堆疊高度限制
# 為什麼這樣設計?
# 讓 JUMPDEST
變成 NOP
因為 JUMPDEST
原本只是跳躍標記,現在已不再需要:
- 可作為效能測試點
- 幫助程式碼對齊
- 作為佔位符指令使用
# 不再需要掃描 JUMPDEST
以前需掃描所有可能跳躍目標以進行驗證。現在使用靜態結構與相對位址後,這項工作不再必要,提升驗證與執行效率。
# 相容性
- 僅適用於 EOFv1 格式的合約
- 舊有合約完全不受影響
# 花費成本
CALLF
:5 GasRETF
:3 Gas
# 總結
EIP-4750 把以往「一條到底」的 EVM 程式,轉換成可以分段、結構化、具可讀性的函式程式風格。搭配回傳堆疊(return stack)的設計,不但使程式更容易分析與最佳化,就像從一堆線變成一組有邏輯的樂高積木,EVM 也即將迎來新的程式架構時代!