Skip to content

Commit

Permalink
feat(GasPriceOracle): Allow caller to set a priority fee multiplier (#…
Browse files Browse the repository at this point in the history
…817)

Co-authored-by: James Morris, MS <[email protected]>
  • Loading branch information
nicholaspai and james-a-morris authored Jan 6, 2025
1 parent 291212b commit f120258
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 40 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
4 changes: 4 additions & 0 deletions src/gasPriceOracle/adapters/arbitrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GasPriceEstimate> {
Expand Down
23 changes: 15 additions & 8 deletions src/gasPriceOracle/adapters/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GasPriceEstimate> {
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);
}

/**
Expand All @@ -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<GasPriceEstimate> {
const [{ baseFeePerGas }, _maxPriorityFeePerGas] = await Promise.all([
provider.getBlock("pending"),
Expand All @@ -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,
};
}

Expand All @@ -61,7 +66,8 @@ export async function eip1559Raw(
export async function eip1559Bad(
provider: providers.Provider,
chainId: number,
baseFeeMultiplier: BigNumber
baseFeeMultiplier: BigNumber,
priorityFeeMultiplier: BigNumber
): Promise<GasPriceEstimate> {
const feeData = await provider.getFeeData();

Expand All @@ -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 };
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/gasPriceOracle/adapters/linea-viem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
8 changes: 8 additions & 0 deletions src/gasPriceOracle/adapters/linea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GasPriceEstimate> {
// 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"
Expand Down
8 changes: 5 additions & 3 deletions src/gasPriceOracle/adapters/polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export async function gasStation(
provider: providers.Provider,
opts: GasPriceEstimateOptions
): Promise<GasPriceEstimate> {
const { chainId, baseFeeMultiplier } = opts;
const { chainId, baseFeeMultiplier, priorityFeeMultiplier } = opts;
let gasStation: PolygonGasStation;
if (process.env.TEST_POLYGON_GAS_STATION === "true") {
gasStation = new MockPolygonGasStation();
Expand All @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/gasPriceOracle/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
};
Expand Down
83 changes: 58 additions & 25 deletions test/GasPriceOracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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;

Expand All @@ -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)
Expand Down

0 comments on commit f120258

Please sign in to comment.