Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update EIP-6900: Spec update 2 #7247

Merged
merged 13 commits into from Jul 3, 2023
100 changes: 41 additions & 59 deletions EIPS/eip-6900.md
Expand Up @@ -52,7 +52,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
- **Execution functions** execute any custom logic allowed by the account.
- **Hooks** execute custom logic and checks before and/or after an execution function.
- A **validation function** is a function that validates authentication and authorization of a caller to the account. There are two types of validation functions:
- **User Operation Validator** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. The function may have any function name, and MUST take in the parameters `(UserOperation calldata, bytes32)`, representing the user operation and user operation hash. It MUST return `(uint256)`, representing packed validation data for `authorizer`, `validUntil`, and `validAfter`.
- **User Operation Validator** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation.
- **Runtime Validator** functions run before an execution function when not called via a user operation, and enforce checks. Common checks include allowing execution only by an owner.
- An **execution function** is a smart contract function that defines the main execution step of a function for a **modular account**.
- The **standard execute functions** are two specific execution functions that are implemented natively by the modular account, and not on a plugin. These allow for open-ended execution.
Expand All @@ -64,6 +64,16 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
- **Associated function** refers to either a validation function or a hook.
- A **plugin** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks.

### Plugin Function Signatures

The plugin function types specified above MUST have the following function signatures:

- **User Operation Validator** functions and **Pre User Operation Validation Hooks**: `function(UserOperation calldata, bytes32) external returns (uint256)`. The parameters sent by the account MUST be the user operation and user operation hash. The return value MUST represent packed validation data for `authorizer`, `validUntil`, and `validAfter`. The packing order MUST be `validAfter` in the first 6 bytes, `validUntil` in the next 6 bytes, and `authorizer` in the last 20 bytes. **Pre User Operation Validation Hooks** MUST NOT return an `authorizer` value other than 0 or 1.
- **Runtime Validator** functions and **Pre Runtime Validation Hooks**: `function(address, uint256, bytes calldata) external`. The parameters sent by the account MUST be the caller address, the call value, and the calldata sent. To indicate that the entire call should revert, the function MUST revert.
- **Pre Execution Hooks**: `function(address, uint256, bytes calldata) external returns (bytes memory)`. The parameters sent by the account MUST be the caller address, the call value, and the calldata sent. To indicate that the entire call should revert, the function MUST revert. The return value MUST contain any context to pass to a **Post Execution Hook**, if present. An empty bytes array MAY be returned.
- **Post Execution Hooks**: `function(bytes calldata) external`. The parameter sent by the account MUST be the context returned by the associated **Pre Execution Hook**. To indicate that the entire call should revert, the function MUST revert.
- **Execution** functions may have any function signature.

### Overview

A modular account handles two kinds of calls: either from the `Entrypoint` through ERC-4337, or through direct calls from externally owned accounts (EOAs) and other smart contracts. This standard supports both use cases.
Expand All @@ -80,12 +90,18 @@ Modular Smart Contract Accounts MUST implement the `IAccount` interface from [ER

#### Common types

The following types are used by both of the following interfaces. Implementors MAY use the elementary value type instead of the type alias.
The following types are common across the following interfaces. Implementors MAY use the elementary value type instead of the type alias.

```solidity
type FunctionReference is bytes24;

type HookGroupId is uint32;

struct Execution {
address target;
uint256 value;
bytes data;
}
```

Variables of the type `FunctionReference` MUST be interpreted as an address in the first 20 bytes and a function selector in the remaining 4 bytes.
Expand Down Expand Up @@ -146,8 +162,7 @@ interface IPluginUpdate {
ExecutionUpdate[] calldata executionUpdates,
HookUpdate[] calldata hookUpdates,
HookGroupUpdate[] calldata hookGroupUpdates,
address init,
bytes calldata callData
Execution[] calldata initializationCalls
) external;
}
```
Expand Down Expand Up @@ -192,20 +207,7 @@ Standard execution interface. Modular Smart Contract Accounts MUST implement thi
```solidity
interface IStandardExecutor {

enum ExecutionMode {
CALL,
DELEGATECALL,
STATICCALL
}

struct Execution {
address target;
uint256 value;
bytes data;
ExecutionMode mode;
}

function execute(address dest, uint256 value, bytes calldata func, ExecutionMode mode, FunctionReference validator)
function execute(address target, uint256 value, bytes calldata data, FunctionReference validator)
external
payable;

Expand All @@ -217,79 +219,59 @@ interface IStandardExecutor {

#### Calls to `updatePlugins`

The function `updatePlugins` takes in arrays of execution updates, hook updates, and hook group updates to perform. It also takes in an optional initialization function. The function MUST perform the update operation sequentially, then, if the address provided in `init` is not `address(0)`, MUST execute `init` with the calldata `callData` through a `delegatecall`.
The function `updatePlugins` takes in arrays of execution updates, hook updates, and hook group updates to perform. It also takes in an optional array of initialization calls. The function MUST perform the update operation sequentially. Then, for each sequential member of the `initializationCalls` array, the MSCA MUST perform a `call` operation towards the specified `target` address with the specified `value` and `callData`.

> **⚠️ The ability to update a plugin is very powerful. The security of the updatePlugins determines the security of the account. It is critical for modular account implementers to make sure updatePlugins has the proper security consideration and access control in place.**

#### Calls to `validateUserOp`

When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validator defined for the selector in `userOp.callData`, which is in the first four bytes. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. Otherwise, the MSCA MUST execute the validator function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validator MUST be returned by `validateUserOp`.
When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validator defined for the selector in `userOp.callData`, which is in the first four bytes. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert.

If the execution selector has associated hook groups with pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer 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.

If the execution selector has associated hook groups with pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert or return false, the outer call MUST revert.
If the call is to a standard execution function, then the modular account MUST verify that the provided `validator` in calldata has previously been associated with either of the standard execution functions. If it was previously added, the specified user operation validator MUST be run.

If the call is to a standard execution function, then the modular account must verify that the provided `validator` in calldata has previously been associated with either of the standard execution functions. If it was previously added, the specified user operation validator MUST be run.
Then, the MSCA MUST execute the validator function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validator MUST be updated, if necessary, by the return values of any pre user operation validation hooks, then returned by `validateUserOp`.

#### Calls to execution functions

When a function other than a natively defined function is called on an MSCA, it MUST find the plugin configuration for the corresponding selector added via `updatePlugins`. If no corresponding plugin is found, the MSCA MUST revert. Otherwise, the MSCA MUST perform the following steps:
When a function other than a natively defined function is called on an MSCA, it MUST find the plugin configuration for the corresponding selector added via `updatePlugins`. If no corresponding plugin is found, the MSCA MUST revert. Otherwise, the following steps MUST be performed.

- If the call is not from the `EntryPoint`, then find an associated `runtimeValidator` function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validator 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, or return a boolean false, execution MUST revert.
- If there are pre execution hooks defined in the associated hook groups of the execution function, execute those hooks with the caller, value, and execution function’s calldata as parameters. If any of thee hooks returns data, it MUST be preserved until the call to the post execution hook. The operation must be done with the `call` opcode.
Alternatively, when the modular account natively implements functions in `IPluginUpdate` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other natively implemented functions MAY perform these steps.

The steps to perform are:

- If the call is not from the `EntryPoint`, then find an associated runtime validator function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validator 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 there are pre execution hooks defined in the associated hook groups of the execution function, execute those hooks with the caller, value, and execution function’s calldata as parameters. If any of thee 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 any of these functions revert, execution MUST revert.
- Run the execution function.
- If any associated post execution hooks are defined, run the functions. If a pre execution hook in the same hook group returned data to the account, that data MUST be passed as a parameter to the post execution hook. The operation must be done with the `call` opcode.
- If any associated post execution hooks are defined, run the functions. If a pre execution hook in the same hook group returned data to the account, that data MUST be passed as a parameter to the post execution hook. The operation MUST be done with the `call` opcode. If any of these functions revert, execution MUST revert.

> **⚠️ If the execution function does not have a definition for either pre runtime validation hooks, pre execution hooks or post execution hooks, the undefined functions will be skipped. The execution function will be run and it may change account state.**

#### Plugin update operations

When `updatePlugins` is called with `PluginAction.ADD`, the following MUST occur:

- Each execution selector must be added as a valid execution function, with the contract specified in `pluginAddress` as implementation contract to call to.
- Each validator function reference must be added to their parent execution function in the role specified by `validatorType`.
- Each hook update must add the hook type specified by `hookType` to the group specified by `hookGroupId`. If that hook type is already defined, the update MUST revert.
- Each hook group update must add that hook group to the execution selectors specified by `executionSelectors`.
- Each execution selector MUST be added as a valid execution function, with the contract specified in `pluginAddress` as implementation contract to call to.
- Each validator function reference MUST be added to their parent execution function in the role specified by `validatorType`.
- Each hook update MUST add the hook type specified by `hookType` to the group specified by `hookGroupId`. If that hook type is already defined, the update MUST revert.
- Each hook group update MUST add that hook group to the execution selectors specified by `executionSelectors`.

Execution function selectors MUST be unique when added.

When `updatePlugins` is called with `PluginAction.REPLACE`, each each execution selector or function reference MUST override any previous definition for said function. Existing associated functions not specified in this operation MUST NOT be modified.

When `updatePlugins` is called with `PluginAction.REMOVE`, execution function definitions and associated function definitions MUST be removed.

#### Plugin Functions

Execution functions may have any function signature, but must be unique for the account. When added to an MSCA via `updatePlugins`, the function selector of the execution function will be mapped from the modular account to the plugin.

User Operation Validation functions may have any function name, and MUST take in the parameters `(UserOperation calldata, bytes32)`, representing the user operation and the user operation hash. The functions MUST return `(uint256)`, representing packed validation data for `authorizer`, `validUntil`, and `validAfter`.

Here is an example function signature of a conformant user operation validator function:

```solidity
function validateSignature(UserOperation calldata userOp, bytes32 userOpHash) external returns (uint256 validationData);
```

Runtime Validation Functions may have any function name, and MUST take in the parameters `(address, uint256, bytes calldata)`.

Here is an example function signature of a conformant runtime validator function:

```solidity
function validateOwnership(address caller, uint256 value, bytes calldata) external;
```

Hooks MAY have any function name.
The pre user operation validation hook functions MUST take in the parameters `(bytes calldata callData, bytes calldata paymasterAndData)` and return `(bool)`.
The pre runtime validation hook functions MUST take in the parameters `(bytes calldata)` and return `(bool)`.
The pre execution hook functions MUST take in the parameters `(address, uint256, bytes calldata)` and return `(bytes calldata)`.
The post execution hook functions MUST take in the parameters `(bytes calldata)`.
When calling `updatePlugins`, the account MUST run the validators and hooks as they are specified before the operation is complete. Notably, the post execution hooks defined prior to the update must run after the update.

## Rationale

ERC-4337 compatible accounts must implement the `IAccount` interface, which consists of only one method that bundles validation with execution: `validateUserOp`. A primary design rationale for this proposal is to extend the possible functions for a smart contract account beyond this single method by unbundling these and other functions, while retaining the benefits of account abstraction.

The diamond pattern of ERC-2535 is the logical starting point for achieving this extension into multiple functionality, given its suitability for implementing multiple execution calls to ERC-4337 compatible accounts from EntryPoint. It also meets our other primary design rationale of generalizability to calls to EOA/SC accounts. However, a strict diamond pattern is constrained by its inability to customize validation schemes and other logic linked to specific execution functions in the context of `validateUserOp`.

This proposal includes several interfaces that build on ERC-4337 and is ispired by ERC-2535. First, we standardize a set of modular plugins that allow smart contract developers greater flexibility in bundling validation, execution and hook logic. We also propose interfaces like `IPluginUpdate` that take inspiration from the diamond standard, and provide methods for updating and querying execution functions, validation schemes, and hooks.
The function routing pattern of ERC-2535 is the logical starting point for achieving this extension into multi-functional accounts. It also meets our other primary design rationale of generalizing execution calls across multiple implementing contracts. However, a strict diamond pattern is constrained by its inability to customize validation schemes for specific execution functions in the context of `validateUserOp`, and its requirement of `delegatecall`.

The diamond standard's interfaces are not required or enforced by this standard. An MSCA implementation is considered compliant with this standard as long as it satisfies the interface requirements and expected behaviors.
This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular plugins that allow smart contract developers greater flexibility in bundling validation, execution and hook logic. We also propose interfaces like `IPluginUpdate` and `IPluginLoupe` that take inspiration from the diamond standard, and provide methods for updating and querying execution functions, validation functions, and hooks.

## Backwards Compatibility

Expand Down
5 changes: 3 additions & 2 deletions assets/eip-6900/MSCA_Two_Call_Paths_Diagram.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.