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

Open
wants to merge 42 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
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
49 changes: 25 additions & 24 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 @@ -589,14 +588,21 @@ export const getHubPoolClient = () => {
);
};

export const getGasMarkup = (chainId: string | number) => {
export const getGasMarkup = (chainId: string | number): BigNumber => {
// If GAS_MARKUP is defined for chain, then use it.
if (typeof gasMarkup[chainId] === "number") {
return gasMarkup[chainId];
return utils.parseEther((1 + gasMarkup[chainId]).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 utils.parseEther(
(
1 +
(sdk.utils.chainIsOPStack(Number(chainId))
? gasMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP)
).toString()
);
};

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

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

Expand Down Expand Up @@ -1931,12 +1936,11 @@ export function getCachedFillGasUsage(
deposit.destinationChainId,
overrides
);
// 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 { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts(
buildDepositForSimulation(deposit),
overrides?.relayerAddress,
{
omitMarkup: true,
}
overrides?.relayerAddress
);
return nativeGasCost;
};
Expand All @@ -1955,7 +1959,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 +1969,13 @@ 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> {
return sdk.gasPriceOracle.getGasPriceEstimate(getProvider(chainId), {
chainId,
baseFeeMultiplier: getGasMarkup(chainId),
});
}

/**
Expand Down
120 changes: 114 additions & 6 deletions api/gas-prices.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,139 @@
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 { 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,
}
);
// 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);
}
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)
),
},
nativeGasCost: gasCosts[i].nativeGasCost.toString(),
tokenGasCost: gasCosts[i].tokenGasCost.toString(),
opStackL1GasCost: gasCosts[i]?.opStackL1GasCost?.toString(),
},
])
),
};

logger.debug({
at: "GasPrices",
Expand Down
2 changes: 2 additions & 0 deletions api/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ const handler = async (
tokenPriceNative,
relayer,
gasUnits,
// !!gas price should be defined and passed into getRelayerFeeDetails so we don't recompute using default
Copy link
Member Author

@nicholaspai nicholaspai Jan 4, 2025

Choose a reason for hiding this comment

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

I think its important to highlight the importance of not letting gasPrice be undefined here, otherwise the effects of the baseFeeMultiplier, based on the developer-set GAS_MARKUP will be discarded. We could even add an assert here...

// settings in the GasPriceOracle
gasPrice
),
callViaMulticall3(provider, multiCalls, {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@across-protocol/constants": "^3.1.24",
"@across-protocol/contracts": "^3.0.19",
"@across-protocol/contracts-v3.0.6": "npm:@across-protocol/[email protected]",
"@across-protocol/sdk": "^3.3.27",
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 SDK change has two important changes that will materially impact the gasFeeTotal value returned in the API response of the /limits (and /suggested-fees) endpoint:

  • Zora chain gets re-added back to OP_STACK family which means the fees will go up since OPStack chains add an L1GasCost component
  • GAS_MARKUP will be applied to base fee, not total token gas cost, which will cut many gas fee totals down significantly because base fees are much smaller than priority fees on most chains, for example most OP Stack chains

"@across-protocol/sdk": "^3.4.3",
"@amplitude/analytics-browser": "^2.3.5",
"@balancer-labs/sdk": "1.1.6-beta.16",
"@emotion/react": "^11.13.0",
Expand Down
49 changes: 37 additions & 12 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
"@uma/common" "^2.17.0"
hardhat "^2.9.3"

"@across-protocol/constants@^3.1.24":
version "3.1.24"
resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.24.tgz#01fe49330bb467dd01813387ddbac741bc74a035"
integrity sha512-guKtvIbif//vsmSZbwGubTWVtfkWiyWenr2sVyo63U/68GOW89ceJRLu4efLjeLVGiSrNAJtFUCv9dTwrrosWA==
"@across-protocol/constants@^3.1.24", "@across-protocol/constants@^3.1.25":
version "3.1.25"
resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.25.tgz#60d6d9814582ff91faf2b6d9f51d6dccb447b4ce"
integrity sha512-GpZoYn7hETYL2BPMM2GqXAer6+l/xuhder+pvpb00HJcb/sqCjF7vaaeKxjKJ3jKtyeulYmdu0NDkeNm5KbNWA==

"@across-protocol/constants@^3.1.9":
version "3.1.13"
Expand Down Expand Up @@ -83,13 +83,13 @@
yargs "^17.7.2"
zksync-web3 "^0.14.3"

"@across-protocol/sdk@^3.3.27":
version "3.3.27"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.3.27.tgz#ca58012c30e7d489ba5493981447c8cf2b82ef58"
integrity sha512-TebERt9Z0CF3NzD4dkurrgEqHn7gqidgoUrvk0kMUy2HzrlZilYj3oKgn+Kevn4XqomPfdWOkuAyiWDV5uNYew==
"@across-protocol/sdk@^3.4.3":
version "3.4.3"
resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.4.3.tgz#4b57028b0a04ceb2387a45d950e689d43ed3e587"
integrity sha512-1BFL5W1USZYGMGVnrFdXBVNTJQu2wdvGI2hB73J8bvO03jFzY3EcWEEGDSp0Bq29V1WuX1vrjBNIczSGTAbrdw==
dependencies:
"@across-protocol/across-token" "^1.0.0"
"@across-protocol/constants" "^3.1.24"
"@across-protocol/constants" "^3.1.25"
"@across-protocol/contracts" "^3.0.19"
"@eth-optimism/sdk" "^3.3.1"
"@ethersproject/bignumber" "^5.7.0"
Expand Down Expand Up @@ -22961,7 +22961,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand Down Expand Up @@ -22996,6 +22996,15 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"

string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
Expand Down Expand Up @@ -23070,7 +23079,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -23098,6 +23107,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2"
Expand Down Expand Up @@ -25814,7 +25830,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down Expand Up @@ -25849,6 +25865,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^8.0.1, wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
Expand Down
Loading