ERC6900 ERC4337

Understanding the Limitations of ERC-4337

 
ERC-4337 introduced a standard that facilitates the seamless use of contract wallets (accounts) without needing modifications to Ethereum clients.
This is achieved by introducing an additional verification process via an entity known as the User Operation (userOp) , which serves as a replacement for traditional transactions.
Contract accounts adhering to the ERC-4337 standard can incorporate various features, such as Paymasters that cover gas fees, batch transactions, BLS signature aggregation, social recovery, and session keys. These functionalities significantly enhance the user experience compared to traditional Externally Owned Accounts (EOAs).
For instance, using ERC-4337 Paymaster feature, users can pay gas fees with ERC-20 tokens instead of ETH, or have the protocol cover the fees for them. Moreover, users could temporarily delegate control of their account to a protocol via a session key, enabling them to interact with the protocol with just a single click, rather than needing to confirm each transaction individually.
 

However, the design of ERC-4337 aims to achieve account abstraction without altering
the underlying protocol, and therefore, it does not specify the exact structure that smart contract accounts should follow.
 

This lack of standardization has led to two primary challenges:

  • Compatibility Challenges Arising from Diverse Account Structures

At present, various companies provide contract accounts through SDKs, but each company offers a different account structure, leading to compatibility challenges.
This diversity makes it difficult for users of applications that support contract wallets to use these wallets outside of the specific app. Additionally, it complicates the process for applications to support users with various types of contract accounts. In essence, while ERC-4337 has enhanced the user experience, it has inadvertently created a situation where users are confined within the ecosystem of a specific app.

  • Limitations in Account Extensibility

Many features enabled by ERC-4337 are implemented within the account itself. However, since these accounts are structured as smart contracts, there are inherent limitations to how easily they can be modified or extended. Although a Proxy structure can be applied to accounts to allow for upgrades, this process is often cumbersome.
For example, if a user wishes to add a new feature to their existing contract account, they must migrate the entire codebase to a new contract that includes the desired feature. This upgrade process can incur additional costs, such as the need for a new audit of the contract. Furthermore, since a uniform account structure is provided to all users, there is a drawback in that users cannot customize the features within their accounts to suit their preferences.

 

To address these challenges, a proposition for a new standard, ERC-6900, came along.

What is ERC-6900?

  ERC-6900 is an Ethereum Improvement Proposal (EIP) titled “Modular Smart Contract Accounts and Plugins,” offering a new standard for accounts that are compatible with ERC-4337.
It introduces a modular architecture that allows for the flexible installation and removal of various features from an account, much like installing or uninstalling apps on an Android device. The modular contracts containing these features are referred to as plugins.

ERC-6900, with its modular approach, empowers users to easily add or remove various plugins (features) from their accounts. This is particularly significant as current contract accounts are often tied to specific applications; with ERC-6900, a single contract account could be effortlessly utilized across multiple applications.

While this concept is highly promising, several factors must be considered when implementing it in actual code, especially regarding security and user experience:

  • Installation and Removal of Plugins

The foremost consideration is how to implement the installation and removal of plugins. The developers of ERC-6900 drew inspiration from the Diamond Proxy architecture. The Diamond Proxy is an advanced structure derived from the traditional Upgradeable Proxy, which divides the contract into multiple components, or facets, and allows selective upgrading of only those components. Unlike simpler Upgradeable Proxy structures, which require replacing the entire logic contract during an upgrade, the Diamond Proxy permits the targeted upgrade of specific components.

In the Diamond Proxy model, a contract’s various functions are divided into facets, and the Proxy contract invokes these facets using ‘delegatecall’ . ‘The delegatecall’ function enables the Proxy to ‘borrow’ external contract functions and execute them within the context of its own contract, allowing it to manipulate its own storage through external functions.

On top of that, the Diamond Proxy keeps a mapping of function selectors, designating the access paths to each facet. This mapping allows the Proxy contract to have free access to functions within each facet, providing a robust framework for the modular management of contract functionality.  

Implementation of ERC-6900: A Modular Approach with Diamond Proxy

ERC-6900 leverages the Diamond Proxy architecture to establish a modular system for plugins and contract accounts.
In this setup, the account functions as the Proxy contract, while each plugin operates as a facet. Consequently, when a User Operation or a direct call is made to the account, the appropriate plugin handles the request through a fallback function. This fallback function is triggered when the called function’s selector is not found within the contract, a mechanism commonly utilized in Proxy patterns.
However, there is a key distinction between ERC-6900 and the traditional Diamond Proxy approach.

In the standard Diamond Proxy, ‘delegatecall’ is used to invoke functions within the facet. A facet, in this context, is a contract that typically contains only execution logic without the need for its own storage. Sometimes, facets are even deployed as libraries without storage. The ‘delegatecall’ function is suitable in such cases because it allows the facet to operate within the context of the Proxy contract, utilizing the Proxy’s storage.

However, ERC-6900 introduces a significant change: instead of using ‘delegatecall’, it opts for ‘call’ when invoking functions within a plugin. This shift is necessary because, unlike facets in the Diamond Proxy, each plugin in ERC-6900 has its own storage.

The rationale behind this change is rooted in security considerations.
If an account were to allow ‘delegatecall’ to a plugin, the plugin’s functions would gain access to the account’s storage data. This could be extremely dangerous, as a malicious plugin might manipulate or even erase the account’s storage information. While this risk was mitigated in the original Diamond Proxy by restricting who could add facets, ERC-6900 envisions a more open environment where anyone can freely create and integrate plugins. In such an environment, allowing ‘delegatecall’ would introduce significant security vulnerabilities.

To address this, ERC-6900 uses ‘call’ instead of ‘delegatecall’, ensuring that each plugin maintains its own separate storage. This approach minimizes the risk of unauthorized access or manipulation of the account’s storage, thus enhancing the overall security of the system.

Let’s examine the User Operation aspect.

In ERC-4337, the process of verifying and executing User Operations is distinct, with the flow divided into verification and execution phases. In contrast, for direct calls within Modular Smart Contract Account, the process undergoes verification via the Runtime Validation function.
Each process involves several stages, including validation functions, hooks, and execution functions.

The functions in this context can be categorized as following :
First, execution functions, which perform specific operations within the account or plugin. For each execution function, there is a corresponding validation function that verifies the legitimacy of the call. Additionally, hooks can be applied before and after each function call. Let’s delve into each of these components in more detail.

A- Validation Function

This function is responsible for verifying the authority of callers to the account. As previously mentioned, if anyone could invoke functions within the account, it could be susceptible to attacks that drain funds through excessive gas consumption. Therefore, functions that consume gas or access storage must be safeguarded by validation functions.

There are two types of validation functions: the User Operation validation function, which handles calls from the entry point, and the Runtime validation function, triggered when an EOA directly calls a function within the account.

These validation functions do not reside within the account itself; they are all embedded within plugins. Consequently, all calls to the account are routed through the validation function in a plugin. Depending on the specific use case, various types of validation functions can be implemented, such as:

        -   Functions that verify signatures and permit only the owner's address.
        -   Functions that verify signatures and allow specific designated addresses.
        -   Functions that permit any address.

B- Execution Function

These functions manage actual fund transfers and interactions with external contracts. There are two primary types:

-1 Standard execute function This refers to the execute and executeBatch functions that align with the IAccount interface of the ERC-4337 reference implementation. These functions facilitate all types of interactions based on the account’s gas fees and, therefore, require stringent validation functions (e.g., restricting access to the owner).

-2 Execution function
These are functions found within each plugin and are common functions that can be executed from the account. For instance, consider a recoverOwner function in a social recovery plugin. Since only the guardian responsible for recovery should invoke this function, it must include an appropriate validation function.

-3 Hook

Hooks are functions that execute before and after other functions.
They define tasks that need to be performed at specific stages. For instance, a ‘DailyGasSpendingLimit’ hook could limit daily gas consumption by checking the gas usage through the ‘gasleft()’ function before and after the execution function, preventing further execution if a certain amount of gas has been used within a day.

Additionally, a pre-validation hook might run before the validation function, especially when multiple validations are required. For instance, if a session key in a session key plugin is linked to a multisig wallet, it would need to pass both multisig and session key validations. In this case, the multisig verification could be executed as a pre-validation hook, followed by the session key validation function.

Dependency

Additionally, a plugin can depend on other plugins for functionality. This is particularly useful when a plugin needs to utilize functions (like validation or execution functions) that are already implemented in other plugins. A prime example is the SingleOwnerPlugin in the Reference Implementation, which contains a function that restricts calls to only the owner:

function userOpValidationFunction(uint8 functionId, UserOperation calldata userOp, bytes32 userOpHash)
    external
    view
    override
    returns (uint256)
{
    if (functionId == uint8(FunctionId.USER_OP_VALIDATION_OWNER)) {
        // Validates the user operation signature against the owner's signature.
        (address signer,) = 
          (userOpHash.toEthSignedMessageHash()).tryRecover(userOp.signature);
        if (signer == address(0) || signer != _owners[msg.sender]) {
            return _SIG_VALIDATION_FAILED;
        }
        return _SIG_VALIDATION_PASSED;
    }
    revert NotImplemented();
}

Because this validation function is quite general, it is likely to be reused by other plugins.

If a plugin is installed with this as a dependency, it can leverage the validation function without needing to reimplement it. This feature is highly beneficial for reducing code redundancy and improving readability, particularly when validation functions overlap for different execution functions. Plugins with functions that require owner-only access can set the SingleOwnerPlugin as a dependency and use its validation function for their own operations. This approach allows plugins to inherit hooks, validation functions, and execution functions from other plugins, enabling a modular plugin architecture based on dependencies.

Manifest

To ensure safe installation without conflicts with existing plugins, each plugin contains a data structure called a manifest. The manifest holds information about the plugin’s execution functions, validation functions, hooks, dependencies, allowed calls, and more. During installation, this information is checked, and all function selectors within the plugin are recorded in the account’s storage.