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

add trace_call support to detect v0.6 reverts #325

Closed
wants to merge 5 commits into from
Closed
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
1 change: 1 addition & 0 deletions scripts/config.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"entrypoints": "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789,0x0000000071727De22E5E9d8BAf0edAc6f37da032",
"entrypoint-simulation-contract": "0xBbe8A301FbDb2a4CD58c4A37c262ecef8f889c47",
"enable-debug-endpoints": true,
"code-override-support": true,
"expiration-check": false,
"safe-mode": false,
"api-version": "v1,v2",
Expand Down
3 changes: 2 additions & 1 deletion src/cli/config/bundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ export const rpcArgsSchema = z.object({
"send-transaction-rpc-url": z.string().url().optional(),
"polling-interval": z.number().int().min(0),
"max-block-range": z.number().int().min(0).optional(),
"block-tag-support": z.boolean().optional().default(true)
"block-tag-support": z.boolean().optional().default(true),
"code-override-support": z.boolean().optional().default(false)
})

export const bundleCopmressionArgsSchema = 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 @@ -326,6 +326,12 @@ export const rpcOptions: CliCommandOptions<IRpcArgsInput> = {
type: "boolean",
require: false,
default: true
},
"code-override-support": {
description: "Does the RPC support code overrides",
type: "boolean",
require: false,
default: false
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/cli/setupServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ const getValidator = ({
utilityWalletAddress,
parsedArgs["binary-search-tolerance-delta"],
parsedArgs["binary-search-gas-allowance"],
parsedArgs["code-override-support"],
parsedArgs["entrypoint-simulation-contract"],
parsedArgs["fixed-gas-limit-for-estimation"],
parsedArgs.tenderly,
Expand All @@ -110,6 +111,7 @@ const getValidator = ({
utilityWalletAddress,
parsedArgs["binary-search-tolerance-delta"],
parsedArgs["binary-search-gas-allowance"],
parsedArgs["code-override-support"],
parsedArgs["entrypoint-simulation-contract"],
parsedArgs["fixed-gas-limit-for-estimation"],
parsedArgs.tenderly,
Expand Down
20 changes: 2 additions & 18 deletions src/rpc/estimation/gasEstimationHandler.ts

Large diffs are not rendered by default.

145 changes: 91 additions & 54 deletions src/rpc/estimation/gasEstimationsV06.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ENTRYPOINT_V06_SIMULATION_OVERRIDE,
EntryPointV06Abi,
EntryPointV06SimulationsAbi,
RpcError,
Expand All @@ -17,23 +18,92 @@ import {
} from "viem"
import { z } from "zod"
import type { SimulateHandleOpResult } from "./types"
import { deepHexlify } from "../../utils/userop"

export class GasEstimatorV06 {
publicClient: PublicClient
blockTagSupport: boolean
utilityWalletAddress: Address
fixedGasLimitForEstimation?: bigint
codeOverrideSupport: boolean

constructor(
publicClient: PublicClient,
blockTagSupport: boolean,
utilityWalletAddress: Address,
codeOverrideSupport: boolean,
fixedGasLimitForEstimation?: bigint
) {
this.publicClient = publicClient
this.blockTagSupport = blockTagSupport
this.utilityWalletAddress = utilityWalletAddress
this.fixedGasLimitForEstimation = fixedGasLimitForEstimation
this.codeOverrideSupport = codeOverrideSupport
}

decodeSimulateHandleOpResult(data: Hex): SimulateHandleOpResult {
if (data === "0x") {
throw new RpcError(
"AA23 reverted: UserOperation called non-existant contract, or reverted with 0x",
ValidationErrors.SimulateValidation
)
}

const decodedError = decodeErrorResult({
abi: [...EntryPointV06Abi, ...EntryPointV06SimulationsAbi],
data
})

if (
decodedError &&
decodedError.errorName === "FailedOp" &&
decodedError.args
) {
return {
result: "failed",
data: decodedError.args[1] as string
} as const
}

// custom error thrown by entryPoint if code override is used
if (
decodedError &&
decodedError.errorName === "CallPhaseReverted" &&
decodedError.args
) {
return {
result: "failed",
data: decodedError.args[0]
} as const
}

if (
decodedError &&
decodedError.errorName === "Error" &&
decodedError.args
) {
return {
result: "failed",
data: decodedError.args[0]
} as const
}

if (decodedError.errorName === "ExecutionResult") {
const parsedExecutionResult = executionResultSchema.parse(
decodedError.args
)

return {
result: "execution",
data: {
executionResult: parsedExecutionResult
} as const
}
}

throw new Error(
"Unexpected error whilst decoding simulateHandleOp result"
)
}

async simulateHandleOpV06({
Expand All @@ -49,14 +119,25 @@ export class GasEstimatorV06 {
entryPoint: Address
stateOverrides?: StateOverrides | undefined
}): Promise<SimulateHandleOpResult> {
const {
publicClient,
blockTagSupport,
utilityWalletAddress,
fixedGasLimitForEstimation
} = this

try {
const {
publicClient,
blockTagSupport,
utilityWalletAddress,
fixedGasLimitForEstimation
} = this

if (this.codeOverrideSupport) {
if (stateOverrides === undefined) {
stateOverrides = {}
}

stateOverrides[entryPoint] = {
...deepHexlify(stateOverrides?.[entryPoint] || {}),
code: ENTRYPOINT_V06_SIMULATION_OVERRIDE
}
}

await publicClient.request({
method: "eth_call",
params: [
Expand Down Expand Up @@ -126,55 +207,11 @@ export class GasEstimatorV06 {
throw new Error(JSON.stringify(err.cause))
}

const cause = causeParseResult.data
const data = causeParseResult.data.data

if (cause.data === "0x") {
throw new RpcError(
"AA23 reverted: UserOperation called non-existant contract, or reverted with 0x",
ValidationErrors.SimulateValidation
)
}

const decodedError = decodeErrorResult({
abi: [...EntryPointV06Abi, ...EntryPointV06SimulationsAbi],
data: cause.data
})

if (
decodedError &&
decodedError.errorName === "FailedOp" &&
decodedError.args
) {
return {
result: "failed",
data: decodedError.args[1] as string
} as const
}

if (
decodedError &&
decodedError.errorName === "Error" &&
decodedError.args
) {
return {
result: "failed",
data: decodedError.args[0]
} as const
}

if (decodedError.errorName === "ExecutionResult") {
const parsedExecutionResult = executionResultSchema.parse(
decodedError.args
)

return {
result: "execution",
data: {
executionResult: parsedExecutionResult
} as const
}
}
return this.decodeSimulateHandleOpResult(data)
}

throw new Error("Unexpected error")
}
}
2 changes: 2 additions & 0 deletions src/rpc/validation/SafeValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class SafeValidator
utilityWalletAddress: Address,
binarySearchToleranceDelta: bigint,
binarySearchGasAllowance: bigint,
codeOverrideSupport: boolean,
entryPointSimulationsAddress?: Address,
fixedGasLimitForEstimation?: bigint,
usingTenderly = false,
Expand All @@ -88,6 +89,7 @@ export class SafeValidator
utilityWalletAddress,
binarySearchToleranceDelta,
binarySearchGasAllowance,
codeOverrideSupport,
entryPointSimulationsAddress,
fixedGasLimitForEstimation,
usingTenderly,
Expand Down
3 changes: 2 additions & 1 deletion src/rpc/validation/UnsafeValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export class UnsafeValidator implements InterfaceValidator {
utilityWalletAddress: Address,
binarySearchToleranceDelta: bigint,
binarySearchGasAllowance: bigint,
codeOverrideSupport: boolean,
entryPointSimulationsAddress?: Address,
fixedGasLimitForEstimation?: bigint,
usingTenderly = false,
Expand All @@ -97,6 +98,7 @@ export class UnsafeValidator implements InterfaceValidator {
blockTagSupport,
utilityWalletAddress,
chainType,
codeOverrideSupport,
entryPointSimulationsAddress,
fixedGasLimitForEstimation
)
Expand Down Expand Up @@ -194,7 +196,6 @@ export class UnsafeValidator implements InterfaceValidator {
addSenderBalanceOverride,
balanceOverrideEnabled: this.balanceOverrideEnabled,
entryPoint,
replacedEntryPoint: false,
targetAddress: zeroAddress,
targetCallData: "0x",
stateOverrides
Expand Down
28 changes: 28 additions & 0 deletions src/types/contracts/EntryPointSimulations06.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
export const EntryPointV06SimulationsAbi = [
{
inputs: [
{
name: "reason",
type: "string"
}
],
name: "Error",
type: "error"
}
] as const

export const EntryPointV07SimulationsAbi = [
{
type: "constructor",
Expand Down
3 changes: 2 additions & 1 deletion src/types/contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from "./TestOpcodesAccountFactory"
export * from "./TestStorageAccount"
export * from "./SimpleAccountFactory"
export * from "./CodeHashGetter"
export * from "./EntryPointSimulations"
export * from "./EntryPointSimulations07"
export * from "./EntryPointSimulations06"
export * from "./PimlicoEntryPointSimulations"
1 change: 1 addition & 0 deletions test/e2e/alto-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"mempool-max-parallel-ops": 10,
"mempool-max-queued-ops": 10,
"enforce-unique-senders-per-bundle": false,
"code-override-support": true,
"enable-instant-bundling-endpoint": true
}
30 changes: 30 additions & 0 deletions test/e2e/tests/eth_estimateUserOperationGas.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import type { EntryPointVersion } from "viem/account-abstraction"
import { beforeEach, describe, expect, test } from "vitest"
import { beforeEachCleanUp, getSmartAccountClient } from "../src/utils"
import {
getRevertCall,
deployRevertingContract
} from "../src/revertingContract"
import { Address, BaseError } from "viem"

describe.each([
{
Expand All @@ -12,7 +17,10 @@ describe.each([
])(
"$entryPointVersion supports eth_estimateUserOperationGas",
({ entryPointVersion }) => {
let revertingContract: Address

beforeEach(async () => {
revertingContract = await deployRevertingContract()
await beforeEachCleanUp()
})

Expand Down Expand Up @@ -113,5 +121,27 @@ describe.each([
expect(estimation.paymasterPostOpGasLimit).toBe(0n)
expect(estimation.paymasterVerificationGasLimit).toBe(0n)
})

test("Should throw revert reason if simulation reverted during callphase", async () => {
const smartAccountClient = await getSmartAccountClient({
entryPointVersion
})

try {
await smartAccountClient.estimateUserOperationGas({
calls: [
{
to: revertingContract,
data: getRevertCall("foobar"),
value: 0n
}
]
})
} catch (e: any) {
expect(e).toBeInstanceOf(BaseError)
const err = e.walk()
expect(err.reason).toEqual("foobar")
}
})
}
)
Loading
Loading