diff --git a/src/rpc/rpcHandler.ts b/src/rpc/rpcHandler.ts index 41b0daa1..982105b0 100644 --- a/src/rpc/rpcHandler.ts +++ b/src/rpc/rpcHandler.ts @@ -381,43 +381,6 @@ export class RpcHandler implements IRpcEndpoint { ) } - let preVerificationGas = await calcPreVerificationGas({ - config: this.config, - userOperation, - entryPoint, - gasPriceManager: this.gasPriceManager, - validate: false - }) - preVerificationGas = scaleBigIntByPercent(preVerificationGas, 110) - - const { - simulationVerificationGasLimit, - simulationCallGasLimit, - simulationPaymasterVerificationGasLimit, - simulationPaymasterPostOpGasLimit - } = this.config - - // biome-ignore lint/style/noParameterAssign: prepare userOperaiton for simulation - userOperation = { - ...userOperation, - preVerificationGas, - verificationGasLimit: simulationVerificationGasLimit, - callGasLimit: simulationCallGasLimit - } - - if (isVersion07(userOperation)) { - userOperation.paymasterVerificationGasLimit = - simulationPaymasterVerificationGasLimit - userOperation.paymasterPostOpGasLimit = - simulationPaymasterPostOpGasLimit - } - - // This is necessary because entryPoint pays - // min(maxFeePerGas, baseFee + maxPriorityFeePerGas) for the verification - // Since we don't want our estimations to depend upon baseFee, we set - // maxFeePerGas to maxPriorityFeePerGas - userOperation.maxPriorityFeePerGas = userOperation.maxFeePerGas - // Check if the nonce is valid // If the nonce is less than the current nonce, the user operation has already been executed // If the nonce is greater than the current nonce, we may have missing user operations in the mempool @@ -462,8 +425,37 @@ export class RpcHandler implements IRpcEndpoint { } } + // Prepare userOperation for simulation + const { + simulationVerificationGasLimit, + simulationCallGasLimit, + simulationPaymasterVerificationGasLimit, + simulationPaymasterPostOpGasLimit + } = this.config + + const simulationUserOperation = { + ...userOperation, + preVerificationGas: 0n, + verificationGasLimit: simulationVerificationGasLimit, + callGasLimit: simulationCallGasLimit + } + + if (isVersion07(simulationUserOperation)) { + simulationUserOperation.paymasterVerificationGasLimit = + simulationPaymasterVerificationGasLimit + simulationUserOperation.paymasterPostOpGasLimit = + simulationPaymasterPostOpGasLimit + } + + // This is necessary because entryPoint pays + // min(maxFeePerGas, baseFee + maxPriorityFeePerGas) for the verification + // Since we don't want our estimations to depend upon baseFee, we set + // maxFeePerGas to maxPriorityFeePerGas + simulationUserOperation.maxPriorityFeePerGas = + simulationUserOperation.maxFeePerGas + const executionResult = await this.validator.getExecutionResult( - userOperation, + simulationUserOperation, entryPoint, queuedUserOperations, true, @@ -472,7 +464,7 @@ export class RpcHandler implements IRpcEndpoint { let { verificationGasLimit, callGasLimit } = calcVerificationGasAndCallGasLimit( - userOperation, + simulationUserOperation, executionResult.data.executionResult, this.config.publicClient.chain.id, executionResult.data.callDataResult @@ -482,8 +474,8 @@ export class RpcHandler implements IRpcEndpoint { let paymasterPostOpGasLimit = 0n if ( - isVersion07(userOperation) && - userOperation.paymaster !== null && + isVersion07(simulationUserOperation) && + simulationUserOperation.paymaster !== null && "paymasterVerificationGasLimit" in executionResult.data.executionResult && "paymasterPostOpGasLimit" in executionResult.data.executionResult @@ -522,23 +514,38 @@ export class RpcHandler implements IRpcEndpoint { callGasLimit = maxBigInt(callGasLimit, 120_000n) } - if (userOperation.callData === "0x") { + if (simulationUserOperation.callData === "0x") { callGasLimit = 0n } - if (isVersion06(userOperation)) { + if (isVersion06(simulationUserOperation)) { callGasLimit = scaleBigIntByPercent( callGasLimit, Number(this.config.callGasLimitMultiplier) ) } + let preVerificationGas = await calcPreVerificationGas({ + config: this.config, + userOperation: { + ...userOperation, + callGasLimit, // use actual callGasLimit + verificationGasLimit, // use actual verificationGasLimit + paymasterPostOpGasLimit, // use actual paymasterPostOpGasLimit + paymasterVerificationGasLimit // use actual paymasterVerificationGasLimit + }, + entryPoint, + gasPriceManager: this.gasPriceManager, + validate: false + }) + preVerificationGas = scaleBigIntByPercent(preVerificationGas, 110) + // TODO: uncomment this // Check if userOperation passes - if (isVersion06(userOperation)) { + if (isVersion06(simulationUserOperation)) { await this.validator.getExecutionResult( { - ...userOperation, + ...simulationUserOperation, preVerificationGas, verificationGasLimit, callGasLimit, @@ -551,7 +558,7 @@ export class RpcHandler implements IRpcEndpoint { ) } - if (isVersion07(userOperation)) { + if (isVersion07(simulationUserOperation)) { return { preVerificationGas, verificationGasLimit, diff --git a/src/types/contracts/OpL1FeeAbi.ts b/src/types/contracts/OpL1FeeAbi.ts index 7ef81a12..79484887 100644 --- a/src/types/contracts/OpL1FeeAbi.ts +++ b/src/types/contracts/OpL1FeeAbi.ts @@ -1,21 +1,45 @@ export const OpL1FeeAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, { - inputs: [ - { - internalType: "bytes", - name: "data", - type: "bytes" - } - ], + inputs: [], + name: "DECIMALS", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "baseFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "pure", + type: "function" + }, + { + inputs: [], + name: "gasPrice", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }], name: "getL1Fee", - outputs: [ - { - internalType: "uint256", - name: "fee", - type: "uint256" - } - ], - stateMutability: "nonpayable", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "bytes", name: "_data", type: "bytes" }], + name: "getL1GasUsed", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", type: "function" }, { @@ -24,5 +48,26 @@ export const OpL1FeeAbi = [ outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function" + }, + { + inputs: [], + name: "overhead", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "scalar", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "version", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function" } ] as const diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 65ccd7a2..5a359cd7 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -16,7 +16,6 @@ import { ContractFunctionRevertedError, EstimateGasExecutionError, FeeCapTooLowError, - type Hex, InsufficientFundsError, IntrinsicGasTooLowError, NonceTooLowError, @@ -24,21 +23,24 @@ import { TransactionExecutionError, type Transport, bytesToHex, - concat, encodeAbiParameters, - getAbiItem, getContract, serializeTransaction, toBytes, - toFunctionSelector, InternalRpcError, - maxUint64 + maxUint64, + encodeFunctionData, + parseGwei, + maxUint256, + toHex, + size } from "viem" import { base, baseGoerli, baseSepolia, lineaSepolia } from "viem/chains" import { maxBigInt, minBigInt, scaleBigIntByPercent } from "./bigInt" -import { isVersion06, toPackedUserOperation } from "./userop" +import { isVersion06, isVersion07, toPackedUserOperation } from "./userop" import type { AltoConfig } from "../createConfig" import { ArbitrumL1FeeAbi } from "../types/contracts/ArbitrumL1FeeAbi" +import crypto from "crypto" export interface GasOverheads { /** @@ -190,21 +192,11 @@ export function removeZeroBytesFromUserOp( nonce: userOpearation.nonce, initCode: userOpearation.initCode, callData: userOpearation.callData, - callGasLimit: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), - verificationGasLimit: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), - preVerificationGas: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), - maxFeePerGas: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), - maxPriorityFeePerGas: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), + callGasLimit: maxUint256, + verificationGasLimit: maxUint256, + preVerificationGas: maxUint256, + maxFeePerGas: maxUint256, + maxPriorityFeePerGas: maxUint256, paymasterAndData: bytesToHex( new Uint8Array(userOpearation.paymasterAndData.length).fill(255) ), @@ -220,16 +212,12 @@ export function removeZeroBytesFromUserOp( return { sender: packedUserOperation.sender, - nonce: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), + nonce: maxUint256, initCode: packedUserOperation.initCode, callData: packedUserOperation.callData, - accountGasLimits: bytesToHex(new Uint8Array(32).fill(255)), - preVerificationGas: BigInt( - "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - ), - gasFees: bytesToHex(new Uint8Array(32).fill(255)), + accountGasLimits: toHex(maxUint256), + preVerificationGas: maxUint256, + gasFees: toHex(maxUint256), paymasterAndData: bytesToHex( new Uint8Array(packedUserOperation.paymasterAndData.length).fill( 255 @@ -379,7 +367,8 @@ export function calcVerificationGasAndCallGasLimit( const calculatedCallGasLimit = callDataResult?.gasUsed ?? - executionResult.paid / userOperation.maxFeePerGas - executionResult.preOpGas + executionResult.paid / userOperation.maxFeePerGas - + executionResult.preOpGas let callGasLimit = maxBigInt(calculatedCallGasLimit, 9000n) + 21_000n + 50_000n @@ -434,39 +423,18 @@ export function calcDefaultPreVerificationGas( // Returns back the bytes for the handleOps call function getHandleOpsCallData(op: UserOperation, entryPoint: Address) { - let selector: Hex - let paramData: Hex - - if (isVersion06(op)) { - const handleOpsAbi = getAbiItem({ - abi: EntryPointV06Abi, - name: "handleOps" - }) - - selector = toFunctionSelector(handleOpsAbi) - paramData = encodeAbiParameters(handleOpsAbi.inputs, [ - [removeZeroBytesFromUserOp(op)], - entryPoint - ]) - } else { - const randomDataUserOp: PackedUserOperation = - removeZeroBytesFromUserOp(op) - - const handleOpsAbi = getAbiItem({ + if (isVersion07(op)) { + return encodeFunctionData({ abi: EntryPointV07Abi, - name: "handleOps" + functionName: "handleOps", + args: [[removeZeroBytesFromUserOp(op)], entryPoint] }) - - selector = toFunctionSelector(handleOpsAbi) - paramData = encodeAbiParameters(handleOpsAbi.inputs, [ - [randomDataUserOp], - entryPoint - ]) } - - const data = concat([selector, paramData]) - - return data + return encodeFunctionData({ + abi: EntryPointV06Abi, + functionName: "handleOps", + args: [[removeZeroBytesFromUserOp(op)], entryPoint] + }) } export async function calcMantlePreVerificationGas( @@ -554,29 +522,61 @@ export async function calcMantlePreVerificationGas( return staticFee + l1RollupFee / l2MaxFee } +function getOpStackHandleOpsCallData( + op: UserOperation, + entryPoint: Address, + verify: boolean +) { + // Only randomize signature during estimations. + if (!verify) { + const randomizeBytes = (length: number) => { + return toHex(crypto.randomBytes(length).toString("hex")) + } + + op = { + ...op, + signature: randomizeBytes(size(op.signature)) + } + } + + if (isVersion07(op)) { + return encodeFunctionData({ + abi: EntryPointV07Abi, + functionName: "handleOps", + args: [[toPackedUserOperation(op)], entryPoint] + }) + } + + return encodeFunctionData({ + abi: EntryPointV06Abi, + functionName: "handleOps", + args: [[op], entryPoint] + }) +} + export async function calcOptimismPreVerificationGas( publicClient: PublicClient, op: UserOperation, entryPoint: Address, staticFee: bigint, gasPriceManager: GasPriceManager, - verify?: boolean + validate: boolean ) { - const data = getHandleOpsCallData(op, entryPoint) + const data = getOpStackHandleOpsCallData(op, entryPoint, validate) const serializedTx = serializeTransaction( { to: entryPoint, chainId: publicClient.chain.id, - nonce: 999999, - gasLimit: maxUint64, - gasPrice: maxUint64, + maxFeePerGas: parseGwei("120"), + maxPriorityFeePerGas: parseGwei("120"), + gas: 10_000_000n, data }, { r: "0x123451234512345123451234512345123451234512345123451234512345", s: "0x123451234512345123451234512345123451234512345123451234512345", - v: 28n + yParity: 1 } ) @@ -588,9 +588,9 @@ export async function calcOptimismPreVerificationGas( } }) - const [{ result: l1Fee }, baseFeePerGas] = await Promise.all([ - opGasPriceOracle.simulate.getL1Fee([serializedTx]), - verify + const [l1Fee, baseFeePerGas] = await Promise.all([ + opGasPriceOracle.read.getL1Fee([serializedTx]), + validate ? gasPriceManager.getMaxBaseFeePerGas() : gasPriceManager.getBaseFee() ])