diff --git a/src/account/UpgradeableModularAccount.sol b/src/account/UpgradeableModularAccount.sol index 59a8ff32..2904e1e6 100644 --- a/src/account/UpgradeableModularAccount.sol +++ b/src/account/UpgradeableModularAccount.sol @@ -130,9 +130,10 @@ contract UpgradeableModularAccount is return execReturnData; } + /// @inheritdoc IAccountExecute /// @notice Execution function that allows UO context to be passed to execution hooks /// @dev This function is only callable by the EntryPoint - function executeUserOp(PackedUserOperation calldata userOp, bytes32) external { + function executeUserOp(PackedUserOperation calldata userOp, bytes32) external override { if (msg.sender != address(_ENTRY_POINT)) { revert NotEntryPoint(); } diff --git a/src/interfaces/IAccountLoupe.sol b/src/interfaces/IAccountLoupe.sol index 658eb5c9..778a52f8 100644 --- a/src/interfaces/IAccountLoupe.sol +++ b/src/interfaces/IAccountLoupe.sol @@ -41,7 +41,7 @@ interface IAccountLoupe { /// @notice Get the validation data for a validation. /// @dev If the selector is a native function, the module address will be the address of the account. - /// @param validationFunction The validationFunction to get the data for. + /// @param validationFunction The validation function to get the data for. /// @return ValidationData The module address for this selector. function getValidationData(ModuleEntity validationFunction) external diff --git a/src/interfaces/IModuleManager.sol b/src/interfaces/IModuleManager.sol index 6a435bf2..ab8ccbe4 100644 --- a/src/interfaces/IModuleManager.sol +++ b/src/interfaces/IModuleManager.sol @@ -26,11 +26,8 @@ interface IModuleManager { bytes calldata moduleInstallData ) external; - /// @notice Temporary install function - pending a different user-supplied install config & manifest validation - /// path. - /// Installs a validation function across a set of execution selectors, and optionally mark it as a global - /// validation. - /// TODO: remove or update. + /// @notice Installs a validation function across a set of execution selectors, and optionally mark it as a + /// global validation. /// @dev This does not validate anything against the manifest - the caller must ensure validity. /// @param validationConfig The validation function to install, along with configuration flags. /// @param selectors The selectors to install the validation function for. @@ -46,7 +43,6 @@ interface IModuleManager { ) external; /// @notice Uninstall a validation function from a set of execution selectors. - /// TODO: remove or update. /// @param validationFunction The validation function to uninstall. /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the /// account. diff --git a/src/interfaces/IStandardExecutor.sol b/src/interfaces/IStandardExecutor.sol index db9a7c19..c757feef 100644 --- a/src/interfaces/IStandardExecutor.sol +++ b/src/interfaces/IStandardExecutor.sol @@ -25,10 +25,9 @@ interface IStandardExecutor { /// @return An array containing the return data from the calls. function executeBatch(Call[] calldata calls) external payable returns (bytes[] memory); - /// @notice Execute a call using a specified runtime validation, as given in the first 21 bytes of - /// `authorization`. + /// @notice Execute a call using a specified runtime validation. /// @param data The calldata to send to the account. - /// @param authorization The authorization data to use for the call. The first 21 bytes specifies which runtime + /// @param authorization The authorization data to use for the call. The first 24 bytes specifies which runtime /// validation to use, and the rest is sent as a parameter to runtime validation. function executeWithAuthorization(bytes calldata data, bytes calldata authorization) external diff --git a/src/interfaces/IValidationModule.sol b/src/interfaces/IValidationModule.sol index da17195a..94441b3d 100644 --- a/src/interfaces/IValidationModule.sol +++ b/src/interfaces/IValidationModule.sol @@ -6,7 +6,7 @@ import {PackedUserOperation} from "@eth-infinitism/account-abstraction/interface import {IModule} from "./IModule.sol"; interface IValidationModule is IModule { - /// @notice Run the user operation validationFunction specified by the `entityId`. + /// @notice Run the user operation validation function specified by the `entityId`. /// @param entityId An identifier that routes the call to different internal implementations, should there /// be more than one. /// @param userOp The user operation. @@ -16,7 +16,7 @@ interface IValidationModule is IModule { external returns (uint256); - /// @notice Run the runtime validationFunction specified by the `entityId`. + /// @notice Run the runtime validation function specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. /// @param account the account to validate for. /// @param entityId An identifier that routes the call to different internal implementations, should there diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 427939f8..4948e771 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -88,52 +88,92 @@ Each step is modular, supporting different implementations for each execution fu **Modular Smart Contract Accounts** **MUST** implement - `IAccount.sol` from [ERC-4337](./eip-4337.md). +- `IAccountExecute.sol` from [ERC-4337](./eip-4337.md). - `IModuleManager.sol` to support installing and uninstalling modules. - `IStandardExecutor.sol` to support open-ended execution. **Calls to modules through this SHOULD revert.** -- `IModuleExecutor.sol` to support execution from modules. **Calls to modules through `executeFromModuleExternal` SHOULD revert.** **Modular Smart Contract Accounts** **MAY** implement -- `IAccountLoupe.sol` to support visibility in module configuration on-chain. +- `IAccountLoupe.sol` to support visibility in account states on-chain. **Modules** **MUST** implement - `IModule.sol` described below and implement [ERC-165](./eip-165.md) for `IModule`. +**Modules** **May** implement one of the following module types + +- `IValidationModule` to support validations for account. +- `IValidationHookModule` to support hooks for validations. +- `IExecutionModule` to support execution functions and their installations on account. +- `IExecutionHookModule` to support pre & post execution hooks for execution functions. + #### `IModuleManager.sol` Module manager interface. Modular Smart Contract Accounts **MUST** implement this interface to support installing and uninstalling modules. ```solidity -// Treats the first 20 bytes as an address, and the last byte as a function identifier. -type ModuleEntity is bytes21; +type ModuleEntity is bytes24; -interface IModuleManager { - event ModuleInstalled(address indexed module, bytes32 manifestHash, ModuleEntity[] dependencies); +type ValidationConfig is bytes26; + +type HookConfig is bytes26; - event ModuleUninstalled(address indexed module, bool indexed onUninstallSucceeded); +interface IModuleManager { + event ExecutionInstalled(address indexed module, ExecutionManifest manifest); + event ExecutionUninstalled(address indexed module, bool onUninstallSucceeded, ExecutionManifest manifest); + event ValidationInstalled(address indexed module, uint32 indexed entityId); + event ValidationUninstalled(address indexed module, uint32 indexed entityId, bool onUninstallSucceeded); /// @notice Install a module to the modular account. /// @param module The module to install. - /// @param manifestHash The hash of the module manifest. + /// @param manifest the manifest describing functions to install /// @param moduleInstallData Optional data to be decoded and used by the module to setup initial module data /// for the modular account. - /// @param dependencies The dependencies of the module, as described in the manifest. Each ModuleEntity - /// MUST be composed of an installed module's address and a function ID of its validation function. - function installModule( + function installExecution( address module, - bytes32 manifestHash, - bytes calldata moduleInstallData, - ModuleEntity[] calldata dependencies + ExecutionManifest calldata manifest, + bytes calldata moduleInstallData + ) external; + + /// @notice Installs a validation function across a set of execution selectors, and optionally mark it as a + /// global validation. + /// @dev This does not validate anything against the manifest - the caller must ensure validity. + /// @param validationConfig The validation function to install, along with configuration flags. + /// @param selectors The selectors to install the validation function for. + /// @param installData Optional data to be decoded and used by the module to setup initial module state. + /// @param hooks Optional hooks to install, associated with the validation function. These may be + /// pre-validation hooks or execution hooks. The expected format is a bytes26 HookConfig, followed by the + /// install data, if any. + function installValidation( + ValidationConfig validationConfig, + bytes4[] calldata selectors, + bytes calldata installData, + bytes[] calldata hooks + ) external; + + /// @notice Uninstall a validation function from a set of execution selectors. + /// @param validationFunction The validation function to uninstall. + /// @param uninstallData Optional data to be decoded and used by the module to clear module data for the + /// account. + /// @param hookUninstallData Optional data to be used by hooks for cleanup. If any are provided, the array must + /// be of a length equal to existing pre-validation hooks plus permission hooks. Hooks are indexed by + /// pre-validation hook order first, then permission hooks. + function uninstallValidation( + ModuleEntity validationFunction, + bytes calldata uninstallData, + bytes[] calldata hookUninstallData ) external; /// @notice Uninstall a module from the modular account. /// @param module The module to uninstall. - /// @param config An optional, implementation-specific field that accounts may use to ensure consistency - /// guarantees. + /// @param manifest the manifest describing functions to uninstall. /// @param moduleUninstallData Optional data to be decoded and used by the module to clear module data for the /// modular account. - function uninstallModule(address module, bytes calldata config, bytes calldata moduleUninstallData) external; + function uninstallExecution( + address module, + ExecutionManifest calldata manifest, + bytes calldata moduleUninstallData + ) external; } ``` @@ -158,8 +198,7 @@ struct Call { interface IStandardExecutor { /// @notice Standard execute method. - /// @dev If the target is a module, the call SHOULD revert. - /// @param target The target address for account to call. + /// @param target The target address for the account to call. /// @param value The value to send with the call. /// @param data The calldata for the call. /// @return The return data from the call. @@ -171,35 +210,12 @@ interface IStandardExecutor { /// @param calls The array of calls. /// @return An array containing the return data from the calls. function executeBatch(Call[] calldata calls) external payable returns (bytes[] memory); -} -``` - -#### `IModuleExecutor.sol` - -Execution interface for calls made from modules. Modular Smart Contract Accounts **MUST** implement this interface to support execution from modules. - -The `executeFromModuleExternal` function SHOULD check whether the call's target implements the `IModule` interface via ERC-165. -**If the target of `executeFromModuleExternal` function is a module, the call SHOULD revert.** - -This prevents accidental misconfiguration or misuse of modules (both installed and uninstalled). Installed modules MAY interact with other installed modules via the `executeFromModule` function. - -```solidity -interface IModuleExecutor { - /// @notice Execute a call from a module through the account. - /// @dev Permissions must be granted to the calling module for the call to go through. + /// @notice Execute a call using a specified runtime validation. /// @param data The calldata to send to the account. - /// @return The return data from the call. - function executeFromModule(bytes calldata data) external payable returns (bytes memory); - - /// @notice Execute a call from a module to a non-module address. - /// @dev If the target is a module, the call SHOULD revert. Permissions must be granted to the calling module - /// for the call to go through. - /// @param target The address to be called. - /// @param value The value to send with the call. - /// @param data The calldata to send to the target. - /// @return The return data from the call. - function executeFromModuleExternal(address target, uint256 value, bytes calldata data) + /// @param authorization The authorization data to use for the call. The first 24 bytes specifies which runtime + /// validation to use, and the rest is sent as a parameter to runtime validation. + function executeWithAuthorization(bytes calldata data, bytes calldata authorization) external payable returns (bytes memory); @@ -211,43 +227,50 @@ interface IModuleExecutor { Module inspection interface. Modular Smart Contract Accounts **MAY** implement this interface to support visibility in module configuration on-chain. ```solidity +// Represents data associated with a specifc function selector. +struct ExecutionDataView { + // The module that implements this execution function. + // If this is a native function, the address must remain address(0). + address module; + // Whether or not the function needs runtime validation, or can be called by anyone. The function can still be + // state changing if this flag is set to true. + // Note that even if this is set to true, user op validation will still be required, otherwise anyone could + // drain the account of native tokens by wasting gas. + bool isPublic; + // Whether or not a global validation function may be used to validate this function. + bool allowGlobalValidation; + // The execution hooks for this function selector. + HookConfig[] executionHooks; +} + +struct ValidationDataView { + // Whether or not this validation can be used as a global validation function. + bool isGlobal; + // Whether or not this validation is a signature validator. + bool isSignatureValidation; + // The pre validation hooks for this validation function. + ModuleEntity[] preValidationHooks; + // Permission hooks for this validation function. + HookConfig[] permissionHooks; + // The set of selectors that may be validated by this validation function. + bytes4[] selectors; +} + interface IAccountLoupe { - /// @notice Config for an execution function, given a selector. - struct ExecutionFunctionConfig { - address module; - ModuleEntity validationFunction; - } - - /// @notice Pre and post hooks for a given selector. - /// @dev It's possible for one of either `preExecHook` or `postExecHook` to be empty. - struct ExecutionHooks { - ModuleEntity hookFunction; - bool isPreHook; - bool isPostHook; - } - - /// @notice Get the validation functions and module address for a selector. + /// @notice Get the execution data for a selector. /// @dev If the selector is a native function, the module address will be the address of the account. - /// @param selector The selector to get the configuration for. - /// @return The configuration for this selector. - function getExecutionFunctionConfig(bytes4 selector) external view returns (ExecutionFunctionConfig memory); - - /// @notice Get the pre and post execution hooks for a selector. - /// @param selector The selector to get the hooks for. - /// @return The pre and post execution hooks for this selector. - function getExecutionHooks(bytes4 selector) external view returns (ExecutionHooks[] memory); - - /// @notice Get the pre user op and runtime validation hooks associated with a selector. - /// @param selector The selector to get the hooks for. - /// @return preValidationHooks The pre validation hooks for this selector. - function getPreValidationHooks(bytes4 selector) + /// @param selector The selector to get the data for. + /// @return ExecutionData The module address for this selector. + function getExecutionData(bytes4 selector) external view returns (ExecutionDataView memory); + + /// @notice Get the validation data for a validation. + /// @dev If the selector is a native function, the module address will be the address of the account. + /// @param validationFunction The validation function to get the data for. + /// @return ValidationData The module address for this selector. + function getValidationData(ModuleEntity validationFunction) external view - returns (ModuleEntity[] memory preValidationHooks); - - /// @notice Get an array of all installed modules. - /// @return The addresses of all installed modules. - function getInstalledModules() external view returns (address[] memory); + returns (ValidationDataView memory); } ``` @@ -256,77 +279,203 @@ interface IAccountLoupe { Module interface. Modules **MUST** implement this interface to support module management and interactions with MSCAs. ```solidity -interface IModule { +struct SelectorPermission { + bytes4 functionSelector; + string permissionDescription; +} + +/// @dev A struct holding fields to describe the module in a purely view context. Intended for front end clients. +struct ModuleMetadata { + // A human-readable name of the module. + string name; + // The version of the module, following the semantic versioning scheme. + string version; + // The author field SHOULD be a username representing the identity of the user or organization + // that created this module. + string author; + // String desciptions of the relative sensitivity of specific functions. The selectors MUST be selectors for + // functions implemented by this module. + SelectorPermission[] permissionDescriptors; + // A list of all ERC-7715 permission strings that the module could possibly use + string[] permissionRequest; +} + +interface IModule is IERC165 { /// @notice Initialize module data for the modular account. - /// @dev Called by the modular account during `installModule`. - /// @param data Optional bytes array to be decoded and used by the module to setup initial module data for the modular account. + /// @dev Called by the modular account during `installExecution`. + /// @param data Optional bytes array to be decoded and used by the module to setup initial module data for the + /// modular account. function onInstall(bytes calldata data) external; /// @notice Clear module data for the modular account. - /// @dev Called by the modular account during `uninstallModule`. - /// @param data Optional bytes array to be decoded and used by the module to clear module data for the modular account. + /// @dev Called by the modular account during `uninstallExecution`. + /// @param data Optional bytes array to be decoded and used by the module to clear module data for the modular + /// account. function onUninstall(bytes calldata data) external; - /// @notice Run the pre user operation validation hook specified by the `entityId`. - /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. - /// @param entityId An identifier that routes the call to different internal implementations, should there be more than one. + /// @notice Describe the metadata of the module. + /// @dev This metadata MUST stay constant over time. + /// @return A metadata struct describing the module. + function moduleMetadata() external pure returns (ModuleMetadata memory); +} +``` + +#### `IValidationModule.sol` + +```solidity +interface IValidationModule is IModule { + /// @notice Run the user operation validation function specified by the `entityId`. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param userOp The user operation. /// @param userOpHash The user operation hash. /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function preUserOpValidationHook(uint8 entityId, PackedUserOperation memory userOp, bytes32 userOpHash) external returns (uint256); + function validateUserOp(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) + external + returns (uint256); + + /// @notice Run the runtime validation function specified by the `entityId`. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param account the account to validate for. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. + /// @param sender The caller address. + /// @param value The call value. + /// @param data The calldata sent. + /// @param authorization Additional data for the validation function to use. + function validateRuntime( + address account, + uint32 entityId, + address sender, + uint256 value, + bytes calldata data, + bytes calldata authorization + ) external; + + /// @notice Validates a signature using ERC-1271. + /// @dev To indicate the entire call should revert, the function MUST revert. + /// @param account the account to validate for. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. + /// @param sender the address that sent the ERC-1271 request to the smart account + /// @param hash the hash of the ERC-1271 request + /// @param signature the signature of the ERC-1271 request + /// @return the ERC-1271 `MAGIC_VALUE` if the signature is valid, or 0xFFFFFFFF if invalid. + function validateSignature( + address account, + uint32 entityId, + address sender, + bytes32 hash, + bytes calldata signature + ) external view returns (bytes4); +} +``` + +#### `IValidationHookModule.sol` - /// @notice Run the user operation validationFunction specified by the `entityId`. - /// @param entityId An identifier that routes the call to different internal implementations, should there be - /// more than one. +```solidity +interface IValidationHookModule is IModule { + /// @notice Run the pre user operation validation hook specified by the `entityId`. + /// @dev Pre user operation validation hooks MUST NOT return an authorizer value other than 0 or 1. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param userOp The user operation. /// @param userOpHash The user operation hash. /// @return Packed validation data for validAfter (6 bytes), validUntil (6 bytes), and authorizer (20 bytes). - function userOpValidationFunction(uint8 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) + function preUserOpValidationHook(uint32 entityId, PackedUserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); /// @notice Run the pre runtime validation hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there be more than one. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param sender The caller address. /// @param value The call value. /// @param data The calldata sent. - function preRuntimeValidationHook(uint8 entityId, address sender, uint256 value, bytes calldata data) external; + function preRuntimeValidationHook( + uint32 entityId, + address sender, + uint256 value, + bytes calldata data, + bytes calldata authorization + ) external; - /// @notice Run the runtime validationFunction specified by the `entityId`. - /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there be - /// more than one. + // TODO: support this hook type within the account & in the manifest + + /// @notice Run the pre signature validation hook specified by the `entityId`. + /// @dev To indicate the call should revert, the function MUST revert. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param sender The caller address. - /// @param value The call value. - /// @param data The calldata sent. - function runtimeValidationFunction(uint8 entityId, address sender, uint256 value, bytes calldata data) - external; + /// @param hash The hash of the message being signed. + /// @param signature The signature of the message. + // function preSignatureValidationHook(uint32 entityId, address sender, bytes32 hash, bytes calldata + // signature) + // external + // view + // returns (bytes4); +} +``` + +#### `IExecutionModule.sol` + +```solidity +struct ManifestExecutionFunction { + // The selector to install + bytes4 executionSelector; + bool isPublic; + // If true, the function can be validated by a global validation function. + bool allowGlobalValidation; +} + +struct ManifestExecutionHook { + bytes4 executionSelector; + uint32 entityId; + bool isPreHook; + bool isPostHook; +} + +/// @dev A struct describing how the module should be installed on a modular account. +struct ExecutionManifest { + // Execution functions defined in this module to be installed on the MSCA. + ManifestExecutionFunction[] executionFunctions; + ManifestExecutionHook[] executionHooks; + // List of ERC-165 interface IDs to add to account to support introspection checks. This MUST NOT include + // IModule's interface ID. + bytes4[] interfaceIds; +} + +interface IExecutionModule is IModule { + /// @notice Describe the contents and intended configuration of the module. + /// @dev This manifest MUST stay constant over time. + /// @return A manifest describing the contents and intended configuration of the module. + function executionManifest() external pure returns (ExecutionManifest memory); +} +``` +#### `IExecutionHookModule.sol` + +```solidity +interface IExecutionHookModule is IModule { /// @notice Run the pre execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there be more than one. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param sender The caller address. /// @param value The call value. /// @param data The calldata sent. /// @return Context to pass to a post execution hook, if present. An empty bytes array MAY be returned. - function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) external returns (bytes memory); + function preExecutionHook(uint32 entityId, address sender, uint256 value, bytes calldata data) + external + returns (bytes memory); /// @notice Run the post execution hook specified by the `entityId`. /// @dev To indicate the entire call should revert, the function MUST revert. - /// @param entityId An identifier that routes the call to different internal implementations, should there be more than one. + /// @param entityId An identifier that routes the call to different internal implementations, should there + /// be more than one. /// @param preExecHookData The context returned by its associated pre execution hook. function postExecutionHook(uint32 entityId, bytes calldata preExecHookData) external; - - /// @notice Describe the contents and intended configuration of the module. - /// @dev This manifest MUST stay constant over time. - /// @return A manifest describing the contents and intended configuration of the module. - function moduleManifest() external pure returns (ModuleManifest memory); - - /// @notice Describe the metadata of the module. - /// @dev This metadata MUST stay constant over time. - /// @return A metadata struct describing the module. - function moduleMetadata() external pure returns (ModuleMetadata memory); } ``` @@ -344,7 +493,7 @@ enum ManifestAssociatedFunctionType { // on external validation functions. It MUST NOT depend on external hooks, or installation will fail. DEPENDENCY, // Resolves to a magic value to always bypass runtime validation for a given function. - // This is only assignable on runtime validation functions. If it were to be used on a user op validationFunction, + // This is only assignable on runtime validation functions. If it were to be used on a user op validation function, // it would risk burning gas from the account. When used as a hook in any hook location, it is equivalent to not // setting a hook and is therefore disallowed. RUNTIME_VALIDATION_ALWAYS_ALLOW, @@ -429,6 +578,12 @@ struct ModuleManifest { ### Expected behavior +#### Validations and their installation /uninstallation + +An account can have more than one validation installed. +An account can have the same validation module installed more than once. +The entityId of a validation installed on an account MUST be unique. + #### Responsibilties of `StandardExecutor` and `ModuleExecutor` `StandardExecutor` functions are used for open-ended calls to external addresses.