1. Problem statement - 4337 wallets cost more gas

Gas costs for interacting with and creating account abstraction wallets is too high. These gas costs are grouped into three main categories: accounts, simulation, and execution.

a. Account Gas: The cost of wallet deployment

To facilitate a userop an account abstraction wallet (as known as a ERC4337-compliant smart contract wallet) must be created via initCode (as seen in UserOperation.sol), or already exist. Normal account abstraction wallet functionality would check the existence of the wallet for the user. The below example from Soul Wallet shows the functions createWallet and getWalletAddress for wallet deployment and the deterministic address for the given input, respectively.

/**
  * @notice  deploy the soul wallet contract using proxy and returns the address of the proxy. should be called by entrypoint with useropeartoin.initcode > 0
  */
function createWallet(bytes memory _initializer, bytes32 _salt) external returns (address proxy) {
    bytes memory deploymentData = abi.encodePacked(type(SoulWalletProxy).creationCode, WALLETIMPL);
    bytes32 salt = _calcSalt(_initializer, _salt);
    assembly ("memory-safe") {
        proxy := create2(0x0, add(deploymentData, 0x20), mload(deploymentData), salt)
    }
    if (proxy == address(0)) {
        revert();
    }
    assembly ("memory-safe") {
        let succ := call(gas(), proxy, 0, add(_initializer, 0x20), mload(_initializer), 0, 0)
        if eq(succ, 0) { revert(0, 0) }
    }
    return proxy;
}

/**
  * @notice  return the counterfactual address of soul wallet as it would be return by createWallet()
  */
function getWalletAddress(bytes memory _initializer, bytes32 _salt) external view returns (address proxy) {
    bytes memory deploymentData = abi.encodePacked(type(SoulWalletProxy).creationCode, WALLETIMPL);
    bytes32 salt = _calcSalt(_initializer, _salt);
    proxy = Create2.computeAddress(salt, keccak256(deploymentData));
}

Example of a smart wallet factory having a method for wallet creation and wallet address retrieval.

The method getWalletAddress, though somewhat unrelated to gas, provides a factory method for deterministically deriving a wallet address. Further, it is important to note that a separate function _calcSalt is used to make calculation on that salt. This last step, in some form, occurs on nearly all wallet factories.

The actual deployment cost for generating an account ranges from 100k to 1m gas. This range can be attributed to varying methods for deployment. These methods can be generalized to proxy non-proxy wallet deployment. Both deployment types have different advantages. It can also be noted that EOA users can often deploy a wallet interacting directly with the factory without an entrypoint or the owner being the msg.sender, thus enabling the possibility for free on-boarding via a relayer.

b. Simulation Gas: Validating the userop

The validation of a userop occurs at various points in the userop life cycle including: account, entrypoint, paymasters, account abstraction gui, bundlers, and builder. For purposes of calculating gas to be used the focus will be validation happening on behalf of the accounts and entrypoint only.

c. Execution Gas: Userop gas vs eoa gas comparison

The aforementioned categories are baked into the overall gas cost for executing a userop, this means a userop will inherently costs more than an EOA transaction. In fact, at the time of writing, a userop execution is an order of magnitude more expensive.

Userops necessitate the need for simulation and validation to support the 4337 ecosystem. The different actors in this ecosystem have varying needs such as: validating userop data, verifying matching deadlines, and verifying sufficient payment exists.

Because userops are inherently more expensive than EOA transactions, the ideal cost will always be the cost of the final execution. Using forge the transaction msg.sender can be fudged to be the entrypoint revealing the raw cost of executing the calldata via the wallet (Exec.call of figure 1a). In this sense, EntryPoint is the simulation and Wallet Proxy is the execution.