ERC-7936: 版本化代理合约接口
允许多方选择实现的 多版本代理合约
Authors | Raphina Liu (@Stamp9), Monica Jin (@mokita-j), Martin Monperrus (@monperrus) |
---|---|
Created | 2025-04-17 |
Discussion Link | https://ethereum-magicians.org/t/new-erc-versioned-proxy-contract-interface/23743 |
摘要
本 EIP 标准化了一个代理合约的接口,该接口允许调用者显式选择他们想要交互的实现合约版本。与仅暴露最新实现的传统代理模式不同,此标准通过保持对先前实现的访问来启用向后兼容性,同时支持升级。版本化代理维护一个实现地址的注册表,该注册表映射到版本标识符,允许调用者在调用时指定所需的版本。
动机
智能合约升级对于修复错误和添加功能至关重要。当前的代理模式通常强制所有调用者使用最新实现,这会在接口更改时破坏现有的集成。
此外,如果升级是恶意的,传统的代理模式会将所有用户置于风险之中,因为他们别无选择,只能使用最新实现。此标准允许用户停留在他们信任的已验证版本上,从而减轻了管理员密钥或治理流程部署有害代码的风险。
本 EIP 解决了几个关键问题:
- 重大更改:新实现中的接口更改可能会破坏现有的集成。
- 逐步采用:没有标准方法允许逐步采用新的合约版本。
- 恶意升级:今天的用户必须无限期地信任代理管理员,因为他们无法选择退出潜在有害的升级,除非完全停止使用合约。
- 信任假设:合约用户必须对治理或管理密钥保持永久信任,而无法选择性地信任特定的、经过审计的实现。
规范
本文档中的关键词“必须”、“禁止”、“需要”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。
接口
interface IVersionedProxy {
/// @notice 当注册新的实现版本时发出
/// @param version 版本标识符
/// @param implementation 实现合约的地址
event VersionRegistered(bytes32 version, address implementation);
/// @notice 当默认版本更改时发出
/// @param oldVersion 之前的默认版本
/// @param newVersion 新的默认版本
event DefaultVersionChanged(bytes32 oldVersion, bytes32 newVersion);
/// @notice 注册一个新的实现版本
/// @param version 版本标识符 (例如, "1.0.0")
/// @param implementation 实现合约的地址
function registerVersion(bytes32 version, address implementation) external;
/// @notice 从注册表中删除一个版本
/// @param version 要删除的版本标识符
function removeVersion(bytes32 version) external;
/// @notice 设置在未指定版本时使用的默认版本
/// @param version 要设置为默认版本的版本标识符
function setDefaultVersion(bytes32 version) external;
/// @notice 获取特定版本的实现地址
/// @param version 版本标识符
/// @return 指定版本的实现地址
function getImplementation(bytes32 version) external view returns (address);
/// @notice 获取当前的默认版本
/// @return 当前的默认版本标识符
function getDefaultVersion() external view returns (bytes32);
/// @notice 获取所有已注册的版本
/// @return 所有已注册的版本标识符的数组
function getVersions() external view returns (bytes32[] memory);
/// @notice 执行对特定实现版本的调用
/// @param version 要调用的实现的版本标识符
/// @param data 要转发到实现的调用数据
/// @return 来自实现调用的返回数据
function executeAtVersion(bytes32 version, bytes calldata data) external payable returns (bytes memory);
}
行为要求
- 代理合约 必须 维护一个从版本标识符到实现地址的映射。
- 代理合约 必须 维护一个在未指定版本时使用的默认版本。
- 当调用
executeAtVersion
时,代理 必须:- 验证指定的版本是否存在
- 将调用转发到相应的实现
- 返回实现返回的任何数据
- 当注册版本或默认版本更改时,代理合约 必须 发出适当的事件。
- 代理合约 应该 为管理功能(注册版本、设置默认值)实现访问控制。
- 代理合约 可以 实现 EIP-1967 存储槽,以便与现有工具兼容。
回退函数
代理合约 应该 实现一个回退函数,该函数在未指定版本时将调用转发到默认实现版本。这保持了与传统代理模式的兼容性。
理由
版本标识符为 bytes32
版本标识符被指定为 bytes32
而不是语义版本控制字符串,以:
- 在版本控制方案中提供灵活性
- 降低存储和比较的 Gas 成本
- 允许基于字符串的版本(转换为 bytes32)和数字版本
- 允许在 SHA-1 或 SHA-256 中存储 Git 提交标识符
显式版本选择
该标准要求调用者通过 executeAtVersion
显式选择一个版本,而不是在调用数据中编码版本信息,以:
- 保持版本选择和函数调用之间的清晰分离
- 避免修改现有的函数签名
- 使版本选择显式且可审计
注册表模式
选择注册表模式而不是如下替代方案:
- 多个代理:为每个版本设置单独的代理会增加部署成本和复杂性
- 存储中的版本:存储单个“当前版本”将不允许不同的调用者同时使用不同的版本
默认版本
默认版本机制允许代理保持与传统代理模式的兼容性,并支持不需要指定版本的调用者。
向后兼容性
本 EIP 旨在增强智能合约的向后兼容性。它不会对现有的以太坊标准或实现引入任何向后不兼容性。
与代理合约交互的现有合约可以继续这样做而无需修改,因为回退函数会将调用路由到默认实现。
安全考虑
本 EIP 旨在显着提高广泛使用的代理模式的安全性。
版权
版权及相关权利通过 CC0 放弃。
Citation
Please cite this document as:
Raphina Liu (@Stamp9), Monica Jin (@mokita-j), Martin Monperrus (@monperrus), "ERC-7936: 版本化代理合约接口 [DRAFT]," Ethereum Improvement Proposals, no. 7936, April 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7936.