Skip to content

Commit

Permalink
feat(gasPriceOracle): Allow caller to pass in base fee multiplier (#801)
Browse files Browse the repository at this point in the history
Signed-off-by: nicholaspai <[email protected]>
Co-authored-by: Paul <[email protected]>
  • Loading branch information
nicholaspai and pxrl authored Jan 3, 2025
1 parent a7615d0 commit c183a52
Show file tree
Hide file tree
Showing 22 changed files with 659 additions and 463 deletions.
57 changes: 0 additions & 57 deletions e2e/oracle.e2e.ts

This file was deleted.

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.3.32",
"version": "3.4.0",
"license": "AGPL-3.0",
"homepage": "https://docs.across.to/reference/sdk",
"files": [
Expand Down
13 changes: 0 additions & 13 deletions src/gasPriceOracle/adapters/arbitrum-viem.ts

This file was deleted.

19 changes: 13 additions & 6 deletions src/gasPriceOracle/adapters/arbitrum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@ import { providers } from "ethers";
import { bnOne } from "../../utils";
import { GasPriceEstimate } from "../types";
import * as ethereum from "./ethereum";
import { GasPriceEstimateOptions } from "../oracle";

// Arbitrum Nitro implements EIP-1559 pricing, but the priority fee is always refunded to the caller.
// Reference: https://docs.arbitrum.io/how-arbitrum-works/gas-fees
export async function eip1559(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
const { maxFeePerGas: _maxFeePerGas, maxPriorityFeePerGas } = await ethereum.eip1559(provider, chainId);
/**
* @notice Return Arbitrum orbit gas fees
* @dev Arbitrum Nitro implements EIP-1559 pricing, but the priority fee is always refunded to the caller.
* Reference: https://docs.arbitrum.io/how-arbitrum-works/gas-fees so we hardcode the priority fee
* to 1 wei.
* @param provider Ethers Provider
* @returns GasPriceEstimate
*/
export async function eip1559(provider: providers.Provider, opts: GasPriceEstimateOptions): Promise<GasPriceEstimate> {
const { maxFeePerGas: _maxFeePerGas, maxPriorityFeePerGas } = await ethereum.eip1559(provider, opts);

// eip1559() sets maxFeePerGas = lastBaseFeePerGas + maxPriorityFeePerGas, so revert that.
// The caller may apply scaling as they wish afterwards.
// eip1559() sets maxFeePerGas = lastBaseFeePerGas + maxPriorityFeePerGas, so back out priority fee.
// The remaining maxFeePerGas should be scaled already.
const maxFeePerGas = _maxFeePerGas.sub(maxPriorityFeePerGas).add(bnOne);

return { maxPriorityFeePerGas: bnOne, maxFeePerGas };
Expand Down
19 changes: 0 additions & 19 deletions src/gasPriceOracle/adapters/ethereum-viem.ts

This file was deleted.

55 changes: 43 additions & 12 deletions src/gasPriceOracle/adapters/ethereum.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,96 @@
import assert from "assert";
import { providers } from "ethers";
import { BigNumber, bnZero, getNetworkName } from "../../utils";
import { BigNumber, bnZero, fixedPointAdjustment, getNetworkName } from "../../utils";
import { GasPriceEstimate } from "../types";
import { gasPriceError } from "../util";
import { GasPriceEstimateOptions } from "../oracle";

// TODO: We intend to remove `eip1559Bad()` as an option and make eip1559Raw the only option eventually. The reason
// they both exist currently is because eip1559Raw is new and untested on production so we will slowly roll it out
// by using the convenient environment variable safety guard.

/**
* @dev If GAS_PRICE_EIP1559_RAW_${chainId}=true, then constructs total fee by adding
* 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 chainId Chain ID of provider instance.
* @returns Promise of gas price estimate object.
*/
export function eip1559(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
const useRaw = process.env[`GAS_PRICE_EIP1559_RAW_${chainId}`] === "true";
return useRaw ? eip1559Raw(provider, chainId) : eip1559Bad(provider, chainId);
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);
}

/**
* @note Performs direct RPC calls to retrieve the RPC-suggested priority fee for the next block.
* @dev Constructs total fee by adding eth_getBlock("pending").baseFee to eth_maxPriorityFeePerGas
* @param provider ethers RPC provider instance.
* @param chainId Chain ID of the provider instance.
* @returns Promise of gas price estimate object.
*/
export async function eip1559Raw(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
export async function eip1559Raw(
provider: providers.Provider,
chainId: number,
baseFeeMultiplier: BigNumber
): Promise<GasPriceEstimate> {
const [{ baseFeePerGas }, _maxPriorityFeePerGas] = await Promise.all([
provider.getBlock("pending"),
(provider as providers.JsonRpcProvider).send("eth_maxPriorityFeePerGas", []),
]);
const maxPriorityFeePerGas = BigNumber.from(_maxPriorityFeePerGas);
assert(BigNumber.isBigNumber(baseFeePerGas), `No baseFeePerGas received on ${getNetworkName(chainId)}`);

const scaledBaseFee = baseFeePerGas.mul(baseFeeMultiplier).div(fixedPointAdjustment);
return {
maxFeePerGas: maxPriorityFeePerGas.add(baseFeePerGas),
maxFeePerGas: maxPriorityFeePerGas.add(scaledBaseFee),
maxPriorityFeePerGas,
};
}

/**
* @note Resolves priority gas pricing poorly, because the priority fee is hardcoded to 1.5 Gwei in ethers v5.
* @notice Returns fee data using provider's getFeeData() method.
* @note Resolves priority gas pricing poorly, because the priority fee is hardcoded to 1.5 Gwei in ethers v5's
* getFeeData() method
* @dev TODO: Remove this function soon. See note above about slowly rolling out eip1559Raw.
* @param provider ethers RPC provider instance.
* @param chainId Chain ID of the provider instance.
* @returns Promise of gas price estimate object.
*/
export async function eip1559Bad(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
export async function eip1559Bad(
provider: providers.Provider,
chainId: number,
baseFeeMultiplier: BigNumber
): Promise<GasPriceEstimate> {
const feeData = await provider.getFeeData();

[feeData.lastBaseFeePerGas, feeData.maxPriorityFeePerGas].forEach((field: BigNumber | null) => {
if (!BigNumber.isBigNumber(field) || field.lt(bnZero)) gasPriceError("getFeeData()", chainId, feeData);
});

const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas as BigNumber;
const maxFeePerGas = maxPriorityFeePerGas.add(feeData.lastBaseFeePerGas as BigNumber);
const scaledLastBaseFeePerGas = (feeData.lastBaseFeePerGas as BigNumber)
.mul(baseFeeMultiplier)
.div(fixedPointAdjustment);
const maxFeePerGas = maxPriorityFeePerGas.add(scaledLastBaseFeePerGas);

return { maxPriorityFeePerGas, maxFeePerGas };
}

export async function legacy(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
/**
* @notice Returns result of eth_gasPrice RPC call
* @dev Its recommended to use the eip1559Raw method over this one where possible as it will be more accurate.
* @returns GasPriceEstimate
*/
export async function legacy(provider: providers.Provider, opts: GasPriceEstimateOptions): Promise<GasPriceEstimate> {
const { chainId, baseFeeMultiplier } = opts;
const gasPrice = await provider.getGasPrice();

if (!BigNumber.isBigNumber(gasPrice) || gasPrice.lt(bnZero)) gasPriceError("getGasPrice()", chainId, gasPrice);

return {
maxFeePerGas: gasPrice,
maxFeePerGas: gasPrice.mul(baseFeeMultiplier).div(fixedPointAdjustment),
maxPriorityFeePerGas: bnZero,
};
}
38 changes: 32 additions & 6 deletions src/gasPriceOracle/adapters/linea-viem.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
import { PublicClient } from "viem";
import { Address, Hex, PublicClient } from "viem";
import { estimateGas } from "viem/linea";
import { DEFAULT_SIMULATED_RELAYER_ADDRESS as account } from "../../constants";
import { InternalGasPriceEstimate } from "../types";
import { GasPriceEstimateOptions } from "../oracle";
import { fixedPointAdjustment } from "../../utils";

export async function eip1559(provider: PublicClient, _chainId?: number): Promise<InternalGasPriceEstimate> {
const { baseFeePerGas, priorityFeePerGas } = await estimateGas(provider, {
account,
to: account,
value: BigInt(1),
/**
* @notice The Linea viem provider calls the linea_estimateGas RPC endpoint to estimate gas. Linea is unique
* in that the recommended fee per gas is hardcoded to 7 wei while the priority fee is dynamic based on the
* compressed transaction size, layer 1 verification costs and capacity, gas price ratio between layer 1 and layer 2,
* the transaction's gas usage, the minimum gas price on layer 2,
* and a minimum margin (for error) for gas price estimation.
* Source: https://docs.linea.build/get-started/how-to/gas-fees#how-gas-works-on-linea
* @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 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
* here: https://docs.linea.build/api/reference/linea-estimategas#parameters
* @returns
*/
export async function eip1559(
provider: PublicClient,
opts: GasPriceEstimateOptions
): Promise<InternalGasPriceEstimate> {
const { unsignedTx, baseFeeMultiplier } = opts;
const { baseFeePerGas, priorityFeePerGas: _priorityFeePerGas } = await estimateGas(provider, {
account: (unsignedTx?.from as Address) ?? account,
to: (unsignedTx?.to as Address) ?? account,
value: BigInt(unsignedTx?.value?.toString() ?? "1"),
data: (unsignedTx?.data as Hex) ?? "0x",
});
const priorityFeePerGas =
(_priorityFeePerGas * BigInt(baseFeeMultiplier.toString())) / BigInt(fixedPointAdjustment.toString());

return {
maxFeePerGas: baseFeePerGas + priorityFeePerGas,
Expand Down
9 changes: 7 additions & 2 deletions src/gasPriceOracle/adapters/linea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
import { providers } from "ethers";
import { GasPriceEstimate } from "../types";
import * as ethereum from "./ethereum";
import { GasPriceEstimateOptions } from "../oracle";

export function eip1559(provider: providers.Provider, chainId: number): Promise<GasPriceEstimate> {
return ethereum.legacy(provider, chainId);
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"
// cases. Based on testing, `eth_gasPrice` returns the closest price to the Linea-specific `linea_estimateGas`
// endpoint which the Viem Linea adapter queries.
return ethereum.legacy(provider, opts);
}
86 changes: 0 additions & 86 deletions src/gasPriceOracle/adapters/polygon-viem.ts

This file was deleted.

Loading

0 comments on commit c183a52

Please sign in to comment.