UNPKG

@owlprotocol/contracts-account-abstraction

Version:

ERC4337 Account Abstraction support for deploying relevant smart contracts and interacting with UserOps.

232 lines (208 loc) 7.35 kB
import { Address, StateOverride, Client, Hex, encodeFunctionData, decodeErrorResult, decodeFunctionResult } from "viem"; import { simulateEntryPoint } from "./simulateEntryPoint.js"; import { parseFailedOpWithRevert } from "../../gasestimation/parseFailedOpWithRevert.js"; import { abi as EntryPointV07SimulationsAbi } from "../../artifacts/EntryPointSimulations.js"; import { PackedUserOperation, ExecutionResult, TargetCallResult, targetCallResultSchema, ExecutionError, ExecutionErrors, ValidationErrors, } from "../../models/index.js"; 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; }; /** * Similar to `simulateHandleOpV07` but reverts on error. * Also a higher-level abstraction as: * - targetAddress = userOp.sender * - targetCallData = userOp.callData * @param packedUserOperation * @param entryPoint * @param client * @param entryPointSimulationsAddress * @param stateOverride * @returns */ export async function getExecutionResult( client: Client, parameters: { packedUserOperation: PackedUserOperation; entryPoint: Address; entryPointSimulationsAddress: Address; stateOverride?: StateOverride[number]; }, ): Promise<SimulateHandleOpResult<"execution">> { const { packedUserOperation, entryPoint, entryPointSimulationsAddress, stateOverride } = parameters; const error = await simulateHandleOpV07(client, { packedUserOperation, entryPoint, targetAddress: packedUserOperation.sender, targetCallData: packedUserOperation.callData, entryPointSimulationsAddress, stateOverride, }); if (error.result === "failed") { throw new ExecutionError( `UserOperation reverted during simulation with reason: ${error.data}`, ExecutionErrors.UserOperationReverted, ); } return error as SimulateHandleOpResult<"execution">; } /** * Simulate UserOpV7. Low level function meant to align with Solidity interface and * decode revert data. * @param packedUserOperation * @param entryPoint * @param client * @param targetAddress currently all calls use userOp.sender but this is here to align with the interface * @param targetCallData currently all calls use userOp.callData but this is here to align with the interface * @param entryPointSimulationsAddress * @param stateOverride * @returns */ export async function simulateHandleOpV07( client: Client, parameters: { packedUserOperation: PackedUserOperation; entryPoint: Address; targetAddress: Address; targetCallData: Hex; entryPointSimulationsAddress: Address; stateOverride?: StateOverride[number] | undefined; }, ): Promise<SimulateHandleOpResult> { const { packedUserOperation, entryPoint, targetAddress, targetCallData, entryPointSimulationsAddress, stateOverride, } = parameters; const entryPointSimulationsSimulateHandleOpCallData = encodeFunctionData({ abi: EntryPointV07SimulationsAbi, functionName: "simulateHandleOp", args: [packedUserOperation], }); const entryPointSimulationsSimulateTargetCallData = encodeFunctionData({ abi: EntryPointV07SimulationsAbi, functionName: "simulateCallData", args: [packedUserOperation, targetAddress, targetCallData], }); const cause = await simulateEntryPoint(client, { entryPoint, entryPointSimulationsCallData: [ entryPointSimulationsSimulateHandleOpCallData, entryPointSimulationsSimulateTargetCallData, ], entryPointSimulationsAddress, stateOverride, }); try { const executionResult = getSimulateHandleOpResult(cause[0]); if (executionResult.result === "failed") { return executionResult; } const targetCallValidationResult = validateTargetCallDataResult(cause[1]); if (targetCallValidationResult.result === "failed") { return targetCallValidationResult; } return { result: "execution", data: { callDataResult: targetCallValidationResult.data, executionResult: (executionResult as SimulateHandleOpResult<"execution">).data.executionResult, }, }; } catch (e) { return { result: "failed", data: "Unknown error, could not parse simulate handle op result.", code: ValidationErrors.SimulateValidation, }; } } function getSimulateHandleOpResult(data: Hex): SimulateHandleOpResult { try { const decodedError = decodeErrorResult({ abi: EntryPointV07SimulationsAbi, data: data, }); if (decodedError && decodedError.errorName === "FailedOp" && decodedError.args) { return { result: "failed", data: decodedError.args[1] as string, code: ValidationErrors.SimulateValidation, } as const; } if (decodedError && decodedError.errorName === "FailedOpWithRevert" && decodedError.args) { return { result: "failed", data: parseFailedOpWithRevert(decodedError.args?.[2] as Hex), code: ValidationErrors.SimulateValidation, } as const; } } catch { // no error we go the result const decodedResult: ExecutionResult = decodeFunctionResult({ abi: EntryPointV07SimulationsAbi, functionName: "simulateHandleOp", data, }) as unknown as ExecutionResult; return { result: "execution", data: { executionResult: decodedResult, } as const, }; } throw new Error("Unexpected error"); } function validateTargetCallDataResult(data: Hex): | { result: "success"; data: TargetCallResult; } | { result: "failed"; data: string; code: number; } { try { const targetCallResult = decodeFunctionResult({ abi: EntryPointV07SimulationsAbi, functionName: "simulateCallData", data: data, }); const parsedTargetCallResult = targetCallResultSchema.parse(targetCallResult); if (parsedTargetCallResult.success) { return { result: "success", data: parsedTargetCallResult, } as const; } return { result: "failed", data: parsedTargetCallResult.returnData, code: ExecutionErrors.UserOperationReverted, } as const; } catch (_e) { // no error we go the result return { result: "failed", data: "Unknown error, could not parse target call data result.", code: ExecutionErrors.UserOperationReverted, } as const; } }