diff --git a/package.json b/package.json index d81fd480..d30bbf5b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@across-protocol/sdk", "author": "UMA Team", - "version": "3.4.3", + "version": "3.4.4", "license": "AGPL-3.0", "homepage": "https://docs.across.to/reference/sdk", "files": [ diff --git a/src/gasPriceOracle/adapters/arbitrum.ts b/src/gasPriceOracle/adapters/arbitrum.ts index 7958dfc0..4a9eac8c 100644 --- a/src/gasPriceOracle/adapters/arbitrum.ts +++ b/src/gasPriceOracle/adapters/arbitrum.ts @@ -10,6 +10,10 @@ import { GasPriceEstimateOptions } from "../oracle"; * Reference: https://docs.arbitrum.io/how-arbitrum-works/gas-fees so we hardcode the priority fee * to 1 wei. * @param provider Ethers Provider + * @param opts See notes below on specific parameters. + * @param baseFeeMultiplier Amount to multiply base fee. + * @param priorityFeeMultiplier Unused in this function because arbitrum priority fee is hardcoded to 1 wei by this + * function. * @returns GasPriceEstimate */ export async function eip1559(provider: providers.Provider, opts: GasPriceEstimateOptions): Promise { diff --git a/src/gasPriceOracle/adapters/ethereum.ts b/src/gasPriceOracle/adapters/ethereum.ts index 668c70f7..4de4f39a 100644 --- a/src/gasPriceOracle/adapters/ethereum.ts +++ b/src/gasPriceOracle/adapters/ethereum.ts @@ -14,13 +14,16 @@ import { GasPriceEstimateOptions } from "../oracle"; * eth_getBlock("pending").baseFee to eth_maxPriorityFeePerGas, otherwise calls the ethers provider's * getFeeData() method which adds eth_getBlock("latest").baseFee to a hardcoded priority fee of 1.5 gwei. * @param provider ethers RPC provider instance. + * @param {GasPriceEstimateOptions} opts See notes below on specific parameters. + * @param baseFeeMultiplier Amount to multiply base fee or total fee for legacy gas pricing. + * @param priorityFeeMultiplier Amount to multiply priority fee or unused for legacy gas pricing. * @returns Promise of gas price estimate object. */ export function eip1559(provider: providers.Provider, opts: GasPriceEstimateOptions): Promise { const useRaw = process.env[`GAS_PRICE_EIP1559_RAW_${opts.chainId}`] === "true"; return useRaw - ? eip1559Raw(provider, opts.chainId, opts.baseFeeMultiplier) - : eip1559Bad(provider, opts.chainId, opts.baseFeeMultiplier); + ? eip1559Raw(provider, opts.chainId, opts.baseFeeMultiplier, opts.priorityFeeMultiplier) + : eip1559Bad(provider, opts.chainId, opts.baseFeeMultiplier, opts.priorityFeeMultiplier); } /** @@ -33,7 +36,8 @@ export function eip1559(provider: providers.Provider, opts: GasPriceEstimateOpti export async function eip1559Raw( provider: providers.Provider, chainId: number, - baseFeeMultiplier: BigNumber + baseFeeMultiplier: BigNumber, + priorityFeeMultiplier: BigNumber ): Promise { const [{ baseFeePerGas }, _maxPriorityFeePerGas] = await Promise.all([ provider.getBlock("pending"), @@ -42,10 +46,11 @@ export async function eip1559Raw( const maxPriorityFeePerGas = BigNumber.from(_maxPriorityFeePerGas); assert(BigNumber.isBigNumber(baseFeePerGas), `No baseFeePerGas received on ${getNetworkName(chainId)}`); + const scaledPriorityFee = maxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); const scaledBaseFee = baseFeePerGas.mul(baseFeeMultiplier).div(fixedPointAdjustment); return { - maxFeePerGas: maxPriorityFeePerGas.add(scaledBaseFee), - maxPriorityFeePerGas, + maxFeePerGas: scaledPriorityFee.add(scaledBaseFee), + maxPriorityFeePerGas: scaledPriorityFee, }; } @@ -61,7 +66,8 @@ export async function eip1559Raw( export async function eip1559Bad( provider: providers.Provider, chainId: number, - baseFeeMultiplier: BigNumber + baseFeeMultiplier: BigNumber, + priorityFeeMultiplier: BigNumber ): Promise { const feeData = await provider.getFeeData(); @@ -70,12 +76,13 @@ export async function eip1559Bad( }); const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas as BigNumber; + const scaledPriorityFee = maxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); const scaledLastBaseFeePerGas = (feeData.lastBaseFeePerGas as BigNumber) .mul(baseFeeMultiplier) .div(fixedPointAdjustment); - const maxFeePerGas = maxPriorityFeePerGas.add(scaledLastBaseFeePerGas); + const maxFeePerGas = scaledPriorityFee.add(scaledLastBaseFeePerGas); - return { maxPriorityFeePerGas, maxFeePerGas }; + return { maxPriorityFeePerGas: scaledPriorityFee, maxFeePerGas }; } /** diff --git a/src/gasPriceOracle/adapters/linea-viem.ts b/src/gasPriceOracle/adapters/linea-viem.ts index 3e258aca..ed75b50e 100644 --- a/src/gasPriceOracle/adapters/linea-viem.ts +++ b/src/gasPriceOracle/adapters/linea-viem.ts @@ -15,10 +15,12 @@ import { fixedPointAdjustment } from "../../utils"; * @dev Because the Linea priority fee is more volatile than the base fee, the base fee multiplier will be applied * to the priority fee. * @param provider Viem PublicClient - * @param _chainId Unused in this adapter + * @param opts Relevant options for Linea are baseFeeMultiplier and unsignedTx. * @param baseFeeMultiplier Amount to multiply priority fee, since Linea's base fee is hardcoded while its priority - * fee is dynamic - * @param _unsignedTx Should contain any params passed to linea_estimateGas, which are listed + * fee is dynamic. + * @param priorityFeeMultiplier Unused in this function because the baseFeeMultiplier is applied to the dynamic + * Linea priority fee while the base fee is hardcoded. + * @param unsignedTx Should contain any params passed to linea_estimateGas, which are listed * here: https://docs.linea.build/api/reference/linea-estimategas#parameters * @returns */ diff --git a/src/gasPriceOracle/adapters/linea.ts b/src/gasPriceOracle/adapters/linea.ts index e3c3f6de..98e2355f 100644 --- a/src/gasPriceOracle/adapters/linea.ts +++ b/src/gasPriceOracle/adapters/linea.ts @@ -7,6 +7,14 @@ import { GasPriceEstimate } from "../types"; import * as ethereum from "./ethereum"; import { GasPriceEstimateOptions } from "../oracle"; +/** + * Returns Linea gas price using the legacy eth_gasPrice RPC call. + * @param provider + * @param opts See notes below on specific parameters. + * @param baseFeeMultiplier Amount to multiply total fee because this function defaults to legacy gas pricing. + * @param priorityFeeMultiplier Unused in this function because this defaults to legacy gas computation. + * @returns + */ export function eip1559(provider: providers.Provider, opts: GasPriceEstimateOptions): Promise { // We use the legacy method to call `eth_gasPrice` which empirically returns a more accurate // gas price estimate than `eth_maxPriorityFeePerGas` or ethersProvider.getFeeData in the EIP1559 "raw" or "bad" diff --git a/src/gasPriceOracle/adapters/polygon.ts b/src/gasPriceOracle/adapters/polygon.ts index a3a6bee6..1dcabc34 100644 --- a/src/gasPriceOracle/adapters/polygon.ts +++ b/src/gasPriceOracle/adapters/polygon.ts @@ -96,7 +96,7 @@ export async function gasStation( provider: providers.Provider, opts: GasPriceEstimateOptions ): Promise { - const { chainId, baseFeeMultiplier } = opts; + const { chainId, baseFeeMultiplier, priorityFeeMultiplier } = opts; let gasStation: PolygonGasStation; if (process.env.TEST_POLYGON_GAS_STATION === "true") { gasStation = new MockPolygonGasStation(); @@ -113,14 +113,16 @@ export async function gasStation( // the baseFeeMultiplier. const baseFeeMinusPriorityFee = maxFeePerGas.sub(maxPriorityFeePerGas); const scaledBaseFee = baseFeeMinusPriorityFee.mul(baseFeeMultiplier).div(fixedPointAdjustment); - maxFeePerGas = scaledBaseFee.add(maxPriorityFeePerGas); + const scaledPriorityFee = maxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); + maxFeePerGas = scaledBaseFee.add(scaledPriorityFee); + maxPriorityFeePerGas = scaledPriorityFee; } catch (err) { // Fall back to the RPC provider. May be less accurate. ({ maxPriorityFeePerGas, maxFeePerGas } = await eip1559(provider, opts)); // Per the GasStation docs, the minimum priority fee on Polygon is 30 Gwei. // https://docs.polygon.technology/tools/gas/polygon-gas-station/#interpretation - const minPriorityFee = parseUnits("30", 9); + const minPriorityFee = parseUnits("30", 9).mul(priorityFeeMultiplier).div(fixedPointAdjustment); if (maxPriorityFeePerGas.lt(minPriorityFee)) { const priorityDelta = minPriorityFee.sub(maxPriorityFeePerGas); maxPriorityFeePerGas = minPriorityFee; diff --git a/src/gasPriceOracle/oracle.ts b/src/gasPriceOracle/oracle.ts index 96566f8a..0bf6e099 100644 --- a/src/gasPriceOracle/oracle.ts +++ b/src/gasPriceOracle/oracle.ts @@ -14,6 +14,8 @@ import * as lineaViem from "./adapters/linea-viem"; export interface GasPriceEstimateOptions { // baseFeeMultiplier Multiplier applied to base fee for EIP1559 gas prices (or total fee for legacy). baseFeeMultiplier: BigNumber; + // priorityFeeMultiplier Multiplier applied to priority fee for EIP1559 gas prices (ignored for legacy). + priorityFeeMultiplier: BigNumber; // legacyFallback In the case of an unrecognized chain, fall back to type 0 gas estimation. legacyFallback: boolean; // chainId The chain ID to query for gas prices. If omitted can be inferred by provider. @@ -43,10 +45,17 @@ export async function getGasPriceEstimate( baseFeeMultiplier.gte(toBNWei("1.0")) && baseFeeMultiplier.lte(toBNWei("5")), `Require 1.0 < base fee multiplier (${baseFeeMultiplier}) <= 5.0 for a total gas multiplier within [+1.0, +5.0]` ); + const priorityFeeMultiplier = opts.priorityFeeMultiplier ?? toBNWei("1"); + assert( + priorityFeeMultiplier.gte(toBNWei("1.0")) && priorityFeeMultiplier.lte(toBNWei("5")), + `Require 1.0 < priority fee multiplier (${priorityFeeMultiplier}) <= 5.0 for a total gas multiplier within [+1.0, +5.0]` + ); + const chainId = opts.chainId ?? (await provider.getNetwork()).chainId; const optsWithDefaults: GasPriceEstimateOptions = { ...GAS_PRICE_ESTIMATE_DEFAULTS, baseFeeMultiplier, + priorityFeeMultiplier, ...opts, chainId, }; diff --git a/test/GasPriceOracle.test.ts b/test/GasPriceOracle.test.ts index 43a3e5e1..2b3d4001 100644 --- a/test/GasPriceOracle.test.ts +++ b/test/GasPriceOracle.test.ts @@ -59,6 +59,24 @@ describe("Gas Price Oracle", function () { "base fee multiplier" ); }); + it("priorityFeeMultiplier is validated", async function () { + // Too low: + await assertPromiseError( + getGasPriceEstimate(provider, { + chainId: 1, + priorityFeeMultiplier: toBNWei("0.5"), + }), + "priority fee multiplier" + ); + // Too high: + await assertPromiseError( + getGasPriceEstimate(provider, { + chainId: 1, + priorityFeeMultiplier: toBNWei("5.5"), + }), + "priority fee multiplier" + ); + }); it("Linea Viem gas price retrieval with unsignedTx", async function () { const chainId = 59144; const chainKey = `NEW_GAS_PRICE_ORACLE_${chainId}`; @@ -74,6 +92,7 @@ describe("Gas Price Oracle", function () { transport: customTransport, unsignedTx, baseFeeMultiplier: toBNWei("2.0"), + priorityFeeMultiplier: toBNWei("2.0"), // Priority fee multiplier should be unused in Linea. }); // For Linea, base fee is expected to be hardcoded and unaffected by the base fee multiplier while @@ -94,6 +113,7 @@ describe("Gas Price Oracle", function () { chainId, transport: customTransport, baseFeeMultiplier: toBNWei("2.0"), + priorityFeeMultiplier: toBNWei("2.0"), // Priority fee multiplier should be unused in Linea. }); // For Linea, base fee is expected to be hardcoded and unaffected by the base fee multiplier while @@ -105,13 +125,15 @@ describe("Gas Price Oracle", function () { }); it("Ethers gas price retrieval", async function () { const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); for (const chainId of ethersProviderChainIds) { const { maxFeePerGas: markedUpMaxFeePerGas, maxPriorityFeePerGas: markedUpMaxPriorityFeePerGas } = - await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier }); + await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, priorityFeeMultiplier }); // Base fee for EIP1559 gas price feeds should be multiplied by multiplier. - // Returned max fee includes priority fee so back it out. - const expectedMarkedUpMaxFeePerGas = stdLastBaseFeePerGas.mul(2); + // Returned max fee includes priority fee so back it out. + const expectedMarkedUpMaxFeePerGas = stdLastBaseFeePerGas.mul(baseFeeMultiplier).div(fixedPointAdjustment); + const expectedMarkedUpPriorityFee = stdMaxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); if (arbOrbitChainIds.includes(chainId)) { expect(markedUpMaxFeePerGas.sub(markedUpMaxPriorityFeePerGas)).to.equal(expectedMarkedUpMaxFeePerGas); @@ -120,65 +142,73 @@ describe("Gas Price Oracle", function () { } else if (legacyChainIds.includes(chainId)) { // Scroll and ZkSync use legacy pricing so priority fee should be 0. expect(markedUpMaxPriorityFeePerGas).to.equal(0); - // Legacy gas price = base fee + priority fee and full value is scaled - expect(markedUpMaxFeePerGas).to.equal(stdLastBaseFeePerGas.add(stdMaxPriorityFeePerGas).mul(2)); + // Legacy gas price = base fee + priority fee and full value is scaled by the base fee multiplier. + expect(markedUpMaxFeePerGas).to.equal( + stdLastBaseFeePerGas.add(stdMaxPriorityFeePerGas).mul(baseFeeMultiplier).div(fixedPointAdjustment) + ); } else { expect(markedUpMaxFeePerGas.sub(markedUpMaxPriorityFeePerGas)).to.equal(expectedMarkedUpMaxFeePerGas); - // Priority fees should be unscaled - expect(markedUpMaxPriorityFeePerGas).to.equal(stdMaxPriorityFeePerGas); + // Priority fees should be scaled by priority fee multiplier. + expect(markedUpMaxPriorityFeePerGas).to.equal(expectedMarkedUpPriorityFee); } } }); it("Ethers EIP1559 Raw", async function () { const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); const chainId = 1; const chainKey = `GAS_PRICE_EIP1559_RAW_${chainId}`; process.env[chainKey] = "true"; const { maxFeePerGas: markedUpMaxFeePerGas, maxPriorityFeePerGas: markedUpMaxPriorityFeePerGas } = - await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier }); + await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, priorityFeeMultiplier }); // Base fee should be multiplied by multiplier. Returned max fee includes priority fee // so back it out before scaling. + const expectedMarkedUpPriorityFee = stdMaxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); const expectedMarkedUpMaxFeePerGas = stdLastBaseFeePerGas .mul(baseFeeMultiplier) .div(fixedPointAdjustment) - .add(stdMaxPriorityFeePerGas); + .add(expectedMarkedUpPriorityFee); expect(markedUpMaxFeePerGas).to.equal(expectedMarkedUpMaxFeePerGas); - // Priority fees should be the same - expect(markedUpMaxPriorityFeePerGas).to.equal(stdMaxPriorityFeePerGas); + // Priority fees should be scaled. + expect(markedUpMaxPriorityFeePerGas).to.equal(expectedMarkedUpPriorityFee); delete process.env[chainKey]; }); it("Ethers EIP1559 Bad", async function () { // This test should return identical results to the Raw test but it makes different // provider calls, so we're really testing that the expected provider functions are called. const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); const chainId = 1; const { maxFeePerGas: markedUpMaxFeePerGas, maxPriorityFeePerGas: markedUpMaxPriorityFeePerGas } = - await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier }); + await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, priorityFeeMultiplier }); // Base fee should be multiplied by multiplier. Returned max fee includes priority fee // so back it out before scaling. + const expectedMarkedUpPriorityFee = stdMaxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); const expectedMarkedUpMaxFeePerGas = stdLastBaseFeePerGas .mul(baseFeeMultiplier) .div(fixedPointAdjustment) - .add(stdMaxPriorityFeePerGas); + .add(expectedMarkedUpPriorityFee); expect(markedUpMaxFeePerGas).to.equal(expectedMarkedUpMaxFeePerGas); - // Priority fees should be the same - expect(markedUpMaxPriorityFeePerGas).to.equal(stdMaxPriorityFeePerGas); + // Priority fees should be scaled. + expect(markedUpMaxPriorityFeePerGas).to.equal(expectedMarkedUpPriorityFee); }); it("Ethers Legacy", async function () { const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); const chainId = 324; const { maxFeePerGas: markedUpMaxFeePerGas, maxPriorityFeePerGas: markedUpMaxPriorityFeePerGas } = - await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier }); + await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, priorityFeeMultiplier }); // Legacy gas price is equal to base fee + priority fee and the full amount - // should be multiplied since the RPC won't return the broken down fee. + // should be multiplied by the base fee multiplier since the RPC won't return the broken down fee. + // The priority fee multiplier should be unused. const expectedGasPrice = stdLastBaseFeePerGas.add(stdMaxPriorityFeePerGas); const expectedMarkedUpMaxFeePerGas = expectedGasPrice.mul(baseFeeMultiplier).div(fixedPointAdjustment); expect(expectedMarkedUpMaxFeePerGas).to.equal(markedUpMaxFeePerGas); @@ -188,24 +218,25 @@ describe("Gas Price Oracle", function () { }); it("Ethers Polygon GasStation", async function () { const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); process.env["TEST_POLYGON_GAS_STATION"] = "true"; const chainId = 137; const { maxFeePerGas, maxPriorityFeePerGas } = await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, + priorityFeeMultiplier, }); + const expectedPriorityFee = MockPolygonGasStationPriorityFee().mul(priorityFeeMultiplier).div(fixedPointAdjustment); expect(maxFeePerGas).to.equal( - MockPolygonGasStationBaseFee() - .mul(baseFeeMultiplier) - .div(fixedPointAdjustment) - .add(MockPolygonGasStationPriorityFee()) + MockPolygonGasStationBaseFee().mul(baseFeeMultiplier).div(fixedPointAdjustment).add(expectedPriorityFee) ); - expect(maxPriorityFeePerGas).to.equal(MockPolygonGasStationPriorityFee()); + expect(maxPriorityFeePerGas).to.equal(expectedPriorityFee); delete process.env["TEST_POLYGON_GAS_STATION"]; }); it("Ethers Polygon GasStation: Fallback", async function () { const baseFeeMultiplier = toBNWei("2.0"); + const priorityFeeMultiplier = toBNWei("1.5"); process.env["TEST_REVERTING_POLYGON_GAS_STATION"] = "true"; const chainId = 137; @@ -214,11 +245,13 @@ describe("Gas Price Oracle", function () { const { maxFeePerGas, maxPriorityFeePerGas } = await getGasPriceEstimate(provider, { chainId, baseFeeMultiplier, + priorityFeeMultiplier, }); - const minPolygonPriorityFee = parseUnits("30", 9); - const expectedPriorityFee = stdMaxPriorityFeePerGas.gt(minPolygonPriorityFee) - ? stdMaxPriorityFeePerGas + const minPolygonPriorityFee = parseUnits("30", 9).mul(priorityFeeMultiplier).div(fixedPointAdjustment); + const markedUpPriorityFee = stdMaxPriorityFeePerGas.mul(priorityFeeMultiplier).div(fixedPointAdjustment); + const expectedPriorityFee = markedUpPriorityFee.gt(minPolygonPriorityFee) + ? markedUpPriorityFee : minPolygonPriorityFee; expect(maxFeePerGas).to.equal( stdLastBaseFeePerGas.mul(baseFeeMultiplier).div(fixedPointAdjustment).add(expectedPriorityFee)