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
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
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
Submit UserOperation to bundler with factory
Submit UserOperation to bundler with paymaster
Submit UserOperation to bundler
Creating HandleOps Transaction
Account API
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
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
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.
UserOp.initCode
: address + calldata
Can wrap existing factory (e.g. GnosisProxyFactory)
Can call “initializer” functions during construction
Must return same address for existing account
Custom Factory
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)
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
Sandboxing - validation is the same on-chain and off-chain
Isolation - UserOperations should not share state
Staking - Bypassing rule (2) causes throttling. Staking make it expensive to re-deploy
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
`
Bundler-Spec-Test
pytest tests validate enforcement of bundling rules, rpc compatibility etc.
Resume presentation
ERC-4337 Account Abstraction via Alternative Mempool One Year Later... Yoav Weiss, Dror Tirosh, Shahaf Nacson https://github.com/eth-infinitism/account-abstraction
{"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}]"}