From a28bea764fb857d198178ec24413d096c731aaa9 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 27 Aug 2024 17:12:41 -0400 Subject: [PATCH 1/3] feat: add call flow spec for validation and execution --- standard/ERCs/erc-6900.md | 64 +++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 4968de1e..2fccc988 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -8,7 +8,7 @@ status: Draft type: Standards Track category: ERC created: 2023-04-18 -requires: 165, 4337 +requires: 165, 1271, 4337 --- ## Abstract @@ -68,6 +68,7 @@ Each step is modular, supporting different implementations, that allows for open - `IAccount.sol` from [ERC-4337](./eip-4337.md). - `IAccountExecute.sol` from [ERC-4337](./eip-4337.md). - `IModularAccount.sol` to support module management and usage, and account identification. +- The function `isValidSignature` from [ERC-1271](./eip-1271.md) **Modular Smart Contract Accounts** **MAY** implement @@ -471,18 +472,6 @@ An account can have the same validation module installed more than once. The entityId of a validation function installed on an account MUST be unique. Validation installation MAY be deferred until a later time, such as upon first use. -#### Responsibilties of `StandardExecutor` and `ModuleExecutor` - -`StandardExecutor` functions are used for open-ended calls to external addresses. - -`ModuleExecutor` functions are specifically used by modules to request the account to execute with account's context. Explicit permissions are required for modules to use `ModuleExecutor`. - -The following behavior MUST be followed: - -- `StandardExecutor` can NOT call module execution functions and/or `ModuleExecutor`. This is guaranteed by checking whether the call's target implements the `IModule` interface via ERC-165 as required. -- `StandardExecutor` can NOT be called by module execution functions and/or `ModuleExecutor`. -- Module execution functions MUST NOT request access to `StandardExecutor`, they MAY request access to `ModuleExecutor`. - #### Calls to `installModule` The function `installModule` accepts 3 parameters: the address of the module to install, the Keccak-256 hash of the module's manifest, ABI-encoded data to pass to the module's `onInstall` callback. @@ -538,36 +527,51 @@ Finally, the function MUST emit the event `ModuleUninstalled` with the module's > **⚠️ Incorrectly uninstalled modules can prevent uninstalls of their dependencies. Therefore, some form of validation that the uninstall step completely and correctly removes the module and its usage of dependencies is required.** -#### Calls to `validateUserOp` +### Validation Call Flow + +Modular accounts support three different calls flows for validation: user op validation, runtime validation, and signature validation. User op validation happens within the account's implementation of the function `validateUserOp`, defined in the ERC-4337 interface `IAccount`. Runtime validation happens through the dispatcher function `executeWithAuthorization`, or when using direct call validation. Signature validation happens within the account's implementation of the function `isValidSignature`, defined in ERC-1271. + +For each of these validation types, an account implementation may specify its own format for selecting which validation function to use, as well as any per-hook data for validation hooks. + +Within the implementation of each type of validation function, the modular account MUST check that the provided validation function applies to the given function selector intended to be run. Then, the account MUST execute all pre validation hooks of the corresponding type associated with the validation function in use. These hooks MUST be executed in the order specified during installation. After the execution of pre validation hooks, the account MUST invoke the validation function of the corresponding type. If any pre validation hooks or the validation function revert, the account MUST revert. It SHOULD include the module's revert data within its revert data. + +The account MUST define a way to pass data separately for each validation hook and the validation function itself. This data MUST be sent as the `userOp.signature` field for user op validation, the `authorization` field for runtime validation, and the `signature` field for signature validation. + +The result of user op validation SHOULD be the intersection of time bounds returned by the pre validation hooks and the validation function. If any validation hooks or the validation functions returns a value of `1` for the authorizer field, indicating a signature verification failure by the ERC-4337 standard, the account MUST return a value of `1` for the authorizer portion of the validation data. + +The set of validation hooks run MUST be the hooks specified by account state at the start of validation. In other words, if the set of applicable hooks changes during validation, the original set of hooks MUST still run, and only future invocations of the same validation should reflect the changed set of hooks. + +#### Checking Validation Applicability + +To enforce module permission isolation, the modular account MUST check validation function applicability as part of each validation function implementation. + +User op validation and runtime validation functions have a configurable range of applicability to functions on the account. This can be configured with selectors installed to a validation. Alternatively, a validation installation may specify the `isGlobal` flag as true, which means the account MUST consider it applicable to any module execution function with the `allowGlobalValidation` flag set to true, or for any account native function that the account MAY allow for global validation. -When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validation function associated to the function selector in the first four bytes of `userOp.callData`. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. +If the selector being checked is `execute` or `executeBatch`, the modular account MUST perform additional checking. If the target of `execute` is the modular account's own address, validation MUST revert, to prevent privilege escalation. If the target of any `Call` within `executeBatch` is the account, the selector of the `Call` MUST also be checked to apply for the given validation. If the batch self-call's selector is again either `execute` or `executeBatch`, validation MUST revert, to prevent privilege escalation without requiring arbitrary-depth calldata decoding. -If the function selector has associated pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer call MUST revert. If the selector has any pre execution hooks set to `PRE_HOOK_ALWAYS_DENY`, the call MUST revert. If any return an `authorizer` value other than 0 or 1, execution MUST revert. If any return an `authorizer` value of 1, indicating an invalid signature, the returned validation data of the outer call MUST also be 1. If any return time-bounded validation by specifying either a `validUntil` or `validBefore` value, the resulting validation data MUST be the intersection of all time bounds provided. +Installed validations have two flag variables indicating what they may be used for. If a validation is attempted to be used for user op validation and the flag `isUserOpValidation` is set to false, validation MUST revert. If the validation is attempted to be used for signature validation and the flag `isSignatureValidation` is set to false, validation MUST revert. -Then, the modular account MUST execute the validation function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validation function MUST be updated, if necessary, by the return values of any pre user operation validation hooks, then returned by `validateUserOp`. +#### Direct Call Validation. -#### Calls to execution functions +If a validation function is installed with the entity ID of `0xffffffff`, it may be used as direct call validation. This occurs when a module or other address calls a function on the modular account, without wrapping its call in the dispatcher function `executeWithAuthorization` to use as a selection mechanism for a runtime validation function. -When a function other than a native function is called on an modular account, it MUST find the module configuration for the corresponding selector added via module installation. If no corresponding module is found, the modular account MUST revert. Otherwise, the following steps MUST be performed. +To implement direct call validation, the modular account MUST treat direct function calls that are not from the modular account itself or the EntryPoint as an attempt to validate using the caller's address and the entity ID of `0xffffffff`. If such a validation function is installed, and applies to the function intended to be called, the modular account MUST allow it to continue, without performing runtime validation. Any pre validation hooks and execution hooks installed to this validation function MUST still run. -Additionally, when the modular account natively implements functions in `IModuleManager` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other native functions MAY perform these steps. +### Execution Call Flow -The steps to perform are: +For all non-view functions within `IModularAccount` except `executeWithAuthorization`, all module-defined execution functions, and any additional native functions that the modular account MAY wish to include, the modular account MUST adhere to these steps during execution: -- If the call is not from the `EntryPoint`, then find an associated runtime validation function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validation function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function's calldata as parameters. If any of these functions revert, execution MUST revert. If any pre execution hooks are set to `PRE_HOOK_ALWAYS_DENY`, execution MUST revert. If the validation function is set to `RUNTIME_VALIDATION_ALWAYS_ALLOW`, the runtime validation function MUST be bypassed. -- If there are pre execution hooks defined for the execution function, execute those hooks with the caller, value, and execution function's calldata as parameters. If any of these hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate pre execution hooks (i.e., hooks with identical `ModuleEntity`s), run the hook only once. If any of these functions revert, execution MUST revert. -- Run the execution function. -- If any post execution hooks are defined, run the functions. If a pre execution hook returned data to the account, that data MUST be passed as a parameter to the associated post execution hook. The operation MUST be done with the `call` opcode. If there are duplicate post execution hooks, run them once for each unique associated pre execution hook. For post execution hooks without an associated pre execution hook, run the hook only once. If any of these functions revert, execution MUST revert. +If the caller is not the EntryPoint or the account, the account MUST check access control for direct call validation. -The set of hooks run for a given execution function MUST be the hooks specified by account state at the start of the execution phase. This is relevant for functions like `installModule` and `uninstallModule`, which modify the account state, and possibly other execution or native functions as well. +Prior to running the target function, the modular account MUST run all pre execution hooks that apply for the current function call. Pre execution hooks apply if they have been installed to the currently running function selector, or if they are installed to the validation function that was used for the current execution. Pre execution hooks MUST run validation-associated hooks first, in the order they were installed, then selector-associated hooks second, in the order they were installed. -#### Calls made from modules +Next, the modular account MUST run the target function, either an account native function or a module-defined execution function. -Modules MAY interact with other modules and external addresses through the modular account using the functions defined in the `IModuleExecutor` interface. These functions MAY be called without a defined validation function, but the modular account MUST enforce these checks and behaviors: +After the execution of the target function, the modular account MUST run any post execution hooks. These MUST be run in the reverse order of the pre execution hooks. If a hook is defined to be both a pre and a post execution hook, and the pre execution hook returned a non-empty `bytes` value to the account, the account MUST pass that data to the post execution hook. -The `executeFromModule` function MUST allow modules to call execution functions installed by modules on the modular account. Hooks matching the function selector provided in `data` MUST be called. If the calling module's manifest did not include the provided function selector within `permittedExecutionSelectors` at the time of installation, execution MUST revert. +The set of hooks run for a given target function MUST be the hooks specified by account state at the start of the execution phase. In other words, if the set of applicable hooks changes during execution, the original set of hooks MUST still run, and only future invocations of the same target function should reflect the changed set of hooks. -The `executeFromModuleExternal` function MUST allow modules to call external addresses as specified by its parameters on behalf of the modular account. If the calling module's manifest did not explicitly allow the external call within `permittedExternalCalls` at the time of installation, execution MUST revert. +Module execution functions where the field `isPublic` is set to true, or native functions without access control, SHOULD omit the runtime validation step, including any runtime validation hooks. Native functions without access control MAY also omit running execution hooks. ### Extension From 97e67a77ca5dd63548ee2e8f39be1fb81ee5778b Mon Sep 17 00:00:00 2001 From: adam Date: Wed, 28 Aug 2024 15:59:14 -0400 Subject: [PATCH 2/3] feat: specify validation-associated exec hooks as permission hooks --- standard/ERCs/erc-6900.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 2fccc988..5831fde0 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -563,7 +563,7 @@ For all non-view functions within `IModularAccount` except `executeWithAuthoriza If the caller is not the EntryPoint or the account, the account MUST check access control for direct call validation. -Prior to running the target function, the modular account MUST run all pre execution hooks that apply for the current function call. Pre execution hooks apply if they have been installed to the currently running function selector, or if they are installed to the validation function that was used for the current execution. Pre execution hooks MUST run validation-associated hooks first, in the order they were installed, then selector-associated hooks second, in the order they were installed. +Prior to running the target function, the modular account MUST run all pre execution hooks that apply for the current function call. Pre execution hooks apply if they have been installed to the currently running function selector, or if they are installed as a permission hook to the validation function that was used for the current execution. Pre execution hooks MUST run validation-associated hooks first, in the order they were installed, then selector-associated hooks second, in the order they were installed. Next, the modular account MUST run the target function, either an account native function or a module-defined execution function. From ca981c8c7911502ea32a10ac0ab1b82d12a621b4 Mon Sep 17 00:00:00 2001 From: adam Date: Thu, 29 Aug 2024 11:56:24 -0400 Subject: [PATCH 3/3] fix: review fixes --- standard/ERCs/erc-6900.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/standard/ERCs/erc-6900.md b/standard/ERCs/erc-6900.md index 5831fde0..ade56652 100644 --- a/standard/ERCs/erc-6900.md +++ b/standard/ERCs/erc-6900.md @@ -533,11 +533,11 @@ Modular accounts support three different calls flows for validation: user op val For each of these validation types, an account implementation may specify its own format for selecting which validation function to use, as well as any per-hook data for validation hooks. -Within the implementation of each type of validation function, the modular account MUST check that the provided validation function applies to the given function selector intended to be run. Then, the account MUST execute all pre validation hooks of the corresponding type associated with the validation function in use. These hooks MUST be executed in the order specified during installation. After the execution of pre validation hooks, the account MUST invoke the validation function of the corresponding type. If any pre validation hooks or the validation function revert, the account MUST revert. It SHOULD include the module's revert data within its revert data. +Within the implementation of each type of validation function, the modular account MUST check that the provided validation function applies to the given function selector intended to be run. Then, the account MUST execute all validation hooks of the corresponding type associated with the validation function in use. These hooks MUST be executed in the order specified during installation. After the execution of validation hooks, the account MUST invoke the validation function of the corresponding type. If any validation hooks or the validation function revert, the account MUST revert. It SHOULD include the module's revert data within its revert data. The account MUST define a way to pass data separately for each validation hook and the validation function itself. This data MUST be sent as the `userOp.signature` field for user op validation, the `authorization` field for runtime validation, and the `signature` field for signature validation. -The result of user op validation SHOULD be the intersection of time bounds returned by the pre validation hooks and the validation function. If any validation hooks or the validation functions returns a value of `1` for the authorizer field, indicating a signature verification failure by the ERC-4337 standard, the account MUST return a value of `1` for the authorizer portion of the validation data. +The result of user op validation SHOULD be the intersection of time bounds returned by the validation hooks and the validation function. If any validation hooks or the validation functions returns a value of `1` for the authorizer field, indicating a signature verification failure by the ERC-4337 standard, the account MUST return a value of `1` for the authorizer portion of the validation data. The set of validation hooks run MUST be the hooks specified by account state at the start of validation. In other words, if the set of applicable hooks changes during validation, the original set of hooks MUST still run, and only future invocations of the same validation should reflect the changed set of hooks. @@ -547,7 +547,7 @@ To enforce module permission isolation, the modular account MUST check validatio User op validation and runtime validation functions have a configurable range of applicability to functions on the account. This can be configured with selectors installed to a validation. Alternatively, a validation installation may specify the `isGlobal` flag as true, which means the account MUST consider it applicable to any module execution function with the `allowGlobalValidation` flag set to true, or for any account native function that the account MAY allow for global validation. -If the selector being checked is `execute` or `executeBatch`, the modular account MUST perform additional checking. If the target of `execute` is the modular account's own address, validation MUST revert, to prevent privilege escalation. If the target of any `Call` within `executeBatch` is the account, the selector of the `Call` MUST also be checked to apply for the given validation. If the batch self-call's selector is again either `execute` or `executeBatch`, validation MUST revert, to prevent privilege escalation without requiring arbitrary-depth calldata decoding. +If the selector being checked is `execute` or `executeBatch`, the modular account MUST perform additional checking. If the target of `execute` is the modular account's own address, or if the target of any `Call` within `executeBatch` is the account, validation MUST either revert, or check that validation applies to the selector(s) being called. Installed validations have two flag variables indicating what they may be used for. If a validation is attempted to be used for user op validation and the flag `isUserOpValidation` is set to false, validation MUST revert. If the validation is attempted to be used for signature validation and the flag `isSignatureValidation` is set to false, validation MUST revert.