diff --git a/src/cli/config/bundler.ts b/src/cli/config/bundler.ts index 87780231..14300e2e 100644 --- a/src/cli/config/bundler.ts +++ b/src/cli/config/bundler.ts @@ -122,7 +122,13 @@ export const bundlerArgsSchema = z.object({ }) export const compatibilityArgsSchema = z.object({ - "chain-type": z.enum(["default", "op-stack", "arbitrum", "hedera"]), + "chain-type": z.enum([ + "default", + "op-stack", + "arbitrum", + "hedera", + "mantle" + ]), "legacy-transactions": z.boolean(), "api-version": z .string() diff --git a/src/cli/config/options.ts b/src/cli/config/options.ts index 833b9950..51ccc626 100644 --- a/src/cli/config/options.ts +++ b/src/cli/config/options.ts @@ -214,7 +214,7 @@ export const compatibilityOptions: CliCommandOptions = description: "Indicates weather the chain is a OP stack chain, arbitrum chain, or default EVM chain", type: "string", - choices: ["default", "op-stack", "arbitrum", "hedera"], + choices: ["default", "op-stack", "arbitrum", "hedera", "mantle"], default: "default" }, "legacy-transactions": { diff --git a/src/handlers/gasPriceManager.ts b/src/handlers/gasPriceManager.ts index 4d0094ba..abbb67c4 100644 --- a/src/handlers/gasPriceManager.ts +++ b/src/handlers/gasPriceManager.ts @@ -5,7 +5,7 @@ import { } from "@alto/types" import { type Logger, maxBigInt, minBigInt } from "@alto/utils" import * as sentry from "@sentry/node" -import { type PublicClient, maxUint128, parseGwei } from "viem" +import { type PublicClient, parseGwei } from "viem" import { avalanche, celo, @@ -27,118 +27,162 @@ enum ChainId { const MIN_POLYGON_GAS_PRICE = parseGwei("31") const MIN_MUMBAI_GAS_PRICE = parseGwei("1") -function getGasStationUrl(chainId: ChainId.Polygon | ChainId.Mumbai): string { - switch (chainId) { - case ChainId.Polygon: - return "https://gasstation.polygon.technology/v2" - case ChainId.Mumbai: - return "https://gasstation-testnet.polygon.technology/v2" - } -} - -class ArbitrumManager { - private queueL1BaseFee: { timestamp: number; baseFee: bigint }[] - private queueL2BaseFee: { timestamp: number; baseFee: bigint }[] - - private maxQueueSize - private queueValidity = 15_000 +class TimedValueQueue { + private queue: { timestamp: number; value: bigint }[] + private maxQueueSize: number + private queueValidity: number - constructor(maxQueueSize: number) { + constructor(maxQueueSize: number, queueValidity: number) { + this.queue = [] this.maxQueueSize = maxQueueSize - this.queueL1BaseFee = [] - this.queueL2BaseFee = [] + this.queueValidity = queueValidity } - public saveL1BaseFee(baseFee: bigint) { - if (baseFee === 0n) { + public saveValue(value: bigint) { + if (value === 0n) { return } - const queue = this.queueL1BaseFee - const last = queue.length > 0 ? queue[queue.length - 1] : null + const last = this.queue[this.queue.length - 1] const timestamp = Date.now() if (!last || timestamp - last.timestamp >= this.queueValidity) { - if (queue.length >= this.maxQueueSize) { - queue.shift() + if (this.queue.length >= this.maxQueueSize) { + this.queue.shift() } - queue.push({ baseFee, timestamp }) - } else if (baseFee < last.baseFee) { - last.baseFee = baseFee + this.queue.push({ value, timestamp }) + } else if (value < last.value) { + last.value = value last.timestamp = timestamp } } - public saveL2BaseFee(baseFee: bigint) { - if (baseFee === 0n) { - return + public getLatestValue(): bigint | null { + if (this.queue.length === 0) { + return null } + return this.queue[this.queue.length - 1].value + } - const queue = this.queueL2BaseFee - const last = queue.length > 0 ? queue[queue.length - 1] : null - const timestamp = Date.now() - - if (!last || timestamp - last.timestamp >= this.queueValidity) { - if (queue.length >= this.maxQueueSize) { - queue.shift() - } - queue.push({ baseFee, timestamp }) - } else if (baseFee < last.baseFee) { - last.baseFee = baseFee - last.timestamp = timestamp + public getMinValue(defaultValue: bigint) { + if (this.queue.length === 0) { + return defaultValue } + return this.queue.reduce( + (acc, cur) => (cur.value < acc ? cur.value : acc), + this.queue[0].value + ) } - public async getMinL1BaseFee() { - const queue = this.queueL1BaseFee - - if (queue.length === 0) { - return 1n + public getMaxValue(defaultValue: bigint) { + if (this.queue.length === 0) { + return defaultValue } - return queue.reduce( - (acc: bigint, cur) => minBigInt(cur.baseFee, acc), - queue[0].baseFee + return this.queue.reduce( + (acc, cur) => (cur.value > acc ? cur.value : acc), + this.queue[0].value ) } - public async getMaxL1BaseFee() { - const queue = this.queueL1BaseFee + public isEmpty(): boolean { + return this.queue.length === 0 + } +} - if (queue.length === 0) { - return maxUint128 - } +function getGasStationUrl(chainId: ChainId.Polygon | ChainId.Mumbai): string { + switch (chainId) { + case ChainId.Polygon: + return "https://gasstation.polygon.technology/v2" + case ChainId.Mumbai: + return "https://gasstation-testnet.polygon.technology/v2" + } +} - return queue.reduce( - (acc: bigint, cur) => maxBigInt(cur.baseFee, acc), - queue[0].baseFee +class MantleManager { + private tokenRatioQueue: TimedValueQueue + private scalarQueue: TimedValueQueue + private rollupDataGasAndOverheadQueue: TimedValueQueue + private l1GasPriceQueue: TimedValueQueue + + constructor(maxQueueSize: number) { + const queueValidity = 15_000 + this.tokenRatioQueue = new TimedValueQueue(maxQueueSize, queueValidity) + this.scalarQueue = new TimedValueQueue(maxQueueSize, queueValidity) + this.rollupDataGasAndOverheadQueue = new TimedValueQueue( + maxQueueSize, + queueValidity ) + this.l1GasPriceQueue = new TimedValueQueue(maxQueueSize, queueValidity) } - public async getMaxL2BaseFee() { - const queue = this.queueL2BaseFee - - if (queue.length === 0) { - return maxUint128 + public getMinMantleOracleValues() { + return { + minTokenRatio: this.tokenRatioQueue.getMinValue(1n), + minScalar: this.scalarQueue.getMinValue(1n), + minRollupDataGasAndOverhead: + this.rollupDataGasAndOverheadQueue.getMinValue(1n), + minL1GasPrice: this.l1GasPriceQueue.getMinValue(1n) } + } - return queue.reduce( - (acc: bigint, cur) => maxBigInt(cur.baseFee, acc), - queue[0].baseFee - ) + public saveMantleOracleValues({ + tokenRatio, + scalar, + rollupDataGasAndOverhead, + l1GasPrice + }: { + tokenRatio: bigint + scalar: bigint + rollupDataGasAndOverhead: bigint + l1GasPrice: bigint + }) { + this.tokenRatioQueue.saveValue(tokenRatio) + this.scalarQueue.saveValue(scalar) + this.rollupDataGasAndOverheadQueue.saveValue(rollupDataGasAndOverhead) + this.l1GasPriceQueue.saveValue(l1GasPrice) + } +} + +class ArbitrumManager { + private l1BaseFeeQueue: TimedValueQueue + private l2BaseFeeQueue: TimedValueQueue + + constructor(maxQueueSize: number) { + const queueValidity = 15_000 + this.l1BaseFeeQueue = new TimedValueQueue(maxQueueSize, queueValidity) + this.l2BaseFeeQueue = new TimedValueQueue(maxQueueSize, queueValidity) + } + + public saveL1BaseFee(baseFee: bigint) { + this.l1BaseFeeQueue.saveValue(baseFee) + } + + public saveL2BaseFee(baseFee: bigint) { + this.l2BaseFeeQueue.saveValue(baseFee) + } + + public getMinL1BaseFee() { + return this.l1BaseFeeQueue.getMinValue(1n) + } + + public getMaxL1BaseFee() { + const maxUint128 = (1n << 128n) - 1n + return this.l1BaseFeeQueue.getMaxValue(maxUint128) + } + + public getMaxL2BaseFee() { + const maxUint128 = (1n << 128n) - 1n + return this.l2BaseFeeQueue.getMaxValue(maxUint128) } } export class GasPriceManager { private readonly config: AltoConfig - private queueBaseFeePerGas: { timestamp: number; baseFeePerGas: bigint }[] = - [] // Store pairs of [price, timestamp] - private queueMaxFeePerGas: { timestamp: number; maxFeePerGas: bigint }[] = - [] // Store pairs of [price, timestamp] - private queueMaxPriorityFeePerGas: { - timestamp: number - maxPriorityFeePerGas: bigint - }[] = [] // Store pairs of [price, timestamp] + private baseFeePerGasQueue: TimedValueQueue + private maxFeePerGasQueue: TimedValueQueue + private maxPriorityFeePerGasQueue: TimedValueQueue public arbitrumManager: ArbitrumManager + public mantleManager: MantleManager private maxQueueSize: number private logger: Logger @@ -152,6 +196,20 @@ export class GasPriceManager { ) this.maxQueueSize = this.config.gasPriceExpiry + const queueValidity = 1000 // milliseconds + this.baseFeePerGasQueue = new TimedValueQueue( + this.maxQueueSize, + queueValidity + ) + this.maxFeePerGasQueue = new TimedValueQueue( + this.maxQueueSize, + queueValidity + ) + this.maxPriorityFeePerGasQueue = new TimedValueQueue( + this.maxQueueSize, + queueValidity + ) + // Periodically update gas prices if specified if (this.config.gasPriceRefreshInterval > 0) { setInterval(() => { @@ -164,6 +222,7 @@ export class GasPriceManager { } this.arbitrumManager = new ArbitrumManager(this.maxQueueSize) + this.mantleManager = new MantleManager(this.maxQueueSize) } public init() { @@ -411,62 +470,6 @@ export class GasPriceManager { return { maxFeePerGas, maxPriorityFeePerGas } } - private saveBaseFeePerGas(gasPrice: bigint, timestamp: number) { - const queue = this.queueBaseFeePerGas - const last = queue.length > 0 ? queue[queue.length - 1] : null - - if (!last || timestamp - last.timestamp >= 1000) { - if (queue.length >= this.maxQueueSize) { - queue.shift() - } - queue.push({ baseFeePerGas: gasPrice, timestamp }) - } else if (gasPrice < last.baseFeePerGas) { - last.baseFeePerGas = gasPrice - last.timestamp = timestamp - } - } - - private saveMaxFeePerGas(gasPrice: bigint, timestamp: number) { - const queue = this.queueMaxFeePerGas - const last = queue.length > 0 ? queue[queue.length - 1] : null - - if (!last || timestamp - last.timestamp >= 1000) { - if (queue.length >= this.maxQueueSize) { - queue.shift() - } - queue.push({ maxFeePerGas: gasPrice, timestamp }) - } else if (gasPrice < last.maxFeePerGas) { - last.maxFeePerGas = gasPrice - last.timestamp = timestamp - } - } - - private saveMaxPriorityFeePerGas(gasPrice: bigint, timestamp: number) { - const queue = this.queueMaxPriorityFeePerGas - const last = queue.length > 0 ? queue[queue.length - 1] : null - - if (!last || timestamp - last.timestamp >= 1000) { - if (queue.length >= this.maxQueueSize) { - queue.shift() - } - queue.push({ maxPriorityFeePerGas: gasPrice, timestamp }) - } else if (gasPrice < last.maxPriorityFeePerGas) { - last.maxPriorityFeePerGas = gasPrice - last.timestamp = timestamp - } - } - - private saveGasPrice(gasPrice: GasPriceParameters, timestamp: number) { - return new Promise((resolve) => { - this.saveMaxFeePerGas(gasPrice.maxFeePerGas, timestamp) - this.saveMaxPriorityFeePerGas( - gasPrice.maxPriorityFeePerGas, - timestamp - ) - resolve() - }) - } - private async innerGetGasPrice(): Promise { let maxFeePerGas = 0n let maxPriorityFeePerGas = 0n @@ -533,12 +536,12 @@ export class GasPriceManager { } const baseFee = latestBlock.baseFeePerGas - this.saveBaseFeePerGas(baseFee, Date.now()) + this.baseFeePerGasQueue.saveValue(baseFee) return baseFee } - public getBaseFee() { + public async getBaseFee(): Promise { if (this.config.legacyTransactions) { throw new RpcError( "baseFee is not available for legacy transactions" @@ -546,41 +549,38 @@ export class GasPriceManager { } if (this.config.gasPriceRefreshInterval === 0) { - return this.updateBaseFee() + return await this.updateBaseFee() } - const { baseFeePerGas } = - this.queueBaseFeePerGas[this.queueBaseFeePerGas.length - 1] + const baseFee = this.baseFeePerGasQueue.getLatestValue() + if (baseFee === null) { + throw new RpcError("No base fee available") + } - return baseFeePerGas + return baseFee } private async updateGasPrice(): Promise { const gasPrice = await this.innerGetGasPrice() - this.saveGasPrice( - { - maxFeePerGas: gasPrice.maxFeePerGas, - maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas - }, - Date.now() - ) + this.maxFeePerGasQueue.saveValue(gasPrice.maxFeePerGas) + this.maxPriorityFeePerGasQueue.saveValue(gasPrice.maxPriorityFeePerGas) return gasPrice } - public getGasPrice() { + public async getGasPrice(): Promise { if (this.config.gasPriceRefreshInterval === 0) { - return this.updateGasPrice() + return await this.updateGasPrice() } - const { maxPriorityFeePerGas } = - this.queueMaxPriorityFeePerGas[ - this.queueMaxPriorityFeePerGas.length - 1 - ] + const maxFeePerGas = this.maxFeePerGasQueue.getLatestValue() + const maxPriorityFeePerGas = + this.maxPriorityFeePerGasQueue.getLatestValue() - const { maxFeePerGas } = - this.queueMaxFeePerGas[this.queueMaxFeePerGas.length - 1] + if (maxFeePerGas === null || maxPriorityFeePerGas === null) { + throw new RpcError("No gas price available") + } return { maxFeePerGas, @@ -588,37 +588,28 @@ export class GasPriceManager { } } - public async getMaxBaseFeePerGas() { - if (this.queueBaseFeePerGas.length === 0) { + public async getMaxBaseFeePerGas(): Promise { + if (this.baseFeePerGasQueue.isEmpty()) { await this.getBaseFee() } - return this.queueBaseFeePerGas.reduce( - (acc: bigint, cur) => maxBigInt(cur.baseFeePerGas, acc), - this.queueBaseFeePerGas[0].baseFeePerGas - ) + return this.baseFeePerGasQueue.getMaxValue(0n) } - private async getMinMaxFeePerGas() { - if (this.queueMaxFeePerGas.length === 0) { + private async getMinMaxFeePerGas(): Promise { + if (this.maxFeePerGasQueue.isEmpty()) { await this.getGasPrice() } - return this.queueMaxFeePerGas.reduce( - (acc: bigint, cur) => minBigInt(cur.maxFeePerGas, acc), - this.queueMaxFeePerGas[0].maxFeePerGas - ) + return this.maxFeePerGasQueue.getMinValue(0n) } - private async getMinMaxPriorityFeePerGas() { - if (this.queueMaxPriorityFeePerGas.length === 0) { + private async getMinMaxPriorityFeePerGas(): Promise { + if (this.maxPriorityFeePerGasQueue.isEmpty()) { await this.getGasPrice() } - return this.queueMaxPriorityFeePerGas.reduce( - (acc, cur) => minBigInt(cur.maxPriorityFeePerGas, acc), - this.queueMaxPriorityFeePerGas[0].maxPriorityFeePerGas - ) + return this.maxPriorityFeePerGasQueue.getMinValue(0n) } public async validateGasPrice(gasPrice: GasPriceParameters) { diff --git a/src/types/contracts/ArbitrumL1FeeAbi.ts b/src/types/contracts/ArbitrumL1FeeAbi.ts new file mode 100644 index 00000000..f2a30488 --- /dev/null +++ b/src/types/contracts/ArbitrumL1FeeAbi.ts @@ -0,0 +1,41 @@ +export const ArbitrumL1FeeAbi = [ + { + inputs: [ + { + internalType: "address", + name: "to", + type: "address" + }, + { + internalType: "bool", + name: "contractCreation", + type: "bool" + }, + { + internalType: "bytes", + name: "data", + type: "bytes" + } + ], + name: "gasEstimateL1Component", + outputs: [ + { + internalType: "uint64", + name: "gasEstimateForL1", + type: "uint64" + }, + { + internalType: "uint256", + name: "baseFee", + type: "uint256" + }, + { + internalType: "uint256", + name: "l1BaseFeeEstimate", + type: "uint256" + } + ], + stateMutability: "nonpayable", + type: "function" + } +] as const diff --git a/src/types/contracts/MantleBvmGasPriceOracle.ts b/src/types/contracts/MantleBvmGasPriceOracle.ts new file mode 100644 index 00000000..29d7ed27 --- /dev/null +++ b/src/types/contracts/MantleBvmGasPriceOracle.ts @@ -0,0 +1,176 @@ +export const MantleBvmGasPriceOracleAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOperator", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "newOperator", + type: "address" + } + ], + name: "OperatorUpdated", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address" + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address" + } + ], + name: "OwnershipTransferred", + type: "event" + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "previousTokenRatio", + type: "uint256" + }, + { + indexed: true, + internalType: "uint256", + name: "newTokenRatio", + type: "uint256" + } + ], + name: "TokenRatioUpdated", + type: "event" + }, + { + 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: "", 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" + }, + { + inputs: [], + name: "l1BaseFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "operator", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "overhead", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "owner", + outputs: [{ internalType: "address", name: "", type: "address" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [], + name: "scalar", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [ + { internalType: "address", name: "_operator", type: "address" } + ], + name: "setOperator", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [ + { internalType: "uint256", name: "_tokenRatio", type: "uint256" } + ], + name: "setTokenRatio", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [], + name: "tokenRatio", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + }, + { + inputs: [{ internalType: "address", name: "_owner", type: "address" }], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [], + name: "version", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function" + } +] as const diff --git a/src/types/contracts/OpL1FeeAbi.ts b/src/types/contracts/OpL1FeeAbi.ts new file mode 100644 index 00000000..7ef81a12 --- /dev/null +++ b/src/types/contracts/OpL1FeeAbi.ts @@ -0,0 +1,28 @@ +export const OpL1FeeAbi = [ + { + inputs: [ + { + internalType: "bytes", + name: "data", + type: "bytes" + } + ], + name: "getL1Fee", + outputs: [ + { + internalType: "uint256", + name: "fee", + type: "uint256" + } + ], + stateMutability: "nonpayable", + type: "function" + }, + { + inputs: [], + name: "l1BaseFee", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function" + } +] as const diff --git a/src/types/contracts/index.ts b/src/types/contracts/index.ts index 4a0e02a9..5288ee02 100644 --- a/src/types/contracts/index.ts +++ b/src/types/contracts/index.ts @@ -15,3 +15,6 @@ export * from "./CodeHashGetter" export * from "./EntryPointSimulationsV6" export * from "./EntryPointSimulationsV7" export * from "./PimlicoEntryPointSimulations" +export * from "./ArbitrumL1FeeAbi" +export * from "./MantleBvmGasPriceOracle" +export * from "./OpL1FeeAbi" diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 15d040ad..797efe97 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -6,7 +6,9 @@ import { type PackedUserOperation, type UserOperation, type UserOperationV06, - type UserOperationV07 + type UserOperationV07, + MantleBvmGasPriceOracleAbi, + OpL1FeeAbi } from "@alto/types" import { type Chain, @@ -29,12 +31,14 @@ import { serializeTransaction, toBytes, toFunctionSelector, - InternalRpcError + InternalRpcError, + maxUint64 } from "viem" -import { base, baseGoerli, baseSepolia } from "viem/chains" +import { base, baseGoerli, baseSepolia, lineaSepolia } from "viem/chains" import { maxBigInt, minBigInt, scaleBigIntByPercent } from "./bigInt" import { isVersion06, toPackedUserOperation } from "./userop" import type { AltoConfig } from "../createConfig" +import { ArbitrumL1FeeAbi } from "../types/contracts/ArbitrumL1FeeAbi" export interface GasOverheads { /** @@ -320,30 +324,41 @@ export async function calcPreVerificationGas({ overheads ) - if (config.publicClient.chain.id === 59140) { - // linea sepolia - preVerificationGas *= 2n - } else if (config.chainType === "op-stack") { - preVerificationGas = await calcOptimismPreVerificationGas( - config.publicClient, - userOperation, - entryPoint, - preVerificationGas, - gasPriceManager, - validate - ) - } else if (config.chainType === "arbitrum") { - preVerificationGas = await calcArbitrumPreVerificationGas( - config.publicClient, - userOperation, - entryPoint, - preVerificationGas, - gasPriceManager, - validate - ) + if (config.publicClient.chain.id === lineaSepolia.id) { + return preVerificationGas * 2n } - return preVerificationGas + switch (config.chainType) { + case "op-stack": + return await calcOptimismPreVerificationGas( + config.publicClient, + userOperation, + entryPoint, + preVerificationGas, + gasPriceManager, + validate + ) + case "arbitrum": + return await calcArbitrumPreVerificationGas( + config.publicClient, + userOperation, + entryPoint, + preVerificationGas, + gasPriceManager, + validate + ) + case "mantle": + return await calcMantlePreVerificationGas( + config.publicClient, + userOperation, + entryPoint, + preVerificationGas, + gasPriceManager, + validate + ) + default: + return preVerificationGas + } } export function calcVerificationGasAndCallGasLimit( @@ -425,50 +440,12 @@ export function calcDefaultPreVerificationGas( return BigInt(ret) } -const maxUint64 = 2n ** 64n - 1n - -const getL1FeeAbi = [ - { - inputs: [ - { - internalType: "bytes", - name: "data", - type: "bytes" - } - ], - name: "getL1Fee", - outputs: [ - { - internalType: "uint256", - name: "fee", - type: "uint256" - } - ], - stateMutability: "nonpayable", - type: "function" - }, - { - inputs: [], - name: "l1BaseFee", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function" - } -] as const - -export async function calcOptimismPreVerificationGas( - publicClient: PublicClient, - op: UserOperation, - entryPoint: Address, - staticFee: bigint, - gasPriceManager: GasPriceManager, - verify?: boolean -) { +// Returns back the bytes for the handleOps call +function getHandleOpsCallData(op: UserOperation, entryPoint: Address) { let selector: Hex let paramData: Hex if (isVersion06(op)) { - const randomDataUserOp: UserOperation = removeZeroBytesFromUserOp(op) const handleOpsAbi = getAbiItem({ abi: EntryPointV06Abi, name: "handleOps" @@ -476,7 +453,7 @@ export async function calcOptimismPreVerificationGas( selector = toFunctionSelector(handleOpsAbi) paramData = encodeAbiParameters(handleOpsAbi.inputs, [ - [randomDataUserOp], + [removeZeroBytesFromUserOp(op)], entryPoint ]) } else { @@ -497,6 +474,104 @@ export async function calcOptimismPreVerificationGas( const data = concat([selector, paramData]) + return data +} + +export async function calcMantlePreVerificationGas( + publicClient: PublicClient, + op: UserOperation, + entryPoint: Address, + staticFee: bigint, + gasPriceManager: GasPriceManager, + verify?: boolean +) { + const data = getHandleOpsCallData(op, entryPoint) + + const serializedTx = serializeTransaction( + { + to: entryPoint, + chainId: publicClient.chain.id, + nonce: 999999, + gasLimit: maxUint64, + gasPrice: maxUint64, + data + }, + { + r: "0x123451234512345123451234512345123451234512345123451234512345", + s: "0x123451234512345123451234512345123451234512345123451234512345", + v: 28n + } + ) + + let tokenRatio: bigint + let scalar: bigint + let rollupDataGasAndOverhead: bigint + let l1GasPrice: bigint + + const mantleManager = gasPriceManager.mantleManager + + if (verify) { + const minValues = mantleManager.getMinMantleOracleValues() + + tokenRatio = minValues.minTokenRatio + scalar = minValues.minScalar + rollupDataGasAndOverhead = minValues.minRollupDataGasAndOverhead + l1GasPrice = minValues.minL1GasPrice + } else { + ;[tokenRatio, scalar, rollupDataGasAndOverhead, l1GasPrice] = + await Promise.all([ + publicClient.readContract({ + address: "0x420000000000000000000000000000000000000F", + abi: MantleBvmGasPriceOracleAbi, + functionName: "tokenRatio" + }), + publicClient.readContract({ + address: "0x420000000000000000000000000000000000000F", + abi: MantleBvmGasPriceOracleAbi, + functionName: "scalar" + }), + publicClient.readContract({ + address: "0x420000000000000000000000000000000000000F", + abi: MantleBvmGasPriceOracleAbi, + functionName: "getL1GasUsed", + args: [serializedTx] + }), + publicClient.readContract({ + address: "0x420000000000000000000000000000000000000F", + abi: MantleBvmGasPriceOracleAbi, + functionName: "l1BaseFee" + }) + ]) + + mantleManager.saveMantleOracleValues({ + tokenRatio, + scalar, + rollupDataGasAndOverhead, + l1GasPrice + }) + } + + const mantleL1RollUpFeeDivisionFactor = 1_000_000n + + const l1RollupFee = + (rollupDataGasAndOverhead * l1GasPrice * tokenRatio * scalar) / + mantleL1RollUpFeeDivisionFactor + + const l2MaxFee = BigInt(op.maxFeePerGas) + + return staticFee + l1RollupFee / l2MaxFee +} + +export async function calcOptimismPreVerificationGas( + publicClient: PublicClient, + op: UserOperation, + entryPoint: Address, + staticFee: bigint, + gasPriceManager: GasPriceManager, + verify?: boolean +) { + const data = getHandleOpsCallData(op, entryPoint) + const serializedTx = serializeTransaction( { to: entryPoint, @@ -514,7 +589,7 @@ export async function calcOptimismPreVerificationGas( ) const opGasPriceOracle = getContract({ - abi: getL1FeeAbi, + abi: OpL1FeeAbi, address: "0x420000000000000000000000000000000000000F", client: { public: publicClient @@ -544,48 +619,6 @@ export async function calcOptimismPreVerificationGas( return staticFee + l1Fee / l2price } -const getArbitrumL1FeeAbi = [ - { - inputs: [ - { - internalType: "address", - name: "to", - type: "address" - }, - { - internalType: "bool", - name: "contractCreation", - type: "bool" - }, - { - internalType: "bytes", - name: "data", - type: "bytes" - } - ], - name: "gasEstimateL1Component", - outputs: [ - { - internalType: "uint64", - name: "gasEstimateForL1", - type: "uint64" - }, - { - internalType: "uint256", - name: "baseFee", - type: "uint256" - }, - { - internalType: "uint256", - name: "l1BaseFeeEstimate", - type: "uint256" - } - ], - stateMutability: "nonpayable", - type: "function" - } -] as const - export async function calcArbitrumPreVerificationGas( publicClient: PublicClient, op: UserOperation, @@ -594,37 +627,7 @@ export async function calcArbitrumPreVerificationGas( gasPriceManager: GasPriceManager, validate: boolean ) { - 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({ - abi: EntryPointV07Abi, - name: "handleOps" - }) - - selector = toFunctionSelector(handleOpsAbi) - paramData = encodeAbiParameters(handleOpsAbi.inputs, [ - [randomDataUserOp], - entryPoint - ]) - } - - const data = concat([selector, paramData]) + const data = getHandleOpsCallData(op, entryPoint) const precompileAddress = "0x00000000000000000000000000000000000000C8" @@ -645,7 +648,7 @@ export async function calcArbitrumPreVerificationGas( ) const arbGasPriceOracle = getContract({ - abi: getArbitrumL1FeeAbi, + abi: ArbitrumL1FeeAbi, address: precompileAddress, client: { public: publicClient @@ -660,22 +663,21 @@ export async function calcArbitrumPreVerificationGas( let [gasForL1, l2BaseFee, l1BaseFeeEstimate] = result - gasPriceManager.arbitrumManager.saveL1BaseFee(l1BaseFeeEstimate) - gasPriceManager.arbitrumManager.saveL2BaseFee(l2BaseFee) + const arbitrumManager = gasPriceManager.arbitrumManager + + arbitrumManager.saveL1BaseFee(l1BaseFeeEstimate) + arbitrumManager.saveL2BaseFee(l2BaseFee) if (validate) { if (l1BaseFeeEstimate === 0n) { - l1BaseFeeEstimate = - await gasPriceManager.arbitrumManager.getMaxL1BaseFee() + l1BaseFeeEstimate = arbitrumManager.getMaxL1BaseFee() } // gasEstimateL1Component source: https://github.com/OffchainLabs/nitro/blob/5cd7d6913eb6b4dedb08f6ea49d7f9802d2cc5b9/execution/nodeInterface/NodeInterface.go#L515-L551 const feesForL1 = (gasForL1 * l2BaseFee) / l1BaseFeeEstimate - const minL1BaseFeeEstimate = - await gasPriceManager.arbitrumManager.getMinL1BaseFee() - const maxL2BaseFee = - await gasPriceManager.arbitrumManager.getMaxL2BaseFee() + const minL1BaseFeeEstimate = arbitrumManager.getMinL1BaseFee() + const maxL2BaseFee = arbitrumManager.getMaxL2BaseFee() gasForL1 = (feesForL1 * minL1BaseFeeEstimate) / maxL2BaseFee }