ERC-7936: Versioned Proxy Contract Interface

Discussion Topic for ERC: Versioned Proxy Contract Interface

Simple Summary

This ERC standardizes a proxy interface that lets users call specific contract versions, enabling backward compatibility and opt-in upgrades.

Abstract

This EIP standardizes an interface for proxy contracts that allows callers to explicitly select which version of an implementation contract they want to interact with. Unlike traditional proxy patterns that only expose the latest implementation, this standard enables backward compatibility by maintaining access to previous implementations while supporting upgrades. The versioned proxy maintains a registry of implementation addresses mapped to version identifiers, allowing callers to specify their desired version at call time.

Motivation

Smart contract upgrades are essential for fixing bugs and adding features. Current proxy patterns typically force all callers to use the latest implementation, which can break existing integrations when interfaces change.

Furthermore, traditional proxy patterns expose all users to risk if an upgrade is malicious, as they have no choice but to use the latest implementation. This standard allows users to remain on verified versions they trust, mitigating the risk of a compromised admin key or governance process deploying harmful code.

This EIP addresses several key problems:

  1. Breaking Changes: Interface changes in new implementations can break existing integrations.
  2. Gradual Adoption: There is no standard way to allow gradual adoption of new contract versions.
  3. Malicious Upgrades: Users today must trust proxy admins indefinitely, as they can’t opt out of potentially harmful upgrades without ceasing use of the contract entirely.
  4. Trust Assumptions: Contract users must maintain perpetual trust in governance or admin keys, with no ability to selectively trust specific, audited implementations.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Interface

interface IVersionedProxy {
    /// @notice Emitted when a new implementation version is registered
    /// @param version The version identifier
    /// @param implementation The address of the implementation contract
    event VersionRegistered(bytes32 version, address implementation);
        
    /// @notice Emitted when the default version is changed
    /// @param oldVersion The previous default version
    /// @param newVersion The new default version
    event DefaultVersionChanged(bytes32 oldVersion, bytes32 newVersion);
    
    /// @notice Registers a new implementation version
    /// @param version The version identifier (e.g., "1.0.0")
    /// @param implementation The address of the implementation contract
    function registerVersion(bytes32 version, address implementation) external;
    
    /// @notice Removes a version from the registry
    /// @param version The version identifier to remove
    function removeVersion(bytes32 version) external;
    
    /// @notice Sets the default version to use when no version is specified
    /// @param version The version identifier to set as default
    function setDefaultVersion(bytes32 version) external;
    
    /// @notice Gets the implementation address for a specific version
    /// @param version The version identifier
    /// @return The implementation address for the specified version
    function getImplementation(bytes32 version) external view returns (address);
    
    /// @notice Gets the current default version
    /// @return The current default version identifier
    function getDefaultVersion() external view returns (bytes32);
    
    /// @notice Gets all registered versions
    /// @return An array of all registered version identifiers
    function getVersions() external view returns (bytes32[] memory);
    
    /// @notice Executes a call to a specific implementation version
    /// @param version The version identifier of the implementation to call
    /// @param data The calldata to forward to the implementation
    /// @return The return data from the implementation call
    function executeAtVersion(bytes32 version, bytes calldata data) external payable returns (bytes memory);
}

Behavior Requirements

  1. The proxy contract MUST maintain a mapping of version identifiers to implementation addresses.
  2. The proxy contract MUST maintain a default version that is used when no version is specified.
  3. When executeAtVersion is called, the proxy MUST:
    • Verify the specified version exists
    • Forward the call to the corresponding implementation
    • Return any data returned by the implementation
  4. The proxy contract MUST emit appropriate events when versions are registered, or when the default version changes.
  5. The proxy contract SHOULD implement access control for administrative functions (registering versions, setting default).
  6. The proxy contract MAY implement EIP-1967 storage slots for compatibility with existing tools.

Fallback Function

The proxy contract SHOULD implement a fallback function that forwards calls to the default implementation version when no version is specified. This maintains compatibility with traditional proxy patterns.

Rationale

Version Identifiers as bytes32

Version identifiers are specified as bytes32 rather than semantic versioning strings to:

  1. Provide flexibility in versioning schemes
  2. Reduce gas costs for storage and comparison
  3. Allow for both string-based versions (converted to bytes32) and numeric versions
  4. Allow for storing a Git commit identifier in SHA-1 or SHA-256

Explicit Version Selection

The standard requires callers to explicitly select a version through executeAtVersion rather than encoding version information in the call data to:

  1. Maintain a clean separation between version selection and function calls
  2. Avoid modifying existing function signatures
  3. Make version selection explicit and auditable

Registry Pattern

The registry pattern was chosen over alternatives like:

  1. Multiple Proxies: Having separate proxies for each version would increase deployment costs and complexity
  2. Version in Storage: Storing a single “current version” would not allow different callers to use different versions simultaneously

Default Version

The default version mechanism allows the proxy to maintain compatibility with traditional proxy patterns and supports callers that don’t need to specify a version.

Backwards Compatibility

This EIP is designed to enhance backward compatibility for smart contracts. It does not introduce any backward incompatibilities with existing Ethereum standards or implementations.

Existing contracts that interact with proxy contracts can continue to do so without modification, as the fallback function will route calls to the default implementation.

Reference Implementation

TBD

1 Like