-
Notifications
You must be signed in to change notification settings - Fork 550
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
590 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
--- | ||
eip: 7579 | ||
title: Minimal Modular Smart Accounts | ||
description: Modular smart account interfaces and behavior for interoperability with minimal standardization for accounts and modules | ||
author: zeroknots (@zeroknots), Konrad Kopp (@kopy-kat), Taek Lee (@leekt), Fil Makarov (@filmakarov), Elim Poon (@yaonam), Lyu Min (@rockmin216) | ||
discussions-to: https://ethereum-magicians.org/t/erc-7579-minimal-modular-smart-accounts/17336 | ||
status: Draft | ||
type: Standards Track | ||
category: ERC | ||
created: 2023-12-14 | ||
requires: 165, 1271, 2771, 4337 | ||
--- | ||
|
||
## Abstract | ||
|
||
This proposal outlines the minimally required interfaces and behavior for modular smart accounts and modules to ensure interoperability across implementations. For accounts, the standard specifies execution, config and fallback interfaces as well as compliance to [ERC-165](./eip-165.md) and [ERC-1271](./eip-1271.md). For modules, the standard specifies a core interface, module types and type-specific interfaces. | ||
|
||
## Motivation | ||
|
||
Contract accounts are gaining adoption with many accounts being built using a modular architecture. These modular contract accounts (hereafter smart accounts) move functionality into external contracts (modules) in order to increase the speed and potential of innovation, to future-proof themselves and to allow customizability by developers and users. However, currently these smart accounts are built in vastly different ways, creating module fragmentation and vendor lock-in. There are several reasons for why standardizing smart accounts is very beneficial to the ecosystem, including: | ||
|
||
- Interoperability for modules to be used across different smart accounts | ||
- Interoperability for smart accounts to be used across different wallet applications and sdks | ||
- Preventing significant vendor lock-in for smart account users | ||
|
||
However, it is highly important that this standardization is done with minimal impact on the implementation logic of the accounts, so that smart account vendors can continue to innovate, while also allowing a flourishing, multi-account-compatible module ecosystem. As a result, the goal of this standard is to define the smart account and module interfaces and behavior that is as minimal as possible while ensuring interoperability between accounts and modules. | ||
|
||
## Specification | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
|
||
### Definitions | ||
|
||
- **Smart account** - An [ERC-4337](./eip-4337.md) compliant smart contract account that has a modular architecture. | ||
- **Module** - A smart contract with self-contained smart account functionality. | ||
- Validator: A module used during the ERC-4337 validation flow to determine if a `UserOperation` is valid. | ||
- Executor: A module that can execute transactions on behalf of the smart account via a callback. | ||
- Fallback Handler: A module that can extend the fallback functionality of a smart account. | ||
- **Entrypoint** - A trusted singleton contract according to ERC-4337 specifications. | ||
|
||
### Account | ||
|
||
#### Validation | ||
|
||
This standard does not dictate how validator selection is implemented. However, should a smart account encode validator selection mechanisms in ERC-4337 `UserOperation` fields (i.e. `userOp.signature`), the smart account MUST sanitize the affected values before invoking the validator. | ||
|
||
The smart account's `validateUserOp` function SHOULD return the return value of the validator. | ||
|
||
#### Execution Behavior | ||
|
||
To comply with this standard, smart accounts MUST implement the two interfaces below. If an account implementation elects to not support any of the execution methods, it MUST implement the function, but revert instead of complying with the specifications below. This is in order to avoid unpredictable behavior with fallbacks. | ||
|
||
```solidity | ||
interface IExecution { | ||
/** | ||
* @dev packing batched transactions in a struct saves gas on both ERC-4337 and Executor Module flows | ||
*/ | ||
struct Execution { | ||
address target; | ||
uint256 value; | ||
bytes callData; | ||
} | ||
/** | ||
* MUST execute a `call` to the target with the provided data and value | ||
* MUST allow ERC-4337 Entrypoint to be the sender and MAY allow `msg.sender == address(this)` | ||
* MUST revert if the call was not successful | ||
*/ | ||
function execute(address target, uint256 value, bytes data) external returns (bytes memory result); | ||
/** | ||
* MUST execute a `call` to all the targets with the provided data and value for each target | ||
* MUST allow ERC-4337 Entrypoint to be the sender and MAY allow `msg.sender == address(this)` | ||
* MUST revert if any call was not successful | ||
*/ | ||
function executeBatch(Execution[] calldata executions) external returns (bytes memory result); | ||
/** | ||
* MUST execute a `call` to the target with the provided data and value | ||
* MUST only allow installed executors to call this function | ||
* MUST revert if the call was not successful | ||
*/ | ||
function executeFromExecutor(address target, uint256 value, bytes data) external returns (bytes memory result); | ||
/** | ||
* MUST execute a `call` to all the targets with the provided data and value for each target | ||
* MUST only allow installed executors to call this function | ||
* MUST revert if any call was not successful | ||
*/ | ||
function executeBatchFromExecutor(Execution[] calldata executions) external returns (bytes memory result); | ||
} | ||
/** | ||
* @dev implementing delegatecall execution on a smart account must be considered carefully and is not recommended in most cases | ||
*/ | ||
interface IExecutionUnsafe { | ||
/** | ||
* MUST execute a `delegatecall` to the target with the provided data | ||
* MUST allow ERC-4337 Entrypoint to be the sender and MAY allow `msg.sender == address(this)` | ||
* MUST revert if the call was not successful | ||
*/ | ||
function executeDelegateCall(address target, bytes data) external returns (bytes memory result); | ||
/** | ||
* MUST execute a `delegatecall` to the target with the provided data and value | ||
* MUST only allow installed executors to call this function | ||
* MUST revert if the call was not successful | ||
*/ | ||
function executeDelegateCallFromExecutor(address target, bytes data) external returns (bytes memory result); | ||
} | ||
``` | ||
|
||
#### Account configurations | ||
|
||
To comply with this standard, smart accounts MUST implement the entire interface below. If an account implementation elects to not support any of the configuration methods, it MUST revert, in order to avoid unpredictable behavior with fallbacks. | ||
|
||
When installing or uninstalling a module on a smart account, the smart account: | ||
|
||
- MUST enforce authorization control on the relevant install or uninstall function for the module type | ||
- MUST call the relevant `onInstall` or `onUninstall` function on the module | ||
- MUST pass the sanitized initialisation data to the module | ||
- MUST emit the relevant event for the module type | ||
|
||
When storing an installed module, the smart account MUST ensure that there is a way to differentiate between module types. For example, the smart account should be able to implement access control that only allows installed executors, but not other installed modules, to call the `executeFromExecutor` function. | ||
|
||
```solidity | ||
interface IAccountConfig { | ||
// VALIDATORS | ||
// Functions | ||
function installValidator(address validator, bytes calldata data) external; | ||
function uninstallValidator(address validator, bytes calldata data) external; | ||
function isValidatorInstalled(address validator) external view returns (bool); | ||
// Events | ||
event InstallValidator(address validator); | ||
event UninstallValidator(address validator); | ||
// EXECUTORS | ||
// Functions | ||
function installExecutor(address executor, bytes calldata data) external; | ||
function uninstallExecutor(address executor, bytes calldata data) external; | ||
function isExecutorInstalled(address executor) external view returns (bool); | ||
// Events | ||
event InstallExecutor(address executor); | ||
event UninstallExecutor(address executor); | ||
// FALLBACK HANDLERS | ||
// Functions | ||
function installFallback(address fallbackHandler, bytes calldata data) external; | ||
function uninstallFallback(address fallbackHandler, bytes calldata data) external; | ||
function isFallbackInstalled(address fallbackHandler) external view returns (bool); | ||
// Events | ||
event InstallFallbackHandler(address fallbackHandler); | ||
event UninstallFallbackHandler(address fallbackHandler); | ||
} | ||
``` | ||
|
||
#### Hooks | ||
|
||
Hooks are an OPTIONAL extension of this standard. Smart accounts MAY use hooks to execute custom logic and checks before and/or after the smart accounts performs a single or batched execution. | ||
|
||
To comply with this OPTIONAL extension, a smart account MUST implement the entire interface below and it: | ||
|
||
- MUST enforce authorization control on the relevant install or uninstall function for hooks | ||
- MUST call the `onInstall` or `onUninstall` function on the module when installing or uninstalling a hook | ||
- MUST pass the sanitized initialisation data to the module when installing or uninstalling a hook | ||
- MUST emit the relevant event for the hook | ||
- MUST call the `preCheck` function on a single and batched execution and on every install function | ||
- MAY call the `preCheck` function on uninstall functions | ||
- MUST call the `postCheck` function after a single or batched execution as well as every install function | ||
- MAY call the `postCheck` function on uninstall functions | ||
|
||
```solidity | ||
interface IAccountConfig_Hook { | ||
// HOOKS | ||
// Functions | ||
function installHook(address hook, bytes calldata data) external; | ||
function uninstallHook(address hook, bytes calldata data) external; | ||
function isHookInstalled(address hook) external view returns (bool); | ||
// Events | ||
event InstallHook(address hook); | ||
event UninstallHook(address hook); | ||
} | ||
``` | ||
|
||
#### ERC-1271 Forwarding | ||
|
||
The smart account MUST implement the ERC-1271 interface. The `isValidSignature` function calls MAY be forwarded to a validator. If ERC-1271 forwarding is implemented, the validator MUST be called with `isValidSignatureWithSender(address sender, bytes32 hash, bytes signature)`, where the sender is the `msg.sender` of the call to the smart account. | ||
|
||
Should the smart account implement any validator selection encoding in the `bytes signature` parameter, the smart account MUST sanitize the parameter, before forwarding it to the validator. | ||
|
||
The smart account's ERC-1271 `isValidSignature` function SHOULD return the return value of the validator that the request was forwarded to. | ||
|
||
#### Fallback | ||
|
||
Smart accounts MAY implement a fallback function that forwards the call to a fallback handler. | ||
|
||
If the smart account has a fallback handler installed, it: | ||
|
||
- MUST implement authorization control | ||
- MUST use `call` to invoke the fallback handler | ||
- MUST utilize [ERC-2771](./eip-2771.md) to add the original `msg.sender` to the `calldata` sent to the fallback handler | ||
|
||
#### ERC-165 | ||
|
||
Smart accounts MUST implement ERC-165. However, for every interface function that reverts instead of implementing the functionality, the smart account MUST return `false` for the corresponding interface id. | ||
|
||
### Modules | ||
|
||
This standard separates modules into the following different types that each has a unique and incremental identifier, which SHOULD be used by accounts, modules and other entities to identify the module type: | ||
|
||
- Validation (type id: 1) | ||
- Execution (type id: 2) | ||
- Fallback (type id: 3) | ||
- Hooks (type id: 4) | ||
|
||
Note: A single module can be of multiple types. | ||
|
||
Modules MUST implement the following interface, which is used by smart accounts to install and uninstall modules: | ||
|
||
```solidity | ||
interface IModule { | ||
function onInstall(bytes calldata data) external; | ||
function onUninstall(bytes calldata data) external; | ||
function isModuleType(uint256 typeID) external view returns(bool); | ||
} | ||
``` | ||
|
||
Modules MUST revert if `onInstall` or `onUninstall` was unsuccessful. | ||
|
||
#### Validators | ||
|
||
Validators MUST implement the `IModule` and the `IValidator` interface and have module type id: `1`. | ||
|
||
```solidity | ||
interface IValidator { | ||
/** | ||
* MUST validate that the signature is a valid signature of the userOpHash, and SHOULD return ERC-4337's SIG_VALIDATION_FAILED (and not revert) on signature mismatch | ||
*/ | ||
function validateUserOp(UserOperation calldata userOp, bytes32 userOpHash) external returns (uint256); | ||
/** | ||
* @dev `sender` is the address that sent the ERC-1271 request to the smart account | ||
* | ||
* MUST return the ERC-1271 `MAGIC_VALUE` if the signature is valid | ||
* MUST NOT modify state | ||
*/ | ||
function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature) external view returns (bytes4); | ||
} | ||
``` | ||
|
||
#### Executors | ||
|
||
Executors MUST implement the `IModule` interface and have module type id: `2`. | ||
|
||
#### Fallback Handlers | ||
|
||
Fallback handlers MUST implement the `IModule` interface and have module type id: `3`. | ||
|
||
Fallback handlers that implement authorization control, MUST NOT rely on `msg.sender` for authorization control but MUST use ERC-2771 `_msgSender()` instead. | ||
|
||
#### Hooks | ||
|
||
Hooks MUST implement the `IModule` and the `IHook` interface and have module type id: `4`. | ||
|
||
```solidity | ||
interface IHook { | ||
/** | ||
* MAY return arbitrary data in the `hookData` return value | ||
*/ | ||
function preCheck(address msgSender, bytes calldata msgData) external returns (bytes memory hookData); | ||
/** | ||
* MAY validate the `hookData` to validate transaction context of the `preCheck` function | ||
*/ | ||
function postCheck(bytes calldata hookData) external returns (bool success); | ||
} | ||
``` | ||
|
||
## Rationale | ||
|
||
### Minimal approach | ||
|
||
Smart accounts are a new concept and we are still learning about the best ways to build them. Therefore, we should not be too opinionated about how they are built. Instead, we should define the most minimal interfaces that allow for interoperability between smart accounts and modules to be used across different account implementations. | ||
|
||
Our approach has been twofold: | ||
|
||
1. Take learnings from existing smart accounts that have been used in production and from building interoperability layers between them | ||
2. Ensure that the interfaces are as minimal and open to alternative architectures as possible | ||
|
||
### Extensions | ||
|
||
While we want to be minimal, we also want to allow for innovation and opinionated features. Some of these features might also need to be standardized (for similar reasons as the core interfaces) even if not all smart accounts will implement them. To ensure that this is possible, we suggest for future standardization efforts to be done as extensions to this standard. This means that the core interfaces will not change, but that new interfaces can be added as extensions. These should be proposed as separate ERCs, for example with the title `[FEATURE] Extension for ERC-7579`. | ||
|
||
### Specifications | ||
|
||
#### Multiple execution functions | ||
|
||
The ERC-4337 validation phase validates the `UserOperation`. Modular validation requires the validation module to know the specific function being validated, especially for session key based Validators. It needs to know: | ||
|
||
1. The function called by Entrypoint on the account. | ||
2. The target address if it is an execution function. | ||
3. Whether it is a `call` or `delegatecall`. | ||
4. Whether it is a single or batched transaction. | ||
5. The function signature used in the interaction with the external contract. | ||
|
||
For a flourishing module ecosystem, compatibility across accounts is crucial. However, if smart accounts implement custom execute functions with different parameters and calldata offsets, it becomes much harder to build reusable modules across accounts. | ||
|
||
#### Differentiating module types | ||
|
||
Not differentiating between module types could present a security issue when enforcing authorization control. For example, if a smart account treats validators and executors as the same type of module, it could allow a validator to execute arbitrary transactions on behalf of the smart account. | ||
|
||
#### Dependence on ERC-4337 | ||
|
||
This standard has a strict dependency on ERC-4337 for the validation flow. However, it is likely that smart account builders will want to build modular accounts in the future that do not use ERC-4337 but, for example, a native account abstraction implementation on a rollup. Once this starts to happen, the proposed upgrade path for this standard is to move the ERC-4337 dependency into an extension (ie a separate ERC) and to make it optional for smart accounts to implement. If it is required to standardize the validation flow for different account abstraction implementations, then these requirements could also be moved into separate extensions. | ||
|
||
The reason this is not done from the start is that currently, the only modular accounts that are being built are using ERC-4337. Therefore, it makes sense to standardize the interfaces for these accounts first and to move the ERC-4337 dependency into an extension once there is a need for it. This is to maximize learnings about how modular accounts would look like when built on different account abstraction implementations. | ||
|
||
## Backwards Compatibility | ||
|
||
### Already deployed smart accounts | ||
|
||
Smart accounts that have already been deployed will most likely be able to implement this standard. If they are deployed as proxies, it is possible to upgrade to a new account implementation that is compliant with this standard. If they are deployed as non-upgradeable contracts, it might still be possible to become compliant, for example by adding a compliant adapter as a fallback handler, if this is supported. | ||
|
||
## Reference Implementation | ||
|
||
A full interface of a smart account can be found in [`IMSA.sol`](../assets/eip-7579/IMSA.sol). | ||
|
||
## Security Considerations | ||
|
||
Needs more discussion. Some initial considerations: | ||
|
||
- Implementing `delegatecall` executions on a smart account must be considered carefully. Note that smart accounts implementing `delegatecall` must ensure that the target contract is safe, otherwise security vulnerabilities are to be expected. | ||
- The `onInstall` and `onUninstall` functions on modules may lead to unexpected callbacks (i.e. reentrancy). Account implementations should consider this by implementing adequate protection routines. Furthermore, modules could maliciously revert on `onUninstall` to stop the account from uninstalling a module and removing it from the account. | ||
- For modules types where only a single module is active at one time (e.g. fallback handlers), calling `install*` on a new module will not properly uninstall the previous module, unless this is properly implemented. This could lead to unexpected behavior if the old module is then added again with left over state. | ||
- Insufficient authorization control in fallback handlers can lead to unauthorized executions. | ||
- Malicious Hooks may revert on `preCheck` or `postCheck`, adding untrusted hooks may lead to a denial of service of the account. | ||
- Currently account configuration functions (e.g. `installValidator`) are designed for single operations. An account could allow these to be called from `address(this)`, creating the possibility to batch configuration operations. However, if an account implements greater authorization control for these functions since they are more sensitive, then these measures can be bypassed by nesting calls to configuration options in calls to self. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
Oops, something went wrong.