diff --git a/zp-relayer/services/fee/DynamicFeeManager.ts b/zp-relayer/services/fee/DynamicFeeManager.ts index 6bb1568f..81f8bc0c 100644 --- a/zp-relayer/services/fee/DynamicFeeManager.ts +++ b/zp-relayer/services/fee/DynamicFeeManager.ts @@ -25,6 +25,11 @@ export class DynamicFeeManager extends FeeManager { async _fetchFeeOptions(): Promise { const gasPrice = await this.gasPrice.fetchOnce() const oneByteFee = FeeManager.executionFee(gasPrice, toBN(NZERO_BYTE_GAS)) - return DynamicFeeOptions.fromGasPice(gasPrice, oneByteFee, relayerConfig.minBaseFee) + return DynamicFeeOptions.fromParams({ + gasPrice, + oneByteFee, + minFee: relayerConfig.minBaseFee, + baseExtra: toBN(0), + }) } } diff --git a/zp-relayer/services/fee/FeeManager.ts b/zp-relayer/services/fee/FeeManager.ts index 8200f937..ebb27047 100644 --- a/zp-relayer/services/fee/FeeManager.ts +++ b/zp-relayer/services/fee/FeeManager.ts @@ -93,10 +93,18 @@ type DynamicFeeKeys = [ 'oneByteFee', 'nativeConvertFee' ] + +interface DynamicFeeParams { + gasPrice: GasPriceValue + oneByteFee: BN + minFee: BN + baseExtra: BN +} + // Utility class for dynamic fee estimations export class DynamicFeeOptions extends FeeOptions { - static fromGasPice(gasPrice: GasPriceValue, oneByteFee: BN, minFee: BN) { - const getFee = (txType: TxType) => FeeManager.executionFee(gasPrice, config.baseTxGas[txType]) + static fromParams({ gasPrice, baseExtra, oneByteFee, minFee }: DynamicFeeParams) { + const getFee = (txType: TxType) => FeeManager.executionFee(gasPrice, config.baseTxGas[txType]).add(baseExtra) const fees: Fees = { [TxType.DEPOSIT]: getFee(TxType.DEPOSIT), [TxType.PERMITTABLE_DEPOSIT]: getFee(TxType.PERMITTABLE_DEPOSIT), diff --git a/zp-relayer/services/fee/OptimismFeeManager.ts b/zp-relayer/services/fee/OptimismFeeManager.ts index f1e8cf4e..ef5c7bff 100644 --- a/zp-relayer/services/fee/OptimismFeeManager.ts +++ b/zp-relayer/services/fee/OptimismFeeManager.ts @@ -10,6 +10,10 @@ import relayerConfig from '@/configs/relayerConfig' import { ZERO_BYTE_GAS, NZERO_BYTE_GAS } from '@/utils/constants' import type { EstimationType, GasPrice } from '../gas-price' +// Rough estimation of tx RLP encoding overhead +const RLP_ENCODING_OVERHEAD = toBN(10 * 16) +const SIGNATURE_GAS = toBN(68 * 16) + export class OptimismFeeManager extends FeeManager { private oracle: Contract private overhead!: BN @@ -27,16 +31,21 @@ export class OptimismFeeManager extends FeeManager { this.scalar = await contractCallRetry(this.oracle, 'scalar').then(toBN) } - private getL1GasUsed(data: string): BN { + private getL1GasUsed(data: string, includeOverhead: boolean): BN { const byteToGas = (byte: number) => (byte === 0 ? ZERO_BYTE_GAS : NZERO_BYTE_GAS) const bytes = hexToBytes(data) - const l1GasUsed = bytes.reduce((acc, byte) => acc + byteToGas(byte), 0) - return toBN(l1GasUsed).add(this.overhead) + const total = bytes.reduce((acc, byte) => acc + byteToGas(byte), 0) + const unsigned = toBN(total) + if (includeOverhead) { + const totalOverhead = this.overhead.add(SIGNATURE_GAS).add(RLP_ENCODING_OVERHEAD) + unsigned.iadd(totalOverhead) + } + return unsigned } // Mimics OP gas price oracle algorithm - private getL1Fee(data: string, l1BaseFee: BN): BN { - const l1GasUsed = this.getL1GasUsed(data) + private getL1Fee(data: string, l1BaseFee: BN, includeOverhead: boolean): BN { + const l1GasUsed = this.getL1GasUsed(data, includeOverhead) const l1Fee = l1GasUsed.mul(l1BaseFee) const divisor = toBN(10).pow(this.decimals) const unscaled = l1Fee.mul(this.scalar) @@ -46,14 +55,9 @@ export class OptimismFeeManager extends FeeManager { async _estimateFee({ txType, nativeConvert, txData }: IFeeEstimateParams, feeOptions: DynamicFeeOptions) { const { [txType]: baseFee, nativeConvertFee, oneByteFee } = feeOptions.fees - - const unscaledL1Fee = this.getL1Fee(txData, oneByteFee) - - // Because oneByteFee = l1BaseFee * NZERO_BYTE_GAS, we need to divide the estimation - // We do it here to get a more accurate result - const l1Fee = unscaledL1Fee.divn(NZERO_BYTE_GAS) - - const fee = baseFee.add(l1Fee) + // -1 to account for the 0x prefix + const calldataLen = (txData.length >> 1) - 1 + const fee = baseFee.add(oneByteFee.muln(calldataLen)) if (nativeConvert) { fee.iadd(nativeConvertFee) } @@ -65,8 +69,14 @@ export class OptimismFeeManager extends FeeManager { const l1BaseFee = await contractCallRetry(this.oracle, 'l1BaseFee').then(toBN) - const oneByteFee = l1BaseFee.muln(NZERO_BYTE_GAS) + const oneByteFee = this.getL1Fee('0xff', l1BaseFee, false) + const baseExtra = this.getL1Fee('0x', l1BaseFee, true) - return DynamicFeeOptions.fromGasPice(gasPrice, oneByteFee, relayerConfig.minBaseFee) + return DynamicFeeOptions.fromParams({ + gasPrice, + oneByteFee, + minFee: relayerConfig.minBaseFee, + baseExtra, + }) } } diff --git a/zp-relayer/test.env b/zp-relayer/test.env index 2131ef66..f5d1118f 100644 --- a/zp-relayer/test.env +++ b/zp-relayer/test.env @@ -21,3 +21,4 @@ RELAYER_SENT_TX_DELAY=2000 RELAYER_GAS_PRICE_FALLBACK= RELAYER_GAS_PRICE_UPDATE_INTERVAL=100000 RELAYER_GAS_PRICE_ESTIMATION_TYPE="eip1559-gas-estimation" +RELAYER_FEE_MANAGER_TYPE=static diff --git a/zp-relayer/test/worker-tests/poolWorker.test.ts b/zp-relayer/test/worker-tests/poolWorker.test.ts index 0cb8ec07..a1fddbd1 100644 --- a/zp-relayer/test/worker-tests/poolWorker.test.ts +++ b/zp-relayer/test/worker-tests/poolWorker.test.ts @@ -38,7 +38,8 @@ import flowZeroAddressWithdraw from '../flows/flow_zero-address_withdraw_2.json' import { Params } from 'libzkbob-rs-node' import { directDepositQueue } from '../../queue/directDepositQueue' import { createDirectDepositWorker } from '../../workers/directDepositWorker' -import { DynamicFeeManager, FeeManager } from '../../services/fee' +import { FeeManager, StaticFeeManager } from '../../services/fee' +import { NativePriceFeed } from '../../services/price-feed' chai.use(chaiAsPromised) const expect = chai.expect @@ -96,18 +97,16 @@ describe('poolWorker', () => { gasPriceService = new GasPrice(web3, { gasPrice: config.gasPriceFallback }, 10000, EstimationType.Web3, {}) await gasPriceService.start() - const mockPriceFeed = { - convert: (amounts: BN[]) => Promise.resolve(amounts.map(() => toBN(0))), - } + const mockPriceFeed = new NativePriceFeed() const managerConfig = { gasPrice: gasPriceService, priceFeed: mockPriceFeed, - scaleFactor: toBN(1), - marginFactor: toBN(1), + scaleFactor: toBN(100), + marginFactor: toBN(100), updateInterval: config.feeManagerUpdateInterval, defaultFeeOptionsParams: { gasLimit: config.relayerGasLimit }, } - feeManager = new DynamicFeeManager(managerConfig, gasPriceService) + feeManager = new StaticFeeManager(managerConfig, toBN(0)) await feeManager.start() txManager = new TxManager(web3, config.relayerPrivateKey, gasPriceService) diff --git a/zp-relayer/utils/constants.ts b/zp-relayer/utils/constants.ts index 1fc2b0e7..69ac9cb8 100644 --- a/zp-relayer/utils/constants.ts +++ b/zp-relayer/utils/constants.ts @@ -1,7 +1,6 @@ import { Constants } from 'libzkbob-rs-node' const BASE_CALLDATA_SIZE = 644 // constant calldata size for tx without memo -const RAW_TX_RLP_OVERHEAD = 110 const constants = { FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000, @@ -19,7 +18,7 @@ const constants = { ZERO_ADDRESS: '0x0000000000000000000000000000000000000000', PERMIT2_CONTRACT: '0x000000000022D473030F116dDEE9F6B43aC78BA3', INIT_ROOT: '11469701942666298368112882412133877458305516134926649826543144744382391691533', - MOCK_CALLDATA: '0x' + 'ff'.repeat(BASE_CALLDATA_SIZE + RAW_TX_RLP_OVERHEAD), + MOCK_CALLDATA: '0x' + 'ff'.repeat(BASE_CALLDATA_SIZE), ZERO_BYTE_GAS: 4, NZERO_BYTE_GAS: 16, OP_GAS_ORACLE_ADDRESS: '0x420000000000000000000000000000000000000F', diff --git a/zp-relayer/utils/helpers.ts b/zp-relayer/utils/helpers.ts index 4d2c52b3..1bc54e2a 100644 --- a/zp-relayer/utils/helpers.ts +++ b/zp-relayer/utils/helpers.ts @@ -250,7 +250,5 @@ export function getFileHash(path: string) { } export function applyDenominator(n: BN, d: BN) { - return d.testn(255) - ? n.div(d.maskn(255)) - : n.mul(d) + return d.testn(255) ? n.div(d.maskn(255)) : n.mul(d) }