Skip to content

Commit

Permalink
improve(API): Apply gas mark up to base fee rather than gas cost (#1339)
Browse files Browse the repository at this point in the history
* improve(API): Apply gas mark up to base fee rather than gas cost

* Update _utils.ts

* Update _utils.ts

* Update _utils.ts

* Update limits.ts

* Update _utils.ts

* 3.4.1 sdk

* Update _utils.ts

* Update _utils.ts

* 3.4.3

* Update _utils.ts

* improve(API): Add gas costs to /gas-prices endpoint

Signed-off-by: nicholaspai <[email protected]>

* Add gas costs to api

* Update _utils.ts

* Update gas-prices.ts

* Update _utils.ts

* Update gas-prices.ts

* Remove reading from cache

* Update gas-prices.ts

* Update gas-prices.ts

* Update gas-prices.ts

* Allow caller to set token symbol

* Update gas-prices.ts

* Update gas-prices.ts

* Update gas-prices.ts

* Return full gas price broken down

* Update gas-prices.ts

* Update gas-prices.ts

* Update gas-prices.ts

* Update gas-prices.ts

* Add op stack gas cost

* refactor

* Revert "refactor"

This reverts commit 207397d.

* Revert "Add op stack gas cost"

This reverts commit 24c2bbd.

* Reapply "Add op stack gas cost"

This reverts commit e48b4f9.

* Try op stack l1 gas cost again

* Update gas-prices.ts

* refactor

* Add priority fee markup

* Update yarn.lock

* Try beta package

* Update _utils.ts

* 3.4.5

---------

Signed-off-by: nicholaspai <[email protected]>
  • Loading branch information
nicholaspai authored Jan 7, 2025
1 parent 3568cea commit f9b431d
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 56 deletions.
133 changes: 95 additions & 38 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
} from "@across-protocol/contracts/dist/typechain";
import acrossDeployments from "@across-protocol/contracts/dist/deployments/deployments.json";
import * as sdk from "@across-protocol/sdk";
import { asL2Provider } from "@eth-optimism/sdk";
import {
BALANCER_NETWORK_CONFIG,
BalancerSDK,
Expand Down Expand Up @@ -86,14 +85,19 @@ const {
REACT_APP_HUBPOOL_CHAINID,
REACT_APP_PUBLIC_INFURA_ID,
REACT_APP_COINGECKO_PRO_API_KEY,
GAS_MARKUP,
GAS_MARKUP, // To be deprecated and replaced by BASE_FEE_MARKUP and PRIORITY_FEE_MARKUP
BASE_FEE_MARKUP,
PRIORITY_FEE_MARKUP,
VERCEL_ENV,
LOG_LEVEL,
} = process.env;

export const gasMarkup: {
export const baseFeeMarkup: {
[chainId: string]: number;
} = GAS_MARKUP ? JSON.parse(GAS_MARKUP) : {};
} = JSON.parse(BASE_FEE_MARKUP || GAS_MARKUP || "{}");
export const priorityFeeMarkup: {
[chainId: string]: number;
} = JSON.parse(PRIORITY_FEE_MARKUP || "{}");
// Default to no markup.
export const DEFAULT_GAS_MARKUP = 0;

Expand Down Expand Up @@ -589,14 +593,47 @@ export const getHubPoolClient = () => {
);
};

export const getGasMarkup = (chainId: string | number) => {
if (typeof gasMarkup[chainId] === "number") {
return gasMarkup[chainId];
export const getGasMarkup = (
chainId: string | number
): { baseFeeMarkup: BigNumber; priorityFeeMarkup: BigNumber } => {
let _baseFeeMarkup: BigNumber | undefined;
let _priorityFeeMarkup: BigNumber | undefined;
if (typeof baseFeeMarkup[chainId] === "number") {
_baseFeeMarkup = utils.parseEther((1 + baseFeeMarkup[chainId]).toString());
}
if (typeof priorityFeeMarkup[chainId] === "number") {
_priorityFeeMarkup = utils.parseEther(
(1 + priorityFeeMarkup[chainId]).toString()
);
}

// Otherwise, use default gas markup (or optimism's for OP stack).
if (_baseFeeMarkup === undefined) {
_baseFeeMarkup = utils.parseEther(
(
1 +
(sdk.utils.chainIsOPStack(Number(chainId))
? baseFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP)
).toString()
);
}
if (_priorityFeeMarkup === undefined) {
_priorityFeeMarkup = utils.parseEther(
(
1 +
(sdk.utils.chainIsOPStack(Number(chainId))
? priorityFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP)
).toString()
);
}

return sdk.utils.chainIsOPStack(Number(chainId))
? gasMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP;
// Otherwise, use default gas markup (or optimism's for OP stack).
return {
baseFeeMarkup: _baseFeeMarkup,
priorityFeeMarkup: _priorityFeeMarkup,
};
};

/**
Expand Down Expand Up @@ -627,7 +664,7 @@ export const getRelayerFeeCalculator = (
);
};

const getRelayerFeeCalculatorQueries = (
export const getRelayerFeeCalculatorQueries = (
destinationChainId: number,
overrides: Partial<{
spokePoolAddress: string;
Expand All @@ -641,8 +678,7 @@ const getRelayerFeeCalculatorQueries = (
overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId),
overrides.relayerAddress,
REACT_APP_COINGECKO_PRO_API_KEY,
getLogger(),
getGasMarkup(destinationChainId)
getLogger()
);
};

Expand All @@ -654,12 +690,13 @@ const getRelayerFeeCalculatorQueries = (
* @param originChainId The origin chain that this token will be transferred from
* @param destinationChainId The destination chain that this token will be transferred to
* @param recipientAddress The address that will receive the transferred funds
* @param tokenPrice An optional overred price to prevent the SDK from creating its own call
* @param message An optional message to include in the transfer
* @param relayerAddress An optional relayer address to use for the transfer
* @param gasUnits An optional gas unit to use for the transfer
* @param gasPrice An optional gas price to use for the transfer
* @returns The a promise to the relayer fee for the given `amount` of transferring `l1Token` to `destinationChainId`
* @param tokenPrice Price of input token in gas token units, used by SDK to compute gas fee percentages.
* @param relayerAddress Relayer address that SDK will use to simulate the fill transaction for gas cost estimation if
* the gasUnits is not defined.
* @param gasPrice Gas price that SDK will use to compute gas fee percentages.
* @param [gasUnits] An optional gas cost to use for the transfer. If not provided, the SDK will recompute this.
* @returns Relayer fee components for a fill of the given `amount` of transferring `l1Token` to `destinationChainId`
*/
export const getRelayerFeeDetails = async (
deposit: {
Expand All @@ -671,10 +708,11 @@ export const getRelayerFeeDetails = async (
recipientAddress: string;
message?: string;
},
tokenPrice?: number,
relayerAddress?: string,
tokenPrice: number,
relayerAddress: string,
gasPrice: sdk.utils.BigNumberish,
gasUnits?: sdk.utils.BigNumberish,
gasPrice?: sdk.utils.BigNumberish
tokenGasCost?: sdk.utils.BigNumberish
): Promise<sdk.relayFeeCalculator.RelayerFeeDetails> => {
const {
inputToken,
Expand Down Expand Up @@ -703,7 +741,8 @@ export const getRelayerFeeDetails = async (
relayerAddress,
tokenPrice,
gasPrice,
gasUnits
gasUnits,
tokenGasCost
);
};

Expand Down Expand Up @@ -1931,18 +1970,34 @@ export function getCachedFillGasUsage(
deposit.destinationChainId,
overrides
);
const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts(
// We don't care about the gas token price or the token gas price, only the raw gas units. In the API
// we'll compute the gas price separately.
const gasCosts = await relayerFeeCalculatorQueries.getGasCosts(
buildDepositForSimulation(deposit),
overrides?.relayerAddress,
{
omitMarkup: true,
// Scale the op stack L1 gas cost component by the base fee multiplier.
// Consider adding a new environment variable OP_STACK_L1_GAS_COST_MARKUP if we want finer-grained control.
opStackL1GasCostMultiplier: getGasMarkup(deposit.destinationChainId)
.baseFeeMarkup,
}
);
return nativeGasCost;
return {
nativeGasCost: gasCosts.nativeGasCost,
tokenGasCost: gasCosts.tokenGasCost,
};
};

return getCachedValue(cacheKey, ttl, fetchFn, (bnFromCache) =>
BigNumber.from(bnFromCache)
return getCachedValue(
cacheKey,
ttl,
fetchFn,
(gasCosts: { nativeGasCost: BigNumber; tokenGasCost: BigNumber }) => {
return {
nativeGasCost: BigNumber.from(gasCosts.nativeGasCost),
tokenGasCost: BigNumber.from(gasCosts.tokenGasCost),
};
}
);
}

Expand All @@ -1955,7 +2010,7 @@ export function latestGasPriceCache(chainId: number) {
return makeCacheGetterAndSetter(
buildInternalCacheKey("latestGasPriceCache", chainId),
ttlPerChain[chainId] || ttlPerChain.default,
() => getMaxFeePerGas(chainId),
async () => (await getMaxFeePerGas(chainId)).maxFeePerGas,
(bnFromCache) => BigNumber.from(bnFromCache)
);
}
Expand All @@ -1965,16 +2020,18 @@ export function latestGasPriceCache(chainId: number) {
* @param chainId The chain ID to resolve the gas price for
* @returns The gas price in the native currency of the chain
*/
export async function getMaxFeePerGas(chainId: number): Promise<BigNumber> {
if (sdk.utils.chainIsOPStack(chainId)) {
const l2Provider = asL2Provider(getProvider(chainId));
return l2Provider.getGasPrice();
}
const { maxFeePerGas } = await sdk.gasPriceOracle.getGasPriceEstimate(
getProvider(chainId),
chainId
);
return maxFeePerGas;
export function getMaxFeePerGas(
chainId: number
): Promise<sdk.gasPriceOracle.GasPriceEstimate> {
const {
baseFeeMarkup: baseFeeMultiplier,
priorityFeeMarkup: priorityFeeMultiplier,
} = getGasMarkup(chainId);
return sdk.gasPriceOracle.getGasPriceEstimate(getProvider(chainId), {
chainId,
baseFeeMultiplier,
priorityFeeMultiplier,
});
}

/**
Expand Down
135 changes: 129 additions & 6 deletions api/gas-prices.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,154 @@
import { VercelResponse } from "@vercel/node";
import {
buildDepositForSimulation,
getGasMarkup,
getLogger,
getMaxFeePerGas,
getRelayerFeeCalculatorQueries,
handleErrorCondition,
latestGasPriceCache,
sendResponse,
} from "./_utils";
import { TypedVercelRequest } from "./_types";
import { ethers, providers, VoidSigner } from "ethers";
import * as sdk from "@across-protocol/sdk";
import { L2Provider } from "@eth-optimism/sdk/dist/interfaces/l2-provider";

import mainnetChains from "../src/data/chains_1.json";
import {
DEFAULT_SIMULATED_RECIPIENT_ADDRESS,
TOKEN_SYMBOLS_MAP,
} from "./_constants";
import { assert, Infer, object, optional, string } from "superstruct";

const chains = mainnetChains;

const QueryParamsSchema = object({
symbol: optional(string()),
});
type QueryParams = Infer<typeof QueryParamsSchema>;

const handler = async (
_: TypedVercelRequest<Record<string, never>>,
{ query }: TypedVercelRequest<QueryParams>,
response: VercelResponse
) => {
const logger = getLogger();
assert(query, QueryParamsSchema);
const tokenSymbol = query.symbol ?? "WETH";

try {
const chainIdsWithToken: { [chainId: string]: string } = Object.fromEntries(
chains
.map(({ chainId }) => {
const tokenAddress =
TOKEN_SYMBOLS_MAP?.[tokenSymbol as keyof typeof TOKEN_SYMBOLS_MAP]
?.addresses[chainId];
return [chainId, tokenAddress];
})
.filter(([, tokenAddress]) => tokenAddress !== undefined)
);
// getMaxFeePerGas will return the gas price after including the baseFeeMultiplier.
const gasPrices = await Promise.all(
chains.map(({ chainId }) => {
return latestGasPriceCache(chainId).get();
Object.keys(chainIdsWithToken).map((chainId) => {
return getMaxFeePerGas(Number(chainId));
})
);
const responseJson = Object.fromEntries(
chains.map(({ chainId }, i) => [chainId, gasPrices[i].toString()])
const gasCosts = await Promise.all(
Object.entries(chainIdsWithToken).map(
async ([chainId, tokenAddress], i) => {
// This is a dummy deposit used to pass into buildDepositForSimulation() to build a fill transaction
// that we can simulate without reversion. The only parameter that matters is that the destinationChainId
// is set to the spoke pool's chain ID we'll be simulating the fill call on.
const depositArgs = {
amount: ethers.BigNumber.from(100),
inputToken: sdk.constants.ZERO_ADDRESS,
outputToken: tokenAddress,
recipientAddress: DEFAULT_SIMULATED_RECIPIENT_ADDRESS,
originChainId: 0, // Shouldn't matter for simulation
destinationChainId: Number(chainId),
};
const deposit = buildDepositForSimulation(depositArgs);
const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries(
Number(chainId)
);
const opStackL1GasCostMultiplier = getGasMarkup(
Number(chainId)
).baseFeeMarkup;
const { nativeGasCost, tokenGasCost } =
await relayerFeeCalculatorQueries.getGasCosts(
deposit,
relayerFeeCalculatorQueries.simulatedRelayerAddress,
{
// Pass in the already-computed gasPrice into this query so that the tokenGasCost includes
// the scaled gas price,
// e.g. tokenGasCost = nativeGasCost * (baseFee * baseFeeMultiplier + priorityFee).
gasPrice: gasPrices[i].maxFeePerGas,
opStackL1GasCostMultiplier,
}
);
// OPStack chains factor in the L1 gas cost of including the L2 transaction in an L1 rollup batch
// into the total gas cost of the L2 transaction.
let opStackL1GasCost: ethers.BigNumber | undefined = undefined;
if (sdk.utils.chainIsOPStack(Number(chainId))) {
const provider = relayerFeeCalculatorQueries.provider;
const _unsignedTx = await sdk.utils.populateV3Relay(
relayerFeeCalculatorQueries.spokePool,
deposit,
relayerFeeCalculatorQueries.simulatedRelayerAddress
);
const voidSigner = new VoidSigner(
relayerFeeCalculatorQueries.simulatedRelayerAddress,
relayerFeeCalculatorQueries.provider
);
const unsignedTx = await voidSigner.populateTransaction({
..._unsignedTx,
gasLimit: nativeGasCost, // prevents additional gas estimation call
});
opStackL1GasCost = await (
provider as L2Provider<providers.Provider>
).estimateL1GasCost(unsignedTx);
opStackL1GasCost = opStackL1GasCostMultiplier
.mul(opStackL1GasCost)
.div(sdk.utils.fixedPointAdjustment);
}
return {
nativeGasCost,
tokenGasCost,
opStackL1GasCost,
};
}
)
);
const responseJson = {
tokenSymbol,
...Object.fromEntries(
Object.keys(chainIdsWithToken).map((chainId, i) => [
chainId,
{
gasPrice: gasPrices[i].maxFeePerGas.toString(),
gasPriceComponents: {
maxFeePerGas: gasPrices[i].maxFeePerGas
.sub(gasPrices[i].maxPriorityFeePerGas)
.toString(),
priorityFeePerGas: gasPrices[i].maxPriorityFeePerGas.toString(),
baseFeeMultiplier: ethers.utils.formatEther(
getGasMarkup(chainId).baseFeeMarkup
),
priorityFeeMultiplier: ethers.utils.formatEther(
getGasMarkup(chainId).priorityFeeMarkup
),
opStackL1GasCostMultiplier: sdk.utils.chainIsOPStack(
Number(chainId)
)
? ethers.utils.formatEther(getGasMarkup(chainId).baseFeeMarkup)
: undefined,
},
nativeGasCost: gasCosts[i].nativeGasCost.toString(),
tokenGasCost: gasCosts[i].tokenGasCost.toString(),
opStackL1GasCost: gasCosts[i]?.opStackL1GasCost?.toString(),
},
])
),
};

logger.debug({
at: "GasPrices",
Expand Down
Loading

0 comments on commit f9b431d

Please sign in to comment.