diff --git a/src/executor/utils.ts b/src/executor/utils.ts index 352a1fcc..9c6235d4 100644 --- a/src/executor/utils.ts +++ b/src/executor/utils.ts @@ -16,6 +16,7 @@ import { } from "@alto/types" import type { Logger } from "@alto/utils" import { + getRevertErrorData, isVersion06, parseViemError, toPackedUserOperation, @@ -207,6 +208,7 @@ export async function filterOpsAndEstimateGas( } catch (err: unknown) { logger.error({ err }, "error estimating gas") const e = parseViemError(err) + if (e instanceof ContractFunctionRevertedError) { const failedOpError = failedOpErrorSchema.safeParse(e.data) const failedOpWithRevertError = @@ -266,8 +268,11 @@ export async function filterOpsAndEstimateGas( resubmitAllOps: false } } - } else if (e instanceof EstimateGasExecutionError) { - if (e.cause instanceof FeeCapTooLowError) { + } else if ( + e instanceof EstimateGasExecutionError || + err instanceof EstimateGasExecutionError + ) { + if (e?.cause instanceof FeeCapTooLowError) { logger.info( { error: e.shortMessage }, "error estimating gas due to max fee < basefee" @@ -280,7 +285,13 @@ export async function filterOpsAndEstimateGas( } try { - const errorHexData = e.details.split("Reverted ")[1] as Hex + let errorHexData: Hex = "0x" + + if (err instanceof EstimateGasExecutionError) { + errorHexData = getRevertErrorData(err) as Hex + } else { + errorHexData = e?.details.split("Reverted ")[1] as Hex + } const errorResult = decodeErrorResult({ abi: isUserOpV06 ? EntryPointV06Abi : EntryPointV07Abi, data: errorHexData @@ -316,7 +327,7 @@ export async function filterOpsAndEstimateGas( )[Number(errorResult.args[0])] failingOp.reason = errorResult.args[1] - } catch (_e: unknown) { + } catch (e: unknown) { logger.error( { error: JSON.stringify(err) }, "failed to parse error result" diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index e55d1ff0..d71d3794 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,6 +1,18 @@ -import { type Address, getAddress } from "viem" +import { + type Address, + getAddress, + BaseError, + type RawContractError +} from "viem" /// Ensure proper equality by converting both addresses into their checksum type export const areAddressesEqual = (a: Address, b: Address) => { return getAddress(a) === getAddress(b) } + +export function getRevertErrorData(err: unknown) { + // biome-ignore lint/style/useBlockStatements: + if (!(err instanceof BaseError)) return undefined + const error = err.walk() as RawContractError + return typeof error?.data === "object" ? error.data?.data : error.data +} diff --git a/test/kinto-e2e/src/index.ts b/test/kinto-e2e/src/index.ts index 3b06d7d7..7af23377 100644 --- a/test/kinto-e2e/src/index.ts +++ b/test/kinto-e2e/src/index.ts @@ -1,5 +1,4 @@ import { - type Address, type Hex, createPublicClient, decodeEventLog, @@ -7,9 +6,10 @@ import { http, parseAbi, parseAbiItem, - getAddress + slice, + hexToNumber } from "viem" -import { BundleBulkerAbi, handleOpsAbi } from "./abi" +import { handleOpsAbi } from "./abi" import { type Pool, createPool } from "@viem/anvil" import type { UserOperation } from "permissionless" import { createPimlicoBundlerClient } from "permissionless/clients/pimlico" @@ -18,22 +18,13 @@ import { KINTO_ENTRYPOINT, kintoMainnet, prettyPrintTxHash, - sleep + sleep, + type OpInfoType, + type CompressedOp, + isCompressed } from "./utils" import { startAlto } from "./setupAlto" -type CompressedOp = { - compressedBytes: Hex - inflator: Address -} - -type OpInfoType = { - opHash: Hex - txHash: Hex - blockNum: bigint - opParams: UserOperation | CompressedOp -} - const canReplayUserOperation = async ({ anvilPool, anvilId, @@ -65,7 +56,7 @@ const canReplayUserOperation = async ({ }) let hash: Hex - if ("inflator" in opParams) { + if (isCompressed(opParams)) { hash = await bundlerClient.sendCompressedUserOperation({ compressedUserOperation: opParams.compressedBytes, inflatorAddress: opParams.inflator, @@ -122,7 +113,8 @@ const main = async () => { let userOperationEvents = await publicClient.getLogs({ address: KINTO_ENTRYPOINT, event: parseAbiItem(userOperationEventAbi), - fromBlock: latestBlock - 10_000n + fromBlock: latestBlock - 10_000n, + toBlock: latestBlock }) userOperationEvents = userOperationEvents.reverse() @@ -163,15 +155,24 @@ const main = async () => { data: rawTx.input }).args[0][0] } catch { - const compressedBytes = decodeFunctionData({ - abi: BundleBulkerAbi, - data: rawTx.input - }).args[0] + // Extract first compressedUserOperation (compressedBytes) + // slice of 9 bytes: + // - 4 Bytes BundleBulker Payload (PerOpInflator Id) + // - 1 Bytes PerOpInflator Payload (number of ops) + // - 4 Bytes PerOpInflator Payload (inflator id) + const bytes = slice(rawTx.input, 9, undefined) + + const compressedLength = hexToNumber(slice(bytes, 0, 2)) + + const compressedBytes = slice( + bytes, + 2, + 2 + compressedLength + ) + opParams = { compressedBytes, - inflator: getAddress( - "0x336a76a7A2a1e97CE20c420F39FC08c441234aa2" - ) + inflator: "0x336a76a7A2a1e97CE20c420F39FC08c441234aa2" } } @@ -229,7 +230,7 @@ const main = async () => { const endTime = performance.now() const elapsedTime = (endTime - startTime) / 1000 - // biome-ignore lint/suspicious/noConsoleLog: + // biome-ignore lint/suspicious/noConsoleLog: console.log( `Processed ${processed}/${totalOps} operations. (processed in ${elapsedTime.toFixed( 2 @@ -240,11 +241,15 @@ const main = async () => { // if any ops failed, print them and exit with 1 if (failedOps.length > 0) { for (const f of failedOps) { + let opType = "uncompressed" + if (isCompressed(f.opParams)) { + opType = "compressed" + } // biome-ignore lint/suspicious/noConsoleLog: console.log( - `FAILED OP: ${f.opHash} (txhash: ${prettyPrintTxHash( - f.txHash - )})` + `[${opType}] FAILED OP: ${ + f.opHash + } (txhash: ${prettyPrintTxHash(f.txHash)})` ) } process.exit(1) diff --git a/test/kinto-e2e/src/utils.ts b/test/kinto-e2e/src/utils.ts index f75b052a..05b26adf 100644 --- a/test/kinto-e2e/src/utils.ts +++ b/test/kinto-e2e/src/utils.ts @@ -1,9 +1,28 @@ -import { defineChain, type Hash } from "viem" +import type { UserOperation } from "permissionless" +import { defineChain, type Hex, type Hash, type Address } from "viem" export const prettyPrintTxHash = (hash: Hash) => { return `https://kintoscan.io/tx/${hash}` } +export type CompressedOp = { + compressedBytes: Hex + inflator: Address +} + +export type OpInfoType = { + opHash: Hex + txHash: Hex + blockNum: bigint + opParams: UserOperation | CompressedOp +} + +export const isCompressed = ( + op: UserOperation | CompressedOp +): op is CompressedOp => { + return "inflator" in op +} + export const kintoMainnet = defineChain({ id: 7887, name: "Kinto Mainnet",