Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AA-398: Standardize 'preVerificationGas' verification & create 'debug_bundler_setConfiguration' #228

Merged
merged 16 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions packages/bundler/contracts/BundlerHelper.sol

This file was deleted.

5 changes: 3 additions & 2 deletions packages/bundler/localconfig/bundler.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"chainId": 1337,
"gasFactor": "1",
"port": "3000",
"privateApiPort": "3001",
Expand All @@ -8,8 +9,8 @@
"minBalance": "1",
"mnemonic": "./localconfig/mnemonic.txt",
"maxBundleGas": 5e6,
"minStake": "1" ,
"minUnstakeDelay": 0 ,
"minStake": "1",
"minUnstakeDelay": 0,
"autoBundleInterval": 3,
"autoBundleMempoolSize": 10,
"rip7560": false,
Expand Down
5 changes: 3 additions & 2 deletions packages/bundler/localconfig/bundler.rip7560.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"chainId": 1337,
"gasFactor": "1",
"port": "3000",
"privateApiPort": "3001",
Expand All @@ -8,8 +9,8 @@
"minBalance": "1",
"mnemonic": "./localconfig/mnemonic.txt",
"maxBundleGas": 30e6,
"minStake": "1" ,
"minUnstakeDelay": 0 ,
"minStake": "1",
"minUnstakeDelay": 0,
"autoBundleInterval": 3,
"autoBundleMempoolSize": 10,
"rip7560": true,
Expand Down
25 changes: 24 additions & 1 deletion packages/bundler/src/BundlerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import ow from 'ow'
// RIP-7560 EntyPoint address
const MIN_UNSTAKE_DELAY = 86400
const MIN_STAKE_VALUE = 1e18.toString()

export interface BundlerConfig {
chainId: number
beneficiary: string
entryPoint: string
gasFactor: string
Expand All @@ -27,10 +29,21 @@ export interface BundlerConfig {
rip7560: boolean
rip7560Mode: string
gethDevMode: boolean

// Config overrides for PreVerificationGas calculation
fixedGasOverhead?: number
perUserOpGasOverhead?: number
perUserOpWordGasOverhead?: number
zeroByteGasCost?: number
nonZeroByteGasCost?: number
expectedBundleSize?: number
estimationSignatureSize?: number
estimationPaymasterDataSize?: number
}

// TODO: implement merging config (args -> config.js -> default) and runtime shape validation
export const BundlerConfigShape = {
chainId: ow.number,
beneficiary: ow.string,
entryPoint: ow.string,
gasFactor: ow.string,
Expand All @@ -52,7 +65,17 @@ export const BundlerConfigShape = {
autoBundleMempoolSize: ow.number,
rip7560: ow.boolean,
rip7560Mode: ow.string.oneOf(['PULL', 'PUSH']),
gethDevMode: ow.boolean
gethDevMode: ow.boolean,

// Config overrides for PreVerificationGas calculation
fixedGasOverhead: ow.optional.number,
perUserOpGasOverhead: ow.optional.number,
perUserOpWordGasOverhead: ow.optional.number,
zeroByteGasCost: ow.optional.number,
nonZeroByteGasCost: ow.optional.number,
expectedBundleSize: ow.optional.number,
estimationSignatureSize: ow.optional.number,
estimationPaymasterDataSize: ow.optional.number
}

// TODO: consider if we want any default fields at all
Expand Down
9 changes: 8 additions & 1 deletion packages/bundler/src/BundlerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ export class BundlerServer {
} = reqItem
debug('>>', { jsonrpc, id, method, params })
try {
const result = deepHexlify(await this.handleMethod(method, params))
const handleResult = await this.handleMethod(method, params)
const result = deepHexlify(handleResult)
debug('sent', method, '-', result)
debug('<<', { jsonrpc, id, result })
return {
Expand Down Expand Up @@ -330,6 +331,12 @@ export class BundlerServer {
case 'debug_bundler_getStakeStatus':
result = await this.debugHandler.getStakeStatus(params[0], params[1])
break
case 'debug_bundler_setConfiguration': {
const pvgc = await this.debugHandler._setConfiguration(params[0])
this.methodHandler.preVerificationGasCalculator = pvgc
}
result = {}
break
default:
throw new RpcError(`Method ${method} is not supported`, -32601)
}
Expand Down
30 changes: 27 additions & 3 deletions packages/bundler/src/DebugMethodHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import ow from 'ow'

import { StakeInfo } from '@account-abstraction/utils'

import { BundlerConfig } from './BundlerConfig'
import { EventsManager } from './modules/EventsManager'
import { ExecutionManager } from './modules/ExecutionManager'
import { ReputationDump, ReputationManager } from './modules/ReputationManager'
import { MempoolManager } from './modules/MempoolManager'
import { ReputationDump, ReputationManager } from './modules/ReputationManager'
import { SendBundleReturn } from './modules/BundleManager'
import { EventsManager } from './modules/EventsManager'
import { StakeInfo } from '@account-abstraction/utils'
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'

/**
* Only parameters in this object can be provided by a 'debug_bundler_setConfiguration' API.
*/
export const BundlerConfigShape = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shadowing definition

fixedGasOverhead: ow.optional.number,
perUserOpGasOverhead: ow.optional.number,
perUserOpWordGasOverhead: ow.optional.number,
zeroByteGasCost: ow.optional.number,
nonZeroByteGasCost: ow.optional.number,
expectedBundleSize: ow.optional.number,
estimationSignatureSize: ow.optional.number,
estimationPaymasterDataSize: ow.optional.number
}

export class DebugMethodHandler {
constructor (
Expand Down Expand Up @@ -76,4 +95,9 @@ export class DebugMethodHandler {
}> {
return await this.repManager.getStakeStatus(address, entryPoint)
}

async _setConfiguration (config: Partial<BundlerConfig>): Promise<PreVerificationGasCalculator> {
ow.object.exactShape(BundlerConfigShape)
return await this.execManager._setConfiguration(config)
}
}
43 changes: 27 additions & 16 deletions packages/bundler/src/MethodHandlerERC4337.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
import debug from 'debug'
import { BigNumber, BigNumberish, Signer } from 'ethers'
import { JsonRpcProvider, Log } from '@ethersproject/providers'
import { EventFragment } from '@ethersproject/abi'

import { MainnetConfig, PreVerificationGasCalculator } from '@account-abstraction/sdk'

import { BundlerConfig } from './BundlerConfig'
import {
AddressZero,
IEntryPoint,
PackedUserOperation,
RpcError,
UserOperation,
UserOperationEventEvent,
ValidationErrors,
requireAddressAndFields,
packUserOp,
PackedUserOperation,
unpackUserOp,
simulationRpcParams,
decodeSimulateHandleOpResult,
AddressZero,
decodeRevertReason,
decodeSimulateHandleOpResult,
deepHexlify,
erc4337RuntimeVersion,
mergeValidationDataValues,
UserOperationEventEvent, IEntryPoint, requireCond, deepHexlify, tostr, erc4337RuntimeVersion
, UserOperation
packUserOp,
requireAddressAndFields,
requireCond,
simulationRpcParams,
tostr,
unpackUserOp
} from '@account-abstraction/utils'
import { BundlerConfig } from './BundlerConfig'

import { ExecutionManager } from './modules/ExecutionManager'
import { StateOverride, UserOperationByHashResponse, UserOperationReceipt } from './RpcTypes'
import { calcPreVerificationGas } from '@account-abstraction/sdk'
import { EventFragment } from '@ethersproject/abi'

export const HEX_REGEX = /^0x[a-fA-F\d]*$/i

Expand Down Expand Up @@ -58,7 +66,8 @@ export class MethodHandlerERC4337 {
readonly provider: JsonRpcProvider,
readonly signer: Signer,
readonly config: BundlerConfig,
readonly entryPoint: IEntryPoint
readonly entryPoint: IEntryPoint,
public preVerificationGasCalculator: PreVerificationGasCalculator
) {
}

Expand Down Expand Up @@ -143,16 +152,18 @@ export class MethodHandlerERC4337 {
} = returnInfo

// todo: use simulateHandleOp for this too...
const callGasLimit = await this.provider.estimateGas({
let callGasLimit = await this.provider.estimateGas({
from: this.entryPoint.address,
to: userOp.sender,
data: userOp.callData
}).then(b => b.toNumber()).catch(err => {
const message = err.message.match(/reason="(.*?)"/)?.at(1) ?? 'execution reverted'
throw new RpcError(message, ValidationErrors.UserOperationReverted)
})
// Results from 'estimateGas' assume making a standalone transaction and paying 21'000 gas extra for it
callGasLimit -= MainnetConfig.transactionGasStipend

const preVerificationGas = calcPreVerificationGas(userOp)
const preVerificationGas = this.preVerificationGasCalculator.estimatePreVerificationGas(userOp)
const verificationGasLimit = BigNumber.from(preOpGas).toNumber()
return {
preVerificationGas,
Expand All @@ -166,7 +177,7 @@ export class MethodHandlerERC4337 {
async sendUserOperation (userOp: UserOperation, entryPointInput: string): Promise<string> {
await this._validateParameters(userOp, entryPointInput)

console.log(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
debug(`UserOperation: Sender=${userOp.sender} Nonce=${tostr(userOp.nonce)} EntryPoint=${entryPointInput} Paymaster=${userOp.paymaster ?? ''}`)
await this.execManager.sendUserOperation(userOp, entryPointInput, false)
return await this.entryPoint.getUserOpHash(packUserOp(userOp))
}
Expand Down
10 changes: 3 additions & 7 deletions packages/bundler/src/modules/BundleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ import {
ValidationErrors,
mergeStorageMap,
packUserOp,
runContractScript
getUserOpHash
} from '@account-abstraction/utils'

import { EventsManager } from './EventsManager'
import { GetUserOpHashes__factory } from '../types'
import { IBundleManager } from './IBundleManager'
import { MempoolEntry } from './MempoolEntry'
import { MempoolManager } from './MempoolManager'
Expand Down Expand Up @@ -373,11 +372,8 @@ export class BundleManager implements IBundleManager {

// helper function to get hashes of all UserOps
async getUserOpHashes (userOps: UserOperation[]): Promise<string[]> {
const { userOpHashes } = await runContractScript(this.entryPoint.provider,
new GetUserOpHashes__factory(),
[this.entryPoint.address, userOps.map(packUserOp)])

return userOpHashes
const network = await this.entryPoint.provider.getNetwork()
return userOps.map(it => getUserOpHash(it, this.entryPoint.address, network.chainId))
}

async getPaymasterBalance (paymaster: string): Promise<BigNumber> {
Expand Down
17 changes: 15 additions & 2 deletions packages/bundler/src/modules/ExecutionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { ReputationManager } from './ReputationManager'
import { IBundleManager } from './IBundleManager'
import {
EmptyValidateUserOpResult,
IValidationManager
IValidationManager, ValidationManager
} from '@account-abstraction/validation-manager'
import { DepositManager } from './DepositManager'
import { BigNumberish, Signer } from 'ethers'
import { BundlerConfig } from '../BundlerConfig'
import { PreVerificationGasCalculator } from '@account-abstraction/sdk'

const debug = Debug('aa.exec')

Expand All @@ -30,7 +32,7 @@ export class ExecutionManager {
constructor (private readonly reputationManager: ReputationManager,
private readonly mempoolManager: MempoolManager,
private readonly bundleManager: IBundleManager,
private readonly validationManager: IValidationManager,
private validationManager: IValidationManager,
private readonly depositManager: DepositManager,
private readonly signer: Signer,
private readonly rip7560: boolean,
Expand Down Expand Up @@ -143,4 +145,15 @@ export class ExecutionManager {
): Promise<[OperationBase[], StorageMap]> {
return await this.bundleManager.createBundle(minBaseFee, maxBundleGas, maxBundleSize)
}

async _setConfiguration (configOverrides: Partial<BundlerConfig>): Promise<PreVerificationGasCalculator> {
const { configuration, entryPoint, unsafe } = this.validationManager._getDebugConfiguration()
const pvgc = new PreVerificationGasCalculator(Object.assign({}, configuration, configOverrides))
this.validationManager = new ValidationManager(
entryPoint,
unsafe,
pvgc
)
return pvgc
}
}
9 changes: 6 additions & 3 deletions packages/bundler/src/modules/initServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,25 @@ import { BundleManagerRIP7560 } from './BundleManagerRIP7560'
import { IBundleManager } from './IBundleManager'
import { DepositManager } from './DepositManager'
import { IRip7560StakeManager__factory } from '@account-abstraction/utils/dist/src/types'
import { PreVerificationGasCalculator, ChainConfigs } from '@account-abstraction/sdk'

/**
* initialize server modules.
* returns the ExecutionManager and EventsManager (for handling events, to update reputation)
* @param config
* @param signer
*/
export function initServer (config: BundlerConfig, signer: Signer): [ExecutionManager, EventsManager, ReputationManager, MempoolManager] {
export function initServer (config: BundlerConfig, signer: Signer): [ExecutionManager, EventsManager, ReputationManager, MempoolManager, PreVerificationGasCalculator] {
const entryPoint = IEntryPoint__factory.connect(config.entryPoint, signer)
const reputationManager = new ReputationManager(getNetworkProvider(config.network), BundlerReputationParams, parseEther(config.minStake), config.minUnstakeDelay)
const mempoolManager = new MempoolManager(reputationManager)
const eventsManager = new EventsManager(entryPoint, mempoolManager, reputationManager)
const mergedPvgcConfig = Object.assign({}, ChainConfigs[config.chainId] ?? {}, config)
const preVerificationGasCalculator = new PreVerificationGasCalculator(mergedPvgcConfig)
let validationManager: IValidationManager
let bundleManager: IBundleManager
if (!config.rip7560) {
validationManager = new ValidationManager(entryPoint, config.unsafe)
validationManager = new ValidationManager(entryPoint, config.unsafe, preVerificationGasCalculator)
bundleManager = new BundleManager(entryPoint, entryPoint.provider as JsonRpcProvider, signer, eventsManager, mempoolManager, validationManager, reputationManager,
config.beneficiary, parseEther(config.minBalance), config.maxBundleGas, config.conditionalRpc)
} else {
Expand All @@ -52,5 +55,5 @@ export function initServer (config: BundlerConfig, signer: Signer): [ExecutionMa
if (config.rip7560 && config.rip7560Mode === 'PUSH') {
executionManager.setAutoBundler(config.autoBundleInterval, config.autoBundleMempoolSize)
}
return [executionManager, eventsManager, reputationManager, mempoolManager]
return [executionManager, eventsManager, reputationManager, mempoolManager, preVerificationGasCalculator]
}
11 changes: 9 additions & 2 deletions packages/bundler/src/runBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,21 @@ export async function runBundler (argv: string[], overrideExit = true): Promise<
execManagerConfig.autoBundleInterval = 0
}

const [execManager, eventsManager, reputationManager, mempoolManager] = initServer(execManagerConfig, wallet)
const [
execManager,
eventsManager,
reputationManager,
mempoolManager,
preVerificationGasCalculator
] = initServer(execManagerConfig, wallet)
const methodHandler = new MethodHandlerERC4337(
execManager,
provider,
wallet,
config,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
entryPoint!
entryPoint!,
preVerificationGasCalculator
)
const methodHandlerRip7560 = new MethodHandlerRIP7560(
execManager,
Expand Down
5 changes: 1 addition & 4 deletions packages/bundler/src/runner/runop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,7 @@ class Runner {
entryPointAddress: this.entryPointAddress,
factoryAddress: accountDeployer,
owner: this.accountOwner,
index: this.index,
overheads: {
// perUserOp: 100000
}
index: this.index
})
return this
}
Expand Down
Loading
Loading