From d3c1fce9be0b14d877928f0a172cbe4669137e79 Mon Sep 17 00:00:00 2001 From: mouseless <97399882+mouseless-eth@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:23:36 +0100 Subject: [PATCH] cleanup --- src/rpc/estimation/gasEstimation.ts | 29 +-- src/rpc/estimation/gasEstimationsV06.ts | 2 +- src/rpc/estimation/gasEstimationsV07.ts | 320 +++++++++++------------- src/rpc/estimation/types.ts | 159 ++++++++++++ 4 files changed, 307 insertions(+), 203 deletions(-) create mode 100644 src/rpc/estimation/types.ts diff --git a/src/rpc/estimation/gasEstimation.ts b/src/rpc/estimation/gasEstimation.ts index 97c3b1b6..1c613bea 100644 --- a/src/rpc/estimation/gasEstimation.ts +++ b/src/rpc/estimation/gasEstimation.ts @@ -1,16 +1,8 @@ -import { - type ExecutionResult, - type UserOperation, - RpcError, - ValidationErrors -} from "@alto/types" -import type { - StateOverrides, - TargetCallResult, - UserOperationV07 -} from "@alto/types" -import { deepHexlify, isVersion06 } from "@alto/utils" +import { type UserOperation, RpcError, ValidationErrors } from "@alto/types" +import type { StateOverrides, UserOperationV07 } from "@alto/types" import type { Hex } from "viem" +import type { SimulateHandleOpResult } from "./types" +import { deepHexlify, isVersion06 } from "@alto/utils" import { type Address, type PublicClient, toHex } from "viem" import { simulateHandleOpV07 } from "./gasEstimationsV07" import { simulateHandleOpV06 } from "./gasEstimationsV06" @@ -53,19 +45,6 @@ function getStateOverrides({ } } -export type SimulateHandleOpResult< - TypeResult extends "failed" | "execution" = "failed" | "execution" -> = { - result: TypeResult - data: TypeResult extends "failed" - ? string - : { - callDataResult?: TargetCallResult - executionResult: ExecutionResult - } - code?: TypeResult extends "failed" ? number : undefined -} - export function simulateHandleOp( userOperation: UserOperation, queuedUserOperations: UserOperation[], diff --git a/src/rpc/estimation/gasEstimationsV06.ts b/src/rpc/estimation/gasEstimationsV06.ts index 0d0899b3..55891efd 100644 --- a/src/rpc/estimation/gasEstimationsV06.ts +++ b/src/rpc/estimation/gasEstimationsV06.ts @@ -16,7 +16,7 @@ import { toHex } from "viem" import { z } from "zod" -import type { SimulateHandleOpResult } from "./gasEstimation" +import type { SimulateHandleOpResult } from "./types" export async function simulateHandleOpV06( userOperation: UserOperationV06, diff --git a/src/rpc/estimation/gasEstimationsV07.ts b/src/rpc/estimation/gasEstimationsV07.ts index 70444f7e..891e1ab7 100644 --- a/src/rpc/estimation/gasEstimationsV07.ts +++ b/src/rpc/estimation/gasEstimationsV07.ts @@ -24,7 +24,10 @@ import { targetCallResultSchema } from "@alto/types" import { getUserOperationHash, toPackedUserOperation } from "@alto/utils" -import type { SimulateHandleOpResult } from "./gasEstimation" +import { + simulationValidationResultStruct, + type SimulateHandleOpResult +} from "./types" import { AccountExecuteAbi } from "../../types/contracts/IAccountExecute" function getSimulateHandleOpResult(data: Hex): SimulateHandleOpResult { @@ -117,29 +120,6 @@ function validateTargetCallDataResult(data: Hex): minGas: bigint } { try { - // check if the result is a SimulationOutOfGas error - const simulationOutOfGasSelector = toFunctionSelector( - "SimulationOutOfGas(uint256 optimalGas, uint256 minGas, uint256 maxGas)" - ) - - if (slice(data, 0, 4) === simulationOutOfGasSelector) { - const res = decodeErrorResult({ - abi: EntryPointV07SimulationsAbi, - data: data - }) - - if (res.errorName === "SimulationOutOfGas") { - const [optimalGas, minGas, maxGas] = res.args - - return { - result: "retry", - optimalGas, - minGas, - maxGas - } as const - } - } - const targetCallResult = decodeFunctionResult({ abi: EntryPointV07SimulationsAbi, functionName: "simulateCallData", @@ -162,6 +142,29 @@ function validateTargetCallDataResult(data: Hex): code: ExecutionErrors.UserOperationReverted } as const } catch (_e) { + // Check if the result hit eth_call gasLimit. + const simulationOutOfGasSelector = toFunctionSelector( + "SimulationOutOfGas(uint256 optimalGas, uint256 minGas, uint256 maxGas)" + ) + + if (slice(data, 0, 4) === simulationOutOfGasSelector) { + const res = decodeErrorResult({ + abi: EntryPointV07SimulationsAbi, + data: data + }) + + if (res.errorName === "SimulationOutOfGas") { + const [optimalGas, minGas, maxGas] = res.args + + return { + result: "retry", + optimalGas, + minGas, + maxGas + } as const + } + } + // no error we go the result return { result: "failed", @@ -171,18 +174,17 @@ function validateTargetCallDataResult(data: Hex): } } -export async function simulateHandleOpV07( - userOperation: UserOperationV07, - queuedUserOperations: UserOperationV07[], - entryPoint: Address, - publicClient: PublicClient, - entryPointSimulationsAddress: Address, - chainId: number, - blockTagSupport: boolean, - utilityWalletAddress: Address, - finalParam: StateOverrides | undefined = undefined, - fixedGasLimitForEstimation?: bigint -): Promise { +function encodeSimulateHandleOpLast({ + userOperation, + queuedUserOperations, + entryPoint, + chainId +}: { + userOperation: UserOperationV07 + queuedUserOperations: UserOperationV07[] + entryPoint: Address + chainId: number +}): Hex { const userOperations = [...queuedUserOperations, userOperation] const packedUserOperations = userOperations.map((uop) => ({ packedUserOperation: toPackedUserOperation(uop), @@ -196,6 +198,20 @@ export async function simulateHandleOpV07( args: [packedUserOperations.map((uop) => uop.packedUserOperation)] }) + return simulateHandleOpCallData +} + +function encodeSimulateCallData({ + userOperation, + queuedUserOperations, + entryPoint, + chainId +}: { + userOperation: UserOperationV07 + queuedUserOperations: UserOperationV07[] + entryPoint: Address + chainId: number +}): Hex { const queuedOps = queuedUserOperations.map((op) => ({ op: toPackedUserOperation(op), target: op.sender, @@ -222,46 +238,119 @@ export async function simulateHandleOpV07( args: [queuedOps, targetOp, entryPoint, 0n, 1_000n, 1_000_000n] }) + return simulateTargetCallData +} + +// Try to get the calldata gas again if the initial simulation reverted due to hitting the eth_call gasLimit. +async function retryGetCallDataGas( + targetOp: UserOperationV07, + queuedOps: UserOperationV07[], + entryPoint: Address, + chainId: number, + publicClient: PublicClient, + entryPointSimulationsAddress: Address, + blockTagSupport: boolean, + utilityWalletAddress: Address, + stateOverrides: StateOverrides | undefined = undefined, + fixedGasLimitForEstimation?: bigint +): Promise { + const simulateCallData = encodeSimulateCallData({ + userOperation: targetOp, + queuedUserOperations: queuedOps, + entryPoint, + chainId + }) + const cause = await callPimlicoEntryPointSimulations( publicClient, entryPoint, - [simulateHandleOpCallData, simulateTargetCallData], + [simulateCallData], entryPointSimulationsAddress, blockTagSupport, utilityWalletAddress, - finalParam, + stateOverrides, fixedGasLimitForEstimation ) - try { - const [simulateHandleOpResult, simulateTargetCallDataResult] = cause + const simulateCallDataResult = validateTargetCallDataResult(cause[0]) - const executionResult = getSimulateHandleOpResult( - simulateHandleOpResult - ) + process.exit(0) +} + +export async function simulateHandleOpV07( + userOperation: UserOperationV07, + queuedUserOperations: UserOperationV07[], + entryPoint: Address, + publicClient: PublicClient, + entryPointSimulationsAddress: Address, + chainId: number, + blockTagSupport: boolean, + utilityWalletAddress: Address, + stateOverrides: StateOverrides | undefined = undefined, + fixedGasLimitForEstimation?: bigint +): Promise { + const simulateHandleOpLast = encodeSimulateHandleOpLast({ + userOperation, + queuedUserOperations, + entryPoint, + chainId + }) + + const simulateCallData = encodeSimulateCallData({ + userOperation, + queuedUserOperations, + entryPoint, + chainId + }) + + const cause = await callPimlicoEntryPointSimulations( + publicClient, + entryPoint, + [simulateHandleOpLast, simulateCallData], + entryPointSimulationsAddress, + blockTagSupport, + utilityWalletAddress, + stateOverrides, + fixedGasLimitForEstimation + ) + + try { + const simulateHandleOpLastResult = getSimulateHandleOpResult(cause[0]) - if (executionResult.result === "failed") { - return executionResult + if (simulateHandleOpLastResult.result === "failed") { + return simulateHandleOpLastResult } - const targetCallValidationResult = validateTargetCallDataResult( - simulateTargetCallDataResult - ) + const simulateCallDataResult = validateTargetCallDataResult(cause[1]) - if (targetCallValidationResult.result === "failed") { - return targetCallValidationResult + if (simulateCallDataResult.result === "failed") { + return simulateCallDataResult + } + + if (simulateCallDataResult.result === "retry") { + return await retryGetCallDataGas( + userOperation, + queuedUserOperations, + entryPoint, + publicClient, + entryPointSimulationsAddress, + blockTagSupport, + utilityWalletAddress, + stateOverrides, + fixedGasLimitForEstimation + ) } return { result: "execution", data: { - callDataResult: targetCallValidationResult.data, + callDataResult: simulateCallDataResult.data, executionResult: ( - executionResult as SimulateHandleOpResult<"execution"> + simulateHandleOpLastResult as SimulateHandleOpResult<"execution"> ).data.executionResult } } - } catch (e) { + } catch (_e) { return { result: "failed", data: "Unknown error, could not parse simulate handle op result.", @@ -415,130 +504,7 @@ export function getSimulateValidationResult(errorData: Hex): { } } catch { const decodedResult = decodeAbiParameters( - [ - { - components: [ - { - components: [ - { - internalType: "uint256", - name: "preOpGas", - type: "uint256" - }, - { - internalType: "uint256", - name: "prefund", - type: "uint256" - }, - { - internalType: "uint256", - name: "accountValidationData", - type: "uint256" - }, - { - internalType: "uint256", - name: "paymasterValidationData", - type: "uint256" - }, - { - internalType: "bytes", - name: "paymasterContext", - type: "bytes" - } - ], - internalType: "struct IEntryPoint.ReturnInfo", - name: "returnInfo", - type: "tuple" - }, - { - components: [ - { - internalType: "uint256", - name: "stake", - type: "uint256" - }, - { - internalType: "uint256", - name: "unstakeDelaySec", - type: "uint256" - } - ], - internalType: "struct IStakeManager.StakeInfo", - name: "senderInfo", - type: "tuple" - }, - { - components: [ - { - internalType: "uint256", - name: "stake", - type: "uint256" - }, - { - internalType: "uint256", - name: "unstakeDelaySec", - type: "uint256" - } - ], - internalType: "struct IStakeManager.StakeInfo", - name: "factoryInfo", - type: "tuple" - }, - { - components: [ - { - internalType: "uint256", - name: "stake", - type: "uint256" - }, - { - internalType: "uint256", - name: "unstakeDelaySec", - type: "uint256" - } - ], - internalType: "struct IStakeManager.StakeInfo", - name: "paymasterInfo", - type: "tuple" - }, - { - components: [ - { - internalType: "address", - name: "aggregator", - type: "address" - }, - { - components: [ - { - internalType: "uint256", - name: "stake", - type: "uint256" - }, - { - internalType: "uint256", - name: "unstakeDelaySec", - type: "uint256" - } - ], - internalType: - "struct IStakeManager.StakeInfo", - name: "stakeInfo", - type: "tuple" - } - ], - internalType: - "struct IEntryPoint.AggregatorStakeInfo", - name: "aggregatorInfo", - type: "tuple" - } - ], - internalType: - "struct IEntryPointSimulations.ValidationResult", - name: "", - type: "tuple" - } - ], + simulationValidationResultStruct, decodedDelegateAndError.args[1] as Hex )[0] diff --git a/src/rpc/estimation/types.ts b/src/rpc/estimation/types.ts new file mode 100644 index 00000000..bd2f8a27 --- /dev/null +++ b/src/rpc/estimation/types.ts @@ -0,0 +1,159 @@ +import type { + Address, + ExecutionResult, + PackedUserOperation, + TargetCallResult +} from "@alto/types" +import type { Hex } from "viem" + +export type SimulateHandleOpResult< + TypeResult extends "failed" | "execution" = "failed" | "execution" +> = { + result: TypeResult + data: TypeResult extends "failed" + ? string + : { + callDataResult?: TargetCallResult + executionResult: ExecutionResult + } + code?: TypeResult extends "failed" ? number : undefined +} + +// Struct used when calling v0.7 EntryPointSimulations.simulateCallData. +export type CallDataSimulationArgs = { + // UserOperation to simulate. + op: PackedUserOperation + // UserOperation sender. + target: Address + // Encoded userOperation calldata to simulate. + targetCallData: Hex +} + +// Result of EntryPointSimulations.simulateCallData when simulation ends early due to hitting eth_call gasLimit. +export type SimulationOutOfGasResult = { + optimalGas: bigint + minGas: bigint + maxGas: bigint +} + +export const simulationValidationResultStruct = [ + { + components: [ + { + components: [ + { + internalType: "uint256", + name: "preOpGas", + type: "uint256" + }, + { + internalType: "uint256", + name: "prefund", + type: "uint256" + }, + { + internalType: "uint256", + name: "accountValidationData", + type: "uint256" + }, + { + internalType: "uint256", + name: "paymasterValidationData", + type: "uint256" + }, + { + internalType: "bytes", + name: "paymasterContext", + type: "bytes" + } + ], + internalType: "struct IEntryPoint.ReturnInfo", + name: "returnInfo", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256", + name: "stake", + type: "uint256" + }, + { + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256" + } + ], + internalType: "struct IStakeManager.StakeInfo", + name: "senderInfo", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256", + name: "stake", + type: "uint256" + }, + { + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256" + } + ], + internalType: "struct IStakeManager.StakeInfo", + name: "factoryInfo", + type: "tuple" + }, + { + components: [ + { + internalType: "uint256", + name: "stake", + type: "uint256" + }, + { + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256" + } + ], + internalType: "struct IStakeManager.StakeInfo", + name: "paymasterInfo", + type: "tuple" + }, + { + components: [ + { + internalType: "address", + name: "aggregator", + type: "address" + }, + { + components: [ + { + internalType: "uint256", + name: "stake", + type: "uint256" + }, + { + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256" + } + ], + internalType: "struct IStakeManager.StakeInfo", + name: "stakeInfo", + type: "tuple" + } + ], + internalType: "struct IEntryPoint.AggregatorStakeInfo", + name: "aggregatorInfo", + type: "tuple" + } + ], + internalType: "struct IEntryPointSimulations.ValidationResult", + name: "", + type: "tuple" + } +] as const