1 of 47

Bundler

(under ERC-4337)

jiahui.cui (doublespending) @ imToken Labs

Dec 21 2023 | Dapp Learning & imToken

Equal access to the Tokenized World

2 of 47

SPEAKER

Jiahui Cui

Researcher @ imToken Labs

You can find more information in

Github: doublespending

Twitter: @0xbbbb_eth

3 of 47

AGENDA

Bundler in ERC-4337

01

UserOp Lifecycle with Bundler

02

Tips for Developers

03

Bundler in the Future

04

4 of 47

Bundler in ERC-4337

01

5 of 47

Role of Bundler in ERC-4337

  • Bundler is an EOA relayer on chain.
  • Bundler triggers Account through EntryPoint.

6 of 47

Bundler Simulation

We need to “simulate” userOp validation (defined by contract) to make sure it is valid, and that it is capable of paying for its own execution.

  • Bundler just cares about userOp validation to get its fee.
  • Bundler does not care about userOp execution.

7 of 47

Bundler Simulation

validateUserOp() {

if block.basefee < 30 gwei {

do something…

}�}�—---------------------------------------------�⇒ Pass ✅

1

2

validateUserOp() {

if block.basefee < 30 gwei {

do something…

}�}�—---------------------------------------------�⇒ Fail ❌

runs simulation at �block number N (basefee = 29 gwei)

sends userOp tx at �block number N+1 (basefee = 31 gwei)

8 of 47

Bundler Simulation

  • We should ensure that userOp validation becomes stateless like EOA validation.

  • If we check based on that rule, we need Node to provide debug_traceCall method.

9 of 47

Forbidden Opcodes During Validation

10 of 47

Forbidden Storage Access During Validation

  • Account is only allowed to access storage slots “associated with its address”
  • “associated with its address” is defined as below (assuming the address is A)
    • Slots of contract A address itself
    • Slot A of any other address
    • Slots of keccak(A || X) + n on any other address (n up to 128)

source: https://eips.ethereum.org/EIPS/eip-4337

11 of 47

Forbidden Storage Access During Validation

Account storage access

undeployed account

account

factory (stake)

account (self & associated storage)

account (self & associated storage)

factory (not stake)

account (self)

account (self & associated storage)

12 of 47

Forbidden Storage Access During Validation

Paymaster storage access

undeployed account

account

paymaster (stake)

account (self & associated storage)

paymaster (self & associated storage)

account (self & associated storage)

paymaster (self & associated storage)

paymaster (not stake)

account (self)

account (self & associated storage)

13 of 47

Forbidden Storage Access During Validation

Factory storage access

undeployed account

account

factory (stake)

account (self & associated storage)

factory (self & associated storage)

account (self & associated storage)

factory (self & associated storage)

factory (not stake)

account (self)

account (self & associated storage)

14 of 47

Forbidden Storage Access During Validation

Why facotry/paymaster should stake to access its storage?

Because factory/paymaster can be used by multi userOps. So, hey can invalid multi userOps at the same time if they can access their storage in validation.

If they stake, we can ban them when they misbehave.

15 of 47

UserOp Lifecycle with Bundler

02

16 of 47

UserOp Lifecycle with Bundler

  • Bundler always works as a provider.

  • It provides apis following ERC-4337 spec.

  • It also provides apis of ETH node.

17 of 47

UserOp Lifecycle with Bundler

STEPS

EOA by Node

AA by Bundler

1. Get Sender

Derive from Private Key

`eth_call` EntryPoint

2. Get Nonce

`eth_getTransactionCount`

`eth_call` EntryPoint

18 of 47

UserOp Lifecycle with Bundler

STEPS

EOA by Node

AA by Bundler

3. Get GasLimit

`eth_estimateGas`

`eth_estimateUserOperationGas`

  • Calculate gas out of `gasleft()`
  • Simulate verification
  • Simulate execution
  • preVerificationGas
  • verificationGas
  • callGasLimit

4. Get GasPrice

`eth_getBlockByNumber` -> baseFee

==============

`eth_maxPriorityFeePerGas` -> priorityFee

`eth_getBlockByNumber` -> baseFee

================

`rundler_maxPriorityFeePerGas` -> priorityFee

To protect bundler, we cannot use priority fee from `eth_maxPriorityFeePerGas` directly on some chains like Arbitrum & Optimism.

19 of 47

UserOp Lifecycle with Bundler

STEPS

EOA by Node

AA by Bundler

5. Send TX

`eth_sendTransaction`

`eth_sendUserOperation`

  • Calculate gas out of `gasleft()`
  • Simulate verification
  • Do not simulate execution
  • Just put the userOp to mempool
  • Bundle Task fetches userOps from mempool

6. Get Recipent

`eth_getTransactionReceipt`

`eth_getUserOperationReceipt`

You can just filter `UserOperationEvent` on EntryPoint instead.

  • `eth_getBlockByNumber`
  • `eth_getLogs`

20 of 47

UserOp Lifecycle with Bundler

OTHER APIS

EOA By Node

AA By Bundler

Get chainId

eth_chainId

eth_chainId

Get supported EntryPoints

eth_supportedEntryPoints

Get Tx By Hash

eth_getTransactionByHash

eth_getUserOperationByHash

21 of 47

Tips for Developers

03

22 of 47

How to inspect a userOp

polygonscan.com

23 of 47

How to inspect a userOp

polygonscan.com

24 of 47

How to inspect a userOp

jiffyscan.xyz

25 of 47

eth_getUserOperationReceipt vs eth_getUserOperationByHash

eth_getUserOperationReceipt

eth_getUserOperationByHash

Only for onchain userOp

  • userOpHash
  • entryPoint
  • Sender
  • Nonce
  • Paymaster
  • actualGasCost
  • actualGasUsed
  • Success
  • Reason
  • logs (by this UserOperation)
  • receipt(for the entire bundle)

Only for onchain userOp

  • userOp
  • entryPoint
  • blockNumber
  • blockHash
  • transactionHash

26 of 47

Tx Error

How I get error messages from my Account / Paymaster / Bundler?

  • Use string error here.
  • EntryPoint can only identify string error.
  • Custom Errors are not a good idea here.

27 of 47

Tx Error

Error Code

Reason

-32602

invalid UserOperation struct/fields

-32500

transaction rejected by entryPoint’s simulateValidation, during wallet creation or validation

-32501

transaction rejected by paymaster’s validatePaymasterUserOp

-32502

transaction rejected because of opcode validation

-32503

UserOperation out of time-range

-32504

transaction rejected because paymaster (or signature aggregator) is throttled/banned

-32505

transaction rejected because paymaster (or signature aggregator) stake or unstake-delay is too low

-32506

transaction rejected because wallet specified unsupported signature aggregator

-32507

transaction rejected because of wallet signature check failed (or paymaster signature)

28 of 47

Validation Rules

Why I cannot pass bundler simulation when my userOp works in foundry?

It is usually because you go against validation rules of bundler.

29 of 47

Forbidden opcodes during validation

PimlicoERC20Paymaster

  • Pay attention to your third party libraries.

30 of 47

Forbidden Storage access during validation

Why my ERC-20 paymaster works for USDT but not USDC?

It is because USDC is a proxy contract. The implementation slot of USDC is not associated storage of account/paymaster.

Paymaster storage access

undeployed account

account

paymaster (stake)

account (self & associated storage)

paymaster (self & associated storage)

account (self & associated storage)

paymaster (self & associated storage)

paymaster (not stake)

account (self)

account (self & associated storage)

31 of 47

Forbidden Storage access during validation

Can we implement a Token Bound Account (TBA) based on ERC-4337?

It’s hard to implement. TBA uses tokenId to fetch owner from the corresponding ERC-721 contract in validation. However, tokenId is not associated storage of account/paymaster.

Paymaster storage access

undeployed account

account

paymaster (stake)

account (self & associated storage)

paymaster (self & associated storage)

account (self & associated storage)

paymaster (self & associated storage)

paymaster (not stake)

account (self)

account (self & associated storage)

32 of 47

Some Implicit Rules

  • MAX_MEMPOOL_USEROPS_PER_SENDER is set by bundler.
    • If you want to send more userOp with the same sender to the mempool, that sender should stake.
    • MAX_MEMPOOL_USEROPS_PER_SENDER = 4
  • If context returned by paymaster is not empty, paymaster should stake

33 of 47

Some Implicit Rules

  • Reputation System

Throttled paymaster can only be used once in a bundle.

34 of 47

Gas Estimation - Dummy Signature

  • Developer should provide dummy account/paymaster signature when calling eth_estimateGas.
    • We should make sure the validation logic can return `validationData` marking signature failure instead of just reverting.
    • If the validation revert, bundler cannot estimate gas.

35 of 47

Gas Estimation - Gas Settings

  • When bundler estimating gas, bundler will set gas limit in userOp to a large value to ensure it can go through all logic and finally get the correct gas limit through the result `paid`.
  • With the large input gas limit, we should not set gas price as usual. Otherwise, the acount/paymaster cannot afford the gas fee and the estimation will revert. The input gas price should be as low as possible.

36 of 47

Gas Estimation - Gas Settings

  • If we setting the input gas price as 0, the result `paid` will become 0 and we cannot derive the correct gas limit. The input gas price cannot be 0.
  • So, setting the input gas price to 1 and the input gas limit to 10e6 maybe a good choice.

Bundler is expected to handle gas setting for us. However, this setting is out of ERC-4337 spec. So, some bundler cannot handle it well.

37 of 47

Gas Estimation - Bundler Interaction

There is currently no spec for bundler gas estimation. Different bundler may use different implementations.

So, if we handle gas estimation in bundler A, the return gas limit from bundler A may be rejected by bundler B.

38 of 47

Gas Price - Priority Fee

  • We cannot use `eth_maxPriorityFeePerGas` to set priority fee of userOp directly on some chains.
  • The Arbitrum sequencer ignores priority fees and eth_maxPriorityFeePerGas always returns 0.
    • To compensate the bundler we require a priority fee that is a fixed percentage of the bundler’s estimated max base fee. We currently charge 5%.
  • Optimism’s priority fee is often orders of magnitude higher than its base fee.
    • Optimism transaction priority fees only apply to the L2 gas component.
    • However, userOp priority fees apply to both L1 and L2 gas component.
    • To compensate the bundler we require a priority fee that is a fixed percentage of the bundler’s estimated max base fee. We currently charge 5%.
  • So, alchemy give a api `rundler_maxPriorityFeePerGas` to implement the above logic.

39 of 47

Gas Price - Buffer

To compensate the bundler, the buffer of gas price is different from normal EOA transaction

40 of 47

Gas Price - Buffer

To compensate the bundler, the buffer of gas price is different from normal EOA transaction

41 of 47

Bundler in the Future

04

42 of 47

P2P Bundler Mempool

UserOp

UserOp

UserOp

userOp mempool

UserOp

UserOp

UserOp

Bundler

Bundler

Bundler

43 of 47

Gas Price Estimation

  • Once the P2P mempool is running, a separate market will emerge for user operation fees.
  • Instead of running static calculations for priority fee as described above, user can use the fee history to estimate their bid.
  • Need a method similar to `eth_feeHistory` that users can query to estimate their bids based on previously bundled operations.

44 of 47

Gas Estimation Specs

45 of 47

Alternative Mempools

UserOp

UserOp

UserOp

Main mempool

UserOp

UserOp

UserOp

Bundler

Bundler

Bundler

UserOp

Alt mempool (white list USDC contract)

46 of 47

Support Aggregator

  • Bundler currently doesn't have support for signature aggregators.
  • Bundler can aggregate signature from the multi userOps
    • save calldata
    • verify signature once for multi userOps
  • For example, bundler can aggregate BLS signatures
    • improve ERC-4337 in L2s.

47 of 47

Thank you

Equal access to the Tokenized World

Dec 21 2023 | Dapp Learning & imToken