A deep dive into the main components of ERC-4337: Account Abstraction Using Alt Mempool — Part 3

Antonio Viggiano
Oak Security
Published in
5 min readDec 11, 2023

In this post we continue our journey through ERC-4337: Account Abstraction via Alternative Mempool. In Part 1, we covered the Bundler, a component responsible for listening to UserOperations, packaging, and sending them as regular transactions to Ethereum nodes. In Part 2, we delved into the EntryPoint, a crucial entity for verifying and executing UserOperations.

Next, we will focus on the Smart Accounts, Smart Contract Wallets, or simply Wallets, the last one of the required components of ERC-4337. In the following post, we will cover the Paymasters extension, an optional component that can sponsor transactions for users.

Source: Ethereum wallets today and tomorrow — EIP-3074 vs. ERC-4337

Design principles

A primary objective of ERC-4337 was to enable users to use smart contract wallets with custom verification logic as their main account, eliminating the need for users to maintain Externally Owned Accounts (EOAs).

For that, an important design goal is to replicate the key property of EOAs in which users do not need to perform any custom action to create their wallets. They can simply generate an address locally and immediately start accepting funds. The wallet creation is thus done by a “factory” contract, with wallet-specific data, which uses CREATE2 to create the wallet in a counterfactual (deterministic) address.

Wallet creation

The wallet creation is performed by the initCode field of the UserOperation struct:

/**
* User Operation struct
* @param sender - The sender account of this request.
* @param nonce - Unique value the sender uses to verify it is not a replay.
* @param initCode - If set, the account contract will be created by this constructor/
* @param callData - The method call to execute on this account.
* @param callGasLimit - The gas limit passed to the callData method call.
* @param verificationGasLimit - Gas used for validateUserOp and validatePaymasterUserOp.
* @param preVerificationGas - Gas not calculated by the handleOps method, but added to the gas paid.
* Covers batch overhead.
* @param maxFeePerGas - Same as EIP-1559 gas parameter.
* @param maxPriorityFeePerGas - Same as EIP-1559 gas parameter.
* @param paymasterAndData - If set, this field holds the paymaster address and paymaster-specific data.
* The paymaster will pay for the transaction instead of the sender.
* @param signature - Sender-verified signature over the entire request, the EntryPoint address and the chain ID.
*/
struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}

When it has a non-zero length, it is parsed as a 20-byte address (the factory), followed by “calldata” to pass to this address. The calldata contains the encoded function signature and arguments, which, when called, is expected to create a wallet (if it does not exist) and return its address (in all cases). This is to make it easier for clients to query the address without knowing if the wallet has already been deployed, by simulating a call to entryPoint.getSenderAddress(), which calls the factory under the hood.

Wallet validation

The wallet contract created by the factory method should implement IAccount, which contains only a single function: validateUserOp. This method validates the UserOp’s signature, which is necessary for the EntryPoint to execute a UserOperation on a Wallet account.

This is interesting, because, although Infinitism's repository provides a BaseAccount implementation of the IAccount interface and a SimpleAccount sample contract (created by SimpleAccountFactory), none of these are actual requirements of the specification. Developers are free to customize the wallet and factory implementation in any way they choose, provided that they correctly perform the necessary validations according to the ERC. Nevertheless, it is essential to address appropriate security considerations, as many security reviews have identified critical and major severity vulnerabilities in custom implementations of these contracts.

During the handleOps execution by the EntryPoint, it validates the UserOperation gas limits and Wallet prepayment through _validatePrepayment. This will internally call _validateAccountPrepayment, which creates the account, if necessary on _createSenderIfNeeded, and executes the validateUserOp function on it, passing the missing account funds (the difference between the required gas prefund and the current account deposit) as an argument if the paymaster does not exist.

Deposits can be made through depositTo, which means any counterfactual address can be equipped with executing UserOperations even before their creation. We can imagine a service provider allowing fiat on-ramp to ERC-4337 wallets by directly depositing funds on behalf of the wallet account, for example.

When missingAccountFunds is non-null, the wallet account is required to transfer ether to the EntryPoint, as can be seen on the BaseAccount implementation of validateUserOp through _payPrefund.

Wallet execution

After validating the prepayment and account nonce (introduced in v0.6), the EntryPoint eventually executes an arbitrary call to the wallet contract with the specified gas limit. In SimpleAccount, this will be either execute or executeBatch.

Wallet upgradeability

ERC-4337 recommends that accounts use DELEGATECALL forwarding contracts to enhance gas efficiency and enable the upgradeability of the wallet.

The account code should have a fixed entry point embedded for the sake of gas efficiency, and in the event of introducing a new entry point, users would still have the ability to update their account’s code address with a new one.

In the case of the SimpleAccount sample contract, this is a UUPS-upgradeable contract:

The _authorizeUpgrade method allows both the account owner to upgrade the contract through a direct Ethereum transaction or the own wallet contract address(this), which happens during a UserOperation through the EntryPoint.

Final remarks

In this post, we demystify the workings of Smart Accounts, Smart Contract Wallets, or simply Wallets in the context of ERC-4337: Account Abstraction via Alternative Mempool. This component enables users to leverage smart contract wallets with custom verification and arbitrary execution logic as their primary accounts, without the need for traditional Externally Owned Accounts (EOAs), offering a more seamless and user-friendly experience.

Looking ahead, our upcoming Part 4 will delve into the Paymasters extension, which plays a role in UserOperations sponsorship. Keep watching as we uncover the remaining pieces of ERC-4337, and don't hesitate to reach out if you have any questions regarding the secure use of account abstraction.

--

--

Antonio Viggiano
Oak Security

Helping protocols improve their invariant tests with echidna 🦔, foundry ⚒️, and medusa 🪼