僅供教育參考,不構成投資建議;投資可能產生損失。
直接答案
代理合約是一種中介合約,會將呼叫轉送給另一個通常稱為實作合約或邏輯合約的合約。在常見的 EVM 設計中,代理使用 delegatecall,因此實作合約的程式碼在代理的執行環境中運作,而狀態與餘額仍保留在代理地址上。
這個間接層讓系統可以在更換實作的同時保留穩定的使用者互動地址。當許多代理共用程式碼時,它也能降低部署成本。代理並非天生可升級:有些最小代理永久指向同一個實作,而可升級代理則加入一種受控方式來更換實作或信標。
因此,使用者必須同時評估目前的實作,以及能夠更換它的權限。僅有經過驗證的代理位元組碼,無法說明明天會執行什麼程式碼。
運作方式
當呼叫到達代理時,其回退路徑會將呼叫資料複製或轉送給某個實作。使用 delegatecall 時,address(this) 是代理,儲存讀寫作用於代理,並保留原始的 msg.sender 與 msg.value。接著,代理會傳回實作合約的回傳資料,或隨實作合約一起回退。
由於代理中繼資料與應用程式狀態共用代理的儲存空間,標準化槽位有助於避免意外衝突。ERC-1967 為實作地址、信標地址與可選管理員定義了槽位,並建議在這些值變更時發出事件。該標準讓代理更容易檢查,但本身並不能保證升級安全。
常見設計將升級權限放在不同位置:
- 透明代理:代理區分管理呼叫與一般使用者呼叫,通常透過獨立的管理員合約實作。
- UUPS 代理:升級邏輯位於實作合約中;實作合約必須授權變更,並持續相容於預期的升級介面。
- 信標代理:代理向信標查詢其實作;變更一個信標會影響所有跟隨該信標的代理。
- 最小複製合約:許多小型代理委派給共用程式碼,而且通常完全沒有升級路徑。
範例
假設一個金庫代理持有使用者餘額,並委派給實作 A。使用者透過代理地址存款,實作 A 的程式碼會更新代理儲存中的餘額紀錄。
之後,治理將 ERC-1967 實作槽改為實作 B。代理地址與已記錄的餘額都沒有移動,但未來的呼叫會執行實作 B 的程式碼。如果 B 保留儲存配置並實作預期規則,使用者就會在同一地址看到新行為。
如果 B 重新排列儲存變數、遺漏授權檢查,或新增一條由升級者控制的提款路徑,同一次升級就可能破壞會計或暴露資產。因此,實務問題不能只問某個合約是否為代理,還要問誰能改變其執行路徑、需要多長延遲,以及經過了哪些驗證。
風險
- 升級金鑰遭入侵:管理員、多重簽章或治理流程可能安裝惡意或有缺陷的程式碼。
- 儲存配置不相容:變更變數順序、型別或繼承關係,可能讓新程式碼誤讀或覆寫現有狀態。
- 初始化失敗:建構函式不會初始化代理儲存;缺少或可重複執行的初始化保護,可能讓其他帳戶取得特權角色。
- 呼叫路由意外:函式選擇器衝突、管理員專用路由或非預期的信標,可能使實際執行路徑不同於可見介面。
- 共用升級影響範圍:一次信標或實作決策可能同時改變多個合約實例。
在存入資產或授予核准前,應在鏈上解析目前的實作或信標,確認升級權限與任何時間鎖,檢查經驗證的原始碼與儲存相容性,並在適用時查看近期的 Upgraded、BeaconUpgraded 與 AdminChanged 事件。首次審查後仍需持續監控,因為執行路徑可能改變。
常見誤解
- 「代理不儲存有意義的狀態。」在
delegatecall下,應用程式狀態與資產通常屬於代理,儘管邏輯來自另一個地址。 - 「實作合約經過驗證,系統就無須信任。」升級金鑰、治理、信標、初始化與未來實作仍是信任模型的一部分。
- 「每個代理都可以升級。」複製合約與其他固定代理可能永久委派給同一個實作;能否升級取決於具體設計與授權程式碼。
相關主題
來源
- 智慧合約簡介 - Solidity Documentation(查閱日期:2026-08-21)
- ERC-1967:代理儲存槽 - Ethereum Improvement Proposals(查閱日期:2026-08-21)
- ERC-1822:通用可升級代理標準(UUPS) - Ethereum Improvement Proposals(查閱日期:2026-08-21)
- 代理 - OpenZeppelin Documentation(查閱日期:2026-08-21)