Skip to content

Commit

Permalink
Feat/7702 support (#367)
Browse files Browse the repository at this point in the history
* initial commit

* add authorization to estimations

* create helper function to find authorization state overrides

* support 7702 estimations

* remove console.log

* cleanup

* fix

* fix build

* keep existing interaction

* undo lockfile changes

* undo lockfile changes

* fix incorrect tx type

* update endpoint name

* fix build

* always extend 7702Actions

* change order

* cleanup logic

* fix build

* use const

---------

Co-authored-by: mouseless <[email protected]>
  • Loading branch information
mouseless0x and mouseless0x authored Dec 19, 2024
1 parent e0a6456 commit f7ef6a1
Show file tree
Hide file tree
Showing 18 changed files with 842 additions and 419 deletions.
3 changes: 2 additions & 1 deletion src/cli/config/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ export const bundlerArgsSchema = z.object({
),
"refilling-wallets": z.boolean().default(true),
"aa95-gas-multiplier": z.string().transform((val) => BigInt(val)),
"enable-instant-bundling-endpoint": z.boolean()
"enable-instant-bundling-endpoint": z.boolean(),
"enable-experimental-7702-endpoints": z.boolean()
})

export const compatibilityArgsSchema = z.object({
Expand Down
6 changes: 6 additions & 0 deletions src/cli/config/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ export const bundlerOptions: CliCommandOptions<IBundlerArgsInput> = {
"Should the bundler enable the pimlico_sendUserOperationNow endpoint",
type: "boolean",
default: false
},
"enable-experimental-7702-endpoints": {
description:
"Should the bundler enable the pimlico_experimental_sendUserOperation7702 and pimlico_experimental_estimateUserOperationGas7702 endpoint",
type: "boolean",
default: false
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/cli/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { setupServer } from "./setupServer"
import { type AltoConfig, createConfig } from "../createConfig"
import { parseArgs } from "./parseArgs"
import { deploySimulationsContract } from "./deploySimulationsContract"
import { eip7702Actions } from "viem/experimental"

const preFlightChecks = async (config: AltoConfig): Promise<void> => {
for (const entrypoint of config.entrypoints) {
Expand Down Expand Up @@ -137,7 +138,7 @@ export async function bundlerHandler(args_: IOptionsInput): Promise<void> {
)
: createWalletTransport(args.rpcUrl),
chain
})
}).extend(eip7702Actions())

// if flag is set, use utility wallet to deploy the simulations contract
if (args.deploySimulationsContract) {
Expand Down
2 changes: 1 addition & 1 deletion src/cli/parseArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type CamelCasedProperties<T> = {
}

function toCamelCase(str: string): string {
return str.replace(/([-_][a-z])/g, (group) =>
return str.replace(/([-_][a-z0-9])/g, (group) =>
group.toUpperCase().replace("-", "").replace("_", "")
)
}
Expand Down
85 changes: 60 additions & 25 deletions src/executor/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ import {
filterOpsAndEstimateGas,
flushStuckTransaction,
simulatedOpsToResults,
isTransactionUnderpricedError
isTransactionUnderpricedError,
getAuthorizationList
} from "./utils"
import type { SendTransactionErrorType } from "viem"
import type { AltoConfig } from "../createConfig"
import { SendTransactionOptions } from "./types"

export interface GasEstimateResult {
preverificationGas: bigint
Expand Down Expand Up @@ -712,7 +714,7 @@ export class Executor {
return {
mempoolUserOperation: op,
userOperationHash: getUserOperationHash(
op,
deriveUserOperation(op),
entryPoint,
this.config.walletClient.chain.id
)
Expand Down Expand Up @@ -796,7 +798,12 @@ export class Executor {
this.config.legacyTransactions,
this.config.fixedGasLimitForEstimation,
this.reputationManager,
childLogger
childLogger,
getAuthorizationList(
opsWithHashes.map(
({ mempoolUserOperation }) => mempoolUserOperation
)
)
)

if (simulatedOps.length === 0) {
Expand Down Expand Up @@ -870,39 +877,67 @@ export class Executor {
try {
const isLegacyTransaction = this.config.legacyTransactions

const gasOptions = isLegacyTransaction
? { gasPrice: gasPriceParameters.maxFeePerGas }
: {
maxFeePerGas: gasPriceParameters.maxFeePerGas,
maxPriorityFeePerGas:
gasPriceParameters.maxPriorityFeePerGas
}

if (this.config.noProfitBundling) {
const gasPrice = totalBeneficiaryFees / gasLimit
if (isLegacyTransaction) {
gasOptions.gasPrice = gasPrice
gasPriceParameters.maxFeePerGas = gasPrice
gasPriceParameters.maxPriorityFeePerGas = gasPrice
} else {
gasOptions.maxFeePerGas = maxBigInt(
gasPriceParameters.maxFeePerGas = maxBigInt(
gasPrice,
gasOptions.maxFeePerGas || 0n
gasPriceParameters.maxFeePerGas || 0n
)
}
}

const opts = {
account: wallet,
gas: gasLimit,
nonce: nonce,
...gasOptions
const authorizationList = getAuthorizationList(
opsWithHashToBundle.map(
({ mempoolUserOperation }) => mempoolUserOperation
)
)

let opts: SendTransactionOptions
if (isLegacyTransaction) {
opts = {
type: "legacy",
gasPrice: gasPriceParameters.maxFeePerGas,
account: wallet,
gas: gasLimit,
nonce
}
} else if (authorizationList) {
opts = {
type: "eip7702",
maxFeePerGas: gasPriceParameters.maxFeePerGas,
maxPriorityFeePerGas:
gasPriceParameters.maxPriorityFeePerGas,
account: wallet,
gas: gasLimit,
nonce,
authorizationList
}
} else {
opts = {
type: "eip1559",
maxFeePerGas: gasPriceParameters.maxFeePerGas,
maxPriorityFeePerGas:
gasPriceParameters.maxPriorityFeePerGas,
account: wallet,
gas: gasLimit,
nonce
}
}

const userOps = opsWithHashToBundle.map((owh) =>
isUserOpVersion06
? owh.mempoolUserOperation
: toPackedUserOperation(
owh.mempoolUserOperation as UserOperationV07
)
const userOps = opsWithHashToBundle.map(
({ mempoolUserOperation }) => {
const op = deriveUserOperation(mempoolUserOperation)

if (isUserOpVersion06) {
return op
}

return toPackedUserOperation(op as UserOperationV07)
}
) as PackedUserOperation[]

transactionHash = await this.sendHandleOpsTransaction({
Expand Down
28 changes: 28 additions & 0 deletions src/executor/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Account } from "viem"
import { SignedAuthorizationList } from "viem/experimental"

export type SendTransactionOptions =
| {
type: "legacy"
gasPrice: bigint
account: Account
gas: bigint
nonce: number
}
| {
type: "eip1559"
maxFeePerGas: bigint
maxPriorityFeePerGas: bigint
account: Account
gas: bigint
nonce: number
}
| {
type: "eip7702"
maxFeePerGas: bigint
maxPriorityFeePerGas: bigint
account: Account
gas: bigint
nonce: number
authorizationList: SignedAuthorizationList
}
38 changes: 30 additions & 8 deletions src/executor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import {
type UserOperationWithHash,
deriveUserOperation,
failedOpErrorSchema,
failedOpWithRevertErrorSchema
failedOpWithRevertErrorSchema,
MempoolUserOperation,
is7702Type
} from "@alto/types"
import type { Logger } from "@alto/utils"
import {
Expand Down Expand Up @@ -42,13 +44,30 @@ import {
numberToHex,
BaseError
} from "viem"
import { SignedAuthorizationList } from "viem/experimental"

export const isTransactionUnderpricedError = (e: BaseError) => {
return e?.details
?.toLowerCase()
.includes("replacement transaction underpriced")
}

export const getAuthorizationList = (
mempoolUserOperations: MempoolUserOperation[]
): SignedAuthorizationList | undefined => {
const authorizationList = mempoolUserOperations
.map((op) => {
if (is7702Type(op)) {
return op.authorization
}

return undefined
})
.filter((auth) => auth !== undefined) as SignedAuthorizationList

return authorizationList.length > 0 ? authorizationList : undefined
}

export function simulatedOpsToResults(
simulatedOps: {
owh: UserOperationWithHash
Expand Down Expand Up @@ -139,7 +158,8 @@ export async function filterOpsAndEstimateGas(
onlyPre1559: boolean,
fixedGasLimitForEstimation: bigint | undefined,
reputationManager: InterfaceReputationManager,
logger: Logger
logger: Logger,
authorizationList?: SignedAuthorizationList
) {
const simulatedOps: {
owh: UserOperationWithHash
Expand Down Expand Up @@ -169,13 +189,11 @@ export async function filterOpsAndEstimateGas(

const opsToSend = simulatedOps
.filter((op) => op.reason === undefined)
.map((op) => {
.map(({ owh }) => {
const op = deriveUserOperation(owh.mempoolUserOperation)
return isUserOpV06
? op.owh.mempoolUserOperation
: toPackedUserOperation(
op.owh
.mempoolUserOperation as UserOperationV07
)
? op
: toPackedUserOperation(op as UserOperationV07)
})

gasLimit = await ep.estimateGas.handleOps(
Expand All @@ -188,6 +206,9 @@ export async function filterOpsAndEstimateGas(
...(fixedEstimationGasLimit !== undefined && {
gas: fixedEstimationGasLimit
}),
...(authorizationList !== undefined && {
authorizationList
}),
...gasOptions
}
)
Expand Down Expand Up @@ -391,6 +412,7 @@ export async function filterOpsAndEstimateGas(
}
return { simulatedOps, gasLimit: 0n }
}

export async function flushStuckTransaction(
publicClient: PublicClient,
walletClient: WalletClient<Transport, Chain, Account | undefined>,
Expand Down
12 changes: 6 additions & 6 deletions src/mempool/mempool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,13 +566,13 @@ export class MemoryMempool {
)
}

validationResult = await this.validator.validateUserOperation(
false,
op,
validationResult = await this.validator.validateUserOperation({
shouldCheckPrefund: false,
userOperation: op,
queuedUserOperations,
opInfo.entryPoint,
opInfo.referencedContracts
)
entryPoint: opInfo.entryPoint,
referencedContracts: opInfo.referencedContracts
})
} catch (e) {
this.logger.error(
{
Expand Down
5 changes: 5 additions & 0 deletions src/rpc/estimation/gasEstimationHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { GasEstimatorV06 } from "./gasEstimationsV06"
import { GasEstimatorV07 } from "./gasEstimationsV07"
import type { SimulateHandleOpResult } from "./types"
import type { AltoConfig } from "../../createConfig"
import { SignedAuthorizationList } from "viem/experimental"

function getStateOverrides({
addSenderBalanceOverride,
Expand Down Expand Up @@ -47,6 +48,7 @@ export class GasEstimationHandler {
entryPoint,
targetAddress,
targetCallData,
authorizationList,
stateOverrides = {}
}: {
userOperation: UserOperation
Expand All @@ -56,6 +58,7 @@ export class GasEstimationHandler {
entryPoint: Address
targetAddress: Address
targetCallData: Hex
authorizationList?: SignedAuthorizationList
stateOverrides?: StateOverrides
}): Promise<SimulateHandleOpResult> {
let finalStateOverride = undefined
Expand All @@ -75,6 +78,7 @@ export class GasEstimationHandler {
entryPoint,
targetAddress,
targetCallData,
authorizationList,
stateOverrides: finalStateOverride
})
}
Expand All @@ -83,6 +87,7 @@ export class GasEstimationHandler {
userOperation: userOperation as UserOperationV07,
queuedUserOperations: queuedUserOperations as UserOperationV07[],
entryPoint,
authorizationList,
stateOverrides
})
}
Expand Down
12 changes: 12 additions & 0 deletions src/rpc/estimation/gasEstimationsV06.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import type { SimulateHandleOpResult } from "./types"
import type { AltoConfig } from "../../createConfig"
import { deepHexlify } from "../../utils/userop"
import { parseFailedOpWithRevert } from "./gasEstimationsV07"
import { SignedAuthorizationList } from "viem/experimental"
import { addAuthorizationStateOverrides } from "@alto/utils"

export class GasEstimatorV06 {
private config: AltoConfig
Expand Down Expand Up @@ -114,12 +116,14 @@ export class GasEstimatorV06 {
targetCallData,
entryPoint,
useCodeOverride = true,
authorizationList,
stateOverrides = undefined
}: {
userOperation: UserOperationV06
targetAddress: Address
targetCallData: Hex
entryPoint: Address
authorizationList?: SignedAuthorizationList
useCodeOverride?: boolean
stateOverrides?: StateOverrides | undefined
}): Promise<SimulateHandleOpResult> {
Expand All @@ -142,6 +146,14 @@ export class GasEstimatorV06 {
}
}

if (authorizationList) {
stateOverrides = await addAuthorizationStateOverrides({
stateOverrides,
authorizationList,
publicClient
})
}

// Remove state override if not supported by network.
if (!this.config.balanceOverride) {
stateOverrides = undefined
Expand Down
Loading

0 comments on commit f7ef6a1

Please sign in to comment.