Getting Smart Accounts EIP-7702 Ready

EIP-7702 is scheduled to be included in Pectra, likely sometime in Q1 2025. It allows EOAs to inherit smart account features, rather than providing a permanent migration path.

Research
Kurt Larsen
November 28, 2024
All posts
Share this:

EIP-7702 is scheduled to be included in Pectra, likely sometime in Q1 2025. It allows EOAs to inherit smart account features, rather than providing a permanent migration path. Overall, however, EIP-7702 is great for the adoption of smart accounts and is aligned with Ethereum’s account abstraction roadmap. For existing smart accounts to be safely used with EIP-7702, some minor changes must be made. We’ve been prototyping these changes in the ERC-7579 reference implementation to serve as a guide for all smart account builders.

EIP-7702 explainer

The tldr of EIP-7702 is that it allows an EOA to sign a delegation transaction, which will set code at the address of the EOA. Specifically, this code is a pointer to a contract address. When this EOA is called in the future, such as by the ERC-4337 EntryPoint, the EVM will treat the EOA as if it had the code that is deployed at the pointer address. This allows existing users to transform their existing EOAs into smart accounts.

EIP-7702 has some shortcomings and caveats. One of these is that the EOA private key remains usable, both for signing new delegations (including a delegation to remove the code entirely) and for doing EOA transactions in parallel to smart account transactions. This entails that some, mostly security-related, features are not feasible for EIP-7702 accounts. For example, converting the EOA into a multisig gives lower security guarantees than actually migrating to a multisig given that the private key can always be used to bypass the multisig logic. Further, resource locking within the account is not feasible for the same reason.

Required changes

As mentioned above, EIP-7702 is very friendly towards existing smart accounts, which is why it was preferred by the community as opposed to similar proposals. However, there are two changes that need to be made to smart accounts.

The first of these is to ensure that storage is namespaced. The reason why this is needed is that a user might move between different accounts and re-delegating does not clear storage. This means that if two account implementations use the same storage slots, the account could be incorrectly configured, bricked, or open to attack vectors. Incidentally, this change also makes it easier for regular smart account users to upgrade their smart account proxy and migrate to a new implementation, which will help reduce vendor lock-in of smart accounts.

The second of the changes required relates to the simplicity of EIP-7702. Specifically, EIP-7702 delegations are only signed over the delegate address (and some “metadata”). This means that EIP-7702 does not have a concept of initialization, instead it pushes the burden to the smart account layer. However, current smart account implementations expect to be deployed and initialized atomically, meaning the initialization function is not access-controlled since it cannot be front-run. This opens up an attack vector when delegating to these contracts where a malicious actor could front-run the initialization and take over the user account relatively easily.

In the sections below, we will walk through the exact changes we made to the ERC-7579 reference implementation so we can use it with EIP-7702 securely.

Storage namespace

When we originally wrote the ERC-7579 reference implementation, we actually already built it with namespaced storage in mind, mostly to make it easier for users to switch between smart accounts by upgrading their proxy. So, while we technically didn’t make any changes here, I will walk through how the storage layout works in the reference implementation anyway.

The idea of namespaced storage is that instead of using consecutive-based storage slots such as Soldity uses for static and dynamic types (although the latter usually are a mixture of consecutive index and some other parameters), storage slots are derived from names. ERC-7201 proposes one way of doing this, which the reference implementation roughly follows. To use this pattern, a developer needs to come up with a name root, such as “modulemanager.storage.msa”, and then use the storage slot that is based on the hash of this root (ERC-7201 has a specific formula for this). Then, the storage is accessed by setting the slot of a struct manually to be equal to this root storage slot, which can be used normally from then on.

This pattern needs to be used for all storage accesses on the account in order to not create any problems when users re-delegate their accounts.

Initialization front-running protection

Before these changes, the ERC-7579 reference implementation was built to be used in a certain way, namely through a proxy that is created for the user and pointing towards the singleton implementation. In the same call as the proxy creation, the proxy would also atomically be initialized by the factory. Further, the address of the proxy (and thus the user account) would be dependent on the proxy initialization parameters, meaning that there would be no way to front-run the account initialization with different parameters since this would yield a different account address. Because of this, the initialization function had no access control to date.

However, this process differs when using EIP-7702. On this flow, the user delegates to the singleton reference implementation, which is roughly the equivalent of deploying a proxy to the user’s EOA address that points to the singleton. The core difference is that the EIP-7702 is itself essentially a proxy, and the “delegatecalling” of the proxy is handled by the EVM itself. However, the core difference for our purposes is that there is no proxy factory, instead the “creation of the proxy”, ie the delegation using the EIP-7702 transaction type, does not contain a way to initialize the account atomically. This means that if we were to use the existing reference implementation, a malicious actor could spot a new delegation in the mempool, take out the signature from the user, and submit it in a frontrunning transaction in which the attacker sets their own initialization parameters.

Design constraints for front-running protection

Before diving into the specific solution that we chose in the reference implementation, I will briefly outline what our requirements were that informed the decision and how the result might be different if we removed any of those requirements:

1. One implementation for EIP-7702 and the established smart account flow

This reduces operational overhead, where higher overhead can lead to more human errors, such as delegating to the wrong (unsafe) implementation, and increases the potential for compounding security. If we choose to relax this requirement, we might even go so far as to build a special-purpose implementation for EIP-7702, for example, one that does not need to be initialized. Some teams, like Safe, are exploring this, but this is sub-par because of the abovementioned points.

2. Minimal code changes on the account

We want existing accounts to carry as much of their security properties over from previous versions.

3. The delegate address should be a singleton

This depends on how wallets choose to use EIP-7702. One thing that seems exceedingly likely is that most wallets will manually opt in to using certain implementations for two reasons: first, since delegation is an action that could give over control of an entire account to a malicious actor, delegation targets need to be vetted. Second, wallets will need to know how to correctly encode calldata for the implementations that are delegated to, so they manually need to allow implementations with different calldata construction mechanisms. It seems very likely that wallets will thus have a simple allowlist of implementations forthey allow their users to delegate to. If we relaxed this requirement, then it would be possible to deploy a slightly modified proxy to the user’s account that has the hash of the initialization data in its’ bytecode, which can and can thus check if initialization is being front-run and prevent this.

4. Minimal gas overhead on delegation and usage

We want as little gas overhead as possible for the user when creating a delegation and using their account. This rules out using a proxy since this costs the user deployment gas and runtime gas, and other solutions that are more costly.

5. In-time initialization

Lastly, we want to preserve the nice property of counterfactual addresses on the established smart account flow, namely that a user doesn’t need to make an onchain interaction until their first transaction. Relaxing this requirement could make initialization easier in terms of code changes since it doesn’t need to happen in the first UserOperation, it feels like a pretty big downside for the user to need to instantly do a transaction when this is not necessary.

Detailed solution

The implementation has two core components: the initialization logic when using EIP-7702 and the initialization function’s access protection. Based on the aforementioned requirements, we implement the initialization logic within the validateUserOp function. This means that a user can sign their delegation transaction, and then in their first UserOperation, the account will be initialized. However, our implementation also allows users to keep using only their EOA private key to sign transactions until they want to properly initialize their modular account and start using modules. While we expect users and applications to want to make use of smart account modules relatively quickly, such as session keys, passkeys, or automations, this means that a user can initially use their ERC-7579 account just for batching and gas abstraction before initializing it as a complete modular account later.

Inside the logic to validate a UserOperation, the account needs to check if a validator that a user is trying to use is installed. Since this check is done anyway, we can use this condition to add our initilization logic, resulting in no extra overhead when using an already initialized account. When it is not initialized, a user will simply sign the UserOperationHash with their EOA. To initialize the account, a user can make a single or batched call to the initializeAccount function and set it up with their desired configuration. Overall, this initialization logic is around 8 lines of code and does not add overhead to the base flow of using an initialized account.

The second component of our changes relates to the access control on the initializeAccount function. Previously, this function was not access control protected, given that in the established smart account flow, it is called atomically by the factory during account creation. Based on the first requirement to have one implementation to use across the established smart account flow and EIP-7702, we decided to access control this function by default (except for allowing the account to call into the function). However, when the established smart account flow is used, the factory will tstore a value to a certain slot in the factory, which allows it to call the initializeAccount function in this call only. This means that the factory can call into the function during account creation but otherwise the function remains access controlled to only allow the account as caller.

Conclusion

In this blog, we’ve outlined the changes we’ve made to the ERC-7579 reference implementation to allow it to be securely used with EIP-7702. The core benefits to this approach are the following:

  • The smart account implementation is both EIP-7702 compliant and compatible with the standard smart account flow, reducing operational overhead, the potential for human error, and improving security.
  • Maintaining the delegate address as a singleton ensures compatibility with the expected contract whitelisting from wallet vendors (such as MetaMask). Ensuring maximum interoperability and composability.
  • In-time initialization allows wallet vendors to inherit baseline AA features, like batching and gas abstraction, without fully initializing smart account modularity. This provides enhanced security for vendors who want to progressively access the power of smart accounts.

We hope this will be useful for other account builders planning to make similar changes. If you are building an ERC-7579 account or upgrading an existing account, please reach out — we’d love to help!

Share this:
Research

Sign up for the latest insights from the bleeding edge. All killer, no filler.

Thanks. We'll be in touch.
There was an error. Please try again.

Read another

WTF is Modular Account Abstraction

After multiple rejected or stalled proposals to add native support for smart contract wallets (smart accounts) to Ethereum, ERC-4337 has been accepted as the (interim) standard....

Research
Kurt Larsen
August 3, 2023

Introducing: ERC-7579

Standardizing a minimal baseline for modular smart account interoperability

Research
Kurt Larsen
December 14, 2023

$5m Seed Led by 1kx to Unlock the Next Era of Smart Accounts. With participation from Circle Ventures, Alchemy Venture.

Research