Skip to content

Commit

Permalink
perf: cache gas units for fill
Browse files Browse the repository at this point in the history
  • Loading branch information
dohaki committed Sep 6, 2024
1 parent f9c0790 commit 279efc3
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 54 deletions.
179 changes: 139 additions & 40 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ import {
} from "@balancer-labs/sdk";
import { Log, Logging } from "@google-cloud/logging";
import axios from "axios";
import { BigNumber, ethers, providers, Signer, utils } from "ethers";
import {
BigNumber,
BigNumberish,
ethers,
providers,
utils,
Signer,
} from "ethers";
import { StructError, define } from "superstruct";

import enabledMainnetRoutesAsJson from "../src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json";
Expand Down Expand Up @@ -532,16 +539,7 @@ export const getRelayerFeeCalculator = (
relayerAddress: string;
}> = {}
) => {
const queries = sdk.relayFeeCalculator.QueryBase__factory.create(
destinationChainId,
getProvider(destinationChainId),
undefined,
overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId),
overrides.relayerAddress,
REACT_APP_COINGECKO_PRO_API_KEY,
getLogger(),
getGasMarkup(destinationChainId)
);
const queries = getRelayerFeeCalculatorQueries(destinationChainId, overrides);
const relayerFeeCalculatorConfig = {
feeLimitPercent: maxRelayFeePct * 100,
queries,
Expand All @@ -557,6 +555,25 @@ export const getRelayerFeeCalculator = (
);
};

const getRelayerFeeCalculatorQueries = (
destinationChainId: number,
overrides: Partial<{
spokePoolAddress: string;
relayerAddress: string;
}> = {}
) => {
return sdk.relayFeeCalculator.QueryBase__factory.create(
destinationChainId,
getProvider(destinationChainId),
undefined,
overrides.spokePoolAddress || getSpokePoolAddress(destinationChainId),
overrides.relayerAddress,
REACT_APP_COINGECKO_PRO_API_KEY,
getLogger(),
getGasMarkup(destinationChainId)
);
};

/**
* Resolves a tokenAddress to a given textual symbol
* @param tokenAddress The token address to convert into a symbol
Expand Down Expand Up @@ -584,55 +601,98 @@ export const getTokenSymbol = (tokenAddress: string): string => {
* @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 gasPrice An optional gas price to use for the transfer
* @param gasUnits An optional gas unit to use for the transfer
* @returns The a promise to the relayer fee for the given `amount` of transferring `l1Token` to `destinationChainId`
*/
export const getRelayerFeeDetails = async (
inputToken: string,
outputToken: string,
amount: sdk.utils.BigNumberish,
originChainId: number,
destinationChainId: number,
recipientAddress: string,
deposit: {
inputToken: string;
outputToken: string;
amount: sdk.utils.BigNumberish;
originChainId: number;
destinationChainId: number;
recipientAddress: string;
},
tokenPrice?: number,
message?: string,
relayerAddress?: string,
gasPrice?: sdk.utils.BigNumberish
gasPrice?: sdk.utils.BigNumberish,
gasUnits?: sdk.utils.BigNumberish
): Promise<sdk.relayFeeCalculator.RelayerFeeDetails> => {
const {
inputToken,
outputToken,
amount,
originChainId,
destinationChainId,
recipientAddress,
} = deposit;
const relayFeeCalculator = getRelayerFeeCalculator(destinationChainId, {
relayerAddress,
});
try {
return await relayFeeCalculator.relayerFeeDetails(
{
inputAmount: sdk.utils.toBN(amount),
outputAmount: sdk.utils.toBN(amount),
depositId: sdk.utils.bnUint32Max.toNumber(),
depositor: recipientAddress,
recipient: recipientAddress,
destinationChainId,
originChainId,
quoteTimestamp: sdk.utils.getCurrentTime() - 60, // Set the quote timestamp to 60 seconds ago ~ 1 ETH block
buildDepositForSimulation({
amount: amount.toString(),
inputToken,
outputToken,
fillDeadline: sdk.utils.bnUint32Max.toNumber(), // Defined as `INFINITE_FILL_DEADLINE` in SpokePool.sol
exclusiveRelayer: sdk.constants.ZERO_ADDRESS,
exclusivityDeadline: 0, // Defined as ZERO in SpokePool.sol
message: message ?? sdk.constants.EMPTY_MESSAGE,
fromLiteChain: false, // FIXME
toLiteChain: false, // FIXME
},
recipientAddress,
originChainId,
destinationChainId,
message,
}),
amount,
sdk.utils.isMessageEmpty(message),
relayerAddress,
tokenPrice
// gasPrice // FIXME
tokenPrice,
gasPrice, // FIXME: We need properly cache the gas price before this can be used reliably
gasUnits
);
} catch (err: unknown) {
const reason = resolveEthersError(err);
throw new InputError(`Relayer fill simulation failed - ${reason}`);
}
};

export const buildDepositForSimulation = (depositArgs: {
amount: BigNumberish;
inputToken: string;
outputToken: string;
recipientAddress: string;
originChainId: number;
destinationChainId: number;
message?: string;
}) => {
const {
amount,
inputToken,
outputToken,
recipientAddress,
originChainId,
destinationChainId,
message,
} = depositArgs;
return {
inputAmount: sdk.utils.toBN(amount),
outputAmount: sdk.utils.toBN(amount),
depositId: sdk.utils.bnUint32Max.toNumber(),
depositor: recipientAddress,
recipient: recipientAddress,
destinationChainId,
originChainId,
quoteTimestamp: sdk.utils.getCurrentTime() - 60, // Set the quote timestamp to 60 seconds ago ~ 1 ETH block
inputToken,
outputToken,
fillDeadline: sdk.utils.bnUint32Max.toNumber(), // Defined as `INFINITE_FILL_DEADLINE` in SpokePool.sol
exclusiveRelayer: sdk.constants.ZERO_ADDRESS,
exclusivityDeadline: 0, // Defined as ZERO in SpokePool.sol
message: message ?? sdk.constants.EMPTY_MESSAGE,
fromLiteChain: false, // FIXME
toLiteChain: false, // FIXME
};
};

/**
* Creates an HTTP call to the `/api/coingecko` endpoint to resolve a CoinGecko price
* @param l1Token The ERC20 token address of the coin to find the cached price of
Expand Down Expand Up @@ -911,7 +971,7 @@ export function getMulticall3(
chainId: number,
signerOrProvider?: Signer | providers.Provider
): Multicall3 | undefined {
const address = sdk.utils.multicall3Addresses[chainId];
const address = sdk.utils.getMulticallAddress(chainId);

// no multicall on this chain
if (!address) {
Expand Down Expand Up @@ -1762,14 +1822,17 @@ export function getCachedLatestBlock(chainId: number) {

export function getCachedGasPrice(chainId: number) {
const ttlPerChain = {
default: 10,
[CHAIN_IDs.MAINNET]: 12,
default: 5,
[CHAIN_IDs.ARBITRUM]: 2,
};

return getCachedValue(
buildInternalCacheKey("gasPrice", chainId),
ttlPerChain[chainId] || ttlPerChain.default,
() => getProvider(chainId).getGasPrice(),
async () => {
const gasPrice = await getProvider(chainId).getGasPrice();
return gasPrice.mul(2);
},
(bnFromCache) => BigNumber.from(bnFromCache)
);
}
Expand All @@ -1792,6 +1855,42 @@ export function getCachedLatestBalance(
);
}

export function getCachedFillGasUsage(
deposit: Parameters<typeof buildDepositForSimulation>[0],
overrides?: Partial<{
spokePoolAddress: string;
relayerAddress: string;
}>
) {
const ttlPerChain = {
default: 10,
[CHAIN_IDs.ARBITRUM]: 10,
};

const cacheKey = buildInternalCacheKey(
"fillGasUsage",
deposit.destinationChainId,
deposit.outputToken
);
const ttl = ttlPerChain[deposit.destinationChainId] || ttlPerChain.default;
const fetchFn = async () => {
const relayerFeeCalculatorQueries = getRelayerFeeCalculatorQueries(
deposit.destinationChainId,
overrides
);
const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts(
buildDepositForSimulation(deposit),
overrides?.relayerAddress,
await getCachedGasPrice(deposit.destinationChainId)
);
return nativeGasCost;
};

return getCachedValue(cacheKey, ttl, fetchFn, (bnFromCache) =>
BigNumber.from(bnFromCache)
);
}

/**
* Builds a URL search string from an object of query parameters.
*
Expand Down
29 changes: 21 additions & 8 deletions api/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
validateChainAndTokenParams,
getCachedLatestBlock,
getCachedGasPrice,
getCachedFillGasUsage,
} from "./_utils";

const LimitsQueryParamsSchema = object({
Expand Down Expand Up @@ -111,7 +112,20 @@ const handler = async (
},
];

const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasPrice] =
const depositArgs = {
amount: ethers.BigNumber.from("10").pow(l1Token.decimals),
inputToken: inputToken.address,
outputToken: outputToken.address,
recipientAddress: DEFAULT_SIMULATED_RECIPIENT_ADDRESS,
originChainId: computedOriginChainId,
destinationChainId,
};
const relayerAddress = getDefaultRelayerAddress(
destinationChainId,
l1Token.symbol
);

const [tokenPriceNative, _tokenPriceUsd, latestBlock, gasPrice, gasUnits] =
await Promise.all([
getCachedTokenPrice(
l1Token.address,
Expand All @@ -120,6 +134,9 @@ const handler = async (
getCachedTokenPrice(l1Token.address, "usd"),
getCachedLatestBlock(HUB_POOL_CHAIN_ID),
getCachedGasPrice(destinationChainId),
getCachedFillGasUsage(depositArgs, {
relayerAddress,
}),
]);
const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString());

Expand All @@ -131,16 +148,12 @@ const handler = async (
fullRelayerMainnetBalances,
] = await Promise.all([
getRelayerFeeDetails(
inputToken.address,
outputToken.address,
ethers.BigNumber.from("10").pow(l1Token.decimals),
computedOriginChainId,
destinationChainId,
DEFAULT_SIMULATED_RECIPIENT_ADDRESS,
depositArgs,
tokenPriceNative,
undefined,
getDefaultRelayerAddress(destinationChainId, l1Token.symbol),
gasPrice
gasPrice,
gasUnits
),
callViaMulticall3(provider, multiCalls, {
blockTag: latestBlock.number,
Expand Down
18 changes: 12 additions & 6 deletions api/suggested-fees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getCachedLimits,
getCachedLatestBlock,
getCachedGasPrice,
getCachedFillGasUsage,
} from "./_utils";
import { selectExclusiveRelayer } from "./_exclusivity";
import { resolveTiming, resolveRebalanceTiming } from "./_timings";
Expand Down Expand Up @@ -205,6 +206,15 @@ const handler = async (
},
];

const depositArgs = {
amount,
inputToken: inputToken.address,
outputToken: outputToken.address,
recipientAddress: recipient,
originChainId: computedOriginChainId,
destinationChainId,
};

const [
[currentUt, nextUt, _quoteTimestamp, rawL1TokenConfig],
tokenPrice,
Expand All @@ -222,6 +232,7 @@ const handler = async (
destinationChainId
),
getCachedGasPrice(destinationChainId),
getCachedFillGasUsage(depositArgs, { relayerAddress: relayer }),
]);
const quoteTimestamp = parseInt(_quoteTimestamp.toString());

Expand Down Expand Up @@ -253,12 +264,7 @@ const handler = async (
);
const lpFeeTotal = amount.mul(lpFeePct).div(ethers.constants.WeiPerEther);
const relayerFeeDetails = await getRelayerFeeDetails(
inputToken.address,
outputToken.address,
amount,
computedOriginChainId,
destinationChainId,
recipient,
depositArgs,
tokenPrice,
message,
relayer,
Expand Down

0 comments on commit 279efc3

Please sign in to comment.