Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve(API): Apply gas mark up to base fee rather than gas cost #1339

Merged
merged 49 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1a21394
improve(API): Apply gas mark up to base fee rather than gas cost
nicholaspai Dec 19, 2024
eaf7fd8
Update _utils.ts
nicholaspai Dec 27, 2024
ed22e76
Update _utils.ts
nicholaspai Jan 2, 2025
00a4557
Update _utils.ts
nicholaspai Jan 2, 2025
cb6b690
Update limits.ts
nicholaspai Jan 2, 2025
cdde913
Update _utils.ts
nicholaspai Jan 3, 2025
396d62b
3.4.1 sdk
nicholaspai Jan 3, 2025
8cf8072
Merge branch 'master' into gas-markup-fee
nicholaspai Jan 3, 2025
99b5da2
Update _utils.ts
nicholaspai Jan 3, 2025
eb77db9
Update _utils.ts
nicholaspai Jan 3, 2025
ea0a94d
Merge branch 'master' into gas-markup-fee
nicholaspai Jan 3, 2025
ed05490
3.4.3
nicholaspai Jan 3, 2025
769296d
Update _utils.ts
nicholaspai Jan 3, 2025
6a524ac
improve(API): Add gas costs to /gas-prices endpoint
nicholaspai Jan 3, 2025
0113ac5
Merge branch 'gas-costs' into gas-markup-fee
nicholaspai Jan 3, 2025
3044ca9
Add gas costs to api
nicholaspai Jan 3, 2025
099d72b
Merge branch 'gas-costs' into gas-markup-fee
nicholaspai Jan 3, 2025
18c3641
Update _utils.ts
nicholaspai Jan 4, 2025
c9a3481
Update gas-prices.ts
nicholaspai Jan 4, 2025
75254f6
Update _utils.ts
nicholaspai Jan 4, 2025
6f2bcf3
Update gas-prices.ts
nicholaspai Jan 4, 2025
051be80
Remove reading from cache
nicholaspai Jan 4, 2025
7f4f2a8
Update gas-prices.ts
nicholaspai Jan 4, 2025
bbfbe1b
Merge branch 'gas-costs' into gas-markup-fee
nicholaspai Jan 4, 2025
5fb79b9
Update gas-prices.ts
nicholaspai Jan 4, 2025
6948d14
Update gas-prices.ts
nicholaspai Jan 4, 2025
752704c
Allow caller to set token symbol
nicholaspai Jan 4, 2025
868d76b
Update gas-prices.ts
nicholaspai Jan 4, 2025
7d91801
Update gas-prices.ts
nicholaspai Jan 4, 2025
47fa488
Update gas-prices.ts
nicholaspai Jan 4, 2025
5f6a1c5
Return full gas price broken down
nicholaspai Jan 4, 2025
2ffb6a6
Update gas-prices.ts
nicholaspai Jan 4, 2025
f10a8e9
Update gas-prices.ts
nicholaspai Jan 4, 2025
fc08217
Update gas-prices.ts
nicholaspai Jan 4, 2025
9fe1b31
Update gas-prices.ts
nicholaspai Jan 4, 2025
24c2bbd
Add op stack gas cost
nicholaspai Jan 4, 2025
207397d
refactor
nicholaspai Jan 4, 2025
ca704ea
Revert "refactor"
nicholaspai Jan 4, 2025
e48b4f9
Revert "Add op stack gas cost"
nicholaspai Jan 4, 2025
894c141
Reapply "Add op stack gas cost"
nicholaspai Jan 4, 2025
0f3d4f2
Try op stack l1 gas cost again
nicholaspai Jan 4, 2025
cd4dd7e
Update gas-prices.ts
nicholaspai Jan 4, 2025
fa9a87e
refactor
nicholaspai Jan 6, 2025
2ac4d07
Add priority fee markup
nicholaspai Jan 6, 2025
35ae6cf
Update yarn.lock
nicholaspai Jan 6, 2025
0237cda
Try beta package
nicholaspai Jan 6, 2025
1840184
Update _utils.ts
nicholaspai Jan 6, 2025
9af605f
3.4.5
nicholaspai Jan 6, 2025
33d4eb5
Merge branch 'master' into gas-markup-fee
nicholaspai Jan 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made gasPrice, tokenPrice and relayerAddress all required since they're never set undefined in this repo and its better to be more constricting on this type where possible

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)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important LOC we're removing. We should use gas price oracle for all estimates since it handles OP stack now

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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has a lot of changes but its an API endpoint we don't depend on in production as its mostly here to compare against the current existing API to compare the expected gas total changes. The main changes are:

  • Adding gas costs (native and token gas cost) to the API response
  • breaking down gas price by base fee and priority fee

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
Loading