-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
970eb3c
commit e1ecc76
Showing
3 changed files
with
179 additions
and
79 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { VercelResponse } from "@vercel/node"; | ||
import { TypedVercelRequest } from "./_types"; | ||
import { | ||
HUB_POOL_CHAIN_ID, | ||
getCachedNativeGasCost, | ||
getCachedOpStackL1DataFee, | ||
getLogger, | ||
handleErrorCondition, | ||
resolveVercelEndpoint, | ||
} from "./_utils"; | ||
import { UnauthorizedError } from "./_errors"; | ||
|
||
import mainnetChains from "../src/data/chains_1.json"; | ||
import { utils, constants } from "@across-protocol/sdk"; | ||
import { 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 lower than TTL in getCachedOpStackL1DataFee | ||
// Set lower than the L1 block time so we can try to get as up to date L1 data fees based on L1 base fees as possible. | ||
const updateIntervalsSecPerChain = { | ||
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 | ||
) => { | ||
const logger = getLogger(); | ||
logger.debug({ | ||
at: "CronCacheL1DataFee", | ||
message: "Starting cron job...", | ||
}); | ||
try { | ||
const authHeader = request.headers?.["authorization"]; | ||
if ( | ||
!process.env.CRON_SECRET || | ||
authHeader !== `Bearer ${process.env.CRON_SECRET}` | ||
) { | ||
throw new UnauthorizedError(); | ||
} | ||
|
||
// Skip cron job on testnet | ||
if (HUB_POOL_CHAIN_ID !== 1) { | ||
logger.info({ | ||
at: "CronCacheL1DataFee", | ||
message: "Skipping cron job on testnet", | ||
}); | ||
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 L1 data fee gas cost cache every `updateL1DataFeeIntervalsSecPerChain` seconds | ||
* up to `maxDurationSec` seconds. | ||
* @param chainId Chain to estimate l1 data fee 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 | ||
): Promise<void> => { | ||
const secondsPerUpdate = updateIntervalsSecPerChain.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(); | ||
if (utils.chainIsOPStack(chainId)) { | ||
const cache = getCachedOpStackL1DataFee(depositArgs, gasCost); | ||
try { | ||
await cache.set(); | ||
} catch (err) { | ||
logger.warn({ | ||
at: "CronCacheL1DataFee#updateL1DataFeePromise", | ||
message: `Failed to set l1 data fee cache for chain ${chainId}`, | ||
depositArgs, | ||
gasCost, | ||
error: err, | ||
}); | ||
} | ||
} | ||
await utils.delay(secondsPerUpdate); | ||
} | ||
}; | ||
|
||
const getOutputTokensToChain = (chainId: number) => | ||
availableRoutes | ||
.filter(({ destinationChainId }) => destinationChainId === chainId) | ||
.map(({ destinationToken }) => destinationToken); | ||
|
||
const cacheUpdatePromise = Promise.all( | ||
mainnetChains.map(async (chain) => { | ||
await Promise.all( | ||
getOutputTokensToChain(chain.chainId).map((outputToken) => | ||
updateL1DataFeePromise(chain.chainId, outputToken) | ||
) | ||
); | ||
}) | ||
); | ||
// There are many routes and therefore many promises to wait to resolve so we force the | ||
// function to stop after `maxDurationSec` seconds. | ||
await Promise.race([cacheUpdatePromise, utils.delay(maxDurationSec)]); | ||
|
||
logger.debug({ | ||
at: "CronCacheL1DataFee", | ||
message: "Finished", | ||
}); | ||
response.status(200); | ||
response.send("OK"); | ||
} catch (error: unknown) { | ||
return handleErrorCondition( | ||
"cron-cache-l1-data-fee", | ||
response, | ||
logger, | ||
error | ||
); | ||
} | ||
}; | ||
|
||
export default handler; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters