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

Feat/7702 support #367

Merged
merged 22 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading