Skip to content

Commit

Permalink
add gas costs to cron job
Browse files Browse the repository at this point in the history
  • Loading branch information
nicholaspai committed Jan 11, 2025
1 parent 9e18813 commit bc5aec1
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 41 deletions.
28 changes: 18 additions & 10 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1980,13 +1980,11 @@ export function getCachedNativeGasCost(
const ttlPerChain = {
default: 60,
};

const cacheKey = buildInternalCacheKey(
"nativeGasCost",
deposit.destinationChainId,
deposit.outputToken
);
const ttl = ttlPerChain.default;
const fetchFn = async () => {
const relayerAddress =
overrides?.relayerAddress ??
Expand All @@ -2007,9 +2005,14 @@ export function getCachedNativeGasCost(
return voidSigner.estimateGas(unsignedFillTxn);
};

return getCachedValue(cacheKey, ttl, fetchFn, (nativeGasCostFromCache) => {
return BigNumber.from(nativeGasCostFromCache);
});
return makeCacheGetterAndSetter(
cacheKey,
ttlPerChain.default,
fetchFn,
(nativeGasCostFromCache) => {
return BigNumber.from(nativeGasCostFromCache);
}
);
}

export function getCachedOpStackL1DataFee(
Expand All @@ -2032,7 +2035,6 @@ export function getCachedOpStackL1DataFee(
deposit.outputToken // This should technically differ based on the output token since the L2 calldata
// size affects the L1 data fee and this calldata can differ based on the output token.
);
const ttl = ttlPerChain.default;
const fetchFn = async () => {
// 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.
Expand All @@ -2058,9 +2060,14 @@ export function getCachedOpStackL1DataFee(
return opStackL1GasCost;
};

return getCachedValue(cacheKey, ttl, fetchFn, (l1DataFeeFromCache) => {
return BigNumber.from(l1DataFeeFromCache);
});
return makeCacheGetterAndSetter(
cacheKey,
ttlPerChain.default,
fetchFn,
(l1DataFeeFromCache) => {
return BigNumber.from(l1DataFeeFromCache);
}
);
}

export function latestGasPriceCache(
Expand All @@ -2071,7 +2078,8 @@ export function latestGasPriceCache(
}>
) {
const ttlPerChain = {
default: 5,
default: 10,
[CHAIN_IDs.MAINNET]: 24,
};
return makeCacheGetterAndSetter(
// If deposit is defined, then the gas price will be dependent on the fill transaction derived from the deposit.
Expand Down
155 changes: 128 additions & 27 deletions api/cron-cache-gas-prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,54 @@ import { VercelResponse } from "@vercel/node";
import { TypedVercelRequest } from "./_types";
import {
HUB_POOL_CHAIN_ID,
getCachedNativeGasCost,
getCachedOpStackL1DataFee,
getLogger,
handleErrorCondition,
latestGasPriceCache,
resolveVercelEndpoint,
} from "./_utils";
import { UnauthorizedError } from "./_errors";

import mainnetChains from "../src/data/chains_1.json";
import { utils } from "@across-protocol/sdk";
import { CHAIN_IDs } from "./_constants";
import { utils, constants } from "@across-protocol/sdk";
import { CHAIN_IDs, DEFAULT_SIMULATED_RECIPIENT_ADDRESS } from "./_constants";
import axios from "axios";
import { ethers } from "ethers";

type Route = {
originChainId: number;
originToken: string;
destinationChainId: number;
destinationToken: string;
originTokenSymbol: string;
destinationTokenSymbol: string;
};

// Set slightly lower than TTL in latestGasPriceCache
const updateIntervalsSecPerChain = {
default: 10,
default: 5,
1: 12,
};

// Set slightly lower than TTL in getCachedOpStackL1DataFee.
const updateL1DataFeeIntervalsSecPerChain = {
default: 10,
};

const maxDurationSec = 60;

const getDepositArgsForChainId = (chainId: number, tokenAddress: string) => {
return {
amount: ethers.BigNumber.from(100),
inputToken: constants.ZERO_ADDRESS,
outputToken: tokenAddress,
recipientAddress: DEFAULT_SIMULATED_RECIPIENT_ADDRESS,
originChainId: 0, // Shouldn't matter for simulation
destinationChainId: Number(chainId),
};
};

const handler = async (
request: TypedVercelRequest<Record<string, never>>,
response: VercelResponse
Expand Down Expand Up @@ -46,36 +77,106 @@ const handler = async (
return;
}

const availableRoutes = (
await axios(`${resolveVercelEndpoint()}/api/available-routes`)
).data as Array<Route>;

// This marks the timestamp when the function started
const functionStart = Date.now();

/**
* @notice Updates the gas price cache every `updateIntervalsSecPerChain` seconds up to `maxDurationSec` seconds.
* @param chainId Chain to estimate gas price for
* @param outputTokenAddress Optional param to set if the gas price is dependent on the calldata of the transaction
* to be submitted on the chainId. This output token will be used to construct a fill transaction to simulate.
*/
const updateGasPricePromise = async (
chainId: number,
outputTokenAddress?: string
) => {
const secondsPerUpdateForChain =
updateIntervalsSecPerChain[
chainId as keyof typeof updateIntervalsSecPerChain
] || updateIntervalsSecPerChain.default;
const cache = latestGasPriceCache(
chainId,
outputTokenAddress
? getDepositArgsForChainId(chainId, outputTokenAddress)
: undefined
);

while (true) {
const diff = Date.now() - functionStart;
// Stop after `maxDurationSec` seconds
if (diff >= maxDurationSec * 1000) {
break;
}
await cache.set();
await utils.delay(secondsPerUpdateForChain);
}
};

/**
* @notice Updates the L1 data fee and L2 gas cost caches every `updateL1DataFeeIntervalsSecPerChain` seconds
* up to `maxDurationSec` seconds.
* @dev This function will also update the L2 gas costs because this value is required to get the L1 data fee.
* @param chainId Chain to estimate gas price for
* @param outputTokenAddress This output token will be used to construct a fill transaction to simulate
* gas costs for.
*/
const updateL1DataFeePromise = async (
chainId: number,
outputTokenAddress: string
) => {
const secondsPerUpdateForChain =
updateL1DataFeeIntervalsSecPerChain.default;
const depositArgs = getDepositArgsForChainId(chainId, outputTokenAddress);
const gasCostCache = getCachedNativeGasCost(depositArgs);

while (true) {
const diff = Date.now() - functionStart;
// Stop after `maxDurationSec` seconds
if (diff >= maxDurationSec * 1000) {
break;
}
const gasCost = await gasCostCache.get();
const cache = getCachedOpStackL1DataFee(depositArgs, gasCost);
await cache.set();
await utils.delay(secondsPerUpdateForChain);
}
};

// The minimum interval for Vercel Serverless Functions cron jobs is 1 minute.
// But we want to update gas prices more frequently than that.
// But we want to update gas data more frequently than that.
// To circumvent this, we run the function in a loop and update gas prices every
// `secondsPerUpdateForChain` seconds and stop after `maxDurationSec` seconds (1 minute).
const gasPricePromises = mainnetChains
// @dev Remove Linea from this cron cache job because Linea's gas price is dependent on the
// calldata of the transaction to be submitted on Linea.
.filter((chain) => CHAIN_IDs.LINEA !== chain.chainId)
.map(async (chain) => {
const secondsPerUpdateForChain =
updateIntervalsSecPerChain[
chain.chainId as keyof typeof updateIntervalsSecPerChain
] || updateIntervalsSecPerChain.default;
// The deposit args don't matter for any chain besides Linea, which is why we filter it out
// above, because gas price on Linea is dependent on the fill transaction args.
const cache = latestGasPriceCache(chain.chainId);

while (true) {
const diff = Date.now() - functionStart;
// Stop after `maxDurationSec` seconds
if (diff >= maxDurationSec * 1000) {
break;
}
await cache.set();
await utils.delay(secondsPerUpdateForChain);
}
});
const gasPricePromises = mainnetChains.map(async (chain) => {
// For each chain:
// - update the destination gas price for the chain
// For each output token on that chain:
// - update the simulated gas costs for the token
const routesToChain = availableRoutes.filter(
({ destinationChainId }) => destinationChainId === chain.chainId
);
const outputTokensForChain = routesToChain.map(
({ destinationToken }) => destinationToken
);
Promise.all(
outputTokensForChain.map((outputToken) =>
updateL1DataFeePromise(chain.chainId, outputToken)
)
);
// @dev Linea gas prices are dependent on the L2 calldata to be submitted so compute one gas price for each output token
if (chain.chainId === CHAIN_IDs.LINEA) {
Promise.all(
outputTokensForChain.map((outputToken) =>
updateGasPricePromise(chain.chainId, outputToken)
)
);
} else {
updateGasPricePromise(chain.chainId);
}
});
await Promise.all(gasPricePromises);

logger.debug({
Expand Down
4 changes: 2 additions & 2 deletions api/gas-prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const handler = async (
tokenAddress
);
return Promise.all([
getCachedNativeGasCost(depositArgs),
getCachedNativeGasCost(depositArgs).get(),
latestGasPriceCache(
Number(chainId),
CHAIN_IDs.LINEA === Number(chainId) ? depositArgs : undefined
Expand All @@ -82,7 +82,7 @@ const handler = async (
);
const [nativeGasCost, gasPrice] = gasData[i];
const opStackL1GasCost = sdk.utils.chainIsOPStack(Number(chainId))
? await getCachedOpStackL1DataFee(depositArgs, nativeGasCost)
? await getCachedOpStackL1DataFee(depositArgs, nativeGasCost).get()
: undefined;
const tokenGasCost = nativeGasCost
.mul(gasPrice.maxFeePerGas)
Expand Down
6 changes: 4 additions & 2 deletions api/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,9 @@ const handler = async (
).get(),
isMessageDefined
? undefined // Only use cached gas units if message is not defined, i.e. standard for standard bridges
: getCachedNativeGasCost(depositArgs, { relayerAddress: relayer }),
: getCachedNativeGasCost(depositArgs, {
relayerAddress: relayer,
}).get(),
]);
const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString());

Expand All @@ -204,7 +206,7 @@ const handler = async (
? // Only use cached gas units if message is not defined, i.e. standard for standard bridges
getCachedOpStackL1DataFee(depositArgs, nativeGasCost, {
relayerAddress: relayer,
})
}).get()
: undefined,
callViaMulticall3(provider, multiCalls, {
blockTag: latestBlock.number,
Expand Down

0 comments on commit bc5aec1

Please sign in to comment.