DeFI Decode: The Inside-Out of Account Abstraction Solidity Implementation-Part I.

Ben Liu
6 min readOct 20, 2023

— Article By BITBLOCK Technology

The code is by eth-infinitism project team (github link).

An infinite game is not played for winning, but for continuing the play

— James Carse, Finite & Infinite Games 1986

Note this is an solidity source code structure & logic walk through article, NOT on general concept of account abstraction. The concept can be found by Vitalik’s writing. This article targets to extend the code structure described in this article from Alchemy. It uses the github implementation by eth-infinitism to fill in such details. After going through this, anyone can clone, work and contribute back to AA.

Enjoy!

Picture Credit link.

Content

  • The overall AA execution flow
  • The deployment/creation of target smart wallet contract
  • The verification by account and paymaster
  • Gas fees calculation and refund

The Overall AA execution Flow

We start from the diagram provided by David Philipson in Alchemy article, reproduced below for easier reference. I recommend you to also go through David’s article before continue here.

In summary, you can see that:

  • Executor mainly call EntryPoint.handleOps
  • EntryPoint.handleOps will deploy contract if needs to
  • It then will call validatePaymasterOp, validateOp, and executeOp to fulfill the user request
  • It finally refund ETH gas fee back to executor.
Figure: Account Abstraction execution flow. Note that the dotted line means executor will do offline verification first before trigger it online (saving gas fee).

The above conceptual flow is implemented in EntryPoint.sol. Function handleOps is shown below for further explanation. The function has 2 input parameters and 3 steps (marked out in red).

  • The 2 inputs are: the UserOperation struct (i.e., user request) and the address to refund back the gas fee.
  • The 3 steps: verification, execution and refund back of gas cost.
Figure: EntryPoint.handleOps execution flow.

Note userOps in an array of multiple userOperation, and the code will verify all first before any execution. this is to prevent that any failed individul userOps cause the whole function to revert back.

The deployment/creation of target smart wallet contract

AA smart contract allows you to send transaction or userOps before the wallet even exists. However, after the EntryPoint receives the transation, it needs this target wallet to do verfication. Hence, it will check and create the wallet if it is not exist.

Now back to the 3 steps in above picture, we can pause and think, in which step should the wallet be created and why?

Pause … pause…

Answer:

  • Where: the verification step
  • Why: before moving to step 2 on execution, the UserOp needs to be verified, done by a function call to the smart wallet from the EntryPoint. Hence the wallet needs to be checked and created before verificationOp.

Now let’s move on to the intersting part — code details. The creation logic is implemented in function _validateAccountPrepayment, shown below. You can also see that, after creation, the code will do funding check before do verification. this is to ensure that the wallet has enough fund to pay back the gas fee.

Further details on how to create an account in gas efficient manner?

Since all smart contract users need this account deployment, how can we do this more gas efficiently? This of course depends on the smart wallet implementation itself. Assume we have done the design/develop of wallet smart contract, how can we deploy it in gas efficient way

Again, pause a bit here and think your own solution …

Pause…

For deployment, we have 2 ways to create an account on chain:

  • Way 1: use wallet contract’s own initCode with constructor parameters to create.
  • Way 2: find another already deployed contract (work as a factory) and call factory.create() with initialising parameters to create

Which way is more gas efficient?

The 2nd way is way more efficient because of the less calldata used. You can use the SampleAccount.sol (link) to get initCode and compare yourself. Way 2 can easily save about 90% of gas cost from passing data.

So then how to get factory address and input parameters? You can put such needed info into the initCode, with an example given below.

bytes memory initCode = abi.encodePacked( address(simpleAcountFact),

abi.encodeWithSignature(“createAccount(address,uint256)”, owner, salt));

Back to solidity, the factory call for creation is in contracts/core/SenderCreator.sol with function createSender. You can see clearly the decoding of InitCode to obtain all needed info for contract creation.

After contract deployment, let’s move on to verification.

Verification by account and paymaster

The verification is the most flexible part. It is expected that different smart wallet will adopt different ways. An fun fact, when Vitalik was asked that if he were to redesign the ethereum, which part he will change. Guess what, he said transaction verification.

We will use ECDSA based verification to continue our explanation.

On the two verification:

  • The account level verification is implemented in _validateSignature by recover the signer address from UserOpHash and signature and compare with setted owner.
  • Paymaster verification is done the same way, as shown in the two screenshot below.

Gas fees calculation and refund

The gas cost are first prefunded, with actual usage measured, and finally returned. The implementation is at various places as expected. Overall. it is done in 4 steps:

The step 1 on total gas allowed is in function _getRequiredPRefund:

Step 2) deposit deduction is in EntryPoint._validateAccountPrepayment,

Step 3) actual gas usage is measured by calling gasleft() in various functions. An example given below, with gasleft() called at begin and end of the related executing function.

Step 4) refund back has 2 sub steps:

  • in _handlePostOp, excess pre-fund amount is returned back to user’s deposit
  • in handleOps, the actual used gas is compensated back to beneficiary.

Details are below.

Figure: return excess prefund amount back to user.
Figure: _compensate used gas to beneficiary.

The End

By now, you should have decent understanding the AA implementation. You can now further try the code and even improve it.

Thanks for your time.

BITBLOCK Technology targets to be the expert on DeFI smart contract implementation and audit. Contact us if you need help or have questions.

--

--