[toc] ## ERC-4337 ## *Account Abstraction* ### *via Alternative Mempool* <div style="text-align: right"> One Year Later... </div> &nbsp; &nbsp; Yoav Weiss, Dror Tirosh, Shahaf Nacson https://github.com/eth-infinitism/account-abstraction --- ### What’s new? - Account changes - Extend Paymaster - Support Signature Aggregation - Relaxed Storage Rules - Staking, Reputations for all Entities - RPC API - Extras: spec-test, ref. impl, sdk Note: account: time-range (session keys) pm: support token paymaster agg: for rollups RPC: simulation, estimation Extras: For standarized bundler compatibility --- ### Key challenges - Why is it so Complicated - EOA is secured by signature, nonce, gas fees. - An existing Tx in mempool can't be invalidated - AA UserOperation is validated by EVM code, and **can** be invalidated before paying. - **Our goal:** make invalidations expensive.DoS attack on the mempool should cost. A lot. - Putting **`n`** UserOps into the mempool should cost **`O(n)`** --- ### UserOperation ```solidity struct UserOperation { address sender; uint256 nonce; bytes initCode; bytes callData; uint256 callGasLimit; uint256 verificationGasLimit; uint256 preVerificationGas; uint256 maxFeePerGas; uint256 maxPriorityFeePerGas; bytes paymasterAndData; bytes signature; } ``` --- ### Submit UserOperation to bundler ```plantuml @startuml autonumber participant User as user #lightgreen participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Account\ncontract" as wallet user->user: create, sign UserOp 'node<--]: UserOp from memooool user->node++ #lightgreen: eth_sendUserOp node->ep++ #gold: traceCall:\nsimulateValidation ep->wallet++ #blue: <font color=blue> validateUserOp return send fee deactivate ep node->node: validate storage access node-->: add to mempool hide footbox ``` Note: explain all entities --- ### Submit UserOperation to bundler with factory ```plantuml @startuml autonumber participant User as user #lightgreen participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Factory\ncontract" as fact participant "Account\ncontract" as wallet user->user: create, sign UserOp 'node<--]: UserOp from memooool user->node++ #lightgreen: eth_sendUserOp node->ep++ #gold: traceCall:\nsimulateValidation ep->fact++ #gray: create (initCode) fact->o wallet: create return account ep->wallet++ #blue: <font color=blue> validateUserOp return send fee deactivate ep node->node: validate storage access node-->: add to mempool hide footbox ``` --- ### Submit UserOperation to bundler with paymaster ```plantuml @startuml autonumber participant User as user #lightgreen participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Account\ncontract" as wallet participant "Paymaster\ncontract" as pm user->user: create, sign UserOp 'node<--]: UserOp from memooool user->node++ #lightgreen: eth_sendUserOp node->ep++ #gold: traceCall:\nsimulateValidation ep->wallet++ #blue: <font color=blue> validateUserOp deactivate wallet ep->pm++ #gray: validatePaymasterUserOp deactivate pm deactivate ep node->node: validate storage access node-->: add to mempool hide footbox ``` --- ### Submit UserOperation to bundler ```plantuml @startuml autonumber participant User as user #lightgreen participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Factory\ncontract" as fact participant "Account\ncontract" as wallet participant "Paymaster\ncontract" as pm user->user: create, sign UserOp 'node<--]: UserOp from memooool user->node++ #lightgreen: eth_sendUserOp node->ep++ #gold: traceCall:\nsimulateValidation ep->fact++ #gray: <font color=gray> create (initCode) fact->o wallet: <font color=gray>create return <font color=gray>account ep->wallet++ #blue: <font color=blue> validateUserOp return send fee (if no paymaster) ep->pm++ #gray: <font color=gray> validatePaymasterUserOp deactivate pm deactivate ep node->node: validate storage access node-->: add to mempool hide footbox ``` --- ### Creating HandleOps Transaction ```plantuml @startuml autonumber participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Account\ncontract" as wallet activate node #lightgreen group <font color=green> Off-chain: pick UserOps from mempool node->ep++ #gold: traceCall:\nsimulateValidation ep->wallet++ #blue: validateUserOp deactivate wallet deactivate ep node->node: validate storage access note over node: add to bundle end group create transaction node->ep++ #blue: handleOps(bundle) group loop all UserOps for validation ep->wallet++ #blue: account.validateUserOp deactivate wallet end group loop all UserOps for execution ep->wallet++ #blue: account.call(callData) deactivate wallet end return pay collected fees end deactivate node hide footbox ``` --- ### Account API ```solidity function validateUserOp(UserOperation userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData); ``` - ValidationData: - Time-range: `validAfter`, `validUntil` - returned signature check: - signature aggregator (optional) - “0” - signature OK - “1” - sig check failed (ok during estimation) - BaseAccount - SimpleAccount: Proxy-based, EOA-like account Notes: missingfunds - maxPossiblecost --- ### Minimal Account ```solidity function validateUserOp(UserOperation userOp, bytes32 userOpHash, uint256 missingFunds) external returns (uint256 validationData) { require(msg.sender == address(entryPoint); bytes32 hash = userOpHash.toEthSignedMessageHash(); bool sigFailed = owner != hash.recover(userOp.signature); validationData = sigFailed ? 1 : 0; require(nonce++ == userOp.nonce); if (missingFunds != 0) entryPoint.depositTo{value : missingFunds}(msg.sender); } ``` --- ### Paymaster ```solidity function validatePaymasterUserOp(UserOperation userOp, bytes32 userOpHash, uint256 maxCost) returns (bytes context, uint256 validationData) function postOp(PostOpMode mode, bytes context, uint256 actualGasCost) ``` - May access account token balance - returned `validationData` like in Account. - May work without stake - But with no self-storage, and no context --- ### Custom Factory - Provided by each Account contract wallet. - <font color=blue>`UserOp.initCode`</font> : address + calldata - Can wrap existing factory (e.g. GnosisProxyFactory) - Can call “initializer” functions during construction - Must return same address for existing account --- ### Custom Factory ```solidity contract SimpleAccountFactory { function createAccount(address owner,bytes32 salt) public returns (address) { address addr = Create2.computeAddress(...); uint codeSize = addr.code.length; if (codeSize > 0) return addr; ret = new Proxy{salt : salt} ( accountImplementation, abi.encodeCall( SimpleAccount.initialize, (owner)) ); } } ``` --- ### **Key challenges - Why is it so Complicated** - **EOA** is secured by signature, nonce, gas fees. - An existing Tx in mempool can't be invalidated - **AA UserOperation** is validated by EVM code, and **can** be invalidated before paying. - **Our goal:** make invalidations expensive. DoS attack on the mempool should cost. A lot. - Putting **`n`** UserOps into the mempool should cost **`O(n)`** Note: The EP protects the account: only execute after its validation The separation of validation help the bundlers to validate userops fast. The rules below protect the bundlers: prevent DoS attacks --- ### Protection Rule Principles 1. **Sandboxing** - validation is the same on-chain and off-chain 2. **Isolation** - UserOperations should not share state 3. **Staking** - Bypassing rule (2) causes throttling. Staking make it expensive to re-deploy 4. **Low validation cost** --- ### Code Validation Rules - Prevent simulation and exectuion validation to behave differently. - Banned opcodes: NUMBER, TIMESTAMP, SELFDESTRUCT etc - Codehash must not change between 1st & 2nd simulations - Must not access EOA - GAS opcode: allowed just before *CALL - Must not revert on OOG --- ### Storage validation rules * Storage Rules * Allowed access to account storage (e.g. `account.getNonce()`) * Allowed access to referenced storage (e.g. `token.balanceOf(account)`) * Staked entities * All entities can be staked * Allowed to access its own storage * Can be Throttled/Banned * Reputations --- ### Bundler deep dive - Upon receiving `eth_sendUserOperation(userOp)`: - Save codehashes of accessed addresses - Run off-chain checks (paymaster balance etc.), then call debug_traceCall `simulateValidation(userOp)` (first simulation). If simulation succeeds, add userOp to mempool. --- ### Bundler deep dive - On bundle creation, for each candidate user operation from mempool perform the following: - validate no codehash changes - validate paymasters deposits - validate paymasters, aggregators, factories stakes/reputations if needed - validate bundle inclusion rules for accounts (e.g. 1 userOp per sender) - call debug_traceCall `simulateValidation(userOp)` (second simulation) --- ### RPC APIs * `eth_sendUserOperation` * `eth_estimateUserOperationGas` * `eth_getUserOperationReceipt` * `eth_getUserOperationByHash` * `eth_supportedEntryPoints` * `eth_chainId * As well as debug apis ` --- ### Signature Aggregation ```plantuml @startuml autonumber participant Bundler as node #lightgreen participant "EntryPoint\ncontract" as ep participant "Account\ncontract" as wallet participant "Signature\nAggregator" as agg activate node #lightgreen group <font color=green> Off-chain: pick UserOps from mempool node->ep++ #gold: traceCall:\nsimulateValidation ep->o wallet: validateUserOp return aggregator node->agg++ #gold: validateUserOpSignature() deactivate agg 'node->node: validate storage access end node->agg++ #gold: aggregateSignatures( UserOp [] ) deactivate agg group create transaction node->ep++ #blue: handleOps(bundle) ep->agg++ #gold: validateSignatures( UserOps [] ) deactivate agg group loop all UserOps for validation ep->o wallet #blue: account.validateUserOp end group loop all UserOps for execution ep->o wallet #blue: account.call(callData) end deactivate ep end deactivate node hide footbox ``` --- ## Bundler-Spec-Test - pytest tests validate enforcement of bundling rules, rpc compatibility etc.
{"metaMigratedAt":"2023-06-17T20:33:04.010Z","metaMigratedFrom":"YAML","title":"PeepAnEIP 4337 Presentation","breaks":true,"slideOptions":"{\"transition\":\"slide\"}","contributors":"[{\"id\":\"f15c8864-c01d-4de0-8022-a461aeac0091\",\"add\":32819,\"del\":24839},{\"id\":\"3784b3e7-9b1a-4f99-bee7-cc80c2ebe28b\",\"add\":2832,\"del\":59}]"}
    1428 views