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

feat: Add origin chain fee multipliers #1365

Draft
wants to merge 19 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
106 changes: 84 additions & 22 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,29 @@ const {
REACT_APP_COINGECKO_PRO_API_KEY,
BASE_FEE_MARKUP,
PRIORITY_FEE_MARKUP,
ORIGIN_BASE_FEE_MARKUP,
ORIGIN_PRIORITY_FEE_MARKUP,
VERCEL_ENV,
LOG_LEVEL,
} = process.env;

// Markup applied to base fee and priority fee based on the destination chain of a fill.
export const baseFeeMarkup: {
[chainId: string]: number;
} = JSON.parse(BASE_FEE_MARKUP || "{}");
export const priorityFeeMarkup: {
[chainId: string]: number;
} = JSON.parse(PRIORITY_FEE_MARKUP || "{}");
// Markup applied to base fee and priority fee based on the origin chain of a fill. Should be set based on the expected
// time it would take for a filler to wait until the deposit is far enough behind HEAD to fill it. For example, some
// chains have higher re-org rates so fillers are expected to wait longer before filling the deposits and therefore,
// a higher buffer should be applied on the estimated gas price to allow for more volatility.
export const originChainBaseFeeMarkup: {
[route: string]: number;
} = JSON.parse(ORIGIN_BASE_FEE_MARKUP || "{}");
Copy link
Member Author

Choose a reason for hiding this comment

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

we might need to make this a origin-destination keyed dictionary. Otherwise this multiplier isn't very useful since the actual multiplier we want to apply is based on the destination chain gas process

export const originChainPriorityFeeMarkup: {
[route: string]: number;
} = JSON.parse(ORIGIN_PRIORITY_FEE_MARKUP || "{}");
// Default to no markup.
export const DEFAULT_GAS_MARKUP = 0;

Expand Down Expand Up @@ -593,16 +606,25 @@ export const getHubPoolClient = () => {
};

export const getGasMarkup = (
chainId: string | number
): { baseFeeMarkup: BigNumber; priorityFeeMarkup: BigNumber } => {
destinationChainId: string | number,
originChainId?: string | number
): {
baseFeeMarkup: BigNumber;
priorityFeeMarkup: BigNumber;
originChainBaseFeeMarkup?: BigNumber;
originChainPriorityFeeMarkup?: BigNumber;
} => {
// First, get the markup for the destination chain.
let _baseFeeMarkup: BigNumber | undefined;
let _priorityFeeMarkup: BigNumber | undefined;
if (typeof baseFeeMarkup[chainId] === "number") {
_baseFeeMarkup = utils.parseEther((1 + baseFeeMarkup[chainId]).toString());
if (typeof baseFeeMarkup[destinationChainId] === "number") {
_baseFeeMarkup = utils.parseEther(
(1 + baseFeeMarkup[destinationChainId]).toString()
);
}
if (typeof priorityFeeMarkup[chainId] === "number") {
if (typeof priorityFeeMarkup[destinationChainId] === "number") {
_priorityFeeMarkup = utils.parseEther(
(1 + priorityFeeMarkup[chainId]).toString()
(1 + priorityFeeMarkup[destinationChainId]).toString()
);
}

Expand All @@ -611,7 +633,7 @@ export const getGasMarkup = (
_baseFeeMarkup = utils.parseEther(
(
1 +
(sdk.utils.chainIsOPStack(Number(chainId))
(sdk.utils.chainIsOPStack(Number(destinationChainId))
? baseFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP)
).toString()
Expand All @@ -621,17 +643,42 @@ export const getGasMarkup = (
_priorityFeeMarkup = utils.parseEther(
(
1 +
(sdk.utils.chainIsOPStack(Number(chainId))
(sdk.utils.chainIsOPStack(Number(destinationChainId))
? priorityFeeMarkup[CHAIN_IDs.OPTIMISM] ?? DEFAULT_GAS_MARKUP
: DEFAULT_GAS_MARKUP)
).toString()
);
}

// Finally, apply any origin chain markup if it exists.
let _originChainBaseFeeMarkup: BigNumber | undefined;
let _originChainPriorityFeeMarkup: BigNumber | undefined;
if (originChainId) {
const routeKey = `${originChainId}-${destinationChainId}`;
if (typeof originChainBaseFeeMarkup[routeKey] === "number") {
_originChainBaseFeeMarkup = utils.parseEther(
(1 + originChainBaseFeeMarkup[routeKey]).toString()
);
_baseFeeMarkup = _baseFeeMarkup
.mul(_originChainBaseFeeMarkup)
.div(sdk.utils.fixedPointAdjustment);
}
if (typeof originChainPriorityFeeMarkup[routeKey] === "number") {
_originChainPriorityFeeMarkup = utils.parseEther(
(1 + originChainPriorityFeeMarkup[routeKey]).toString()
);
_priorityFeeMarkup = _priorityFeeMarkup
.mul(_originChainPriorityFeeMarkup)
.div(sdk.utils.fixedPointAdjustment);
}
}

// Otherwise, use default gas markup (or optimism's for OP stack).
return {
baseFeeMarkup: _baseFeeMarkup,
priorityFeeMarkup: _priorityFeeMarkup,
originChainBaseFeeMarkup: _originChainBaseFeeMarkup,
originChainPriorityFeeMarkup: _originChainPriorityFeeMarkup,
};
};

Expand Down Expand Up @@ -1977,8 +2024,10 @@ export function getCachedFillGasUsage(
{
// 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,
opStackL1GasCostMultiplier: getGasMarkup(
deposit.destinationChainId,
deposit.originChainId
).baseFeeMarkup,
}
);
return {
Expand All @@ -2000,37 +2049,50 @@ export function getCachedFillGasUsage(
);
}

export function latestGasPriceCache(chainId: number) {
export function latestGasPriceCache(
destinationChainId: number,
originChainId: number
) {
const ttlPerChain = {
default: 30,
[CHAIN_IDs.ARBITRUM]: 15,
};

return makeCacheGetterAndSetter(
buildInternalCacheKey("latestGasPriceCache", chainId),
ttlPerChain[chainId] || ttlPerChain.default,
async () => (await getMaxFeePerGas(chainId)).maxFeePerGas,
buildInternalCacheKey(
"latestGasPriceCache",
originChainId,
destinationChainId
),
ttlPerChain[destinationChainId] || ttlPerChain.default,
async () =>
(await getMaxFeePerGas(destinationChainId, originChainId)).maxFeePerGas,
(bnFromCache) => BigNumber.from(bnFromCache)
);
}

/**
* Resolve the current gas price for a given chain
* @param chainId The chain ID to resolve the gas price for
* @param destinationChainId The chain ID to resolve the gas price for
* @param originChainId
* @returns The gas price in the native currency of the chain
*/
export function getMaxFeePerGas(
chainId: number
destinationChainId: number,
originChainId?: number
): Promise<sdk.gasPriceOracle.GasPriceEstimate> {
const {
baseFeeMarkup: baseFeeMultiplier,
priorityFeeMarkup: priorityFeeMultiplier,
} = getGasMarkup(chainId);
return sdk.gasPriceOracle.getGasPriceEstimate(getProvider(chainId), {
chainId,
baseFeeMultiplier,
priorityFeeMultiplier,
});
} = getGasMarkup(destinationChainId, originChainId);
return sdk.gasPriceOracle.getGasPriceEstimate(
getProvider(destinationChainId),
{
chainId: destinationChainId,
baseFeeMultiplier,
priorityFeeMultiplier,
}
);
}

/**
Expand Down
40 changes: 25 additions & 15 deletions api/cron-cache-gas-prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,32 @@ const handler = async (
// But we want to update gas prices 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.map(async (chain) => {
const secondsPerUpdateForChain =
updateIntervalsSecPerChain[
chain.chainId as keyof typeof updateIntervalsSecPerChain
] || updateIntervalsSecPerChain.default;
const cache = latestGasPriceCache(chain.chainId);
const gasPricePromises = mainnetChains.map((originChain) => {
return Promise.all(
mainnetChains.map(async (destinationChain) => {
if (originChain.chainId === destinationChain.chainId) {
return;
}
const secondsPerUpdateForChain =
updateIntervalsSecPerChain[
destinationChain.chainId as keyof typeof updateIntervalsSecPerChain
] || updateIntervalsSecPerChain.default;
const cache = latestGasPriceCache(
destinationChain.chainId,
originChain.chainId
);

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

Expand Down
43 changes: 42 additions & 1 deletion api/gas-prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,48 @@ const handler = async (
}
)
);
const getOriginChainFeeMarkups = (destinationChainId: number) => {
const dictionary = Object.fromEntries(
Object.keys(chainIdsWithToken)
.map((originChainId) => {
const markup = getGasMarkup(destinationChainId, originChainId);
const originChainBaseFeeMarkup = markup.originChainBaseFeeMarkup
? ethers.utils.formatEther(markup.originChainBaseFeeMarkup)
: undefined;
const originChainPriorityFeeMarkup =
markup.originChainPriorityFeeMarkup
? ethers.utils.formatEther(markup.originChainPriorityFeeMarkup)
: undefined;
if (!originChainBaseFeeMarkup && !originChainPriorityFeeMarkup) {
return undefined;
}
return [
`${originChainId}-${destinationChainId}`,
{
baseFeeMarkup: originChainBaseFeeMarkup,
priorityFeeMarkup: originChainPriorityFeeMarkup,
},
];
})
.filter((x) => x !== undefined)
);
if (!Object.keys(dictionary).length) {
return undefined;
}
return dictionary;
};
const responseJson = {
tokenSymbol,
reference: {
tokenSymbol,
gasPrice:
"maxFeePerGas * baseFeeMultiplier + priorityFee * priorityFeeMultiplier",
maxFeePerGas: "estimated maximum base fee",
maxPriorityFeePerGas: "estimated maximum tip",
opStackL1GasCostMultiplier:
"L1 data fee added to all OPStack transactions",
originChainFeeMarkups:
"Additional multiplier applied to base fee and/or priority fee for certain originChain-destChain routes. NOT applied in this calculation.",
},
...Object.fromEntries(
Object.keys(chainIdsWithToken).map((chainId, i) => [
chainId,
Expand All @@ -141,6 +181,7 @@ const handler = async (
)
? ethers.utils.formatEther(getGasMarkup(chainId).baseFeeMarkup)
: undefined,
originChainFeeMarkups: getOriginChainFeeMarkups(Number(chainId)),
},
nativeGasCost: gasCosts[i].nativeGasCost.toString(),
tokenGasCost: gasCosts[i].tokenGasCost.toString(),
Expand Down
2 changes: 1 addition & 1 deletion api/limits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ const handler = async (
: getCachedFillGasUsage(depositArgs, {
relayerAddress: relayer,
}),
latestGasPriceCache(destinationChainId).get(),
latestGasPriceCache(destinationChainId, computedOriginChainId).get(),
]);
const tokenPriceUsd = ethers.utils.parseUnits(_tokenPriceUsd.toString());

Expand Down
Loading