Context

https://github.com/erc6900/resources/issues/13

Design

  1. When replacing a plugin, it's necessary to transfer data from the old plugin to the new one. Since allowing users direct access to the plugin's internal storage can pose security risks, a view function will be implemented in each plugin to return migration data. This approach leverages the view function to safely transfer necessary data without compromising security.

  2. Each plugin follows semantic versioning for its versioning scheme. The scope that replacePlugin can be applied is defined to include upgrades or downgrades that change only the patch version. This policy allows only updates with gas optimization or bug fixes, thereby preventing major / minor changes from being misrepresented as patch updates. Since versioning is defined within each plugin, there's a potential for misuse, such as significant changes being falsely presented as minor updates. Identifying such edge cases on-chain is nearly impossible, necessitating off-chain support, such as verification by a predetermined committee.

    To address this, a VersionRegistry will be established for each plugin, with the following specifications:

    1. Only the owner of a VersionRegistry can register new versions of a plugin.

      When registering a new version of a plugin, a designated owner (or committee) checks the compatibility of the version off-chain before registration. This process aims to prevent the security risks associated with the misuse of versioning.

    2. Each plugin can use its own VersionRegistry.

      While a single global registry could manage the versions of all plugins, this approach has several drawbacks:

      • Risk of centralization

      • Reduced flexibility for plugin developers

      • Difficulty in distinguishing between different plugins

        If the registry were to maintain a list or mapping of compatible plugins, distinguishing them might require hashing the plugin's name or author. If the registry uses the plugin's name for identification, it could lead to issues where plugins with the same name cannot be registered.

      Therefore, allowing each plugin to register its own preferred form of registry is advised. The ERC-6900 reference implementation will include a minimal interface and a basic form of the VersionRegistry code.

    3. Version information is retrieved from pluginMetadata.

      Consequently, a function to decode the version string into a version struct is required.

      struct Version {
      	uint256 major;
      	uint256 minor;
      	uint256 patch;
      }
      

Interface

  1. replacePlugin

    function replacePlugin(address oldPlugin, address newPlugin, bytes32 newManifestHash) external;
    
  2. VersionRegistry

    Implement a VersionRegistry with the following interface.

    interface IVersionRegistry {
        /// @notice Register a new plugin version in the registry.
        /// @dev This function can be restricted to only be callable by the contract owner or a specific role.
        /// @param plugin The address of the plugin to register.
        function registerPlugin(address plugin) external;
    
        /// @notice Retrieve the version information of a given plugin.
        /// @param plugin The address of the plugin whose version information is being queried.
        /// @return The version information of the plugin.
        function getPluginVersion(address plugin) external view returns (Version memory);
    
        /// @notice Checks if the given two plugins are compatible for the replacement.
        /// @param oldPlugin The address of plugin to be replaced.
    		/// @param newPlugin The address of plugin replacing the existing plugin.
        /// @return A boolean indicating the compatibility of two plugins.
        function isPluginCompatible(address oldPlugin, address newPlugin) external view returns (bool);
    }
    

    Register each plugin in the registry before deployment. Accordingly, the form of PluginManifest needs to be changed as follows.

    struct PluginManifest {
        // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include
        // IPlugin's interface ID.
        bytes4[] interfaceIds;
        // If this plugin depends on other plugins' validation functions, the interface IDs of those plugins MUST be
        // provided here, with its position in the array matching the `dependencyIndex` members of `ManifestFunction`
        // structs used in the manifest.
        bytes4[] dependencyInterfaceIds;
        // Execution functions defined in this plugin to be installed on the MSCA.
        bytes4[] executionFunctions;
        // Plugin execution functions already installed on the MSCA that this plugin will be able to call.
        bytes4[] permittedExecutionSelectors;
        // Boolean to indicate whether the plugin can call any external address.
        bool permitAnyExternalAddress;
        // Boolean to indicate whether the plugin needs access to spend native tokens of the account. If false, the
        // plugin MUST still be able to spend up to the balance that it sends to the account in the same call.
        bool canSpendNativeToken;
    		**// address of version registry for this plugin, which is used in replacePlugin operation.
        address versionRegistry;**
        ManifestExternalCallPermission[] permittedExternalCalls;
        ManifestAssociatedFunction[] userOpValidationFunctions;
        ManifestAssociatedFunction[] runtimeValidationFunctions;
        ManifestAssociatedFunction[] preUserOpValidationHooks;
        ManifestAssociatedFunction[] preRuntimeValidationHooks;
        ManifestExecutionHook[] executionHooks;
    }
    
  3. Data Migration during replacement

    Add the following functions within IPlugin.

    /// @notice Retrieves data for migrating from the old plugin to a new plugin.
    /// @dev Called by the plugin manager during the plugin replacement process.
    /// It should return all the necessary state information of the plugin in a serialized format.
    /// In the case of SingleOwnerPlugin, it returns the owner's address.
    /// @return bytes Migration data to migrate from old plugin to new plugin
    function getDataForReplacement() external view returns (bytes memory);
    
    /// @notice Cleans up the plugin data when the plugin is being replaced.
    /// @dev This function is called during the plugin replacement process to allow the current (old) plugin
    /// to clean up its data or state before being replaced. For the SingleOwnerPlugin, this might involve
    /// resetting ownership information.
    function onReplaceForOldPlugin() external;
    
    /// @notice Initialize new plugin with migrated data.
    /// @dev Called during the plugin replacement process. This function initializes the state of the new plugin
    /// with the data provided. For SingleOwnerPlugin, it sets the new owner based on the migrated data.
    /// @param migrationData Migrationdata from old plugin, exported form getDataForMigration() function.
    function onReplaceForNewPlugin(bytes memory migrationData) external;
    

    The migrationData entering onReplaceForNewPlugin is the data taken from getDataForReplacement.

Considerations & Questions

We are currently contemplating the following aspects.