DeFi Security 101 - 2023 - 04 - Dimitri Kamenski & Richard Skinner
三句話摘要
智能合約代理模式的設計對比、實現方法與安全最佳實踐。 選擇 UUPS 或透明代理應基於具體場景權衡,但最重要是理解初始化風險、避免混淆兩種模式、並使用成熟框架進行防護性實現。 代理的核心機制:代理合約持有指向實現合約的參考,透過委托調用執行實現合約的代碼,但使用代理自身的存儲空間。這樣升級時只需改變指向的實現合約地址,用戶存儲與資金保持不變。
重點整理
重點- 1
代理的核心機制:代理合約持有指向實現合約的參考,透過委托調用執行實現合約的代碼,但使用代理自身的存儲空間。這樣升級時只需改變指向的實現合約地址,用戶存儲與資金保持不變。
- 2
透明代理 vs UUPS 的關鍵差異:透明代理在代理合約中內置升級邏輯與管理員檢查,每次調用都需檢查調用者身份(gas 開銷高);UUPS 將升級邏輯移到實現合約,降低普通調用的 gas 成本,但要求實現合約實現訪問控制。
- 3
初始化函數的風險:由於代理無法使用傳統構造函數,需用初始化函數執行初始配置。但初始化函數仍可被直接調用,攻擊者可繞過代理直接初始化實現合約。必須在實現合約構造函數中呼叫 `disableInitializers()` 防止此漏洞。
- 4
常見配置誤區:同時使用透明代理與 UUPS 模式會造成控制流混亂與人為錯誤風險,鑽石代理模式過於複雜,不建議無特殊需求時使用。應在部署和初始化於同一交易完成,防止交易排序攻擊導致前置初始化。
實用技巧與重點
乾貨- 代理概念相關
- 兩種調用類型:標準調用(使用被調用合約的代碼、存儲、地址)vs 委托調用(使用被調用合約的代碼,但用調用者自身的存儲、地址、調用者身份)
- 代理合約本身代碼最少,只含必要的代理邏輯,實現合約包含業務邏輯
- 升級時改變代理中的實現合約地址指針即可
- 透明代理特性
- 升級邏輯在代理合約內部處理
- 需要管理員用戶或白名單合約
- 每次調用都需檢查 `msg.sender` 是否為管理員(產生 gas 開銷)
- 管理員調用回退函數觸發升級,更新實現合約地址
- UUPS 特性
- 所有調用委托轉發到實現合約
- 實現合約負責實現升級邏輯與訪問控制
- 升級檢查只在升級時執行,普通調用無額外 gas 開銷
- 可將 UUPS 合約升級至無升級功能的版本,實現永久鎖定
- 初始化函數保護
- OpenZeppelin Zeppelin 代理庫中使用 `disableInitializers()` 內部函數
- 在實現合約構造函數中呼叫此函數,防止直接初始化
- 部署和初始化應在同一交易完成,防止交易排序攻擊
- 其他模式
- 鑽石代理(Diamond Proxy):極其複雜,解決合約大小限制,除非必要否則不建議使用
- 建議:無特殊需求不用代理;必須升級時選擇 Transparent 或 UUPS;優先使用現成成熟庫而非自建
結論
結論“選擇 UUPS 或透明代理應基於具體場景權衡,但最重要是理解初始化風險、避免混淆兩種模式、並使用成熟框架進行防護性實現。”
完整解析
詳細以太坊智能合約部署後代碼基本不可更改,這既是優點(保證可信度),也是缺點(無法修復漏洞或增加功能)。代理模式通過引入中間層解決此問題。代理合約本身代碼簡潔,只持有一個指向實現合約地址的變量。當用戶在代理上調用函數時,代理透過委托調用借用實現合約的代碼邏輯,但執行時使用代理自身的存儲空間。這樣同一份代碼可被多個代理使用,每個代理維護獨立存儲;升級時只需改變代理指向的實現合約地址,無需遷移用戶資金或更改用戶交互地址。
講者介紹了兩種主流實現方式。透明代理在代理合約中內置升級邏輯——管理員調用代理的特定回退函數時,代理不轉發調用,而是直接修改內部存儲槽以更新實現合約地址。為了區分管理員操作與普通用戶調用,代理需在每次調用時檢查 `msg.sender` 是否為管理員,只有非管理員的調用才被轉發到實現合約。這個檢查在每次交互都執行,產生固定的 gas 開銷。UUPS(通用可升級代理標準)採用不同策略——代理無條件將所有調用委托到實現合約,升級邏輯實現在實現合約自身,由實現合約判斷調用者權限決定是否執行升級。這樣普通用戶調用時無額外檢查,gas 成本更低,升級檢查僅在升級時執行。UUPS 還提供更強的靈活性:可將合約升級至完全移除升級功能的版本,實現永久鎖定;而透明代理的升級邏輯內置於代理本身,無法完全移除。
初始化函數是代理使用中的關鍵議題。由於構造函數只在部署時執行後被丟棄,無法在代理升級後被調用,代理模式改用初始化函數(通常命名 `initialize`)執行初始配置。然而初始化函數仍保留在實現合約代碼中,攻擊者可直接調用實現合約的初始化函數而非通過代理,導致實現合約被直接初始化。為防止此攻擊,OpenZeppelin 庫提供 `disableInitializers()` 函數——在實現合約的構造函數中呼叫它,告訴實現合約已被初始化,任何重複初始化嘗試都會失敗。另一個風險是代理部署與初始化若在不同交易進行,惡意用戶可在中間插入交易搶先初始化,接管合約控制權。建議將部署和初始化合併在同一交易中完成。
講者強調了幾個常見誤區。同時使用透明代理與 UUPS 模式極易發生,特別是自動化部署工具或框架默認值混合使用時。由於兩種模式在技術上兼容,這種配置看似正常運作卻不是預期行為,增加人為錯誤風險(如透明代理的管理員權限遺忘在他人手中)。鑽石代理模式雖在某些場景能解決合約大小限制,但複雜度極高,無特殊需求不應使用。最佳實踐總結為:無升級需求就不用代理;選定一種代理模式並堅持;優先使用經過驗證的現成庫如 OpenZeppelin,而非自行實現。
關鍵時刻
Pipeline v2帶時間戳的重點,會在逐字稿層級分析上線後產生。目前請先透過原始影片觀看。
事實查核
Pipeline v2說法查證是下一次管線升級的一部分。KeyFrame 只會顯示它真正能驗證的內容。

