#AA 01 Explanation of account abstraction
Introduction of Account Abstraction
Ethereum has two types of accounts: externally owned accounts (EOA) and contract accounts (CA), or smart contracts.
EOAs are user-controlled accounts that can send transactions. Users maintain ownership of their accounts through their private keys, and can execute transactions by signing, thus changing the internal state of their EOAs or the state of external contract accounts. Since the private key (or seed phrase) is the only representation of EOA ownership, EOAs are not suitable for defining complex ownership or transaction logic, such as social login and multisig transactions. The limitations of EOAs lead to poor user experiences; some cumbersome steps, such as private key and secret phase management, directly hinder Web2 users from entering Web3. Most popular wallets, such as MetaMask, are based on the EOA model.
CAs can host arbitrary Solidity code, thus being able to leverage the full Turing completeness of the EVM. Unfortunately, contract accounts cannot send transactions, so their functionality must be triggered by EOAs. A smart contract wallet is a type of contract account that is indirectly triggered by its user through the EOA networks of wallet providers, whether through relays or bundlers.
Account Abstraction (AA), the core of which is to decouple the signing authority and ownership of accounts, allowing for more flexible combinations of CAs and EOAs, thus enabling features like gas abstraction, programmable permissions, etc. through smart contract code.
The ERC4337 standard is a measure for implementing Account Abstraction without changing the Ethereum consensus layer, and it's also the proposal with the highest feasibility. In the past year, a large number of applications based on this protocol have also emerged. The series of account abstraction data analysis articles are all centered around this standard.
ERC4337 Execution Flow
In the ERC4337 account abstraction standard implementation, there are four roles involved:
User: the owner of the smart wallet, corresponding to the smart wallet contract.
Bundler: responsible for submitting the transaction information sent by the user to the entry point contract (EntryPoint). These are EOA accounts.
Paymaster: responsible for paying gas fees for operations, but this is optional. If not set the gas is paid by the smart contract wallet.
Wallet Factory: responsible for creating new smart contract wallets.
The specific execution flow is as follows:
The user submits User Operations through an account abstraction supported wallet, these User Operations will enter an Alt Mempool;
The bundler monitors this Mempool, retrieves User Operations from it, and submits these Ops to the entry point contract;
If there is an initcode field in this wallet, the factory contract will be called to deploy a new smart wallet contract;
If there is no initcode field, the smart wallet contract will be called to execute the specific operation;
In steps 3 and 4, if the user has set a paymaster in the UserOp, then the ETH comes from the paymaster. Otherwise, the gas is paid by the wallet.
Details of EntryPoint Contract
From the above flow we can see that all smart contract wallets are executed through the EntryPoint contract. From a data analysis perspective, let's focus on the functions and events involved in the EntryPoint.
Function | Event |
function addStake | event AccountDeployed |
function balanceOf | event BeforeExecution |
function depositTo | event Deposited |
function deposits | event SignatureAggregatorChanged |
function getDepositInfo | event StakeLocked |
function getNonce | event StakeUnlocked |
function getSenderAddress | event StakeWithdrawn |
function getUserOpHash | event UserOperationEvent |
function incrementNonce | event UserOperationRevertReason |
function innerHandleOp | event Withdrawn |
function nonceSequenceNumber | |
function simulateHandleOp | |
function simulateValidation | |
function unlockStake | |
function withdrawStake | |
function withdrawTo | |
function handleOps | |
function handleAggregatedOps |
handleOps function
Among all the functions, handleOps is the most important function of the entire contract, as all user submitted operations will pass through this function.
Let's take a transaction on Ethereum as an example to illustrate. This operation first creates a new smart wallet contract, then it calls Lido's Warpped stETH contract to swap 0.0001 ETH for wstETH.
0x56971f8e053a236b1700e428f37a6bf9cc197e5f59e36a4571323a84888764eb
The specific execution flow is as follows: https://phalcon.blocksec.com/explorer/tx/eth/0x56971f8e053a236b1700e428f37a6bf9cc197e5f59e36a4571323a84888764eb
Let's first check the handleOps
function definition. This function takes in two parameters: an array of UserOps
called ops
, and a bundler’s beneficiary
address for gas payment.
The details of UserOp
are as follows, with the above example referencing this transaction:
# | Name | Type | Explain | Example |
UserOps | ops.sender | address | the sender account of this request. | 0x8a6a006AFC3f95Fc9CC8f98d8c0eb0f813aC932e |
ops.nonce | uint256 | unique value the sender uses to verify it is not a replay. | 0 | |
ops.initCode | bytes | if set, the account contract will be created by this constructor/ | 0x4e4946298614fc299b50c947289f4ad0572cb9ce5fbfb9cf000000000000000000000000ae8d5ff3714a99588fb35b1a5ad163e203aa94e10000000000000000000000000000000000000000000000000000000000000000 | |
ops.callData | bytes | the method call to execute on this account. | 0x940d3c600000000000000000000000007f39c581f595b53c5cb19bd0b3f8da6c935e2ca000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 | |
ops.callGasLimit | uint256 | the gas limit passed to the callData method call. | 266290 | |
ops.verificationGasLimit | uint256 | gas used for validateUserOp and validatePaymasterUserOp. | 543226 | |
ops.preVerificationGas | uint256 | gas not calculated by the handleOps method, but added to the gas paid. Covers batch overhead. | 54704 | |
ops.maxFeePerGas | uint256 | same as EIP-1559 gas parameter. | 38795079329 | |
ops.maxPriorityFeePerGas | uint256 | same as EIP-1559 gas parameter. | 37236001 | |
ops.paymasterAndData | bytes | if set, this field holds the paymaster address and paymaster-specific data. the paymaster will pay for the transaction instead of the sender. | 0xe93eca6595fe94091dc1af46aac2a8b5d79907700000000000000000000000000000000000000000000000000000000064b5b3d9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff5460f056b99d4b6628a43f0b18567ca8ea2438ead90f1f67bd72cf8080eeea39845e44ad30a33b6604cfac8173816b9d80fd9ed8a17b009f40b459299e03d71c | |
ops.signature | bytes | sender-verified signature over the entire request, the EntryPoint address and the chain ID. | 0x4b6f2be4077cfc39fa37270836820bb5e43be4b38dd7bb5a3664a91bc86980d27ee538937bc15e120261b714eee2d48560c7964354cbfa6af55bd2e1898480921c | |
beneficiary | beneficiary | address | the address to receive the fees | 0x25Df024637d4e56c1aE9563987Bf3e92C9f534c0 |
The above example contains initCode, so the first step will call the wallet factory to create a smart wallet contract. This example uses ZeroDev's, and at this time the transaction will record an event log, containing:
userOpHash
: the userOp that deployed this account. UserOperationEvent will follow.sender
: the account that is deployedfactory
: the factory used to deploy this account (in the initCode)paymaster
: the paymaster used by this UserOp
It then begins to execute the Validate operation, but this operation is not designed with too much information, so let's not consider it for now.
Finally, the EntryPoint will call the smart wallet contract to execute some tasks, such as transfers, interacting with dex, staking, etc. It will finally emit an event log to record the basic information of this Userop, including the following information:
userOpHash
: unique identifier for the request (hash its entire content, except signature).sender
: the account that generates this request.paymaster
: if non-null, the paymaster that pays for this request.nonce
: the nonce value from the request.success
: true if the sender transaction succeeded, false if reverted.actualGasCost
: actual amount paid (by account or paymaster) for this UserOperation.actualGasUsed
: total gas used by this UserOperation (including preVerification, creation, validation and execution).
Reference
Last updated