Alert Source Discuss
⚠️ Draft Standards Track: ERC

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 解决了几个关键问题:

  1. 重大更改:新实现中的接口更改可能会破坏现有的集成。
  2. 逐步采用:没有标准方法允许逐步采用新的合约版本。
  3. 恶意升级:今天的用户必须无限期地信任代理管理员,因为他们无法选择退出潜在有害的升级,除非完全停止使用合约。
  4. 信任假设:合约用户必须对治理或管理密钥保持永久信任,而无法选择性地信任特定的、经过审计的实现。

规范

本文档中的关键词“必须”、“禁止”、“需要”、“应该”、“不应该”、“推荐”、“可以”和“可选”应按照 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);
}

行为要求

  1. 代理合约 必须 维护一个从版本标识符到实现地址的映射。
  2. 代理合约 必须 维护一个在未指定版本时使用的默认版本。
  3. 当调用 executeAtVersion 时,代理 必须
    • 验证指定的版本是否存在
    • 将调用转发到相应的实现
    • 返回实现返回的任何数据
  4. 当注册版本或默认版本更改时,代理合约 必须 发出适当的事件。
  5. 代理合约 应该 为管理功能(注册版本、设置默认值)实现访问控制。
  6. 代理合约 可以 实现 EIP-1967 存储槽,以便与现有工具兼容。

回退函数

代理合约 应该 实现一个回退函数,该函数在未指定版本时将调用转发到默认实现版本。这保持了与传统代理模式的兼容性。

理由

版本标识符为 bytes32

版本标识符被指定为 bytes32 而不是语义版本控制字符串,以:

  1. 在版本控制方案中提供灵活性
  2. 降低存储和比较的 Gas 成本
  3. 允许基于字符串的版本(转换为 bytes32)和数字版本
  4. 允许在 SHA-1 或 SHA-256 中存储 Git 提交标识符

显式版本选择

该标准要求调用者通过 executeAtVersion 显式选择一个版本,而不是在调用数据中编码版本信息,以:

  1. 保持版本选择和函数调用之间的清晰分离
  2. 避免修改现有的函数签名
  3. 使版本选择显式且可审计

注册表模式

选择注册表模式而不是如下替代方案:

  1. 多个代理:为每个版本设置单独的代理会增加部署成本和复杂性
  2. 存储中的版本:存储单个“当前版本”将不允许不同的调用者同时使用不同的版本

默认版本

默认版本机制允许代理保持与传统代理模式的兼容性,并支持不需要指定版本的调用者。

向后兼容性

本 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.