# EIP-3540 電腦程式的新衣服 - EVM 物件格式 (EOF)
# 注意事項
本文並沒有經過作者外的其他審查者審核,因此若內容有誤,請到 issue 區提出問題,我會儘速修改,謝謝。
# 簡單說明
我們要給電腦程式一種新的「包裝方式」,就像是給程式穿上一件新衣服。這種新包裝讓EVM 可以快速辨識程式的不同部分,比如哪部分是實際要執行的指令,哪部分只是資料。而且這件新衣服還可以隨時升級,加入更多功能!
這種新包裝的程式長得像這樣:
魔法標記, 版本號, (區塊類型, 區塊大小)+, 0, <區塊內容>
# 為什麼需要這個?
現在的以太坊區塊鏈上,程式沒有固定的結構。導致 EVM 需要每次在執行期間都需要重新檢查整個程式,這樣會額外消耗執行時間與資源。
有了這個新包裝,電腦只需要在程式上傳到區塊鏈前檢查一次。這樣做有幾個好處:
- 可以區分「指令」和「資料」,就像分清楚「食譜步驟」和「食材」一樣
- 引入版本的概念,可以更容易地加入新功能或移除舊功能,不像以前需要透過硬分岔的區塊高度來控制行為
- 讓程式更安全,因為電腦可以事先確認程式是否正確
# 詳細規格
新的格式用特殊的開頭標記(魔法標記)來識別。這個標記是 0xEF00
,這有點像是一個特殊印章,說明「這是一個使用 EOF 格式的程式碼」。
# 容器規格
這個新包裝(我們稱為 EOF 容器)包含:
| 說明 | 長度 | 值 | | |-------------|----------|------------| | 魔法標記 | 2位元組 | 0xEF00 | | 版本號 | 1位元組 | 0x01–0xFF |
EOF 容器的開頭後面跟著至少一個區塊標頭。每個區塊標頭包含兩個欄位:
區塊類型
區塊大小
或區塊大小列表
,取決於該區塊的類型。當區塊類型可以是多個且重複時,區塊大小列表
會依序列出每個區塊的大小,其長度與該區塊類型的數量相同。
說明 | 長度 | 值 | |
---|---|---|---|
區塊類型 | 1位元組 | 0x01–0xFF | uint8 |
區塊大小 | 2位元組 | 0x0000–0xFFFF | uint16 |
區塊大小列表 | 動態 | n/a | uint16, uint16+ |
區塊標頭列表以區塊標頭終止位元組 0x00
結束。區塊內容主體緊接在其後面。
# 容器驗證規則
版本號
不能為0
。區塊類型
不能為0
。數值0
是為區塊標頭終止位元組保留的。- 必須至少有一個區塊(因此至少有一個區塊標頭)。
- 區塊外不能有多餘的位元組。這包括最後一個區塊後多出來的尾端資料。
# EOF 第一版
EOF 第一版由數個 EIP 組成,包括本 EIP。由於細節都被詳細定義在各自的 EIP 中,因此本規範中只是簡要討論。要理解 EOF 的完整範圍,請深入檢查對應的 EIP。
# 容器
EOF 第一版容器由 標頭
和 主體
組成。
容器 := 標頭, 主體
標頭 :=
魔法標記, 版本號碼,
類型區塊標記, 類型區塊大小,
程式區塊標記, 程式區塊數量, 程式區塊大小+,
[容器區塊標記, 容器區塊數量, 容器區塊大小+,]
資料區塊標記, 資料區塊大小,
終止符號
主體 := 類型區塊, 程式區塊+, 容器區塊*, 資料區塊
類型區塊 := (輸入, 輸出, 最大堆疊增加)+
注意:,
是連接運算符,+
應解釋為「一個或更多」前一項,*
應解釋為「零個或更多」前一項,[物件]
應解釋為可選物件。
# 標頭
名稱 | 長度 | 值 | 說明 |
---|---|---|---|
魔法標記 | 2 位元組 | 0xEF00 | |
版本號 | 1 位元組 | 0x01 | EOF 版本 |
類型區塊標記 | 1 位元組 | 0x01 | 類型區塊的標記 |
類型區塊大小 | 2 位元組 | 0x0004-0x1000 | 16位無符號(unsigned)大端(big-endian)整數,表示類型區塊內容的長度,每個程式區塊4位元組 |
程式區塊標記 | 1 位元組 | 0x02 | 程式區塊大小區塊的標記 |
程式區塊數量 | 2 位元組 | 0x0001-0x0400 | 16位無符號大端整數,表示程式區塊的數量 |
程式區塊大小 | 2 位元組 | 0x0001-0xFFFF | 16位無符號大端整數,表示程式區塊內容的長度 |
容器區塊標記 | 1 位元組 | 0x03 | 容器區塊大小區塊的標記 |
容器區塊數量 | 2 位元組 | 0x0001-0x0100 | 16位無符號大端整數,表示容器區塊的數量 |
容器區塊大小 | 4 位元組 | 0x00000001-0xFFFFFFFF | 32位無符號大端整數,表示容器區塊內容的長度 |
資料區塊標記 | 1 位元組 | 0xFF | 資料區塊大小區塊的標記 |
資料區塊大小 | 2 位元組 | 0x0000-0xFFFF | 16位無符號大端整數,表示資料區塊內容的長度 (*) |
終止符號 | 1 位元組 | 0x00 | 標記標頭的結束 |
(*) 對於尚未部署的容器,這可能大於實際內容長度,我們在部署的章節會特別討論。
# 主體
名稱 | 長度 | 值 | 說明 |
---|---|---|---|
類型區塊 | 可變 | n/a | 存儲程式區塊詮釋資料 |
輸入 | 1 位元組 | 0x00-0x7F | 程式區塊消耗的堆疊元素數量 |
輸出 | 1 位元組 | 0x00-0x7F | 程式區塊回傳的堆疊元素數量 |
最大堆疊增加 | 2 位元組 | 0x0000-0x03FF | 程式執行期間堆疊可能增加的最大高度 |
程式區塊 | 可變 | n/a | 任意位元碼 |
容器區塊 | 可變 | n/a | 任意 EOF 格式的容器 |
資料區塊 | 可變 | n/a | 任意位元組序列 |
注意:輸出
的特殊值 0x80
被指定用於表示不回傳的函數,在單獨的 EIP 中定義。
# EOF 第一版驗證規則
對容器格式有以下有效性約束:
類型區塊大小
可被4
整除- 程式區塊的數量必須等於
類型區塊大小 / 4
- 對於尚未部署的容器,資料主體長度可能短於
資料區塊大小
- 容器的總大小不得超過
MAX_INITCODE_SIZE
(如 EIP-3860 中定義)
# 執行語意的更改
對於 EOF 合約:
- 執行從程式區塊 0 的第一個位元組開始
CODESIZE
、CODECOPY
、EXTCODESIZE
、EXTCODECOPY
、EXTCODEHASH
、GAS
在 EOF 合約中被禁止,且將無後續的對應替代指令,驗證階段將被拒絕CALL
、DELEGATECALL
、STATICCALL
在 EOF 合約中也被禁止使用,其相關的替代指令將在單獨的 EIP 中另外定義。- 從 EOF 合約到非 EOF 合約(傳統合約、EOA、空帳戶)的
DELEGATECALL
(或任何 EOF 的替代指令)是不允許的,如被觸發,將以呼叫深度檢查失敗的相同模式觸發錯誤。反之,允許從傳統合約到 EOF 合約的DELEGATECALL
,這樣能使現存的代理合約能夠使用 EOF 升級。
對於傳統合約:
- 如果
EXTCODECOPY
的目標帳戶是 EOF 合約,那麼它將複製最多 2 個位元組的EF00
。 - 如果
EXTCODEHASH
的目標帳戶是 EOF 合約,那麼它將回傳0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5
(EF00
的雜湊值)。 - 如果
EXTCODESIZE
的目標帳戶是 EOF 合約,那麼它將回傳 2。
注意 與傳統合約一樣,上述 EXTCODECOPY
、EXTCODEHASH
和 EXTCODESIZE
的行為不適用於正在部署中的 EOF 合約目標。
# 設計原因
- 魔法標記的第一個位元組
0xEF
是特別保留的 - 第二個位元組
0x00
是為了避免與現有的程式衝突 - 版本號從 1 開始,這樣我們可以把舊的程式稱為「版本0」
- 把資料區塊放在最後,因為這是在部署程式時最可能會添加內容的部分,比如有部分的資料需要在初始化階段才能計算出來