ERC 草案:PhaseGuard - 智能合約生命週期狀態的標準介面
本提案介紹了 PhaseGuard,這是一個透過有限狀態機管理智能合約生命週期的標準介面,旨在防止重入攻擊和未初始化狀態暴露等常見漏洞。它提供了一個統一的 6 階段轉換矩陣和存取策略位元遮罩,以確保合約進入點的一致性,並提高外部調用者的可組合性。
摘要
這是一個針對透過有限狀態機管理其生命週期的智慧合約的標準介面。符合規範的合約透過四個讀取函數和一個事件,公開其當前階段(Phase)、各階段的存取策略位元遮罩(Bitmask)以及轉換合法性檢查。所有受保護的進入點共享單一的 6 階段轉換矩陣,因此生命週期保護是在階段層級而非函數層級應用的。
動機
智慧合約的生命週期保護通常由獨立的機制組成:重入鎖(Reentrancy Guards)、暫停開關(Pause Switches)和初始化保護。每一項都必須正確地應用於每個相關的進入點,且沒有任何機制能將它們連結起來,或在合約的公開介面上強制執行一致性。
這種「按函數處理」的方法不斷產生相同類型的漏洞:重入(包括唯讀和跨函數變體)、暫停覆蓋不完全以及未初始化狀態的暴露。請參閱「安全性考量」以了解各類漏洞與階段模型的詳細對應關係。
現有的模式解決了個別問題,但僅公開了孤立且特定於實作的信號。合約可能會公開 paused(),但外部調用者沒有標準方法來查詢引導狀態(Bootstrap State)、進行中的變更(In-flight Mutation)、維護模式、終端結算(Terminal Finalization)或轉換合法性。
標準介面將允許組合合約在路由資金或執行治理操作之前,檢查依賴項的生命週期階段。合約可以避免在依賴項處於 MUTATING(變更中)時調用它,時間鎖可以在執行排隊提案前驗證目標是否處於 READY(就緒)狀態,監控系統可以觀察任何符合規範的合約的階段變化,而無需為每個協議開發適配器。
介面
interface IPhaseGuard /* is ERC165 */ {
event PhaseTransition(uint8 indexed fromPhase, uint8 indexed toPhase);
function phase() external view returns (uint8);
function getPolicy(uint8 phase_) external pure returns (uint8);
function isTransitionAllowed(uint8 from, uint8 to) external pure returns (bool);
function isStable(uint8 phase_) external pure returns (bool);
}
介面 ID:0x5ae3f743
階段
| ID | 階段 | 類型 | 描述 |
|---|---|---|---|
| 0 | UNINITIALIZED | 不穩定 | 未初始化。在與部署相同的交易中轉換至 READY。 |
| 1 | READY | 穩定 | 正常運行狀態。允許用戶進入、管理員進入和視圖(View)調用。 |
| 2 | MUTATING | 不穩定 | 臨時執行狀態。所有受保護的進入點和受保護的視圖均被阻斷。 |
| 3 | FINALIZED | 穩定 | 終端狀態。不再有後續轉換。僅限視圖調用。 |
| 4 | PAUSED | 穩定 | 緊急停止。用戶進入被阻斷。管理員進入和視圖調用仍被允許。 |
| 5 | MAINTENANCE | 穩定 | 僅限管理員的運行窗口。用戶進入被阻斷。管理員進入和視圖調用仍被允許。 |
核心不變量:在任何受保護的函數完成之前,合約必須返回到穩定階段(READY、FINALIZED、PAUSED 或 MAINTENANCE)。
MUTATING 由保護機制內部進入和退出。它在轉換矩陣中沒有前向轉換,且在受保護函數返回時不會作為全域階段持久存在。
UNINITIALIZED 是部署時的預設狀態。如果引導未完成,所有受保護的進入點將保持阻斷。
轉換矩陣
isTransitionAllowed 返回的值與以下內容一致:
| 從 / 至 | UNINITIALIZED | READY | MUTATING | FINALIZED | PAUSED | MAINTENANCE |
|---|---|---|---|---|---|---|
| UNINITIALIZED | - | 是 | 否 | 否 | 否 | 否 |
| READY | 否 | - | 是 | 是 | 是 | 是 |
| MUTATING | 否 | 否 | - | 否 | 否 | 否 |
| FINALIZED | 否 | 否 | 否 | - | 否 | 否 |
| PAUSED | 否 | 是 | 否 | 是 | - | 是 |
| MAINTENANCE | 否 | 是 | 是 | 否 | 否 | - |
FINALIZED 是一個終端狀態。一旦進入,不允許任何轉換。
PAUSED 和 MAINTENANCE 共享相同的策略位元,但在轉換矩陣中有所不同:允許 MAINTENANCE → MUTATING,但不允許 PAUSED → MUTATING。PAUSED 是一個停止狀態,而 MAINTENANCE 是一個僅限管理員的運行狀態。
策略位元遮罩
getPolicy 返回一個 uint8。位元 0–2:
- 位元 0:ALLOW_USER — 非管理員調用者可以進入受保護的狀態變更函數
- 位元 1:ALLOW_ADMIN — 管理員調用者可以進入受保護的狀態變更函數
- 位元 2:ALLOW_VIEWS — 調用者可以進入受保護的視圖函數
位元 3-7 保留供未來使用。
| 位元標誌 / 階段 | UNINITIALIZED | READY | MUTATING | FINALIZED | PAUSED | MAINTENANCE |
|---|---|---|---|---|---|---|
| ALLOW_USER | 0 | 1 | 0 | 0 | 0 | 0 |
| ALLOW_ADMIN | 0 | 1 | 0 | 0 | 1 | 1 |
| ALLOW_VIEWS | 0 | 1 | 0 | 1 | 1 | 1 |
轉換矩陣和策略矩陣均由規範固定。
涵蓋的漏洞
該模型旨在涵蓋五類經常發生的漏洞:
- 透過
MUTATING解決常規、唯讀和跨函數重入 - 透過
PAUSED解決暫停覆蓋不完全 - 透過
UNINITIALIZED解決未初始化狀態暴露
UNINITIALIZED 提供失敗關閉(Fail-closed)的進入阻斷,但它不能替代 OpenZeppelin Initializable 等代理初始化輔助工具。
完整的漏洞映射表和漏洞測試位於 README 中:
- 完整映射:README 安全性考量
- 漏洞測試:README 測試案例
參考實作
完整的規範、實作、測試、Gas 基準測試、整合指南以及一個 ERC-4626 金庫範例:
github.com/athcb/solidity-phaseguard
GitHub - athcb/solidity-phaseguard: PhaseGuard 是一個 Solidity 生命週期保護器...
PhaseGuard 是一個 Solidity 生命週期保護器,它在初始化、變更、暫停、維護和結算過程中強制執行固定的狀態機,防止重入變體(包括唯讀重入)、未初始化狀態暴露以及受保護進入點的暫停繞過漏洞。
問題
我希望獲得關於以下幾項設計決策的反饋:
- 規範鎖定了 6 個階段和固定的轉換矩陣。另一種選擇是讓部署者定義自己的階段,但這會破壞組合性,因為調用者在不知道合約自定義架構的情況下無法解釋
phase()。好奇是否有人有不同看法。 - 每個階段的位元遮罩(策略矩陣)也是由規範設定的,而非按部署設定。合約定義的策略會更靈活,但外部調用者在不信任實作的情況下無法信任
getPolicy。對此持開放討論態度。 - 介面包含四個視圖函數和一個事件。如果其中任何一個顯得冗餘或缺少了什麼,請告訴我。
- 與獨立的
nonReentrant(約 2.7–7.2k Gas)相比,此保護器在每次受保護調用中增加了約 16–35k Gas。但其範圍更廣(包括唯讀重入 + 暫停 + 生命週期)。您認為這種權衡是否合理,或者我應該致力於在參考實作中降低成本?
歡迎任何貢獻、替代方案和反對意見。
完整的規範、原理和測試都在 repo README 中。
1 則貼文 - 1 位參與者