[toc]
## ERC-4337
## *Account Abstraction*
### *via Alternative Mempool*
<div style="text-align: right">
One Year Later...
</div>
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}]"}