From 201162bbb705c500827662899e926d3aed000be4 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:42:03 +0200 Subject: [PATCH 01/41] improve(relayer): Permit per-chain external listener customisation (#1843) This can be used to test a viem-based or non-evm-based external listener. --- src/relayer/RelayerClientHelper.ts | 1 + src/relayer/RelayerConfig.ts | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/relayer/RelayerClientHelper.ts b/src/relayer/RelayerClientHelper.ts index b45cbdb34..c598b38d4 100644 --- a/src/relayer/RelayerClientHelper.ts +++ b/src/relayer/RelayerClientHelper.ts @@ -96,6 +96,7 @@ export async function constructRelayerClients( const opts = { lookback: config.maxRelayerLookBack, blockRange: config.maxBlockLookBack[chainId], + path: config.listenerPath[chainId], }; return [chainId, await indexedSpokePoolClient(baseSigner, hubPoolClient, chainId, opts)]; }) diff --git a/src/relayer/RelayerConfig.ts b/src/relayer/RelayerConfig.ts index 0d4dba09f..3af672aa4 100644 --- a/src/relayer/RelayerConfig.ts +++ b/src/relayer/RelayerConfig.ts @@ -28,7 +28,7 @@ type DepositConfirmationConfig = { export class RelayerConfig extends CommonConfig { readonly externalIndexer: boolean; - readonly indexerPath: string; + readonly listenerPath: { [chainId: number]: string } = {}; readonly inventoryConfig: InventoryConfig; readonly debugProfitability: boolean; readonly sendingRelaysEnabled: boolean; @@ -88,7 +88,6 @@ export class RelayerConfig extends CommonConfig { MIN_DEPOSIT_CONFIRMATIONS, RELAYER_IGNORE_LIMITS, RELAYER_EXTERNAL_INDEXER, - RELAYER_SPOKEPOOL_INDEXER_PATH, RELAYER_TRY_MULTICALL_CHAINS, RELAYER_USE_GENERIC_ADAPTER, RELAYER_LOGGING_INTERVAL = "30", @@ -100,7 +99,6 @@ export class RelayerConfig extends CommonConfig { // External indexing is dependent on looping mode being configured. this.externalIndexer = this.pollingDelay > 0 && RELAYER_EXTERNAL_INDEXER === "true"; - this.indexerPath = RELAYER_SPOKEPOOL_INDEXER_PATH ?? Constants.RELAYER_DEFAULT_SPOKEPOOL_INDEXER; // Empty means all chains. this.relayerOriginChains = JSON.parse(RELAYER_ORIGIN_CHAINS ?? "[]"); @@ -326,7 +324,7 @@ export class RelayerConfig extends CommonConfig { * @param logger Optional logger object. */ override validate(chainIds: number[], logger: winston.Logger): void { - const { relayerOriginChains, relayerDestinationChains } = this; + const { listenerPath, minFillTime, relayerOriginChains, relayerDestinationChains } = this; const relayerChainIds = relayerOriginChains.length > 0 && relayerDestinationChains.length > 0 ? dedupArray([...relayerOriginChains, ...relayerDestinationChains]) @@ -343,9 +341,13 @@ export class RelayerConfig extends CommonConfig { }); } - chainIds.forEach( - (chainId) => (this.minFillTime[chainId] = Number(process.env[`RELAYER_MIN_FILL_TIME_${chainId}`] ?? 0)) - ); + const { RELAYER_SPOKEPOOL_INDEXER_PATH = Constants.RELAYER_DEFAULT_SPOKEPOOL_INDEXER } = process.env; + + chainIds.forEach((chainId) => { + minFillTime[chainId] = Number(process.env[`RELAYER_MIN_FILL_TIME_${chainId}`] ?? 0); + listenerPath[chainId] = + process.env[`RELAYER_SPOKEPOOL_INDEXER_PATH_${chainId}`] ?? RELAYER_SPOKEPOOL_INDEXER_PATH; + }); // Only validate config for chains that the relayer cares about. super.validate(relayerChainIds, logger); From 82e4042054b40aba4639951652fc6a1199277bf2 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:51:24 +0200 Subject: [PATCH 02/41] improve(relayer): Permit up to 120s for SpokePoolClient readiness (#1838) The existing limit of 10 loops was somewhat imprecise and was too short. as observed in production. This would occasionally trigger asserts in the BundleDataClient because it requires all SpokePoolClient instances to be updated. Instead, permit up to 60 seconds before proceeding. --- src/relayer/index.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/relayer/index.ts b/src/relayer/index.ts index da8413ee6..1c526c189 100644 --- a/src/relayer/index.ts +++ b/src/relayer/index.ts @@ -61,14 +61,19 @@ export async function runRelayer(_logger: winston.Logger, baseSigner: Signer): P const ready = await relayer.update(); const activeRelayer = redis ? await redis.get(botIdentifier) : undefined; - // If there is another active relayer, allow up to 10 update cycles for this instance to be ready, - // then proceed unconditionally to protect against any RPC outages blocking the relayer. - if (!ready && activeRelayer && run < 10) { - const runTime = Math.round((performance.now() - tLoopStart) / 1000); - const delta = pollingDelay - runTime; - logger.debug({ at: "Relayer#run", message: `Not ready to relay, waiting ${delta} seconds.` }); - await delay(delta); - continue; + // If there is another active relayer, allow up to 120 seconds for this instance to be ready. + // If this instance can't update, throw an error (for now). + if (!ready && activeRelayer) { + if (run * pollingDelay < 120) { + const runTime = Math.round((performance.now() - tLoopStart) / 1000); + const delta = pollingDelay - runTime; + logger.debug({ at: "Relayer#run", message: `Not ready to relay, waiting ${delta} seconds.` }); + await delay(delta); + continue; + } + + const badChains = Object.values(spokePoolClients).filter(({ isUpdated }) => !isUpdated); + throw new Error(`Unable to start relayer due to chains ${badChains}`); } // Signal to any existing relayer that a handover is underway, or alternatively From 4a39db467d09431a0b06ea379db9a50fa52e8b4c Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:44:14 +0200 Subject: [PATCH 03/41] refactor(relayer): Factor out common listener utils (#1841) This is a pre-emptive change made to make it easier to reuse common components in alternative listener implementations. Examples for a viem-based listener, as well as an eventual Solana listener. There are subsequent changes in the pipeline to factor out evm-specific parts. --- src/libexec/RelayerSpokePoolIndexer.ts | 139 ++++--------------------- src/libexec/types.ts | 9 ++ src/libexec/util/evm/index.ts | 1 + src/libexec/util/evm/util.ts | 69 ++++++++++++ src/libexec/util/ipc.ts | 43 ++++++++ 5 files changed, 144 insertions(+), 117 deletions(-) create mode 100644 src/libexec/types.ts create mode 100644 src/libexec/util/evm/index.ts create mode 100644 src/libexec/util/evm/util.ts create mode 100644 src/libexec/util/ipc.ts diff --git a/src/libexec/RelayerSpokePoolIndexer.ts b/src/libexec/RelayerSpokePoolIndexer.ts index 951f528e7..e6b211734 100644 --- a/src/libexec/RelayerSpokePoolIndexer.ts +++ b/src/libexec/RelayerSpokePoolIndexer.ts @@ -1,10 +1,8 @@ import assert from "assert"; import minimist from "minimist"; -import { Contract, EventFilter, providers as ethersProviders, utils as ethersUtils } from "ethers"; +import { Contract, providers as ethersProviders, utils as ethersUtils } from "ethers"; import { utils as sdkUtils } from "@across-protocol/sdk"; import * as utils from "../../scripts/utils"; -import { Log } from "../interfaces"; -import { SpokePoolClientMessage } from "../clients"; import { disconnectRedisClients, EventManager, @@ -19,20 +17,13 @@ import { getRedisCache, getWSProviders, Logger, - paginatedEventQuery, - sortEventsAscending, winston, } from "../utils"; +import { postEvents, removeEvent } from "./util/ipc"; +import { ScraperOpts } from "./types"; +import { getEventFilter, getEventFilterArgs, scrapeEvents as _scrapeEvents } from "./util/evm"; type WebSocketProvider = ethersProviders.WebSocketProvider; -type EventSearchConfig = sdkUtils.EventSearchConfig; -type ScraperOpts = { - lookback?: number; // Event lookback (in seconds). - deploymentBlock: number; // SpokePool deployment block - maxBlockRange?: number; // Maximum block range for paginated getLogs queries. - filterArgs?: { [event: string]: string[] }; // Event-specific filter criteria to apply. -}; - const { NODE_SUCCESS, NODE_APP_ERR } = utils; const INDEXER_POLLING_PERIOD = 2_000; // ms; time to sleep between checking for exit request via SIGHUP. @@ -45,108 +36,21 @@ let stop = false; let oldestTime = 0; /** - * Given an event name and contract, return the corresponding Ethers EventFilter object. - * @param contract Ethers Constract instance. - * @param eventName The name of the event to be filtered. - * @param filterArgs Optional filter arguments to be applied. - * @returns An Ethers EventFilter instance. - */ -function getEventFilter(contract: Contract, eventName: string, filterArgs?: string[]): EventFilter { - const filter = contract.filters[eventName]; - if (!isDefined(filter)) { - throw new Error(`Event ${eventName} not defined for contract`); - } - - return isDefined(filterArgs) ? filter(...filterArgs) : filter(); -} - -function getEventFilterArgs(relayer?: string): { [event: string]: string[] } { - const FilledV3Relay = !isDefined(relayer) - ? undefined - : [null, null, null, null, null, null, null, null, null, null, relayer]; - - return { FilledV3Relay }; -} - -/** - * Given the inputs for a SpokePoolClient update, consolidate the inputs into a message and submit it to the parent - * process (if defined). - * @param blockNumber Block number up to which the update applies. - * @param currentTime The SpokePool timestamp at blockNumber. - * @param events An array of Log objects to be submitted. - * @returns void - */ -function postEvents(blockNumber: number, currentTime: number, events: Log[]): void { - if (!isDefined(process.send) || stop) { - return; - } - - // Drop the array component of event.args and retain the named k/v pairs, - // otherwise stringification tends to retain only the array. - events = sortEventsAscending(events); - - const message: SpokePoolClientMessage = { - blockNumber, - currentTime, - oldestTime, - nEvents: events.length, - data: JSON.stringify(events, sdkUtils.jsonReplacerWithBigNumbers), - }; - process.send(JSON.stringify(message)); -} - -/** - * Given an event removal notification, post the message to the parent process. - * @param event Log instance. - * @returns void - */ -function removeEvent(event: Log): void { - if (!isDefined(process.send) || stop) { - return; - } - - const message: SpokePoolClientMessage = { - event: JSON.stringify(event, sdkUtils.jsonReplacerWithBigNumbers), - }; - process.send(JSON.stringify(message)); -} - -/** - * Given a SpokePool contract instance and an event name, scrape all corresponding events and submit them to the - * parent process (if defined). + * Aggregate utils/scrapeEvents for a series of event names. * @param spokePool Ethers Constract instance. - * @param eventName The name of the event to be filtered. + * @param eventNames The array of events to be queried. * @param opts Options to configure event scraping behaviour. * @returns void */ -async function scrapeEvents(spokePool: Contract, eventName: string, opts: ScraperOpts): Promise { - const { lookback, deploymentBlock, filterArgs, maxBlockRange } = opts; - const { provider } = spokePool; - const { chainId } = await provider.getNetwork(); - const chain = getNetworkName(chainId); - - let tStart: number, tStop: number; - - const pollEvents = async (filter: EventFilter, searchConfig: EventSearchConfig): Promise => { - tStart = performance.now(); - const events = await paginatedEventQuery(spokePool, filter, searchConfig); - tStop = performance.now(); - logger.debug({ - at: "SpokePoolIndexer::listen", - message: `Indexed ${events.length} ${chain} events in ${Math.round((tStop - tStart) / 1000)} seconds`, - searchConfig, - }); - return events; - }; - - const { number: toBlock, timestamp: currentTime } = await provider.getBlock("latest"); - const fromBlock = Math.max(toBlock - (lookback ?? deploymentBlock), deploymentBlock); - assert(toBlock > fromBlock, `${toBlock} > ${fromBlock}`); - const searchConfig = { fromBlock, toBlock, maxBlockLookBack: maxBlockRange }; - - const filter = getEventFilter(spokePool, eventName, filterArgs[eventName]); - const events = await pollEvents(filter, searchConfig); - postEvents(toBlock, currentTime, events); +export async function scrapeEvents(spokePool: Contract, eventNames: string[], opts: ScraperOpts): Promise { + const { number: toBlock, timestamp: currentTime } = await spokePool.provider.getBlock("latest"); + const events = await Promise.all( + eventNames.map((eventName) => _scrapeEvents(spokePool, eventName, { ...opts, toBlock }, logger)) + ); + + if (!stop) { + postEvents(toBlock, oldestTime, currentTime, events.flat()); + } } /** @@ -178,7 +82,9 @@ async function listen( // Post an update to the parent. Do this irrespective of whether there were new events or not, since there's // information in blockNumber and currentTime alone. - postEvents(blockNumber, currentTime, events); + if (!stop) { + postEvents(blockNumber, oldestTime, currentTime, events); + } }); // Add a handler for each new instance of a subscribed event. @@ -191,7 +97,9 @@ async function listen( if (event.removed) { eventMgr.remove(event, host); // Notify the parent immediately in case the event was already submitted. - removeEvent(event); + if (!stop) { + removeEvent(event); + } } else { eventMgr.add(event, host); } @@ -277,10 +185,7 @@ async function run(argv: string[]): Promise { if (latestBlock.number > startBlock) { const events = ["V3FundsDeposited", "FilledV3Relay", "RelayedRootBundle", "ExecutedRelayerRefundRoot"]; const _spokePool = spokePool.connect(quorumProvider); - await Promise.all([ - resolveOldestTime(_spokePool, startBlock), - ...events.map((event) => scrapeEvents(_spokePool, event, opts)), - ]); + await Promise.all([resolveOldestTime(_spokePool, startBlock), scrapeEvents(_spokePool, events, opts)]); } // If no lookback was specified then default to the timestamp of the latest block. diff --git a/src/libexec/types.ts b/src/libexec/types.ts new file mode 100644 index 000000000..c1986dfb0 --- /dev/null +++ b/src/libexec/types.ts @@ -0,0 +1,9 @@ +export { Log } from "../interfaces"; +export { SpokePoolClientMessage } from "../clients"; + +export type ScraperOpts = { + lookback?: number; // Event lookback (in seconds). + deploymentBlock: number; // SpokePool deployment block + maxBlockRange?: number; // Maximum block range for paginated getLogs queries. + filterArgs?: { [event: string]: string[] }; // Event-specific filter criteria to apply. +}; diff --git a/src/libexec/util/evm/index.ts b/src/libexec/util/evm/index.ts new file mode 100644 index 000000000..181e76f24 --- /dev/null +++ b/src/libexec/util/evm/index.ts @@ -0,0 +1 @@ +export * from "./util"; diff --git a/src/libexec/util/evm/util.ts b/src/libexec/util/evm/util.ts new file mode 100644 index 000000000..c19a86199 --- /dev/null +++ b/src/libexec/util/evm/util.ts @@ -0,0 +1,69 @@ +import assert from "assert"; +import { Contract, EventFilter } from "ethers"; +import { getNetworkName, isDefined, paginatedEventQuery, winston } from "../../../utils"; +import { Log, ScraperOpts } from "../../types"; + +/** + * Given an event name and contract, return the corresponding Ethers EventFilter object. + * @param contract Ethers Constract instance. + * @param eventName The name of the event to be filtered. + * @param filterArgs Optional filter arguments to be applied. + * @returns An Ethers EventFilter instance. + */ +export function getEventFilter(contract: Contract, eventName: string, filterArgs?: string[]): EventFilter { + const filter = contract.filters[eventName]; + if (!isDefined(filter)) { + throw new Error(`Event ${eventName} not defined for contract`); + } + + return isDefined(filterArgs) ? filter(...filterArgs) : filter(); +} + +/** + * Get a general event filter mapping to be used for filtering SpokePool contract events. + * This is currently only useful for filtering the relayer address on FilledV3Relay events. + * @param relayer Optional relayer address to filter on. + * @returns An argument array for input to an Ethers EventFilter. + */ +export function getEventFilterArgs(relayer?: string): { [event: string]: (null | string)[] } { + const FilledV3Relay = !isDefined(relayer) + ? undefined + : [null, null, null, null, null, null, null, null, null, null, relayer]; + + return { FilledV3Relay }; +} + +/** + * Given a SpokePool contract instance and an event name, scrape all corresponding events and submit them to the + * parent process (if defined). + * @param spokePool Ethers Constract instance. + * @param eventName The name of the event to be filtered. + * @param opts Options to configure event scraping behaviour. + * @returns void + */ +export async function scrapeEvents( + spokePool: Contract, + eventName: string, + opts: ScraperOpts & { toBlock: number }, + logger: winston.Logger +): Promise { + const { lookback, deploymentBlock, filterArgs, maxBlockRange, toBlock } = opts; + const { chainId } = await spokePool.provider.getNetwork(); + const chain = getNetworkName(chainId); + + const fromBlock = Math.max(toBlock - (lookback ?? deploymentBlock), deploymentBlock); + assert(toBlock > fromBlock, `${toBlock} > ${fromBlock}`); + const searchConfig = { fromBlock, toBlock, maxBlockLookBack: maxBlockRange }; + + const tStart = performance.now(); + const filter = getEventFilter(spokePool, eventName, filterArgs[eventName]); + const events = await paginatedEventQuery(spokePool, filter, searchConfig); + const tStop = performance.now(); + logger.debug({ + at: "scrapeEvents", + message: `Scraped ${events.length} ${chain} ${eventName} events in ${Math.round((tStop - tStart) / 1000)} seconds`, + searchConfig, + }); + + return events; +} diff --git a/src/libexec/util/ipc.ts b/src/libexec/util/ipc.ts new file mode 100644 index 000000000..b5bfb81c3 --- /dev/null +++ b/src/libexec/util/ipc.ts @@ -0,0 +1,43 @@ +import { utils as sdkUtils } from "@across-protocol/sdk"; +import { isDefined, sortEventsAscending } from "../../utils"; +import { Log, SpokePoolClientMessage } from "./../types"; + +/** + * Given the inputs for a SpokePoolClient update, consolidate the inputs into a message and submit it to the parent + * process (if defined). + * @param blockNumber Block number up to which the update applies. + * @param currentTime The SpokePool timestamp at blockNumber. + * @param events An array of Log objects to be submitted. + * @returns void + */ +export function postEvents(blockNumber: number, oldestTime: number, currentTime: number, events: Log[]): void { + if (!isDefined(process.send)) { + return; + } + + events = sortEventsAscending(events); + const message: SpokePoolClientMessage = { + blockNumber, + currentTime, + oldestTime, + nEvents: events.length, + data: JSON.stringify(events, sdkUtils.jsonReplacerWithBigNumbers), + }; + process.send(JSON.stringify(message)); +} + +/** + * Given an event removal notification, post the message to the parent process. + * @param event Log instance. + * @returns void + */ +export function removeEvent(event: Log): void { + if (!isDefined(process.send)) { + return; + } + + const message: SpokePoolClientMessage = { + event: JSON.stringify(event, sdkUtils.jsonReplacerWithBigNumbers), + }; + process.send(JSON.stringify(message)); +} From 560abc06405f863057774a28dbfe102bb67b7e9a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:08:36 +0200 Subject: [PATCH 04/41] feat(relayer): Add viem-based external SpokePool listener (#1842) viem supports keepalives and timeouts on websocket transports. In testing it has appeared to successfully websocket connections for longer than ethers v5. --- package.json | 1 + src/libexec/SpokePoolListenerExperimental.ts | 262 +++++++++++++++++++ yarn.lock | 127 +++++++-- 3 files changed, 362 insertions(+), 28 deletions(-) create mode 100644 src/libexec/SpokePoolListenerExperimental.ts diff --git a/package.json b/package.json index 5e84d3e23..79bc3e84c 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", + "viem": "^2.21.7", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" }, diff --git a/src/libexec/SpokePoolListenerExperimental.ts b/src/libexec/SpokePoolListenerExperimental.ts new file mode 100644 index 000000000..e5e2b5d31 --- /dev/null +++ b/src/libexec/SpokePoolListenerExperimental.ts @@ -0,0 +1,262 @@ +import assert from "assert"; +import minimist from "minimist"; +import { Contract, providers as ethersProviders, utils as ethersUtils } from "ethers"; +import { BaseError, Block, createPublicClient, Log as viemLog, webSocket } from "viem"; +import * as chains from "viem/chains"; +import { utils as sdkUtils } from "@across-protocol/sdk"; +import * as utils from "../../scripts/utils"; +import { + CHAIN_IDs, + disconnectRedisClients, + EventManager, + exit, + isDefined, + getBlockForTimestamp, + getChainQuorum, + getDeploymentBlockNumber, + getNetworkName, + getNodeUrlList, + getOriginFromURL, + getProvider, + getRedisCache, + Logger, + winston, +} from "../utils"; +import { ScraperOpts } from "./types"; +import { postEvents, removeEvent } from "./util/ipc"; +import { getEventFilterArgs, scrapeEvents as _scrapeEvents } from "./util/evm"; + +const { NODE_SUCCESS, NODE_APP_ERR } = utils; + +const INDEXER_POLLING_PERIOD = 2_000; // ms; time to sleep between checking for exit request via SIGHUP. + +let logger: winston.Logger; +let chainId: number; +let chain: string; +let stop = false; +let oldestTime = 0; + +// This mapping is necessary because viem imposes extremely narrow type inference. @todo: Improve? +const _chains = { + [CHAIN_IDs.ARBITRUM]: chains.arbitrum, + [CHAIN_IDs.BASE]: chains.base, + [CHAIN_IDs.BLAST]: chains.blast, + [CHAIN_IDs.LINEA]: chains.linea, + [CHAIN_IDs.LISK]: chains.lisk, + [CHAIN_IDs.MAINNET]: chains.mainnet, + [CHAIN_IDs.MODE]: chains.mode, + [CHAIN_IDs.OPTIMISM]: chains.optimism, + [CHAIN_IDs.POLYGON]: chains.polygon, + [CHAIN_IDs.REDSTONE]: chains.redstone, + [CHAIN_IDs.SCROLL]: chains.scroll, + [CHAIN_IDs.ZK_SYNC]: chains.zksync, + [CHAIN_IDs.ZORA]: chains.zora, +} as const; + +/** + * Aggregate utils/scrapeEvents for a series of event names. + * @param spokePool Ethers Constract instance. + * @param eventNames The array of events to be queried. + * @param opts Options to configure event scraping behaviour. + * @returns void + */ +export async function scrapeEvents(spokePool: Contract, eventNames: string[], opts: ScraperOpts): Promise { + const { number: toBlock, timestamp: currentTime } = await spokePool.provider.getBlock("latest"); + const events = await Promise.all( + eventNames.map((eventName) => _scrapeEvents(spokePool, eventName, { ...opts, toBlock }, logger)) + ); + + if (!stop) { + postEvents(toBlock, oldestTime, currentTime, events.flat()); + } +} + +/** + * Given a SpokePool contract instance and an array of event names, subscribe to all future event emissions. + * Periodically transmit received events to the parent process (if defined). + * @param eventMgr Ethers Constract instance. + * @param eventName The name of the event to be filtered. + * @param opts Options to configure event scraping behaviour. + * @returns void + */ +async function listen(eventMgr: EventManager, spokePool: Contract, eventNames: string[], quorum = 1): Promise { + const urls = getNodeUrlList(chainId, quorum, "wss"); + let nProviders = urls.length; + assert(nProviders >= quorum, `Insufficient providers for ${chain} (required ${quorum} by quorum)`); + + const providers = urls.map((url) => + createPublicClient({ + chain: _chains[chainId], + transport: webSocket(url, { name: getOriginFromURL(url) }), + }) + ); + + // On each new block, submit any "finalised" events. + const newBlock = (block: Block) => { + const [blockNumber, currentTime] = [parseInt(block.number.toString()), parseInt(block.timestamp.toString())]; + const events = eventMgr.tick(blockNumber); + postEvents(blockNumber, oldestTime, currentTime, events); + }; + + const blockError = (error: Error, provider: string) => { + const at = "RelayerSpokePoolListener::run"; + const message = `Caught ${chain} provider error.`; + const { message: errorMessage, details, shortMessage, metaMessages } = error as BaseError; + logger.debug({ at, message, errorMessage, shortMessage, provider, details, metaMessages }); + + if (!stop && --nProviders < quorum) { + stop = true; + logger.warn({ + at: "RelayerSpokePoolListener::run", + message: `Insufficient ${chain} providers to continue.`, + quorum, + nProviders, + }); + } + }; + + providers.forEach((provider, idx) => { + if (idx === 0) { + provider.watchBlocks({ + emitOnBegin: true, + onBlock: (block: Block) => newBlock(block), + onError: (error: Error) => blockError(error, provider.name), + }); + } + + const abi = JSON.parse(spokePool.interface.format(ethersUtils.FormatTypes.json) as string); + eventNames.forEach((eventName) => { + provider.watchContractEvent({ + address: spokePool.address as `0x${string}`, + abi, + eventName, + onLogs: (logs: viemLog[]) => + logs.forEach((log) => { + const event = { + ...log, + args: log["args"], + blockNumber: Number(log.blockNumber), + event: log["eventName"], + topics: [], // Not supplied by viem, but not actually used by the relayer. + }; + if (log.removed) { + eventMgr.remove(event, provider.name); + removeEvent(event); + } else { + eventMgr.add(event, provider.name); + } + }), + }); + }); + }); + + do { + await sdkUtils.delay(INDEXER_POLLING_PERIOD); + } while (!stop); +} + +/** + * Main entry point. + */ +async function run(argv: string[]): Promise { + const minimistOpts = { + string: ["lookback", "relayer"], + }; + const args = minimist(argv, minimistOpts); + + ({ chainId } = args); + const { lookback, relayer = null, maxBlockRange = 10_000 } = args; + assert(Number.isInteger(chainId), "chainId must be numeric "); + assert(Number.isInteger(maxBlockRange), "maxBlockRange must be numeric"); + assert(!isDefined(relayer) || ethersUtils.isAddress(relayer), `relayer address is invalid (${relayer})`); + + const { quorum = getChainQuorum(chainId) } = args; + assert(Number.isInteger(quorum), "quorum must be numeric "); + + chain = getNetworkName(chainId); + + const quorumProvider = await getProvider(chainId); + const blockFinder = undefined; + const cache = await getRedisCache(); + const latestBlock = await quorumProvider.getBlock("latest"); + + const deploymentBlock = getDeploymentBlockNumber("SpokePool", chainId); + let startBlock = latestBlock.number; + if (/^@[0-9]+$/.test(lookback)) { + // Lookback to a specific block (lookback = @). + startBlock = Number(lookback.slice(1)); + } else if (isDefined(lookback)) { + // Resolve `lookback` seconds from head to a specific block. + assert(Number.isInteger(Number(lookback)), `Invalid lookback (${lookback})`); + startBlock = Math.max( + deploymentBlock, + await getBlockForTimestamp(chainId, latestBlock.timestamp - lookback, blockFinder, cache) + ); + } else { + logger.debug({ at: "RelayerSpokePoolListener::run", message: `Skipping lookback on ${chain}.` }); + } + + const opts = { + quorum, + deploymentBlock, + lookback: latestBlock.number - startBlock, + maxBlockRange, + filterArgs: getEventFilterArgs(relayer), + }; + + logger.debug({ at: "RelayerSpokePoolListener::run", message: `Starting ${chain} SpokePool Indexer.`, opts }); + const spokePool = await utils.getSpokePoolContract(chainId); + + process.on("SIGHUP", () => { + logger.debug({ at: "Relayer#run", message: `Received SIGHUP in ${chain} listener, stopping...` }); + stop = true; + }); + + process.on("disconnect", () => { + logger.debug({ at: "Relayer::run", message: `${chain} parent disconnected, stopping...` }); + stop = true; + }); + + // Note: An event emitted between scrapeEvents() and listen(). @todo: Ensure that there is overlap and dedpulication. + logger.debug({ at: "RelayerSpokePoolListener::run", message: `Scraping previous ${chain} events.`, opts }); + + // The SpokePoolClient reports on the timestamp of the oldest block searched. The relayer likely doesn't need this, + // but resolve it anyway for consistency with the main SpokePoolClient implementation. + const resolveOldestTime = async (spokePool: Contract, blockTag: ethersProviders.BlockTag) => { + oldestTime = (await spokePool.getCurrentTime({ blockTag })).toNumber(); + }; + + if (latestBlock.number > startBlock) { + const events = ["V3FundsDeposited", "FilledV3Relay", "RelayedRootBundle", "ExecutedRelayerRefundRoot"]; + const _spokePool = spokePool.connect(quorumProvider); + await Promise.all([resolveOldestTime(_spokePool, startBlock), scrapeEvents(_spokePool, events, opts)]); + } + + // If no lookback was specified then default to the timestamp of the latest block. + oldestTime ??= latestBlock.timestamp; + + // Events to listen for. + const events = ["V3FundsDeposited", "FilledV3Relay"]; + const eventMgr = new EventManager(logger, chainId, quorum); + + logger.debug({ at: "RelayerSpokePoolListener::run", message: `Starting ${chain} listener.`, events, opts }); + await listen(eventMgr, spokePool, events, quorum); +} + +if (require.main === module) { + logger = Logger; + + run(process.argv.slice(2)) + .then(() => { + process.exitCode = NODE_SUCCESS; + }) + .catch((error) => { + logger.error({ at: "RelayerSpokePoolListener", message: `${chain} listener exited with error.`, error }); + process.exitCode = NODE_APP_ERR; + }) + .finally(async () => { + await disconnectRedisClients(); + logger.debug({ at: "RelayerSpokePoolListener", message: `Exiting ${chain} listener.` }); + exit(process.exitCode); + }); +} diff --git a/yarn.lock b/yarn.lock index e5e4cbd42..62478b7bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -74,6 +74,11 @@ superstruct "^0.15.4" tslib "^2.6.2" +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@arbitrum/sdk@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.1.3.tgz#75236043717a450b569faaa087687c51d525b0c3" @@ -1503,11 +1508,42 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== +"@noble/curves@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/curves@^1.4.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" + integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== + dependencies: + "@noble/hashes" "1.5.0" + +"@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/hashes@1.0.0", "@noble/hashes@~1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg== +"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + +"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" + integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== + "@noble/secp256k1@1.5.5", "@noble/secp256k1@~1.5.2": version "1.5.5" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.5.5.tgz#315ab5745509d1a8c8e90d0bdf59823ccf9bcfc3" @@ -2373,6 +2409,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.0.0.tgz#109fb595021de285f05a7db6806f2f48296fcee7" integrity sha512-gIVaYhUsy+9s58m/ETjSJVKHhKTBMmcRb9cEV5/5dwvfDlfORjKrFsDeDHWRrm6RjcPvCLZFwGJjAjLj1gg4HA== +"@scure/base@~1.1.6", "@scure/base@~1.1.8": + version "1.1.8" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" + integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== + "@scure/bip32@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.0.1.tgz#1409bdf9f07f0aec99006bb0d5827693418d3aa5" @@ -2382,6 +2423,15 @@ "@noble/secp256k1" "~1.5.2" "@scure/base" "~1.0.0" +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.0.0.tgz#47504e58de9a56a4bbed95159d2d6829fa491bb0" @@ -2390,6 +2440,14 @@ "@noble/hashes" "~1.0.0" "@scure/base" "~1.0.0" +"@scure/bip39@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.4.0.tgz#664d4f851564e2e1d4bffa0339f9546ea55960a6" + integrity sha512-BEEm6p8IueV/ZTfQLp/0vhw4NPnT9oWf5+28nvmeUICjP99f4vr2d+qc7AVGDDtwRep6ifR43Yed9ERVmiITzw== + dependencies: + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.8" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -3414,6 +3472,11 @@ abbrev@1.0.x: web3-eth-abi "^1.2.1" web3-utils "^1.2.1" +abitype@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" + integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -9030,6 +9093,11 @@ isomorphic-unfetch@^3.0.0: node-fetch "^2.6.1" unfetch "^4.2.0" +isows@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" + integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -13619,7 +13687,7 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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== @@ -13645,15 +13713,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -13723,7 +13782,7 @@ stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", 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== @@ -13751,13 +13810,6 @@ 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.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14658,6 +14710,21 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viem@^2.21.7: + version "2.21.7" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.7.tgz#c933fd3adb6f771e5c5fe2b4d7a14d8701ddc32f" + integrity sha512-PFgppakInuHX31wHDx1dzAjhj4t6Po6WrWtutDi33z2vabIT0Wv8qT6tl7DLqfLy2NkTqfN2mdshYLeoI5ZHvQ== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.4.0" + abitype "1.0.5" + isows "1.0.4" + webauthn-p256 "0.0.5" + ws "8.17.1" + walk-up-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" @@ -15150,6 +15217,14 @@ web3@1.8.2, web3@^1.6.0: web3-shh "1.8.2" web3-utils "1.8.2" +webauthn-p256@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" + integrity sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -15317,7 +15392,7 @@ workerpool@6.2.1: 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-cjs@npm:wrap-ansi@^7.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== @@ -15343,15 +15418,6 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.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.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -15381,6 +15447,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" From 6b42f7941558f41b93642de586727b04203b5d12 Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Wed, 2 Oct 2024 07:57:10 -0500 Subject: [PATCH 05/41] improve: swap Optimism and Base OpStackAdapters with BaseChainAdapters (#1839) * improve: swap Optimism and Base OpStackAdapters with BaseChainAdapters Signed-off-by: bennett --------- Signed-off-by: bennett --- src/adapter/bridges/SnxOptimismBridge.ts | 36 ++++++++++++++++++--- src/clients/bridges/AdapterManager.ts | 20 ++++++++---- src/common/abi/SnxOptimismBridgeL1.json | 2 +- test/AdapterManager.SendTokensCrossChain.ts | 16 ++++----- test/generic-adapters/OpStack.ts | 20 ++++++------ 5 files changed, 63 insertions(+), 31 deletions(-) diff --git a/src/adapter/bridges/SnxOptimismBridge.ts b/src/adapter/bridges/SnxOptimismBridge.ts index e7cb310f5..791f563a4 100644 --- a/src/adapter/bridges/SnxOptimismBridge.ts +++ b/src/adapter/bridges/SnxOptimismBridge.ts @@ -1,4 +1,12 @@ -import { Contract, BigNumber, paginatedEventQuery, EventSearchConfig, Signer, Provider } from "../../utils"; +import { + Contract, + BigNumber, + paginatedEventQuery, + EventSearchConfig, + Signer, + Provider, + isContractDeployedToAddress, +} from "../../utils"; import { CONTRACT_ADDRESSES } from "../../common"; import { BaseBridgeAdapter, BridgeTransactionDetails, BridgeEvents } from "./BaseBridgeAdapter"; import { processEvent } from "../utils"; @@ -43,11 +51,19 @@ export class SnxOptimismBridge extends BaseBridgeAdapter { toAddress: string, eventConfig: EventSearchConfig ): Promise { - // @dev For the SnxBridge, only the `toAddress` is indexed on the L2 event so we treat the `fromAddress` as the - // toAddress when fetching the L1 event. + const hubPoolAddress = this.getHubPool().address; + // @dev Since the SnxOptimism bridge has no _from field when querying for finalizations, we cannot use + // the hub pool to determine cross chain transfers (since we do not assume knowledge of the spoke pool address). + if (fromAddress === hubPoolAddress) { + return Promise.resolve({}); + } + // If `toAddress` is a contract on L2, then assume the contract is the spoke pool, and further assume that the sender + // is the hub pool. + const isSpokePool = await this.isL2ChainContract(toAddress); + fromAddress = isSpokePool ? hubPoolAddress : fromAddress; const events = await paginatedEventQuery( this.getL1Bridge(), - this.getL1Bridge().filters.DepositInitiated(undefined, toAddress), + this.getL1Bridge().filters.DepositInitiated(fromAddress), eventConfig ); return { @@ -70,4 +86,16 @@ export class SnxOptimismBridge extends BaseBridgeAdapter { [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "_amount", "_to", "_from")), }; } + + private getHubPool(): Contract { + const hubPoolContractData = CONTRACT_ADDRESSES[this.hubChainId]?.hubPool; + if (!hubPoolContractData) { + throw new Error(`hubPoolContractData not found for chain ${this.hubChainId}`); + } + return new Contract(hubPoolContractData.address, hubPoolContractData.abi, this.l1Signer); + } + + private isL2ChainContract(address: string): Promise { + return isContractDeployedToAddress(address, this.getL2Bridge().provider); + } } diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index ec8a9d732..d23ec0bf8 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -9,7 +9,7 @@ import { import { InventoryConfig, OutstandingTransfers } from "../../interfaces"; import { BigNumber, isDefined, winston, Signer, getL2TokenAddresses, TransactionResponse, assert } from "../../utils"; import { SpokePoolClient, HubPoolClient } from "../"; -import { ArbitrumAdapter, PolygonAdapter, ZKSyncAdapter, LineaAdapter, OpStackAdapter, ScrollAdapter } from "./"; +import { ArbitrumAdapter, PolygonAdapter, ZKSyncAdapter, LineaAdapter, ScrollAdapter } from "./"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; import { BaseChainAdapter } from "../../adapter"; @@ -60,12 +60,15 @@ export class AdapterManager { ); }; if (this.spokePoolClients[OPTIMISM] !== undefined) { - this.adapters[OPTIMISM] = new OpStackAdapter( + this.adapters[OPTIMISM] = new BaseChainAdapter( + spokePoolClients, OPTIMISM, + hubChainId, + filterMonitoredAddresses(OPTIMISM), logger, SUPPORTED_TOKENS[OPTIMISM], - spokePoolClients, - filterMonitoredAddresses(OPTIMISM) + constructBridges(OPTIMISM), + DEFAULT_GAS_MULTIPLIER[OPTIMISM] ?? 1 ); } if (this.spokePoolClients[POLYGON] !== undefined) { @@ -78,12 +81,15 @@ export class AdapterManager { this.adapters[ZK_SYNC] = new ZKSyncAdapter(logger, spokePoolClients, filterMonitoredAddresses(ZK_SYNC)); } if (this.spokePoolClients[BASE] !== undefined) { - this.adapters[BASE] = new OpStackAdapter( + this.adapters[BASE] = new BaseChainAdapter( + spokePoolClients, BASE, + hubChainId, + filterMonitoredAddresses(BASE), logger, SUPPORTED_TOKENS[BASE], - spokePoolClients, - filterMonitoredAddresses(BASE) + constructBridges(BASE), + DEFAULT_GAS_MULTIPLIER[BASE] ?? 1 ); } if (this.spokePoolClients[LINEA] !== undefined) { diff --git a/src/common/abi/SnxOptimismBridgeL1.json b/src/common/abi/SnxOptimismBridgeL1.json index dc278956a..4f6d07ae9 100644 --- a/src/common/abi/SnxOptimismBridgeL1.json +++ b/src/common/abi/SnxOptimismBridgeL1.json @@ -3,7 +3,7 @@ "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "_from", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "_to", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "_to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "_amount", "type": "uint256" } ], "name": "DepositInitiated", diff --git a/test/AdapterManager.SendTokensCrossChain.ts b/test/AdapterManager.SendTokensCrossChain.ts index 235d8053f..f6bd5e77f 100644 --- a/test/AdapterManager.SendTokensCrossChain.ts +++ b/test/AdapterManager.SendTokensCrossChain.ts @@ -126,7 +126,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.bal, // l1 token getL2TokenAddresses(mainnetTokens.bal)[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.bal].l2Gas, // l2Gas "0x" // data ); @@ -142,7 +142,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.usdc, // l1 token TOKEN_SYMBOLS_MAP["USDC.e"].addresses[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.usdc].canonicalBridge.l2Gas, // l2Gas "0x" // data ); @@ -175,7 +175,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.dai, // l1 token getL2TokenAddresses(mainnetTokens.dai)[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.dai].l2Gas, // l2Gas "0x" // data ); @@ -184,7 +184,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { expect(l1AtomicDepositor.bridgeWethToOvm).to.have.been.calledWith( relayer.address, // to amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.weth].l2Gas, // l2Gas chainId // chainId ); }); @@ -354,7 +354,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.usdc, // l1 token TOKEN_SYMBOLS_MAP.USDbC.addresses[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.usdc].canonicalBridge.l2Gas, // l2Gas "0x" // data ); @@ -363,7 +363,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.bal, // l1 token getL2TokenAddresses(mainnetTokens.bal)[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.bal].l2Gas, // l2Gas "0x" // data ); @@ -373,7 +373,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { mainnetTokens.dai, // l1 token getL2TokenAddresses(mainnetTokens.dai)[chainId], // l2 token amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.dai].l2Gas, // l2Gas "0x" // data ); @@ -382,7 +382,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { expect(l1AtomicDepositor.bridgeWethToOvm).to.have.been.calledWith( relayer.address, // to amountToSend, // amount - addAttrib(adapterManager.adapters[chainId]).l2Gas, // l2Gas + addAttrib(adapterManager.adapters[chainId]).bridges[mainnetTokens.weth].l2Gas, // l2Gas chainId // chainId ); }); diff --git a/test/generic-adapters/OpStack.ts b/test/generic-adapters/OpStack.ts index 18537ec74..cefa2ddf7 100644 --- a/test/generic-adapters/OpStack.ts +++ b/test/generic-adapters/OpStack.ts @@ -183,17 +183,15 @@ describe("Cross Chain Adapter: OP Stack", async function () { describe("Custom bridge: SNX", () => { it("return only relevant L1 bridge init events", async () => { const snxBridge = adapter.bridges[l1SnxAddress]; - await snxBridgeContract.emitDepositInitiated(monitoredEoa, notMonitoredEoa, 1); - await snxBridgeContract.emitDepositInitiated(notMonitoredEoa, monitoredEoa, 1); + await snxBridgeContract.emitDepositInitiated(monitoredEoa, monitoredEoa, 1); + await snxBridgeContract.emitDepositInitiated(notMonitoredEoa, notMonitoredEoa, 1); const events = ( await snxBridge.queryL1BridgeInitiationEvents(l1SnxAddress, monitoredEoa, monitoredEoa, searchConfig) )[l2SnxAddress]; expect(events.length).to.equal(1); - // For the SnxBridge, only the `toAddress` is indexed on the L2 event so we treat the `fromAddress` as the - // toAddress when fetching the L1 event. expect(events[0].to).to.equal(monitoredEoa); - expect(events[0].from).to.equal(notMonitoredEoa); + expect(events[0].from).to.equal(monitoredEoa); }); it("return only relevant L2 bridge finalization events", async () => { @@ -321,8 +319,8 @@ describe("Cross Chain Adapter: OP Stack", async function () { // WETH transfers: 1x outstanding await wethBridgeContract.emitDepositInitiated(atomicDepositorAddress, monitoredEoa, outstandingAmount); // SNX transfers: 1x outstanding, 1x finalized - await snxBridgeContract.emitDepositInitiated(notMonitoredEoa, monitoredEoa, outstandingAmount); - await snxBridgeContract.emitDepositInitiated(notMonitoredEoa, monitoredEoa, finalizedAmount); + await snxBridgeContract.emitDepositInitiated(monitoredEoa, monitoredEoa, outstandingAmount); + await snxBridgeContract.emitDepositInitiated(monitoredEoa, monitoredEoa, finalizedAmount); await snxBridgeContract.emitDepositFinalized(monitoredEoa, finalizedAmount); // DAI transfers: 1x outstanding, 1x finalized await daiBridgeContract.emitDepositInitiated( @@ -371,16 +369,16 @@ describe("Cross Chain Adapter: OP Stack", async function () { // Get deposit tx hashes of outstanding transfers const outstandingWethEvent = ( - await wethBridge.queryL1BridgeInitiationEvents(l1WethAddress, monitoredEoa, undefined, searchConfig) + await wethBridge.queryL1BridgeInitiationEvents(l1WethAddress, monitoredEoa, monitoredEoa, searchConfig) )[l2WethAddress].find((event) => event.amount.toNumber() === outstandingAmount); const outstandingSnxEvent = ( - await snxBridge.queryL1BridgeInitiationEvents(l1SnxAddress, monitoredEoa, undefined, searchConfig) + await snxBridge.queryL1BridgeInitiationEvents(l1SnxAddress, monitoredEoa, monitoredEoa, searchConfig) )[l2SnxAddress].find((event) => event.amount.toNumber() === outstandingAmount); const outstandingDaiEvent = ( - await daiBridge.queryL1BridgeInitiationEvents(l1DaiAddress, monitoredEoa, undefined, searchConfig) + await daiBridge.queryL1BridgeInitiationEvents(l1DaiAddress, monitoredEoa, monitoredEoa, searchConfig) )[l2DaiAddress].find((event) => event.amount.toNumber() === outstandingAmount); const outstandingErc20Event = ( - await erc20Bridge.queryL1BridgeInitiationEvents(l1Erc20Address, monitoredEoa, undefined, searchConfig) + await erc20Bridge.queryL1BridgeInitiationEvents(l1Erc20Address, monitoredEoa, monitoredEoa, searchConfig) )[l2Erc20Address].find((event) => event.amount.toNumber() === outstandingAmount); const outstandingOfMonitored = ( From d3a88d528542075c349ed859c6f8cbfd635b4c0b Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 4 Oct 2024 11:43:45 +0200 Subject: [PATCH 06/41] improve(relayer): Redirect in-protocol swap message (#1848) This message can be quite spammy in the main logging channel, so redirect it to the more acceptably-noisy "unprofitable fills" channel. --- src/relayer/Relayer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 31826ce55..5ce2aadea 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -292,6 +292,7 @@ export class Relayer { at: "Relayer::filterDeposit", message: "Skipping deposit including in-protocol token swap.", deposit, + notificationPath: "across-unprofitable-fills", }); return false; } From 47c156ffc12eb8a8baba721630bb2b1479ff7e7a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:09:02 +0200 Subject: [PATCH 07/41] improve(relayer): Defer deposit version computation (#1847) getUnfilledDeposits() currently unconditionally computes the deposit version (i.e. the ConfigStore VERSION value applicable at the deposit quoteTimestamp), and then filters out all the deposits that have been filled. Determining the relevant version implies a lot of Array.find() calls, all of which is wasted when the object is subsequently discarded. --- src/utils/FillUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utils/FillUtils.ts b/src/utils/FillUtils.ts index cce7d59b9..010afdc67 100644 --- a/src/utils/FillUtils.ts +++ b/src/utils/FillUtils.ts @@ -32,11 +32,14 @@ export function getUnfilledDeposits( return deposits .map((deposit) => { - const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); const { unfilledAmount, invalidFills } = destinationClient.getValidUnfilledAmountForDeposit(deposit); - return { deposit, version, unfilledAmount, invalidFills }; + return { deposit, unfilledAmount, invalidFills }; }) - .filter(({ unfilledAmount }) => unfilledAmount.gt(bnZero)); + .filter(({ unfilledAmount }) => unfilledAmount.gt(bnZero)) + .map(({ deposit, ...rest }) => { + const version = hubPoolClient.configStoreClient.getConfigStoreVersionForTimestamp(deposit.quoteTimestamp); + return { deposit, ...rest, version }; + }); } export function getAllUnfilledDeposits( From d5bf653a7961a0cdc70720d9474938ca63f45506 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:41:39 +0200 Subject: [PATCH 08/41] fix(relayer): Fix "failed start" chain log message (#1846) --- src/relayer/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/relayer/index.ts b/src/relayer/index.ts index 1c526c189..a0187748e 100644 --- a/src/relayer/index.ts +++ b/src/relayer/index.ts @@ -72,8 +72,10 @@ export async function runRelayer(_logger: winston.Logger, baseSigner: Signer): P continue; } - const badChains = Object.values(spokePoolClients).filter(({ isUpdated }) => !isUpdated); - throw new Error(`Unable to start relayer due to chains ${badChains}`); + const badChains = Object.values(spokePoolClients) + .filter(({ isUpdated }) => !isUpdated) + .map(({ chainId }) => getNetworkName(chainId)); + throw new Error(`Unable to start relayer due to chains ${badChains.join(", ")}`); } // Signal to any existing relayer that a handover is underway, or alternatively From 1af89a2582e569a4cd232f4d3649b65432a5fb5f Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 7 Oct 2024 20:15:45 +0200 Subject: [PATCH 09/41] improve(spokepool): Dump relayData hash on fill logs (#1849) This implementation is a bit hacky, but it works and is quite useful when supporting third-parties to debug fillStatus queries. --- scripts/spokepool.ts | 10 +++++++++- src/interfaces/index.ts | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/spokepool.ts b/scripts/spokepool.ts index cef9687cd..4d4d1b056 100644 --- a/scripts/spokepool.ts +++ b/scripts/spokepool.ts @@ -6,6 +6,7 @@ import { Contract, ethers, Signer } from "ethers"; import { LogDescription } from "@ethersproject/abi"; import { constants as sdkConsts, utils as sdkUtils } from "@across-protocol/sdk"; import { ExpandedERC20__factory as ERC20 } from "@across-protocol/contracts"; +import { RelayData } from "../src/interfaces"; import { BigNumber, formatFeePct, @@ -62,11 +63,18 @@ function printDeposit(originChainId: number, log: LogDescription): void { function printFill(destinationChainId: number, log: LogDescription): void { const { originChainId, outputToken } = log.args; const eventArgs = Object.keys(log.args).filter((key) => isNaN(Number(key))); - const padLeft = eventArgs.reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0); + + const relayDataHash = sdkUtils.getRelayDataHash( + Object.fromEntries(eventArgs.map((arg) => [arg, log.args[arg]])) as RelayData, + destinationChainId + ); + + const padLeft = [...eventArgs, "relayDataHash"].reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0); const fields = { tokenSymbol: resolveTokenSymbols([outputToken], destinationChainId)[0], ...Object.fromEntries(eventArgs.map((key) => [key, log.args[key]])), + relayDataHash, }; console.log( `Fill for ${getNetworkName(originChainId)} deposit # ${log.args.depositId}:\n` + diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 783e38024..5cbdeac7d 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -52,6 +52,7 @@ export type SetPoolRebalanceRoot = interfaces.SetPoolRebalanceRoot; export type PendingRootBundle = interfaces.PendingRootBundle; // SpokePool interfaces +export type RelayData = interfaces.RelayData; export type FundsDepositedEvent = interfaces.FundsDepositedEvent; export type Deposit = interfaces.Deposit; export type DepositWithBlock = interfaces.DepositWithBlock; From bffa498f95def75927dacdc4cd47d56da23cac26 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 7 Oct 2024 21:18:41 +0200 Subject: [PATCH 10/41] chore: Bump SDK (#1850) This includes a change that speeds up deposit/fill matching. In the relayer this can save multiple seconds per loop, so it's going to have a meaningful impact on performance. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 79bc3e84c..b08a3bee0 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.14", "@across-protocol/contracts": "^3.0.10", - "@across-protocol/sdk": "^3.2.0", + "@across-protocol/sdk": "^3.2.4", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index 62478b7bf..dfd213345 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,10 +50,10 @@ axios "^1.6.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.0.tgz#bd011965c7c3ffc6c76c9e00bb9baa5f80d33c7d" - integrity sha512-ks+A9fvXMdcstedvMzV+uTAFObSuXs/4xSm11CEIMu2RfgGTQe/pCN0KSqPhW+H4v53sblxGJpsL8Y/msfH/HQ== +"@across-protocol/sdk@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.4.tgz#3c7b9dac1c289ca6161c61358f3efb56e97d8d49" + integrity sha512-3/BzwgQdsWv4MsFsmj5OsmjV66QHHBfAuefPOA/TmPK9PC6CAgpNZNuNmp17Kk5pgagy3UDwBF7EDvWLY+aOhQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.14" From a6260543cbfc9971fa31a03d461514b2d30043c3 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:05:02 +0200 Subject: [PATCH 11/41] feat: Support World Chain (#1855) Co-authored-by: bmzig <57361391+bmzig@users.noreply.github.com> --- contracts/AtomicWethDepositor.sol | 3 ++ deployments/mainnet/AtomicWethDepositor.json | 41 ++++++++++++------- .../0b9a38081e5f67aae8aeed6c044fb8c5.json | 21 ++++++++++ package.json | 6 +-- src/clients/ProfitClient.ts | 1 + src/clients/bridges/AdapterManager.ts | 12 ++++++ src/common/Constants.ts | 41 +++++++++++++++++-- src/common/ContractAddresses.ts | 15 ++++++- src/relayer/Relayer.ts | 7 +++- test/Dataworker.loadData.slowFill.ts | 2 + test/Relayer.BasicFill.ts | 9 ++-- test/Relayer.SlowFill.ts | 1 + yarn.lock | 39 ++++++++---------- 13 files changed, 150 insertions(+), 48 deletions(-) create mode 100644 deployments/mainnet/solcInputs/0b9a38081e5f67aae8aeed6c044fb8c5.json diff --git a/contracts/AtomicWethDepositor.sol b/contracts/AtomicWethDepositor.sol index 41680f5ec..2808bf81d 100644 --- a/contracts/AtomicWethDepositor.sol +++ b/contracts/AtomicWethDepositor.sol @@ -52,6 +52,7 @@ contract AtomicWethDepositor { OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08); OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69); OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524); + OvmL1Bridge public immutable worldChainL1Bridge = OvmL1Bridge(0x470458C91978D2d929704489Ad730DC3E3001113); OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631); PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77); ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324); @@ -72,6 +73,8 @@ contract AtomicWethDepositor { baseL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); } else if (chainId == 34443) { modeL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); + } else if (chainId == 480) { + worldChainL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); } else if (chainId == 1135) { liskL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); } else if (chainId == 81457) { diff --git a/deployments/mainnet/AtomicWethDepositor.json b/deployments/mainnet/AtomicWethDepositor.json index ccd2388ee..39a63f12b 100644 --- a/deployments/mainnet/AtomicWethDepositor.json +++ b/deployments/mainnet/AtomicWethDepositor.json @@ -1,5 +1,5 @@ { - "address": "0xE48278aD2b9b402A8E3C2E0ffbaD7EEe3905bf94", + "address": "0xa679201903847f3723Dc88CA7530c8B665bC51a5", "abi": [ { "anonymous": false, @@ -313,6 +313,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "worldChainL1Bridge", + "outputs": [ + { + "internalType": "contract OvmL1Bridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "zkSyncL1Bridge", @@ -344,26 +357,26 @@ "type": "receive" } ], - "transactionHash": "0xe66a3a0652de755c60dfb9631a85d0a691362b8b3168845b0a98e140976d8fc8", + "transactionHash": "0xc357db947a4f937ca325e6246f6a6bcd972b4ad32ad2bcb66741f8f24b1b8ff0", "receipt": { "to": null, "from": "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D", - "contractAddress": "0xE48278aD2b9b402A8E3C2E0ffbaD7EEe3905bf94", - "transactionIndex": 120, - "gasUsed": "1317812", + "contractAddress": "0xa679201903847f3723Dc88CA7530c8B665bC51a5", + "transactionIndex": 158, + "gasUsed": "1379246", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x42c5ec9b27101c0ec10f47883073ed42b147572414e2ae70e19082b578abc7ac", - "transactionHash": "0xe66a3a0652de755c60dfb9631a85d0a691362b8b3168845b0a98e140976d8fc8", + "blockHash": "0xbeddf54fffcd886bbc27d552bd71808d082946081f8bf53c058a429a8a0331ec", + "transactionHash": "0xc357db947a4f937ca325e6246f6a6bcd972b4ad32ad2bcb66741f8f24b1b8ff0", "logs": [], - "blockNumber": 20512566, - "cumulativeGasUsed": "13084477", + "blockNumber": 20927440, + "cumulativeGasUsed": "14135373", "status": 1, "byzantium": true }, "args": [], - "numDeployments": 8, - "solcInputHash": "dfa1a8c5a6b030fb38fb617a6ddc8435", - "metadata": "{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"LineaEthDepositInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"OvmEthDepositInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ZkSyncEthDepositInitiated\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"baseL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blastL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bobaL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToLinea\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"l2Gas\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToOvm\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToPolygon\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"l2GasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"l2GasPerPubdataByteLimit\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"refundRecipient\",\"type\":\"address\"}],\"name\":\"bridgeWethToZkSync\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lineaL1MessageService\",\"outputs\":[{\"internalType\":\"contract LineaL1MessageService\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liskL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"modeL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"polygonL1Bridge\",\"outputs\":[{\"internalType\":\"contract PolygonL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"redstoneL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"weth\",\"outputs\":[{\"internalType\":\"contract Weth\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"zkSyncL1Bridge\",\"outputs\":[{\"internalType\":\"contract ZkSyncL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"zoraL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH, not WETH.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/AtomicWethDepositor.sol\":\"AtomicWethDepositor\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[],\"viaIR\":true},\"sources\":{\"contracts/AtomicWethDepositor.sol\":{\"keccak256\":\"0xf3a524efe64fbe7ec5fc1635059a8852ab919eff0edddd4c5397fdd306685f0e\",\"license\":\"GPL-3.0-only\",\"urls\":[\"bzz-raw://5448a851d7804aba3805779e13489e3a8b1546f701bb408628fbe414917c5bc3\",\"dweb:/ipfs/QmfP1BybGNvXiBbd5ba2EXNBvr3w1YyoZAWiAUEV3wt9US\"]}},\"version\":1}", - "bytecode": "0x610200806040523461021e5773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26080527399c9fc46f92e8a1c0dec1b1747d010903e884be160a05273735adbbe72226bd52e818e7181953f42e3b0ff2160c05273dc1664458d2f0b6090bea60a8793a4e66c2f1c0060e05261010090733154cf16ccdb4c6d922629664174b904d80f2c358252610120732658723bf70c7667de6b25f99fcce13a16d25d08815261014073c473ca7e02af24c129c2eef51f2adf0411c1df69815261016073697402166fbf2f22e970df8a6486ef171dbfc5248152610180733e2ea9b92b7e48a52296fd261dc26fd99528463181526101a09173a0c68c638235ee32657e8f720a23cec1bfc77c7783526101c0937332400084c286cf3e17e7b677ea9583e60a00032485526101e09573d19d4b5d358258f05d7b411e21a1460d11b0876f875261170798896102238a396080518981816101bb01528181610b6901528181610f0201528181610f780152611223015260a0518981816102940152610933015260c0518981816104700152610d46015260e0518981816107ac015261160f0152518881816103a20152610a80015251878181610128015261051401525186818161065d0152610aef0152518581816105b90152610db50152518481816107020152610e930152518381816109a20152610c3a015251828181610e24015281816111e30152818161131901526113d8015251818181610a1101526110490152f35b5f80fdfe6080806040526004908136101561001d575b5050361561001b57005b005b5f915f3560e01c918263019f8e81146115c757508163128d5f681461113e57816336918a9714610f265781633fc8cef314610eb75781634fbf95d714610e485781635970eafa14610dd957816362c8eb5c14610d6a578163645b6f1114610cfb57828263b3d5ccc314610b1357508163b6865d6e14610aa4578163b745c3f314610a35578163c04b9534146109c6578163c80dcc3814610957578163d3cdc8f9146108e8578163e88650c41461014f575063f43873c4146100de5780610011565b3461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b80fd5b9050346108e45760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126108e457610188611633565b604435916024359163ffffffff8416840361038357846064359473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016803b1561039257604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523384820190815230602082015291820189905290859082908190606001038183865af19081156108d95785916108c5575b5050803b15610392578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528c888401525af19081156108ba5784916108a2575b5050600a870361039657847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761032c9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103875761036f575b50505b60405192835216917fcde53d24289bf7d0b2baeea6140c533d8388fb574b055364d718f637bedea7a460203393a480f35b61037890611656565b61038357845f61033b565b8480fd5b6040513d84823e3d90fd5b8380fd5b612105870361046457847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af1801561038757610450575b505061033e565b61045990611656565b61038357845f610449565b61868b870361050857847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b61046f87036105ac57847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b62013e31870361065157847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6102b287036106f557847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6276adf1870361079a57847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b909150610120860361084457908691847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6064906020604051917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601360248201527f496e76616c6964204f564d20636861696e4964000000000000000000000000006044820152fd5b6108ab90611656565b6108b657825f610287565b8280fd5b6040513d86823e3d90fd5b6108ce90611656565b61039257835f61023a565b6040513d87823e3d90fd5b5080fd5b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b915034610cf85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610cf857610b4c611633565b6024359073ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b15610cb657604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523387820190815230602082015291820186905290879082908190606001038183865af1908115610ced578791610cd9575b5050803b15610cb6578580916024604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1908115610cce578691610cba575b5050817f00000000000000000000000000000000000000000000000000000000000000001690813b15610cb657859360249260405196879586947f4faa8a2600000000000000000000000000000000000000000000000000000000865216908401525af1801561038757610ca65750f35b610caf90611656565b61014c5780f35b8580fd5b610cc390611656565b61038357845f610c35565b6040513d88823e3d90fd5b610ce290611656565b610cb657855f610be8565b6040513d89823e3d90fd5b50fd5b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b905060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126108e457610f5a611633565b826024359173ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b1561039257604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523388820190815230602082015291820187905290859082908190606001038183865af19081156108d957859161112a575b5050803b15610392578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528a8c8401525af19081156108ba578491611116575b5050817f000000000000000000000000000000000000000000000000000000000000000016916110743486611697565b833b1561038357849260849160405195869485937f9f3ce55a00000000000000000000000000000000000000000000000000000000855216809a840152346024840152606060448401528560648401525af1801561038757611102575b50506040519081527f61ed67a945fe5f4d777919629ad666c7e81d66dc5fbaf4c143edd000c15d67dd60203392a380f35b61110b90611656565b6108b657825f6110d1565b61111f90611656565b6108b657825f611044565b61113390611656565b61039257835f610ff7565b823461155b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261155b57611176611633565b602490604435906064359460843573ffffffffffffffffffffffffffffffffffffffff9384821680920361155b576040517fb473318e0000000000000000000000000000000000000000000000000000000081523a848201528187820152886044820152602081606481897f0000000000000000000000000000000000000000000000000000000000000000165afa8015611550575f9061155f575b61121f9150873590611697565b91857f000000000000000000000000000000000000000000000000000000000000000016803b1561155b57604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233878201908152306020820152918201869052905f9082908190606001038183865af180156115505761153d575b50803b156115395788809189604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1801561152e5790899161151a575b505060405191602083019280841067ffffffffffffffff8511176114ef57899a8460409b98999a9b52878252897f0000000000000000000000000000000000000000000000000000000000000000163b156114eb5760409a989695949a99979951957feb6724190000000000000000000000000000000000000000000000000000000087528888169087015288358987015260e060448701528960e48701526101048601926064870152608486015261010060a48601525180915261012490818501918160051b860101999189905b82821061144d575050505082809281808b8b979560c4899701520391887f0000000000000000000000000000000000000000000000000000000000000000165af1801561038757611439575b50506040519235835216907fa3e601130860a6f97b42655ad74f631ddf0c8e5adaa98402fded9c09bc35a44060203392a380f35b61144290611656565b610392578385611405565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc878d969596030181528b855180518092528c5b8281106114d5575050808d0160209081018d9052601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909c018c019b939484019360019290920191016113b9565b808f602082818095870101519201015201611481565b8780fd5b886041877f4e487b71000000000000000000000000000000000000000000000000000000005f52525ffd5b61152390611656565b6114eb57878a6112ea565b6040513d8b823e3d90fd5b8880fd5b611548919950611656565b5f978a61129f565b6040513d5f823e3d90fd5b5f80fd5b5060203d6020116115c0575b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f820116820182811067ffffffffffffffff8211176114ef5760209183916040528101031261155b5761121f9051611212565b503d61156b565b3461155b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261155b5760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361155b57565b67ffffffffffffffff811161166a57604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b919082018092116116a457565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffdfea26469706673582212205888bf681cf0d9036082fce3d2d0a7cbdfb2d39c83fdb5d03d2fff0b90c6af1064736f6c63430008170033", - "deployedBytecode": "0x6080806040526004908136101561001d575b5050361561001b57005b005b5f915f3560e01c918263019f8e81146115c757508163128d5f681461113e57816336918a9714610f265781633fc8cef314610eb75781634fbf95d714610e485781635970eafa14610dd957816362c8eb5c14610d6a578163645b6f1114610cfb57828263b3d5ccc314610b1357508163b6865d6e14610aa4578163b745c3f314610a35578163c04b9534146109c6578163c80dcc3814610957578163d3cdc8f9146108e8578163e88650c41461014f575063f43873c4146100de5780610011565b3461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b80fd5b9050346108e45760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126108e457610188611633565b604435916024359163ffffffff8416840361038357846064359473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016803b1561039257604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523384820190815230602082015291820189905290859082908190606001038183865af19081156108d95785916108c5575b5050803b15610392578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528c888401525af19081156108ba5784916108a2575b5050600a870361039657847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761032c9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103875761036f575b50505b60405192835216917fcde53d24289bf7d0b2baeea6140c533d8388fb574b055364d718f637bedea7a460203393a480f35b61037890611656565b61038357845f61033b565b8480fd5b6040513d84823e3d90fd5b8380fd5b612105870361046457847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af1801561038757610450575b505061033e565b61045990611656565b61038357845f610449565b61868b870361050857847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b61046f87036105ac57847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b62013e31870361065157847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6102b287036106f557847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6276adf1870361079a57847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b909150610120860361084457908691847f00000000000000000000000000000000000000000000000000000000000000001691823b156103925761043a9285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6064906020604051917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601360248201527f496e76616c6964204f564d20636861696e4964000000000000000000000000006044820152fd5b6108ab90611656565b6108b657825f610287565b8280fd5b6040513d86823e3d90fd5b6108ce90611656565b61039257835f61023a565b6040513d87823e3d90fd5b5080fd5b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b915034610cf85760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610cf857610b4c611633565b6024359073ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b15610cb657604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523387820190815230602082015291820186905290879082908190606001038183865af1908115610ced578791610cd9575b5050803b15610cb6578580916024604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1908115610cce578691610cba575b5050817f00000000000000000000000000000000000000000000000000000000000000001690813b15610cb657859360249260405196879586947f4faa8a2600000000000000000000000000000000000000000000000000000000865216908401525af1801561038757610ca65750f35b610caf90611656565b61014c5780f35b8580fd5b610cc390611656565b61038357845f610c35565b6040513d88823e3d90fd5b610ce290611656565b610cb657855f610be8565b6040513d89823e3d90fd5b50fd5b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461014c57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261014c57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b905060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126108e457610f5a611633565b826024359173ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b1561039257604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523388820190815230602082015291820187905290859082908190606001038183865af19081156108d957859161112a575b5050803b15610392578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528a8c8401525af19081156108ba578491611116575b5050817f000000000000000000000000000000000000000000000000000000000000000016916110743486611697565b833b1561038357849260849160405195869485937f9f3ce55a00000000000000000000000000000000000000000000000000000000855216809a840152346024840152606060448401528560648401525af1801561038757611102575b50506040519081527f61ed67a945fe5f4d777919629ad666c7e81d66dc5fbaf4c143edd000c15d67dd60203392a380f35b61110b90611656565b6108b657825f6110d1565b61111f90611656565b6108b657825f611044565b61113390611656565b61039257835f610ff7565b823461155b5760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261155b57611176611633565b602490604435906064359460843573ffffffffffffffffffffffffffffffffffffffff9384821680920361155b576040517fb473318e0000000000000000000000000000000000000000000000000000000081523a848201528187820152886044820152602081606481897f0000000000000000000000000000000000000000000000000000000000000000165afa8015611550575f9061155f575b61121f9150873590611697565b91857f000000000000000000000000000000000000000000000000000000000000000016803b1561155b57604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233878201908152306020820152918201869052905f9082908190606001038183865af180156115505761153d575b50803b156115395788809189604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1801561152e5790899161151a575b505060405191602083019280841067ffffffffffffffff8511176114ef57899a8460409b98999a9b52878252897f0000000000000000000000000000000000000000000000000000000000000000163b156114eb5760409a989695949a99979951957feb6724190000000000000000000000000000000000000000000000000000000087528888169087015288358987015260e060448701528960e48701526101048601926064870152608486015261010060a48601525180915261012490818501918160051b860101999189905b82821061144d575050505082809281808b8b979560c4899701520391887f0000000000000000000000000000000000000000000000000000000000000000165af1801561038757611439575b50506040519235835216907fa3e601130860a6f97b42655ad74f631ddf0c8e5adaa98402fded9c09bc35a44060203392a380f35b61144290611656565b610392578385611405565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc878d969596030181528b855180518092528c5b8281106114d5575050808d0160209081018d9052601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909c018c019b939484019360019290920191016113b9565b808f602082818095870101519201015201611481565b8780fd5b886041877f4e487b71000000000000000000000000000000000000000000000000000000005f52525ffd5b61152390611656565b6114eb57878a6112ea565b6040513d8b823e3d90fd5b8880fd5b611548919950611656565b5f978a61129f565b6040513d5f823e3d90fd5b5f80fd5b5060203d6020116115c0575b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f820116820182811067ffffffffffffffff8211176114ef5760209183916040528101031261155b5761121f9051611212565b503d61156b565b3461155b575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261155b5760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361155b57565b67ffffffffffffffff811161166a57604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b919082018092116116a457565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffdfea26469706673582212205888bf681cf0d9036082fce3d2d0a7cbdfb2d39c83fdb5d03d2fff0b90c6af1064736f6c63430008170033" + "numDeployments": 9, + "solcInputHash": "0b9a38081e5f67aae8aeed6c044fb8c5", + "metadata": "{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"LineaEthDepositInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"OvmEthDepositInitiated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"ZkSyncEthDepositInitiated\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"baseL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blastL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bobaL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToLinea\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint32\",\"name\":\"l2Gas\",\"type\":\"uint32\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToOvm\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"bridgeWethToPolygon\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"l2GasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"l2GasPerPubdataByteLimit\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"refundRecipient\",\"type\":\"address\"}],\"name\":\"bridgeWethToZkSync\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lineaL1MessageService\",\"outputs\":[{\"internalType\":\"contract LineaL1MessageService\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liskL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"modeL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"polygonL1Bridge\",\"outputs\":[{\"internalType\":\"contract PolygonL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"redstoneL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"weth\",\"outputs\":[{\"internalType\":\"contract Weth\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"worldChainL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"zkSyncL1Bridge\",\"outputs\":[{\"internalType\":\"contract ZkSyncL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"zoraL1Bridge\",\"outputs\":[{\"internalType\":\"contract OvmL1Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH, not WETH.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/AtomicWethDepositor.sol\":\"AtomicWethDepositor\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":1000000},\"remappings\":[],\"viaIR\":true},\"sources\":{\"contracts/AtomicWethDepositor.sol\":{\"keccak256\":\"0x279a415c1fcf0365315770894547b175fb652c77f12627fa86a5ba290a91dbd7\",\"license\":\"GPL-3.0-only\",\"urls\":[\"bzz-raw://05a980264232fb71de48253205b2d6c647c86b73fe677208f7597c97ad5ee7e9\",\"dweb:/ipfs/QmaCzsBFWC9unHJhRWhK35V1BaJfMUfT6iE4HboMxQMoSV\"]}},\"version\":1}", + "bytecode": "0x61022080604052346102475773c02aaa39b223fe8d0a0e5c4f27ead9083c756cc26080527399c9fc46f92e8a1c0dec1b1747d010903e884be160a05273735adbbe72226bd52e818e7181953f42e3b0ff2160c05273dc1664458d2f0b6090bea60a8793a4e66c2f1c0060e05261010090733154cf16ccdb4c6d922629664174b904d80f2c358252610120732658723bf70c7667de6b25f99fcce13a16d25d08815261014073c473ca7e02af24c129c2eef51f2adf0411c1df69815261016073697402166fbf2f22e970df8a6486ef171dbfc524815261018073470458c91978d2d929704489ad730dc3e300111381526101a090733e2ea9b92b7e48a52296fd261dc26fd99528463182526101c09273a0c68c638235ee32657e8f720a23cec1bfc77c7784526101e0947332400084c286cf3e17e7b677ea9583e60a00032486526102009673d19d4b5d358258f05d7b411e21a1460d11b0876f8852611825998a61024c8b396080518a81816101c601528181610c1801528181610fb1015281816110270152611341015260a0518a818161029f01526109e2015260c0518a818161047b0152610df5015260e0518a818161085b015261172d0152518981816103ad0152610b2f01525188818161013301526105c301525187818161070c0152610b9e0152518681816106680152610e6401525185818161051f01526112380152518481816107b10152610f42015251838181610a510152610ce9015251828181610ed3015281816113010152818161143701526114f6015251818181610ac001526110f80152f35b5f80fdfe6080806040526004908136101561001d575b5050361561001b57005b005b5f915f3560e01c918263019f8e81146116e557508163128d5f681461125c5781632a8ecb13146111ed57816336918a9714610fd55781633fc8cef314610f665781634fbf95d714610ef75781635970eafa14610e8857816362c8eb5c14610e19578163645b6f1114610daa57828263b3d5ccc314610bc257508163b6865d6e14610b53578163b745c3f314610ae4578163c04b953414610a75578163c80dcc3814610a06578163d3cdc8f914610997578163e88650c41461015a575063f43873c4146100e95780610011565b3461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b80fd5b9050346109935760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261099357610193611751565b604435916024359163ffffffff8416840361038e57846064359473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016803b1561039d57604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523384820190815230602082015291820189905290859082908190606001038183865af1908115610988578591610974575b5050803b1561039d578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528c888401525af1908115610969578491610951575b5050600a87036103a157847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576103379285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103925761037a575b50505b60405192835216917fcde53d24289bf7d0b2baeea6140c533d8388fb574b055364d718f637bedea7a460203393a480f35b61038390611774565b61038e57845f610346565b8480fd5b6040513d84823e3d90fd5b8380fd5b612105870361046f57847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103925761045b575b5050610349565b61046490611774565b61038e57845f610454565b61868b870361051357847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6101e087036105b757847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b61046f870361065b57847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b62013e31870361070057847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6102b287036107a457847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6276adf1870361084957847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b90915061012086036108f357908691847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6064906020604051917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601360248201527f496e76616c6964204f564d20636861696e4964000000000000000000000000006044820152fd5b61095a90611774565b61096557825f610292565b8280fd5b6040513d86823e3d90fd5b61097d90611774565b61039d57835f610245565b6040513d87823e3d90fd5b5080fd5b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b915034610da75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610da757610bfb611751565b6024359073ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b15610d6557604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523387820190815230602082015291820186905290879082908190606001038183865af1908115610d9c578791610d88575b5050803b15610d65578580916024604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1908115610d7d578691610d69575b5050817f00000000000000000000000000000000000000000000000000000000000000001690813b15610d6557859360249260405196879586947f4faa8a2600000000000000000000000000000000000000000000000000000000865216908401525af1801561039257610d555750f35b610d5e90611774565b6101575780f35b8580fd5b610d7290611774565b61038e57845f610ce4565b6040513d88823e3d90fd5b610d9190611774565b610d6557855f610c97565b6040513d89823e3d90fd5b50fd5b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b905060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261099357611009611751565b826024359173ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b1561039d57604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523388820190815230602082015291820187905290859082908190606001038183865af19081156109885785916111d9575b5050803b1561039d578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528a8c8401525af19081156109695784916111c5575b5050817f0000000000000000000000000000000000000000000000000000000000000000169161112334866117b5565b833b1561038e57849260849160405195869485937f9f3ce55a00000000000000000000000000000000000000000000000000000000855216809a840152346024840152606060448401528560648401525af18015610392576111b1575b50506040519081527f61ed67a945fe5f4d777919629ad666c7e81d66dc5fbaf4c143edd000c15d67dd60203392a380f35b6111ba90611774565b61096557825f611180565b6111ce90611774565b61096557825f6110f3565b6111e290611774565b61039d57835f6110a6565b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b82346116795760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261167957611294611751565b602490604435906064359460843573ffffffffffffffffffffffffffffffffffffffff93848216809203611679576040517fb473318e0000000000000000000000000000000000000000000000000000000081523a848201528187820152886044820152602081606481897f0000000000000000000000000000000000000000000000000000000000000000165afa801561166e575f9061167d575b61133d91508735906117b5565b91857f000000000000000000000000000000000000000000000000000000000000000016803b1561167957604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233878201908152306020820152918201869052905f9082908190606001038183865af1801561166e5761165b575b50803b156116575788809189604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1801561164c57908991611638575b505060405191602083019280841067ffffffffffffffff85111761160d57899a8460409b98999a9b52878252897f0000000000000000000000000000000000000000000000000000000000000000163b156116095760409a989695949a99979951957feb6724190000000000000000000000000000000000000000000000000000000087528888169087015288358987015260e060448701528960e48701526101048601926064870152608486015261010060a48601525180915261012490818501918160051b860101999189905b82821061156b575050505082809281808b8b979560c4899701520391887f0000000000000000000000000000000000000000000000000000000000000000165af1801561039257611557575b50506040519235835216907fa3e601130860a6f97b42655ad74f631ddf0c8e5adaa98402fded9c09bc35a44060203392a380f35b61156090611774565b61039d578385611523565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc878d969596030181528b855180518092528c5b8281106115f3575050808d0160209081018d9052601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909c018c019b939484019360019290920191016114d7565b808f60208281809587010151920101520161159f565b8780fd5b886041877f4e487b71000000000000000000000000000000000000000000000000000000005f52525ffd5b61164190611774565b61160957878a611408565b6040513d8b823e3d90fd5b8880fd5b611666919950611774565b5f978a6113bd565b6040513d5f823e3d90fd5b5f80fd5b5060203d6020116116de575b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f820116820182811067ffffffffffffffff82111761160d576020918391604052810103126116795761133d9051611330565b503d611689565b34611679575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126116795760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361167957565b67ffffffffffffffff811161178857604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b919082018092116117c257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffdfea26469706673582212205607a85a9e0ca52451b4663fa28c093065e7dee23fa38dcc76041544e413a52e64736f6c63430008170033", + "deployedBytecode": "0x6080806040526004908136101561001d575b5050361561001b57005b005b5f915f3560e01c918263019f8e81146116e557508163128d5f681461125c5781632a8ecb13146111ed57816336918a9714610fd55781633fc8cef314610f665781634fbf95d714610ef75781635970eafa14610e8857816362c8eb5c14610e19578163645b6f1114610daa57828263b3d5ccc314610bc257508163b6865d6e14610b53578163b745c3f314610ae4578163c04b953414610a75578163c80dcc3814610a06578163d3cdc8f914610997578163e88650c41461015a575063f43873c4146100e95780610011565b3461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b80fd5b9050346109935760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261099357610193611751565b604435916024359163ffffffff8416840361038e57846064359473ffffffffffffffffffffffffffffffffffffffff93847f000000000000000000000000000000000000000000000000000000000000000016803b1561039d57604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523384820190815230602082015291820189905290859082908190606001038183865af1908115610988578591610974575b5050803b1561039d578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528c888401525af1908115610969578491610951575b5050600a87036103a157847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576103379285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103925761037a575b50505b60405192835216917fcde53d24289bf7d0b2baeea6140c533d8388fb574b055364d718f637bedea7a460203393a480f35b61038390611774565b61038e57845f610346565b8480fd5b6040513d84823e3d90fd5b8380fd5b612105870361046f57847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b03925af180156103925761045b575b5050610349565b61046490611774565b61038e57845f610454565b61868b870361051357847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6101e087036105b757847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b61046f870361065b57847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b62013e31870361070057847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6102b287036107a457847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6276adf1870361084957847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b90915061012086036108f357908691847f00000000000000000000000000000000000000000000000000000000000000001691823b1561039d576104459285888694604051968795869485937f9a2ac6d500000000000000000000000000000000000000000000000000000000855284019060809273ffffffffffffffffffffffffffffffffffffffff63ffffffff92168352166020820152606060408201525f60608201520190565b6064906020604051917f08c379a0000000000000000000000000000000000000000000000000000000008352820152601360248201527f496e76616c6964204f564d20636861696e4964000000000000000000000000006044820152fd5b61095a90611774565b61096557825f610292565b8280fd5b6040513d86823e3d90fd5b61097d90611774565b61039d57835f610245565b6040513d87823e3d90fd5b5080fd5b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b915034610da75760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610da757610bfb611751565b6024359073ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b15610d6557604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523387820190815230602082015291820186905290879082908190606001038183865af1908115610d9c578791610d88575b5050803b15610d65578580916024604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1908115610d7d578691610d69575b5050817f00000000000000000000000000000000000000000000000000000000000000001690813b15610d6557859360249260405196879586947f4faa8a2600000000000000000000000000000000000000000000000000000000865216908401525af1801561039257610d555750f35b610d5e90611774565b6101575780f35b8580fd5b610d7290611774565b61038e57845f610ce4565b6040513d88823e3d90fd5b610d9190611774565b610d6557855f610c97565b6040513d89823e3d90fd5b50fd5b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b905060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261099357611009611751565b826024359173ffffffffffffffffffffffffffffffffffffffff90817f000000000000000000000000000000000000000000000000000000000000000016803b1561039d57604080517f23b872dd0000000000000000000000000000000000000000000000000000000081523388820190815230602082015291820187905290859082908190606001038183865af19081156109885785916111d9575b5050803b1561039d578380916024604051809481937f2e1a7d4d0000000000000000000000000000000000000000000000000000000083528a8c8401525af19081156109695784916111c5575b5050817f0000000000000000000000000000000000000000000000000000000000000000169161112334866117b5565b833b1561038e57849260849160405195869485937f9f3ce55a00000000000000000000000000000000000000000000000000000000855216809a840152346024840152606060448401528560648401525af18015610392576111b1575b50506040519081527f61ed67a945fe5f4d777919629ad666c7e81d66dc5fbaf4c143edd000c15d67dd60203392a380f35b6111ba90611774565b61096557825f611180565b6111ce90611774565b61096557825f6110f3565b6111e290611774565b61039d57835f6110a6565b823461015757807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261015757602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b82346116795760a07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261167957611294611751565b602490604435906064359460843573ffffffffffffffffffffffffffffffffffffffff93848216809203611679576040517fb473318e0000000000000000000000000000000000000000000000000000000081523a848201528187820152886044820152602081606481897f0000000000000000000000000000000000000000000000000000000000000000165afa801561166e575f9061167d575b61133d91508735906117b5565b91857f000000000000000000000000000000000000000000000000000000000000000016803b1561167957604080517f23b872dd00000000000000000000000000000000000000000000000000000000815233878201908152306020820152918201869052905f9082908190606001038183865af1801561166e5761165b575b50803b156116575788809189604051809481937f2e1a7d4d000000000000000000000000000000000000000000000000000000008352898b8401525af1801561164c57908991611638575b505060405191602083019280841067ffffffffffffffff85111761160d57899a8460409b98999a9b52878252897f0000000000000000000000000000000000000000000000000000000000000000163b156116095760409a989695949a99979951957feb6724190000000000000000000000000000000000000000000000000000000087528888169087015288358987015260e060448701528960e48701526101048601926064870152608486015261010060a48601525180915261012490818501918160051b860101999189905b82821061156b575050505082809281808b8b979560c4899701520391887f0000000000000000000000000000000000000000000000000000000000000000165af1801561039257611557575b50506040519235835216907fa3e601130860a6f97b42655ad74f631ddf0c8e5adaa98402fded9c09bc35a44060203392a380f35b61156090611774565b61039d578385611523565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc878d969596030181528b855180518092528c5b8281106115f3575050808d0160209081018d9052601f919091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016909c018c019b939484019360019290920191016114d7565b808f60208281809587010151920101520161159f565b8780fd5b886041877f4e487b71000000000000000000000000000000000000000000000000000000005f52525ffd5b61164190611774565b61160957878a611408565b6040513d8b823e3d90fd5b8880fd5b611666919950611774565b5f978a6113bd565b6040513d5f823e3d90fd5b5f80fd5b5060203d6020116116de575b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f820116820182811067ffffffffffffffff82111761160d576020918391604052810103126116795761133d9051611330565b503d611689565b34611679575f7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126116795760209073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361167957565b67ffffffffffffffff811161178857604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b919082018092116117c257565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffdfea26469706673582212205607a85a9e0ca52451b4663fa28c093065e7dee23fa38dcc76041544e413a52e64736f6c63430008170033" } diff --git a/deployments/mainnet/solcInputs/0b9a38081e5f67aae8aeed6c044fb8c5.json b/deployments/mainnet/solcInputs/0b9a38081e5f67aae8aeed6c044fb8c5.json new file mode 100644 index 000000000..b44f5bbd5 --- /dev/null +++ b/deployments/mainnet/solcInputs/0b9a38081e5f67aae8aeed6c044fb8c5.json @@ -0,0 +1,21 @@ +{ + "language": "Solidity", + "sources": { + "contracts/AtomicWethDepositor.sol": { + "content": "// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.8.0;\n\ninterface Weth {\n function withdraw(uint256 _wad) external;\n\n function transferFrom(address _from, address _to, uint256 _wad) external;\n}\n\ninterface OvmL1Bridge {\n function depositETHTo(address _to, uint32 _l2Gas, bytes calldata _data) external payable;\n}\n\ninterface PolygonL1Bridge {\n function depositEtherFor(address _to) external payable;\n}\n\ninterface ZkSyncL1Bridge {\n function requestL2Transaction(\n address _contractL2,\n uint256 _l2Value,\n bytes calldata _calldata,\n uint256 _l2GasLimit,\n uint256 _l2GasPerPubdataByteLimit,\n bytes[] calldata _factoryDeps,\n address _refundRecipient\n ) external payable;\n\n function l2TransactionBaseCost(\n uint256 _gasPrice,\n uint256 _l2GasLimit,\n uint256 _l2GasPerPubdataByteLimit\n ) external pure returns (uint256);\n}\n\ninterface LineaL1MessageService {\n function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;\n}\n\n/**\n * @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain\n * bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH,\n * not WETH.\n */\n\ncontract AtomicWethDepositor {\n Weth public immutable weth = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);\n OvmL1Bridge public immutable optimismL1Bridge = OvmL1Bridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1);\n OvmL1Bridge public immutable modeL1Bridge = OvmL1Bridge(0x735aDBbE72226BD52e818E7181953f42E3b0FF21);\n OvmL1Bridge public immutable bobaL1Bridge = OvmL1Bridge(0xdc1664458d2f0B6090bEa60A8793A4E66c2F1c00);\n OvmL1Bridge public immutable baseL1Bridge = OvmL1Bridge(0x3154Cf16ccdb4C6d922629664174b904d80F2C35);\n OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08);\n OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69);\n OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524);\n OvmL1Bridge public immutable worldChainL1Bridge = OvmL1Bridge(0x470458C91978D2d929704489Ad730DC3E3001113);\n OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631);\n PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77);\n ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324);\n LineaL1MessageService public immutable lineaL1MessageService =\n LineaL1MessageService(0xd19d4B5d358258f05D7B411E21A1460D11B0876F);\n\n event ZkSyncEthDepositInitiated(address indexed from, address indexed to, uint256 amount);\n event LineaEthDepositInitiated(address indexed from, address indexed to, uint256 amount);\n event OvmEthDepositInitiated(uint256 indexed chainId, address indexed from, address indexed to, uint256 amount);\n\n function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n\n if (chainId == 10) {\n optimismL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 8453) {\n baseL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 34443) {\n modeL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 480) {\n worldChainL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 1135) {\n liskL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 81457) {\n blastL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 690) {\n redstoneL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 7777777) {\n zoraL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 288) {\n bobaL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else {\n revert(\"Invalid OVM chainId\");\n }\n\n emit OvmEthDepositInitiated(chainId, msg.sender, to, amount);\n }\n\n function bridgeWethToPolygon(address to, uint256 amount) public {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n polygonL1Bridge.depositEtherFor{ value: amount }(to);\n }\n\n function bridgeWethToLinea(address to, uint256 amount) public payable {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n lineaL1MessageService.sendMessage{ value: amount + msg.value }(to, msg.value, \"\");\n // Emit an event that we can easily track in the Linea-related adapters/finalizers\n emit LineaEthDepositInitiated(msg.sender, to, amount);\n }\n\n function bridgeWethToZkSync(\n address to,\n uint256 amount,\n uint256 l2GasLimit,\n uint256 l2GasPerPubdataByteLimit,\n address refundRecipient\n ) public {\n // The ZkSync Mailbox contract checks that the msg.value of the transaction is enough to cover the transaction base\n // cost. The transaction base cost can be queried from the Mailbox by passing in an L1 \"executed\" gas price,\n // which is the priority fee plus base fee. This is the same as calling tx.gasprice on-chain as the Mailbox\n // contract does here:\n // https://github.com/matter-labs/era-contracts/blob/3a4506522aaef81485d8abb96f5a6394bd2ba69e/ethereum/contracts/zksync/facets/Mailbox.sol#L287\n uint256 l2TransactionBaseCost = zkSyncL1Bridge.l2TransactionBaseCost(\n tx.gasprice,\n l2GasLimit,\n l2GasPerPubdataByteLimit\n );\n uint256 valueToSubmitXChainMessage = l2TransactionBaseCost + amount;\n weth.transferFrom(msg.sender, address(this), valueToSubmitXChainMessage);\n weth.withdraw(valueToSubmitXChainMessage);\n zkSyncL1Bridge.requestL2Transaction{ value: valueToSubmitXChainMessage }(\n to,\n amount,\n \"\",\n l2GasLimit,\n l2GasPerPubdataByteLimit,\n new bytes[](0),\n refundRecipient\n );\n\n // Emit an event that we can easily track in the ZkSyncAdapter because otherwise there is no easy event to\n // track ETH deposit initiations.\n emit ZkSyncEthDepositInitiated(msg.sender, to, amount);\n }\n\n fallback() external payable {}\n\n // Included to remove a compilation warning.\n // NOTE: this should not affect behavior.\n receive() external payable {}\n}\n" + } + }, + "settings": { + "optimizer": { + "enabled": true, + "runs": 1000000 + }, + "viaIR": true, + "outputSelection": { + "*": { + "*": ["abi", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "metadata"], + "": ["ast"] + } + } + } +} diff --git a/package.json b/package.json index b08a3bee0..3861c1f95 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "node": ">=20" }, "dependencies": { - "@across-protocol/constants": "^3.1.14", - "@across-protocol/contracts": "^3.0.10", - "@across-protocol/sdk": "^3.2.4", + "@across-protocol/constants": "^3.1.16", + "@across-protocol/contracts": "^3.0.11", + "@across-protocol/sdk": "^3.2.6", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index 70a6be144..ec5907e52 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -590,6 +590,7 @@ export class ProfitClient { [CHAIN_IDs.BLAST]: "USDB", [CHAIN_IDs.LISK]: "USDT", // USDC is not yet supported on Lisk, so revert to USDT. @todo: Update. [CHAIN_IDs.REDSTONE]: "WETH", // Redstone only supports WETH. + [CHAIN_IDs.WORLD_CHAIN]: "WETH", // USDC deferred on World Chain. }; const prodRelayer = process.env.RELAYER_FILL_SIMULATION_ADDRESS ?? PROD_RELAYER; const [defaultTestSymbol, relayer] = diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index d23ec0bf8..e232d5089 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -146,6 +146,18 @@ export class AdapterManager { if (this.spokePoolClients[SCROLL] !== undefined) { this.adapters[SCROLL] = new ScrollAdapter(logger, spokePoolClients, filterMonitoredAddresses(SCROLL)); } + if (this.spokePoolClients[CHAIN_IDs.WORLD_CHAIN] !== undefined) { + this.adapters[CHAIN_IDs.WORLD_CHAIN] = new BaseChainAdapter( + spokePoolClients, + CHAIN_IDs.WORLD_CHAIN, + hubChainId, + filterMonitoredAddresses(CHAIN_IDs.WORLD_CHAIN), + logger, + SUPPORTED_TOKENS[CHAIN_IDs.WORLD_CHAIN], + constructBridges(CHAIN_IDs.WORLD_CHAIN), + DEFAULT_GAS_MULTIPLIER[CHAIN_IDs.WORLD_CHAIN] ?? 1 + ); + } if (this.spokePoolClients[ZORA] !== undefined) { this.adapters[ZORA] = new BaseChainAdapter( spokePoolClients, diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 2da9646e6..ae817a340 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -51,6 +51,7 @@ export const DATAWORKER_FAST_LOOKBACK: { [chainId: number]: number } = { [CHAIN_IDs.POLYGON]: 138240, [CHAIN_IDs.REDSTONE]: 172800, // OP stack [CHAIN_IDs.SCROLL]: 115200, // 4 * 24 * 20 * 60, + [CHAIN_IDs.WORLD_CHAIN]: 172800, // OP stack [CHAIN_IDs.ZK_SYNC]: 345600, // 4 * 24 * 60 * 60, [CHAIN_IDs.ZORA]: 172800, // OP stack }; @@ -88,6 +89,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain [CHAIN_IDs.POLYGON]: 128, // Commonly used finality level for CEX's that accept Polygon deposits [CHAIN_IDs.REDSTONE]: 120, [CHAIN_IDs.SCROLL]: 30, + [CHAIN_IDs.WORLD_CHAIN]: 120, [CHAIN_IDs.ZK_SYNC]: 120, [CHAIN_IDs.ZORA]: 120, }, @@ -103,6 +105,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain [CHAIN_IDs.POLYGON]: 100, // Probabilistically safe level based on historic Polygon reorgs [CHAIN_IDs.REDSTONE]: 60, [CHAIN_IDs.SCROLL]: 1, + [CHAIN_IDs.WORLD_CHAIN]: 60, [CHAIN_IDs.ZK_SYNC]: 0, [CHAIN_IDs.ZORA]: 60, }, @@ -118,6 +121,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain [CHAIN_IDs.POLYGON]: 80, [CHAIN_IDs.REDSTONE]: 60, [CHAIN_IDs.SCROLL]: 1, + [CHAIN_IDs.WORLD_CHAIN]: 60, [CHAIN_IDs.ZK_SYNC]: 0, [CHAIN_IDs.ZORA]: 60, }, @@ -141,8 +145,9 @@ export const CHAIN_MAX_BLOCK_LOOKBACK = { [CHAIN_IDs.MODE]: 10000, [CHAIN_IDs.OPTIMISM]: 10000, // Quick [CHAIN_IDs.POLYGON]: 10000, - [CHAIN_IDs.REDSTONE]: 10000, // xxx verify + [CHAIN_IDs.REDSTONE]: 10000, [CHAIN_IDs.SCROLL]: 10000, + [CHAIN_IDs.WORLD_CHAIN]: 10000, [CHAIN_IDs.ZK_SYNC]: 10000, [CHAIN_IDs.ZORA]: 10000, // Testnets: @@ -173,6 +178,7 @@ export const BUNDLE_END_BLOCK_BUFFERS = { [CHAIN_IDs.POLYGON]: 128, // 2s/block. Polygon reorgs often so this number is set larger than the largest observed reorg. [CHAIN_IDs.REDSTONE]: 60, // 2s/block [CHAIN_IDs.SCROLL]: 40, // ~3s/block + [CHAIN_IDs.WORLD_CHAIN]: 60, // 2s/block [CHAIN_IDs.ZK_SYNC]: 120, // ~1s/block. ZkSync is a centralized sequencer but is relatively unstable so this is kept higher than 0 [CHAIN_IDs.ZORA]: 60, // 2s/block // Testnets: @@ -217,6 +223,7 @@ export const CHAIN_CACHE_FOLLOW_DISTANCE: { [chainId: number]: number } = { [CHAIN_IDs.POLYGON]: 256, [CHAIN_IDs.REDSTONE]: 120, [CHAIN_IDs.SCROLL]: 100, + [CHAIN_IDs.WORLD_CHAIN]: 120, [CHAIN_IDs.ZK_SYNC]: 512, [CHAIN_IDs.ZORA]: 120, // Testnets: @@ -246,6 +253,7 @@ export const DEFAULT_NO_TTL_DISTANCE: { [chainId: number]: number } = { [CHAIN_IDs.POLYGON]: 86400, [CHAIN_IDs.REDSTONE]: 86400, [CHAIN_IDs.SCROLL]: 57600, + [CHAIN_IDs.WORLD_CHAIN]: 86400, [CHAIN_IDs.ZK_SYNC]: 172800, [CHAIN_IDs.ZORA]: 86400, }; @@ -261,6 +269,7 @@ export const DEFAULT_GAS_FEE_SCALERS: { [CHAIN_IDs.MODE]: { maxFeePerGasScaler: 2, maxPriorityFeePerGasScaler: 0.01 }, [CHAIN_IDs.OPTIMISM]: { maxFeePerGasScaler: 2, maxPriorityFeePerGasScaler: 0.01 }, [CHAIN_IDs.REDSTONE]: { maxFeePerGasScaler: 2, maxPriorityFeePerGasScaler: 0.01 }, + [CHAIN_IDs.WORLD_CHAIN]: { maxFeePerGasScaler: 2, maxPriorityFeePerGasScaler: 0.01 }, [CHAIN_IDs.ZORA]: { maxFeePerGasScaler: 2, maxPriorityFeePerGasScaler: 0.01 }, }; @@ -284,6 +293,7 @@ export const multicall3Addresses = { [CHAIN_IDs.OPTIMISM]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.POLYGON]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.SCROLL]: "0xcA11bde05977b3631167028862bE2a173976CA11", + [CHAIN_IDs.WORLD_CHAIN]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.ZK_SYNC]: "0xF9cda624FBC7e059355ce98a31693d299FACd963", [CHAIN_IDs.ZORA]: "0xcA11bde05977b3631167028862bE2a173976CA11", // Testnet: @@ -311,6 +321,7 @@ export const spokesThatHoldEthAndWeth = [ CHAIN_IDs.OPTIMISM, CHAIN_IDs.REDSTONE, CHAIN_IDs.SCROLL, + CHAIN_IDs.WORLD_CHAIN, CHAIN_IDs.ZK_SYNC, CHAIN_IDs.ZORA, ]; @@ -346,6 +357,7 @@ export const SUPPORTED_TOKENS: { [chainId: number]: string[] } = { [CHAIN_IDs.POLYGON]: ["USDC", "USDT", "WETH", "DAI", "WBTC", "UMA", "BAL", "ACX", "POOL"], [CHAIN_IDs.REDSTONE]: ["WETH"], [CHAIN_IDs.SCROLL]: ["WETH", "USDC", "USDT", "WBTC"], + [CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC"], // xxx @todo add USDC after new bridge adapter is supported [CHAIN_IDs.ZK_SYNC]: ["USDC", "USDT", "WETH", "WBTC", "DAI"], [CHAIN_IDs.ZORA]: ["USDC", "WETH"], @@ -398,6 +410,7 @@ export const CANONICAL_BRIDGE: { [CHAIN_IDs.POLYGON]: PolygonERC20Bridge, [CHAIN_IDs.REDSTONE]: OpStackDefaultERC20Bridge, [CHAIN_IDs.SCROLL]: ScrollERC20Bridge, + [CHAIN_IDs.WORLD_CHAIN]: OpStackDefaultERC20Bridge, [CHAIN_IDs.ZK_SYNC]: ZKSyncBridge, [CHAIN_IDs.ZORA]: OpStackDefaultERC20Bridge, }; @@ -444,13 +457,16 @@ export const CUSTOM_BRIDGE: { [TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: UsdcTokenSplitterBridge, [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, }, - [CHAIN_IDs.REDSTONE]: { - [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, - }, [CHAIN_IDs.POLYGON]: { [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: PolygonWethBridge, [TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: UsdcTokenSplitterBridge, }, + [CHAIN_IDs.REDSTONE]: { + [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, + }, + [CHAIN_IDs.WORLD_CHAIN]: { + [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, + }, [CHAIN_IDs.ZK_SYNC]: { [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: ZKSyncWethBridge, }, @@ -515,6 +531,7 @@ export const EXPECTED_L1_TO_L2_MESSAGE_TIME = { [CHAIN_IDs.POLYGON]: 60 * 60, [CHAIN_IDs.REDSTONE]: 20 * 60, [CHAIN_IDs.SCROLL]: 60 * 60, + [CHAIN_IDs.WORLD_CHAIN]: 20 * 60, [CHAIN_IDs.ZK_SYNC]: 60 * 60, [CHAIN_IDs.ZORA]: 20 * 60, }; @@ -569,6 +586,21 @@ export const OPSTACK_CONTRACT_OVERRIDES = { }, l2: DEFAULT_L2_CONTRACT_ADDRESSES, }, + [CHAIN_IDs.WORLD_CHAIN]: { + l1: { + AddressManager: "0x5891090d5085679714cb0e62f74950a3c19146a8", + L1CrossDomainMessenger: "0xf931a81D18B1766d15695ffc7c1920a62b7e710a", + L1StandardBridge: CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET].ovmStandardBridge_480.address, + StateCommitmentChain: ZERO_ADDRESS, + CanonicalTransactionChain: ZERO_ADDRESS, + BondManager: ZERO_ADDRESS, + OptimismPortal: "0xd5ec14a83B7d95BE1E2Ac12523e2dEE12Cbeea6C", + L2OutputOracle: "0x19A6d1E9034596196295CF148509796978343c5D", + OptimismPortal2: ZERO_ADDRESS, + DisputeGameFactory: ZERO_ADDRESS, + }, + l2: DEFAULT_L2_CONTRACT_ADDRESSES, + }, }; export const DEFAULT_GAS_MULTIPLIER: { [chainId: number]: number } = { @@ -577,6 +609,7 @@ export const DEFAULT_GAS_MULTIPLIER: { [chainId: number]: number } = { [CHAIN_IDs.LISK]: 1.5, [CHAIN_IDs.MODE]: 1.5, [CHAIN_IDs.REDSTONE]: 1.5, + [CHAIN_IDs.WORLD_CHAIN]: 1.5, [CHAIN_IDs.ZORA]: 1.5, }; diff --git a/src/common/ContractAddresses.ts b/src/common/ContractAddresses.ts index 59a5defb0..a82e1e582 100644 --- a/src/common/ContractAddresses.ts +++ b/src/common/ContractAddresses.ts @@ -73,7 +73,7 @@ export const CONTRACT_ADDRESSES: { // OVM, ZkSync, Linea, and Polygon can't deposit WETH directly so we use an atomic depositor contract that unwraps WETH and // bridges ETH other the canonical bridge. atomicDepositor: { - address: "0xE48278aD2b9b402A8E3C2E0ffbaD7EEe3905bf94", + address: "0xa679201903847f3723Dc88CA7530c8B665bC51a5", abi: ATOMIC_DEPOSITOR_ABI, }, // Since there are multiple ovmStandardBridges on mainnet for different OP Stack chains, we append the chain id of the Op @@ -82,6 +82,10 @@ export const CONTRACT_ADDRESSES: { address: "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", abi: OVM_L1_STANDARD_BRIDGE_ABI, }, + ovmStandardBridge_480: { + address: "0x470458C91978D2d929704489Ad730DC3E3001113", + abi: OVM_L1_STANDARD_BRIDGE_ABI, + }, ovmStandardBridge_690: { address: "0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69", abi: OVM_L1_STANDARD_BRIDGE_ABI, @@ -225,6 +229,15 @@ export const CONTRACT_ADDRESSES: { abi: WETH_ABI, }, }, + 480: { + ovmStandardBridge: { + address: "0x4200000000000000000000000000000000000010", + abi: OVM_L2_STANDARD_BRIDGE_ABI, + }, + eth: { + address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", + }, + }, 690: { ovmStandardBridge: { address: "0x4200000000000000000000000000000000000010", diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 5ce2aadea..c2c29b43b 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -19,6 +19,7 @@ import { winston, fixedPointAdjustment, TransactionResponse, + ZERO_ADDRESS, } from "../utils"; import { RelayerClients } from "./RelayerClientHelper"; import { RelayerConfig } from "./RelayerConfig"; @@ -283,7 +284,11 @@ export class Relayer { return false; } - if (deposit.exclusivityDeadline > currentTime && getAddress(deposit.exclusiveRelayer) !== this.relayerAddress) { + if ( + deposit.exclusiveRelayer !== ZERO_ADDRESS && + deposit.exclusivityDeadline > currentTime && + getAddress(deposit.exclusiveRelayer) !== this.relayerAddress + ) { return false; } diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index d299216e8..8ae55d4f0 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -321,6 +321,8 @@ describe("BundleDataClient: Slow fill handling & validation", async function () const destinationChainDeposit = spokePoolClient_2.getDeposits()[0]; // Generate slow fill requests for the slow fill-eligible deposits + await spokePool_1.setCurrentTime(depositsWithSlowFillRequests[1].exclusivityDeadline + 1); // Temporary workaround + await spokePool_2.setCurrentTime(depositsWithSlowFillRequests[0].exclusivityDeadline + 1); // Temporary workaround await requestSlowFill(spokePool_2, relayer, depositsWithSlowFillRequests[0]); await requestSlowFill(spokePool_1, relayer, depositsWithSlowFillRequests[1]); const lastDestinationChainSlowFillRequestBlock = await spokePool_2.provider.getBlockNumber(); diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 731796c46..19cb1b314 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -420,8 +420,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { }); it("Ignores exclusive deposits", async function () { - const currentTime = (await spokePool_2.getCurrentTime()).toNumber(); - const exclusivityDeadline = currentTime + 7200; + const exclusivityDeadline = 7200; const deposits: Deposit[] = []; const { fillStatus, relayerAddress } = relayerInstance; @@ -456,7 +455,9 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { expect((await txnReceipts[destinationChainId]).length).to.equal(0); expect(lastSpyLogIncludes(spy, "0 unfilled deposits found")).to.be.true; - await spokePool_2.setCurrentTime(exclusivityDeadline + 1); + const exclusiveDeposit = deposits.find(({ exclusiveRelayer }) => exclusiveRelayer !== relayerAddress); + expect(exclusiveDeposit).to.exist; + await spokePool_2.setCurrentTime(exclusiveDeposit!.exclusivityDeadline + 1); await updateAllClients(); // Relayer can unconditionally fill after the exclusivityDeadline. @@ -952,6 +953,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { spy.getCalls().find(({ lastArg }) => lastArg.message.includes("Skipping fill for deposit with message")) ).to.not.be.undefined; } else { + await spokePool_2.setCurrentTime(deposit.exclusivityDeadline + 1); // Temporary workaround. // Now speed up deposit again with a higher fee and a message of 0x. This should be filled. expect((await txnReceipts[destinationChainId]).length).to.equal(1); expect(lastSpyLogIncludes(spy, "Filled v3 deposit")).to.be.true; @@ -1025,6 +1027,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { depositor ); + await spokePool_2.setCurrentTime(deposit.exclusivityDeadline + 1); // Temporary workaround. await updateAllClients(); txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); expect((await txnReceipts[destinationChainId]).length).to.equal(1); diff --git a/test/Relayer.SlowFill.ts b/test/Relayer.SlowFill.ts index 008559a1b..15e4d305a 100644 --- a/test/Relayer.SlowFill.ts +++ b/test/Relayer.SlowFill.ts @@ -209,6 +209,7 @@ describe("Relayer: Initiates slow fill requests", async function () { ); expect(deposit).to.exist; + await spokePool_2.setCurrentTime(deposit.exclusivityDeadline + 1); // Temporary workaround await updateAllClients(); const _txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); const txnHashes = await _txnReceipts[destinationChainId]; diff --git a/yarn.lock b/yarn.lock index dfd213345..df0f1b08a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,15 +11,10 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants@^3.1.13": - version "3.1.13" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.13.tgz#b4caf494e9d9fe50290cca91b7883ea408fdb90a" - integrity sha512-EsTJgQL5p+XXs40aBxOSbMOpQr/f4ty+Iyget8Oh6MT/cncCa2+W8a78fbqYqTtSpH6Sm7E8nvT8gPuSS6ef1w== - -"@across-protocol/constants@^3.1.14": - version "3.1.14" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.14.tgz#03b0e8b1104d5ec6559cdb8c25186cb365c67cba" - integrity sha512-RhtuHCai2GZTRYEeMqsOK4yoIZZGfhUgCxhrY4Fo26tkEOBL+qf/Rjgw9THZHtlqNuGGFIeR4VyxS8GmgxjyZQ== +"@across-protocol/constants@^3.1.16": + version "3.1.16" + resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.16.tgz#c126085d29d4d051fd02a04c833d804d37c3c219" + integrity sha512-+U+AecGWnfY4b4sSfKBvsDj/+yXKEqpTXcZgI8GVVmUTkUhs1efA0kN4q3q10yy5TXI5TtagaG7R9yZg1zgKKg== "@across-protocol/contracts@^0.1.4": version "0.1.4" @@ -30,12 +25,12 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" -"@across-protocol/contracts@^3.0.10": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-3.0.10.tgz#965baa824b53af09cd4fb755dadf23776214ef77" - integrity sha512-qZBIkvuU1faZYUj0qFTWN7DVHfkEFK4jn7u5SMHBtAFEtbW2kSIREbE8afWPbhXa5Zah409nQINzYkcfo/I5rw== +"@across-protocol/contracts@^3.0.11": + version "3.0.11" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-3.0.11.tgz#d010e2a1a44a7ac8184848a54bb9c7b2d41875b0" + integrity sha512-T2C8jOetkcqFDbp8fqI894Dd9qm7D9X7h1kqsI7rYu9paXdaqAUVSR/XcMTq2aHhNAVgb0OlKY/do982ujd0xw== dependencies: - "@across-protocol/constants" "^3.1.13" + "@across-protocol/constants" "^3.1.16" "@defi-wonderland/smock" "^2.3.4" "@eth-optimism/contracts" "^0.5.40" "@ethersproject/abstract-provider" "5.7.0" @@ -47,17 +42,17 @@ "@uma/common" "^2.34.0" "@uma/contracts-node" "^0.4.17" "@uma/core" "^2.56.0" - axios "^1.6.2" + axios "^1.7.4" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.2.4": - version "3.2.4" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.4.tgz#3c7b9dac1c289ca6161c61358f3efb56e97d8d49" - integrity sha512-3/BzwgQdsWv4MsFsmj5OsmjV66QHHBfAuefPOA/TmPK9PC6CAgpNZNuNmp17Kk5pgagy3UDwBF7EDvWLY+aOhQ== +"@across-protocol/sdk@^3.2.6": + version "3.2.6" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.6.tgz#3b3158d2b38d968829b6030ca1d75b53f8e31556" + integrity sha512-ANMQg8WN52WXUqk8NK/abKkze/b5b25Hbe//NhCCzxbsy41W0jaq7fPtwq5gllXc8wHeu+Uvb45WPqM89ByLCw== dependencies: "@across-protocol/across-token" "^1.0.0" - "@across-protocol/constants" "^3.1.14" - "@across-protocol/contracts" "^3.0.10" + "@across-protocol/constants" "^3.1.16" + "@across-protocol/contracts" "^3.0.11" "@eth-optimism/sdk" "^3.3.1" "@ethersproject/bignumber" "^5.7.0" "@pinata/sdk" "^2.1.0" @@ -4059,7 +4054,7 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.6.2, axios@^1.7.4: +axios@^1.7.4: version "1.7.4" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== From 97b7ee04df0d4cbb4fb2d88d0689a36e7bb43f78 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Thu, 10 Oct 2024 18:03:06 +0200 Subject: [PATCH 12/41] feat: Support finalizing on World Chain (#1859) This should probably have been part of the larger commit to support World Chain. It has been tested locally and successfully submitted a proof for a WBTC withdrawal. --- src/finalizer/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index 4cedb298f..ceb9f9102 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -102,6 +102,10 @@ const chainFinalizers: { [chainId: number]: { finalizeOnL2: ChainFinalizer[]; fi finalizeOnL1: [opStackFinalizer], finalizeOnL2: [], }, + [CHAIN_IDs.WORLD_CHAIN]: { + finalizeOnL1: [opStackFinalizer], + finalizeOnL2: [], + }, // Testnets [CHAIN_IDs.BASE_SEPOLIA]: { finalizeOnL1: [cctpL2toL1Finalizer], From d794b4b369edf8ea65fbc342946ee933d8ca8d8e Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Fri, 11 Oct 2024 02:10:02 -0500 Subject: [PATCH 13/41] fix: force maximum USDC rebalance amount over CCTP to 1M (#1860) This is a simple fix to guarantee that the rebalancer will not try to send an amount over CCTP which is greater than 1 million USDC, however, if we needed to send over 1 million USDC, this fix would split the transfers into multiple runs. There might be a follow-up to permit multiple rebalances per run, but this is OK for now. Signed-off-by: bennett --- src/adapter/bridges/UsdcCCTPBridge.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/adapter/bridges/UsdcCCTPBridge.ts b/src/adapter/bridges/UsdcCCTPBridge.ts index c26f4e282..1186805e2 100644 --- a/src/adapter/bridges/UsdcCCTPBridge.ts +++ b/src/adapter/bridges/UsdcCCTPBridge.ts @@ -1,11 +1,21 @@ import { Contract, Signer } from "ethers"; import { CONTRACT_ADDRESSES, chainIdsToCctpDomains } from "../../common"; import { BridgeTransactionDetails, BaseBridgeAdapter, BridgeEvents } from "./BaseBridgeAdapter"; -import { BigNumber, EventSearchConfig, Provider, TOKEN_SYMBOLS_MAP, compareAddressesSimple, assert } from "../../utils"; +import { + BigNumber, + EventSearchConfig, + Provider, + TOKEN_SYMBOLS_MAP, + compareAddressesSimple, + assert, + toBN, +} from "../../utils"; import { processEvent } from "../utils"; import { cctpAddressToBytes32, retrieveOutstandingCCTPBridgeUSDCTransfers } from "../../utils/CCTPUtils"; export class UsdcCCTPBridge extends BaseBridgeAdapter { + private CCTP_MAX_SEND_AMOUNT = toBN(1_000_000_000_000); // 1MM USDC. + constructor(l2chainId: number, hubChainId: number, l1Signer: Signer, l2SignerOrProvider: Signer | Provider) { super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [ CONTRACT_ADDRESSES[hubChainId].cctpTokenMessenger.address, @@ -38,6 +48,7 @@ export class UsdcCCTPBridge extends BaseBridgeAdapter { amount: BigNumber ): Promise { assert(compareAddressesSimple(_l1Token, TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); + amount = amount.gt(this.CCTP_MAX_SEND_AMOUNT) ? this.CCTP_MAX_SEND_AMOUNT : amount; return Promise.resolve({ contract: this.getL1Bridge(), method: "depositForBurn", From 4b7a4d7fa5927892deb83612a138987f56ae355a Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:42:35 +0200 Subject: [PATCH 14/41] feat: Support World Chain USDC bridges (#1856) Co-authored-by: bennett --- src/adapter/bridges/OpStackUSDCBridge.ts | 68 ++++++++++++ src/adapter/bridges/index.ts | 1 + src/common/Constants.ts | 4 +- src/common/ContractAddresses.ts | 11 +- src/common/abi/OpStackUSDCBridge.json | 135 +++++++++++++++++++++++ 5 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 src/adapter/bridges/OpStackUSDCBridge.ts create mode 100644 src/common/abi/OpStackUSDCBridge.json diff --git a/src/adapter/bridges/OpStackUSDCBridge.ts b/src/adapter/bridges/OpStackUSDCBridge.ts new file mode 100644 index 000000000..898b268b3 --- /dev/null +++ b/src/adapter/bridges/OpStackUSDCBridge.ts @@ -0,0 +1,68 @@ +import { Contract, BigNumber, paginatedEventQuery, EventSearchConfig, Signer, Provider } from "../../utils"; +import { CONTRACT_ADDRESSES } from "../../common"; +import { BaseBridgeAdapter, BridgeTransactionDetails, BridgeEvents } from "./BaseBridgeAdapter"; +import { processEvent } from "../utils"; + +export class OpStackUSDCBridge extends BaseBridgeAdapter { + private readonly l2Gas = 200000; + + constructor( + l2chainId: number, + hubChainId: number, + l1Signer: Signer, + l2SignerOrProvider: Signer | Provider, + _l1Token: string + ) { + // Lint Appeasement + _l1Token; + const { address: l1Address, abi: l1Abi } = CONTRACT_ADDRESSES[hubChainId][`opUSDCBridge_${l2chainId}`]; + const { address: l2Address, abi: l2Abi } = CONTRACT_ADDRESSES[l2chainId].opUSDCBridge; + super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [l1Address]); + + this.l1Bridge = new Contract(l1Address, l1Abi, l1Signer); + this.l2Bridge = new Contract(l2Address, l2Abi, l2SignerOrProvider); + } + + async constructL1ToL2Txn( + toAddress: string, + _l1Token: string, + _l2Token: string, + amount: BigNumber + ): Promise { + return Promise.resolve({ + contract: this.getL1Bridge(), + method: "sendMessage", + args: [toAddress, amount, this.l2Gas], + }); + } + + async queryL1BridgeInitiationEvents( + l1Token: string, + _fromAddress: string, + toAddress: string, + eventConfig: EventSearchConfig + ): Promise { + const l1Bridge = this.getL1Bridge(); + const events = await paginatedEventQuery(l1Bridge, l1Bridge.filters.MessageSent(undefined, toAddress), eventConfig); + return { + [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "_amount", "_to", "_user")), + }; + } + + async queryL2BridgeFinalizationEvents( + l1Token: string, + _fromAddress: string, + toAddress: string, + eventConfig: EventSearchConfig + ): Promise { + const l2Bridge = this.getL2Bridge(); + const events = await paginatedEventQuery( + l2Bridge, + l2Bridge.filters.MessageReceived(undefined, toAddress), + eventConfig + ); + return { + [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "_amount", "_user", "_spender")), + }; + } +} diff --git a/src/adapter/bridges/index.ts b/src/adapter/bridges/index.ts index e05a0058a..ef672f619 100644 --- a/src/adapter/bridges/index.ts +++ b/src/adapter/bridges/index.ts @@ -14,3 +14,4 @@ export * from "./ZKSyncWethBridge"; export * from "./LineaWethBridge"; export * from "./BlastBridge"; export * from "./ScrollERC20Bridge"; +export * from "./OpStackUSDCBridge"; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index ae817a340..2cb9f579e 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -16,6 +16,7 @@ import { LineaWethBridge, BlastBridge, ScrollERC20Bridge, + OpStackUSDCBridge, } from "../adapter/bridges"; import { DEFAULT_L2_CONTRACT_ADDRESSES } from "@eth-optimism/sdk"; import { CONTRACT_ADDRESSES } from "./ContractAddresses"; @@ -357,7 +358,7 @@ export const SUPPORTED_TOKENS: { [chainId: number]: string[] } = { [CHAIN_IDs.POLYGON]: ["USDC", "USDT", "WETH", "DAI", "WBTC", "UMA", "BAL", "ACX", "POOL"], [CHAIN_IDs.REDSTONE]: ["WETH"], [CHAIN_IDs.SCROLL]: ["WETH", "USDC", "USDT", "WBTC"], - [CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC"], // xxx @todo add USDC after new bridge adapter is supported + [CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC", "USDC"], [CHAIN_IDs.ZK_SYNC]: ["USDC", "USDT", "WETH", "WBTC", "DAI"], [CHAIN_IDs.ZORA]: ["USDC", "WETH"], @@ -465,6 +466,7 @@ export const CUSTOM_BRIDGE: { [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, }, [CHAIN_IDs.WORLD_CHAIN]: { + [TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: OpStackUSDCBridge, [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: OpStackWethBridge, }, [CHAIN_IDs.ZK_SYNC]: { diff --git a/src/common/ContractAddresses.ts b/src/common/ContractAddresses.ts index a82e1e582..4433ed226 100644 --- a/src/common/ContractAddresses.ts +++ b/src/common/ContractAddresses.ts @@ -5,8 +5,9 @@ import ATOMIC_DEPOSITOR_ABI from "./abi/AtomicDepositor.json"; import WETH_ABI from "./abi/Weth.json"; import HUB_POOL_ABI from "./abi/HubPool.json"; import VOTING_V2_ABI from "./abi/VotingV2.json"; -import OVM_L2_STANDARD_BRIDGE_ABI from "./abi/OpStackStandardBridgeL2.json"; +import OP_USDC_BRIDGE_ABI from "./abi/OpStackUSDCBridge.json"; import OVM_L1_STANDARD_BRIDGE_ABI from "./abi/OpStackStandardBridgeL1.json"; +import OVM_L2_STANDARD_BRIDGE_ABI from "./abi/OpStackStandardBridgeL2.json"; import SNX_OPTIMISM_BRIDGE_L1_ABI from "./abi/SnxOptimismBridgeL1.json"; import SNX_OPTIMISM_BRIDGE_L2_ABI from "./abi/SnxOptimismBridgeL2.json"; import DAI_OPTIMISM_BRIDGE_L1_ABI from "./abi/DaiOptimismBridgeL1.json"; @@ -76,6 +77,10 @@ export const CONTRACT_ADDRESSES: { address: "0xa679201903847f3723Dc88CA7530c8B665bC51a5", abi: ATOMIC_DEPOSITOR_ABI, }, + opUSDCBridge_480: { + address: "0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB", + abi: OP_USDC_BRIDGE_ABI, + }, // Since there are multiple ovmStandardBridges on mainnet for different OP Stack chains, we append the chain id of the Op // Stack chain to the name to differentiate. This one is for Optimism. ovmStandardBridge_10: { @@ -230,6 +235,10 @@ export const CONTRACT_ADDRESSES: { }, }, 480: { + opUSDCBridge: { + address: "0xbD80b06d3dbD0801132c6689429aC09Ca6D27f82", + abi: OP_USDC_BRIDGE_ABI, + }, ovmStandardBridge: { address: "0x4200000000000000000000000000000000000010", abi: OVM_L2_STANDARD_BRIDGE_ABI, diff --git a/src/common/abi/OpStackUSDCBridge.json b/src/common/abi/OpStackUSDCBridge.json new file mode 100644 index 000000000..3a4b049f6 --- /dev/null +++ b/src/common/abi/OpStackUSDCBridge.json @@ -0,0 +1,135 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amountWithdrawn", + "type": "uint256" + } + ], + "name": "LockedFundsWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "_messenger", + "type": "address" + } + ], + "name": "MessageReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "_messenger", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + } + ], + "name": "MessageSent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_user", + "type": "address" + }, + { + "internalType": "address", + "name": "_spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "receiveMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "_minGasLimit", + "type": "uint32" + } + ], + "name": "sendMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] From dd3a69d04c5c9b0d7c642a642286c5491d306592 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:02:01 +0200 Subject: [PATCH 15/41] chore: Bump SDK (#1863) This includes an optimisation for the SpokePoolClient to bundle two separate SpokePool calls into a SpokePool multicall. Ideally this will reduce the instance of RPC errors when this query occurs against a block that's slightly ahead of one of the RPC providers. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3861c1f95..9bf8842b7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.16", "@across-protocol/contracts": "^3.0.11", - "@across-protocol/sdk": "^3.2.6", + "@across-protocol/sdk": "^3.2.7", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index df0f1b08a..045e6f404 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,10 +45,10 @@ axios "^1.7.4" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.2.6": - version "3.2.6" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.6.tgz#3b3158d2b38d968829b6030ca1d75b53f8e31556" - integrity sha512-ANMQg8WN52WXUqk8NK/abKkze/b5b25Hbe//NhCCzxbsy41W0jaq7fPtwq5gllXc8wHeu+Uvb45WPqM89ByLCw== +"@across-protocol/sdk@^3.2.7": + version "3.2.7" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.7.tgz#c0d2ad49065a33d05f01f846e328989c4e6585c2" + integrity sha512-oUZ8uIw10/y+ZsRjlZTivJnK3AGpwCJbt3VLMHhv6TUYGNcQW/Vr71LFhyeieKfoY4+lGZq/UV4QIqsNSWiSZA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.16" From bd9bde439dd764adca0b0dd62688d2913503e3b2 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:54:13 +0200 Subject: [PATCH 16/41] chore: Bump viem (#1858) To gain the recently-added worldchain definitions. --- package.json | 2 +- src/libexec/SpokePoolListenerExperimental.ts | 1 + yarn.lock | 106 ++++++++----------- 3 files changed, 48 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 9bf8842b7..6d074695c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", - "viem": "^2.21.7", + "viem": "^2.21.18", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" }, diff --git a/src/libexec/SpokePoolListenerExperimental.ts b/src/libexec/SpokePoolListenerExperimental.ts index e5e2b5d31..5236575c7 100644 --- a/src/libexec/SpokePoolListenerExperimental.ts +++ b/src/libexec/SpokePoolListenerExperimental.ts @@ -49,6 +49,7 @@ const _chains = { [CHAIN_IDs.POLYGON]: chains.polygon, [CHAIN_IDs.REDSTONE]: chains.redstone, [CHAIN_IDs.SCROLL]: chains.scroll, + [CHAIN_IDs.WORLD_CHAIN]: chains.worldchain, [CHAIN_IDs.ZK_SYNC]: chains.zksync, [CHAIN_IDs.ZORA]: chains.zora, } as const; diff --git a/yarn.lock b/yarn.lock index 045e6f404..effb7bb56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -69,10 +69,10 @@ superstruct "^0.15.4" tslib "^2.6.2" -"@adraffy/ens-normalize@1.10.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" - integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== +"@adraffy/ens-normalize@1.11.0": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== "@arbitrum/sdk@^3.1.3": version "3.1.3" @@ -1503,37 +1503,18 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== -"@noble/curves@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" - integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== - dependencies: - "@noble/hashes" "1.4.0" - -"@noble/curves@^1.4.0": +"@noble/curves@1.6.0", "@noble/curves@^1.4.0", "@noble/curves@~1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== dependencies: "@noble/hashes" "1.5.0" -"@noble/curves@~1.4.0": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" - integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== - dependencies: - "@noble/hashes" "1.4.0" - "@noble/hashes@1.0.0", "@noble/hashes@~1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg== -"@noble/hashes@1.4.0", "@noble/hashes@~1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" - integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== - "@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" @@ -2404,7 +2385,12 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.0.0.tgz#109fb595021de285f05a7db6806f2f48296fcee7" integrity sha512-gIVaYhUsy+9s58m/ETjSJVKHhKTBMmcRb9cEV5/5dwvfDlfORjKrFsDeDHWRrm6RjcPvCLZFwGJjAjLj1gg4HA== -"@scure/base@~1.1.6", "@scure/base@~1.1.8": +"@scure/base@~1.1.7": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" + integrity sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg== + +"@scure/base@~1.1.8": version "1.1.8" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== @@ -2418,14 +2404,14 @@ "@noble/secp256k1" "~1.5.2" "@scure/base" "~1.0.0" -"@scure/bip32@1.4.0": - version "1.4.0" - resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" - integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== +"@scure/bip32@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.5.0.tgz#dd4a2e1b8a9da60e012e776d954c4186db6328e6" + integrity sha512-8EnFYkqEQdnkuGBVpCzKxyIwDCBLDVj3oiX0EKUFre/tOjL/Hqba1D6n/8RcmaQy4f95qQFrO2A8Sr6ybh4NRw== dependencies: - "@noble/curves" "~1.4.0" - "@noble/hashes" "~1.4.0" - "@scure/base" "~1.1.6" + "@noble/curves" "~1.6.0" + "@noble/hashes" "~1.5.0" + "@scure/base" "~1.1.7" "@scure/bip39@1.0.0": version "1.0.0" @@ -3467,10 +3453,10 @@ abbrev@1.0.x: web3-eth-abi "^1.2.1" web3-utils "^1.2.1" -abitype@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" - integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== +abitype@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" + integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== abort-controller@^3.0.0: version "3.0.0" @@ -9088,10 +9074,10 @@ isomorphic-unfetch@^3.0.0: node-fetch "^2.6.1" unfetch "^4.2.0" -isows@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" - integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== isstream@~0.1.2: version "0.1.2" @@ -14705,20 +14691,20 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -viem@^2.21.7: - version "2.21.7" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.7.tgz#c933fd3adb6f771e5c5fe2b4d7a14d8701ddc32f" - integrity sha512-PFgppakInuHX31wHDx1dzAjhj4t6Po6WrWtutDi33z2vabIT0Wv8qT6tl7DLqfLy2NkTqfN2mdshYLeoI5ZHvQ== +viem@^2.21.18: + version "2.21.21" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.21.tgz#11a5001fa18c8a47548a4b20ae9ddd8cfb14de3f" + integrity sha512-KJPqpAXy8kyZQICx1nURUXqd8aABP9RweAZhfp27MzMPsAAxP450cWPlEffEAUrvsyyj5edVbIcHESE8DYVzFA== dependencies: - "@adraffy/ens-normalize" "1.10.0" - "@noble/curves" "1.4.0" - "@noble/hashes" "1.4.0" - "@scure/bip32" "1.4.0" + "@adraffy/ens-normalize" "1.11.0" + "@noble/curves" "1.6.0" + "@noble/hashes" "1.5.0" + "@scure/bip32" "1.5.0" "@scure/bip39" "1.4.0" - abitype "1.0.5" - isows "1.0.4" - webauthn-p256 "0.0.5" - ws "8.17.1" + abitype "1.0.6" + isows "1.0.6" + webauthn-p256 "0.0.10" + ws "8.18.0" walk-up-path@^1.0.0: version "1.0.0" @@ -15212,10 +15198,10 @@ web3@1.8.2, web3@^1.6.0: web3-shh "1.8.2" web3-utils "1.8.2" -webauthn-p256@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" - integrity sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg== +webauthn-p256@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" + integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== dependencies: "@noble/curves" "^1.4.0" "@noble/hashes" "^1.4.0" @@ -15442,10 +15428,10 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.17.1: - version "8.17.1" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" - integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== ws@^3.0.0: version "3.3.3" From 3de3d54d37934e4cc217dd968c8d5980b8ec2c84 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:31:10 -0400 Subject: [PATCH 17/41] improve(unwrapWeth): load WETH addresses from constants (#1840) * improve(unwrapWeth): Add Redstone to unwrap weth script * Update unwrapWeth.ts * Update unwrapWeth.ts * Update unwrapWeth.ts * Update unwrapWeth.ts --- scripts/unwrapWeth.ts | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/scripts/unwrapWeth.ts b/scripts/unwrapWeth.ts index f1b710dd1..56263c7f4 100644 --- a/scripts/unwrapWeth.ts +++ b/scripts/unwrapWeth.ts @@ -1,4 +1,13 @@ -import { ethers, retrieveSignerFromCLIArgs, getProvider, WETH9, toBN, isKeyOf, getNetworkName } from "../src/utils"; +import { + ethers, + retrieveSignerFromCLIArgs, + getProvider, + WETH9, + toBN, + getNetworkName, + TOKEN_SYMBOLS_MAP, + assert, +} from "../src/utils"; import { askYesNoQuestion } from "./utils"; import minimist from "minimist"; @@ -15,16 +24,6 @@ const args = minimist(process.argv.slice(2), { // \ --wallet gckms // \ --keys bot1 -const WETH_ADDRESSES = { - 1: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - 10: "0x4200000000000000000000000000000000000006", - 42161: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", - 137: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", - 288: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", - 324: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91", - 8453: "0x4200000000000000000000000000000000000006", -}; - export async function run(): Promise { if (!Object.keys(args).includes("chainId")) { throw new Error("Define `chainId` as the chain you want to connect on"); @@ -36,10 +35,8 @@ export async function run(): Promise { const signerAddr = await baseSigner.getAddress(); const chainId = Number(args.chainId); const connectedSigner = baseSigner.connect(await getProvider(chainId)); - if (!isKeyOf(chainId, WETH_ADDRESSES)) { - throw new Error("chainId does not have a defined WETH address"); - } - const token = WETH_ADDRESSES[chainId]; + assert(TOKEN_SYMBOLS_MAP.WETH.addresses[chainId], "chainId does not have a defined WETH address"); + const token = TOKEN_SYMBOLS_MAP.WETH.addresses[chainId]; const weth = new ethers.Contract(token, WETH9.abi, connectedSigner); const decimals = 18; const amountFromWei = ethers.utils.formatUnits(args.amount, decimals); From 119ca6b37898b40d898a0cb1577ffc211f61b596 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:45:16 -0400 Subject: [PATCH 18/41] feat: Automate withdrawing ETH from OpStack chains (#1866) * feat: Automate withdrawing ETH from OpStack chains - Adds a new script to withdraw ETH via the OVM standard L2 bridge to the signer's address on L1 - Adds a feature to the OP stack finalizer that lets the user specify addresses that they want to finalize OVM withdrawals for, in addition to any existing SpokePool withdrawals. This pairs with the above script to allow us to one-step manually move ETH from OP stack chains back to the same EOA on Ethereum - To use this new feature, we need to add new chains to the finalizer config (i.e. 7777777 and 1135) and also set `WITHDRAWAL_TO_ADDRESSES=[x]` where `x` are addresses that we plan to execute manual withdrawals from * Update opStack.ts * Update OpStackStandardBridgeL2.json * Update opStack.ts * Update Constants.ts * Update scripts/withdrawFromOpStack.ts Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> * Add safety checks * Update opStack.ts * Update opStack.ts --------- Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- scripts/utils.ts | 16 ++- scripts/withdrawFromOpStack.ts | 121 ++++++++++++++++++++ src/common/Constants.ts | 1 + src/common/abi/OpStackStandardBridgeL2.json | 48 ++++++++ src/finalizer/index.ts | 8 ++ src/finalizer/utils/opStack.ts | 48 ++++++++ 6 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 scripts/withdrawFromOpStack.ts diff --git a/scripts/utils.ts b/scripts/utils.ts index 18499bbb9..701620b03 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,5 +1,5 @@ import assert from "assert"; -import { Contract, ethers, utils as ethersUtils } from "ethers"; +import { Contract, ethers, utils as ethersUtils, Signer } from "ethers"; import readline from "readline"; import * as contracts from "@across-protocol/contracts"; import { utils as sdkUtils } from "@across-protocol/sdk"; @@ -133,3 +133,17 @@ export async function getSpokePoolContract(chainId: number): Promise { const contract = new Contract(spokePoolAddr, contracts.SpokePool__factory.abi); return contract; } + +/** + * @description Instantiate an Across OVM SpokePool contract instance. + * @param chainId Chain ID for the SpokePool deployment. + * @returns SpokePool contract instance. + */ +export async function getOvmSpokePoolContract(chainId: number, signer?: Signer): Promise { + const hubChainId = resolveHubChainId(chainId); + const hubPool = await getContract(hubChainId, "HubPool"); + const spokePoolAddr = (await hubPool.crossChainContracts(chainId))[1]; + + const contract = new Contract(spokePoolAddr, contracts.Ovm_SpokePool__factory.abi, signer); + return contract; +} diff --git a/scripts/withdrawFromOpStack.ts b/scripts/withdrawFromOpStack.ts new file mode 100644 index 000000000..10bd05b61 --- /dev/null +++ b/scripts/withdrawFromOpStack.ts @@ -0,0 +1,121 @@ +// Submits a bridge from OpStack L2 to L1. +// For now, this script only supports ETH withdrawals. + +import { + ethers, + retrieveSignerFromCLIArgs, + getProvider, + ERC20, + WETH9, + TOKEN_SYMBOLS_MAP, + assert, + getL1TokenInfo, + Contract, + fromWei, + blockExplorerLink, + CHAIN_IDs, +} from "../src/utils"; +import { CONTRACT_ADDRESSES } from "../src/common"; +import { askYesNoQuestion, getOvmSpokePoolContract } from "./utils"; + +import minimist from "minimist"; + +const cliArgs = ["amount", "chainId"]; +const args = minimist(process.argv.slice(2), { + string: cliArgs, +}); + +// Example run: +// ts-node ./scripts/withdrawFromOpStack.ts +// \ --amount 3000000000000000000 +// \ --chainId 1135 +// \ --wallet gckms +// \ --keys bot1 + +export async function run(): Promise { + assert( + cliArgs.every((cliArg) => Object.keys(args).includes(cliArg)), + `Missing cliArg, expected: ${cliArgs}` + ); + const baseSigner = await retrieveSignerFromCLIArgs(); + const signerAddr = await baseSigner.getAddress(); + const chainId = parseInt(args.chainId); + const connectedSigner = baseSigner.connect(await getProvider(chainId)); + const l2Token = TOKEN_SYMBOLS_MAP.WETH?.addresses[chainId]; + assert(l2Token, `WETH not found on chain ${chainId} in TOKEN_SYMBOLS_MAP`); + const l1TokenInfo = getL1TokenInfo(l2Token, chainId); + console.log("Fetched L1 token info:", l1TokenInfo); + assert(l1TokenInfo.symbol === "ETH", "Only WETH withdrawals are supported for now."); + const amount = args.amount; + const amountFromWei = ethers.utils.formatUnits(amount, l1TokenInfo.decimals); + console.log(`Amount to bridge from chain ${chainId}: ${amountFromWei} ${l2Token}`); + + const erc20 = new Contract(l2Token, ERC20.abi, connectedSigner); + const currentBalance = await erc20.balanceOf(signerAddr); + const currentEthBalance = await connectedSigner.getBalance(); + console.log( + `Current WETH balance for account ${signerAddr}: ${fromWei(currentBalance, l1TokenInfo.decimals)} ${l2Token}` + ); + console.log(`Current ETH balance for account ${signerAddr}: ${fromWei(currentEthBalance, l1TokenInfo.decimals)}`); + + // First offer user option to unwrap WETH into ETH. + const weth = new Contract(l2Token, WETH9.abi, connectedSigner); + if (await askYesNoQuestion(`\nUnwrap ${amount} of WETH @ ${weth.address}?`)) { + const unwrap = await weth.withdraw(amount); + console.log(`Submitted transaction: ${blockExplorerLink(unwrap.hash, chainId)}.`); + const receipt = await unwrap.wait(); + console.log("Unwrap complete...", receipt); + } + + // Now, submit a withdrawal: + const ovmStandardBridgeObj = CONTRACT_ADDRESSES[chainId].ovmStandardBridge; + assert(CONTRACT_ADDRESSES[chainId].ovmStandardBridge, "ovmStandardBridge for chain not found in CONTRACT_ADDRESSES"); + const ovmStandardBridge = new Contract(ovmStandardBridgeObj.address, ovmStandardBridgeObj.abi, connectedSigner); + const bridgeETHToArgs = [ + signerAddr, // to + 200_000, // minGasLimit + "0x", // extraData + { value: amount }, // msg.value + ]; + + console.log( + `Submitting bridgeETHTo on the OVM standard bridge @ ${ovmStandardBridge.address} with the following args: `, + ...bridgeETHToArgs + ); + + // Sanity check that the ovmStandardBridge contract is the one we expect by comparing its stored addresses + // with the ones we have recorded. + const spokePool = await getOvmSpokePoolContract(chainId, connectedSigner); + const expectedL2Messenger = await spokePool.MESSENGER(); + const l2Messenger = await ovmStandardBridge.MESSENGER(); + assert( + l2Messenger === expectedL2Messenger, + `Unexpected L2 messenger address in ovmStandardBridge contract, expected: ${expectedL2Messenger}, got: ${l2Messenger}` + ); + const l1StandardBridge = await ovmStandardBridge.l1TokenBridge(); + const expectedL1StandardBridge = CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET][`ovmStandardBridge_${chainId}`].address; + assert( + l1StandardBridge === expectedL1StandardBridge, + `Unexpected L1 standard bridge address in ovmStandardBridge contract, expected: ${expectedL1StandardBridge}, got: ${l1StandardBridge}` + ); + if (!(await askYesNoQuestion("\nDo you want to proceed?"))) { + return; + } + const withdrawal = await ovmStandardBridge.bridgeETHTo(...bridgeETHToArgs); + console.log(`Submitted withdrawal: ${blockExplorerLink(withdrawal.hash, chainId)}.`); + const receipt = await withdrawal.wait(); + console.log("Receipt", receipt); +} + +if (require.main === module) { + run() + .then(async () => { + // eslint-disable-next-line no-process-exit + process.exit(0); + }) + .catch(async (error) => { + console.error("Process exited with", error); + // eslint-disable-next-line no-process-exit + process.exit(1); + }); +} diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 2cb9f579e..404f054b1 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -293,6 +293,7 @@ export const multicall3Addresses = { [CHAIN_IDs.MODE]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.OPTIMISM]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.POLYGON]: "0xcA11bde05977b3631167028862bE2a173976CA11", + [CHAIN_IDs.REDSTONE]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.SCROLL]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.WORLD_CHAIN]: "0xcA11bde05977b3631167028862bE2a173976CA11", [CHAIN_IDs.ZK_SYNC]: "0xF9cda624FBC7e059355ce98a31693d299FACd963", diff --git a/src/common/abi/OpStackStandardBridgeL2.json b/src/common/abi/OpStackStandardBridgeL2.json index 434d41194..72431186a 100644 --- a/src/common/abi/OpStackStandardBridgeL2.json +++ b/src/common/abi/OpStackStandardBridgeL2.json @@ -11,5 +11,53 @@ ], "name": "DepositFinalized", "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "indexed": false, "internalType": "bytes", "name": "extraData", "type": "bytes" } + ], + "name": "ETHBridgeInitiated", + "type": "event" + }, + { + "inputs": [ + { "internalType": "address", "name": "_to", "type": "address" }, + { "internalType": "uint32", "name": "_minGasLimit", "type": "uint32" }, + { "internalType": "bytes", "name": "_extraData", "type": "bytes" } + ], + "name": "bridgeETHTo", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "MESSENGER", + "outputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "l1TokenBridge", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" } ] diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index ceb9f9102..baeb3e720 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -98,6 +98,14 @@ const chainFinalizers: { [chainId: number]: { finalizeOnL2: ChainFinalizer[]; fi finalizeOnL1: [opStackFinalizer], finalizeOnL2: [], }, + [CHAIN_IDs.ZORA]: { + finalizeOnL1: [opStackFinalizer], + finalizeOnL2: [], + }, + [CHAIN_IDs.REDSTONE]: { + finalizeOnL1: [opStackFinalizer], + finalizeOnL2: [], + }, [CHAIN_IDs.BLAST]: { finalizeOnL1: [opStackFinalizer], finalizeOnL2: [], diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index f6a1f44b4..3d4e82883 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -22,6 +22,7 @@ import { chainIsProd, Contract, ethers, + paginatedEventQuery, } from "../../utils"; import { CONTRACT_ADDRESSES, Multicall2Call, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; import { FinalizerPromise, CrossChainMessage } from "../types"; @@ -94,6 +95,53 @@ export async function opStackFinalizer( latestBlockToProve, }); + // Experimental feature: Add in all ETH withdrawals from OPStack chain to the finalizer. This will help us + // in the short term to automate ETH withdrawals from Lite chains, which can build up ETH balances over time + // and because they are lite chains, our only way to withdraw them is to initiate a slow bridge of ETH from the + // the lite chain to Ethereum. + const withdrawalToAddresses: string[] = process.env.FINALIZER_WITHDRAWAL_TO_ADDRESSES + ? JSON.parse(process.env.FINALIZER_WITHDRAWAL_TO_ADDRESSES).map((address) => ethers.utils.getAddress(address)) + : []; + if (!CONTRACT_ADDRESSES[chainId].ovmStandardBridge) { + logger.warn({ + at: "opStackFinalizer", + message: `No OVM standard bridge contract found for chain ${networkName} in CONTRACT_ADDRESSES`, + }); + } else if (withdrawalToAddresses.length > 0) { + const ovmStandardBridge = new Contract( + CONTRACT_ADDRESSES[chainId].ovmStandardBridge.address, + CONTRACT_ADDRESSES[chainId].ovmStandardBridge.abi, + spokePoolClient.spokePool.provider + ); + const withdrawalEvents = await paginatedEventQuery( + ovmStandardBridge, + ovmStandardBridge.filters.ETHBridgeInitiated( + null, // from + withdrawalToAddresses // to + ), + { + ...spokePoolClient.eventSearchConfig, + toBlock: spokePoolClient.latestBlockSearched, + } + ); + // If there are any found withdrawal initiated events, then add them to the list of TokenBridged events we'll + // submit proofs and finalizations for. + withdrawalEvents.forEach((event) => { + const tokenBridgedEvent: TokensBridged = { + ...event, + amountToReturn: event.args.amount, + chainId, + leafId: 0, + l2TokenAddress: TOKEN_SYMBOLS_MAP.WETH.addresses[chainId], + }; + if (event.blockNumber >= latestBlockToProve) { + recentTokensBridgedEvents.push(tokenBridgedEvent); + } else { + olderTokensBridgedEvents.push(tokenBridgedEvent); + } + }); + } + const proofs = await multicallOptimismL1Proofs( chainId, recentTokensBridgedEvents, From be3fd4155fea16e0926872206c177c33fa1a0e2a Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Tue, 22 Oct 2024 08:59:22 -0500 Subject: [PATCH 19/41] feat: add spoke pool balance reporting to the monitor (#1868) * feat: add spoke pool balance reporting to the monitor Signed-off-by: bennett --------- Signed-off-by: bennett Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> --- src/monitor/Monitor.ts | 149 +++++++++++++++++++++++++++++++++++ src/monitor/MonitorConfig.ts | 12 +++ src/monitor/index.ts | 6 ++ 3 files changed, 167 insertions(+) diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index b7ca6e791..e2004fc5f 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -37,6 +37,9 @@ import { CHAIN_IDs, WETH9, runTransaction, + isDefined, + resolveTokenDecimals, + sortEventsDescending, } from "../utils"; import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; @@ -596,6 +599,152 @@ export class Monitor { } } + async checkSpokePoolRunningBalances(): Promise { + // We define a custom format function since we do not want the same precision that `convertFromWei` gives us. + const formatWei = (weiVal: string, decimals: number) => + weiVal === "0" ? "0" : createFormatFunction(1, 4, false, decimals)(weiVal); + + const hubPoolClient = this.clients.hubPoolClient; + const monitoredTokenSymbols = this.monitorConfig.monitoredTokenSymbols; + const chainIds = + this.monitorConfig.monitoredSpokePoolChains.length !== 0 + ? this.monitorChains.filter((chain) => this.monitorConfig.monitoredSpokePoolChains.includes(chain)) + : this.monitorChains; + + const l2TokenForChain = (chainId: number, symbol: string) => { + return TOKEN_SYMBOLS_MAP[symbol]?.addresses[chainId]; + }; + const currentSpokeBalances = {}; + const pendingRelayerRefunds = {}; + const pendingRebalanceRoots = {}; + + // Get the pool rebalance leaves of the currently outstanding proposed root bundle. + const poolRebalanceRoot = await this.clients.bundleDataClient.getLatestPoolRebalanceRoot(); + const poolRebalanceLeaves = poolRebalanceRoot.root.leaves; + + const enabledTokens = [...hubPoolClient.getL1Tokens()]; + for (const leaf of poolRebalanceLeaves) { + if (!chainIds.includes(leaf.chainId)) { + continue; + } + const l2TokenMap = this.getL2ToL1TokenMap(enabledTokens, leaf.chainId); + pendingRebalanceRoots[leaf.chainId] = {}; + Object.entries(l2TokenMap).forEach(([l2Token, l1Token]) => { + const rebalanceAmount = leaf.netSendAmounts[leaf.l1Tokens.findIndex((token) => token === l1Token.address)]; + pendingRebalanceRoots[leaf.chainId][l2Token] = rebalanceAmount ?? bnZero; + }); + } + + // Take the validated bundles from the hub pool client. + const validatedBundles = sortEventsDescending(hubPoolClient.getValidatedRootBundles()).slice( + 0, + this.monitorConfig.bundlesCount + ); + const previouslyValidatedBundleRefunds: CombinedRefunds[] = await mapAsync( + validatedBundles, + async (bundle) => await this.clients.bundleDataClient.getPendingRefundsFromBundle(bundle) + ); + + // Here are the current outstanding refunds. + const nextBundleRefunds = await this.clients.bundleDataClient.getNextBundleRefunds(); + + // Calculate the pending refunds and the spoke pool balances in parallel. + for (const chainId of chainIds) { + const spokePool = this.clients.spokePoolClients[chainId].spokePool.address; + const l2TokenAddresses = monitoredTokenSymbols + .map((symbol) => l2TokenForChain(chainId, symbol)) + .filter(isDefined); + currentSpokeBalances[chainId] = {}; + pendingRelayerRefunds[chainId] = {}; + void (await mapAsync(l2TokenAddresses, async (l2Token) => { + const pendingValidatedDeductions = previouslyValidatedBundleRefunds + .map((refund) => refund[chainId]?.[l2Token]) + .filter(isDefined) + .reduce( + (totalPendingRefunds, refunds) => + Object.values(refunds).reduce( + (totalBundleRefunds, bundleRefund) => totalBundleRefunds.add(bundleRefund), + bnZero + ), + bnZero + ); + const nextBundleDeductions = nextBundleRefunds + .map((refund) => refund[chainId]?.[l2Token]) + .filter(isDefined) + .reduce( + (totalPendingRefunds, refunds) => + Object.values(refunds).reduce( + (totalBundleRefunds, bundleRefund) => totalBundleRefunds.add(bundleRefund), + bnZero + ), + bnZero + ); + const totalObligations = pendingValidatedDeductions.add(nextBundleDeductions); + currentSpokeBalances[chainId][l2Token] = ( + await this._getBalances([ + { + token: l2Token, + chainId: chainId, + account: spokePool, + }, + ]) + )[0]; + pendingRelayerRefunds[chainId][l2Token] = totalObligations; + })); + } + + // Print the output: The current spoke pool balance, the amount of refunds to payout, the pending pool rebalances, and then the sum of the three. + let tokenMarkdown = + "Token amounts: current, pending relayer refunds, pool rebalances, adjusted spoke pool balance\n"; + for (const tokenSymbol of monitoredTokenSymbols) { + tokenMarkdown += `*[${tokenSymbol}]*\n`; + for (const chainId of chainIds) { + const tokenAddress = l2TokenForChain(chainId, tokenSymbol); + + // If the token does not exist on the chain, then ignore this report. + if (!isDefined(tokenAddress)) { + continue; + } + + const tokenDecimals = resolveTokenDecimals(tokenSymbol); + const currentSpokeBalance = formatWei(currentSpokeBalances[chainId][tokenAddress].toString(), tokenDecimals); + + // Relayer refunds may be undefined when there were no refunds included in the last bundle. + const currentRelayerRefunds = formatWei( + (pendingRelayerRefunds[chainId]?.[tokenAddress] ?? bnZero).toString(), + tokenDecimals + ); + // Rebalance roots will be undefined when there was no root in the last bundle for the chain. + const currentRebalanceRoots = formatWei( + (pendingRebalanceRoots[chainId]?.[tokenAddress] ?? bnZero).toString(), + tokenDecimals + ); + const virtualSpokeBalance = formatWei( + currentSpokeBalances[chainId][tokenAddress] + .add(pendingRebalanceRoots[chainId]?.[tokenAddress] ?? bnZero) + .sub(pendingRelayerRefunds[chainId]?.[tokenAddress] ?? bnZero) + .toString(), + tokenDecimals + ); + tokenMarkdown += `${getNetworkName(chainId)}: `; + tokenMarkdown += + currentSpokeBalance + + `, ${currentRelayerRefunds !== "0" ? "-" : ""}` + + currentRelayerRefunds + + ", " + + currentRebalanceRoots + + ", " + + virtualSpokeBalance + + "\n"; + } + } + this.logger.info({ + at: "Monitor#checkSpokePoolRunningBalances", + message: "Spoke pool balance report", + mrkdwn: tokenMarkdown, + }); + } + // We approximate stuck rebalances by checking if there are still any pending cross chain transfers to any SpokePools // some fixed amount of time (grace period) after the last bundle execution. This can give false negative if there are // transfers stuck for longer than 1 bundle and the current time is within the last bundle execution + grace period. diff --git a/src/monitor/MonitorConfig.ts b/src/monitor/MonitorConfig.ts index 2fa3e0f73..a161a4852 100644 --- a/src/monitor/MonitorConfig.ts +++ b/src/monitor/MonitorConfig.ts @@ -10,6 +10,7 @@ export interface BotModes { stuckRebalancesEnabled: boolean; utilizationEnabled: boolean; // Monitors pool utilization ratio unknownRootBundleCallersEnabled: boolean; // Monitors relay related events triggered by non-whitelisted addresses + spokePoolBalanceReportEnabled: boolean; } export class MonitorConfig extends CommonConfig { @@ -20,9 +21,12 @@ export class MonitorConfig extends CommonConfig { readonly hubPoolEndingBlock: number | undefined; readonly stuckRebalancesEnabled: boolean; readonly monitoredRelayers: string[]; + readonly monitoredSpokePoolChains: number[]; + readonly monitoredTokenSymbols: string[]; readonly whitelistedDataworkers: string[]; readonly whitelistedRelayers: string[]; readonly knownV1Addresses: string[]; + readonly bundlesCount: number; readonly botModes: BotModes; readonly refillEnabledBalances: { chainId: number; @@ -63,6 +67,10 @@ export class MonitorConfig extends CommonConfig { REFILL_BALANCES_ENABLED, STUCK_REBALANCES_ENABLED, MONITOR_USE_GENERIC_ADAPTER, + REPORT_SPOKE_POOL_BALANCES, + MONITORED_SPOKE_POOL_CHAINS, + MONITORED_TOKEN_SYMBOLS, + BUNDLES_COUNT, } = env; this.botModes = { @@ -72,6 +80,7 @@ export class MonitorConfig extends CommonConfig { utilizationEnabled: UTILIZATION_ENABLED === "true", unknownRootBundleCallersEnabled: UNKNOWN_ROOT_BUNDLE_CALLERS_ENABLED === "true", stuckRebalancesEnabled: STUCK_REBALANCES_ENABLED === "true", + spokePoolBalanceReportEnabled: REPORT_SPOKE_POOL_BALANCES === "true", }; this.useGenericAdapter = MONITOR_USE_GENERIC_ADAPTER === "true"; @@ -83,6 +92,9 @@ export class MonitorConfig extends CommonConfig { // Used to monitor balances, activities, etc. from the specified relayers. this.monitoredRelayers = parseAddressesOptional(MONITORED_RELAYERS); this.knownV1Addresses = parseAddressesOptional(KNOWN_V1_ADDRESSES); + this.monitoredSpokePoolChains = JSON.parse(MONITORED_SPOKE_POOL_CHAINS ?? "[]"); + this.monitoredTokenSymbols = JSON.parse(MONITORED_TOKEN_SYMBOLS ?? "[]"); + this.bundlesCount = Number(BUNDLES_COUNT ?? 4); // Used to send tokens if available in wallet to balances under target balances. if (REFILL_BALANCES) { diff --git a/src/monitor/index.ts b/src/monitor/index.ts index 9732a8f27..fc5fe3027 100644 --- a/src/monitor/index.ts +++ b/src/monitor/index.ts @@ -55,6 +55,12 @@ export async function runMonitor(_logger: winston.Logger, baseSigner: Signer): P logger.debug({ at: "Monitor#index", message: "CheckBalances monitor disabled" }); } + if (config.botModes.spokePoolBalanceReportEnabled) { + await acrossMonitor.checkSpokePoolRunningBalances(); + } else { + logger.debug({ at: "Monitor#index", message: "Check spoke pool balances monitor disabled" }); + } + await clients.multiCallerClient.executeTxnQueues(); logger.debug({ at: "Monitor#index", message: `Time to loop: ${(Date.now() - loopStart) / 1000}s` }); From a55a99f286b341db1042c7e680dbc47ba31915a6 Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:15:29 -0500 Subject: [PATCH 20/41] fix: resolve proper zora usdc address (#1872) Signed-off-by: bennett --- src/utils/AddressUtils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/AddressUtils.ts b/src/utils/AddressUtils.ts index 434cb562b..b00128097 100644 --- a/src/utils/AddressUtils.ts +++ b/src/utils/AddressUtils.ts @@ -88,7 +88,10 @@ export function getTranslatedTokenAddress( } if (compareAddressesSimple(l1Token, TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId])) { const onBase = l2ChainId === CHAIN_IDs.BASE || l2ChainId === CHAIN_IDs.BASE_SEPOLIA; - return TOKEN_SYMBOLS_MAP[isNativeUsdc ? "USDC" : onBase ? "USDbC" : "USDC.e"].addresses[l2ChainId]; + const onZora = l2ChainId === CHAIN_IDs.ZORA; + return isNativeUsdc + ? TOKEN_SYMBOLS_MAP.USDC.addresses[l2ChainId] + : TOKEN_SYMBOLS_MAP[onBase ? "USDbC" : onZora ? "USDzC" : "USDC.e"].addresses[l2ChainId]; } else if ( l2ChainId === CHAIN_IDs.BLAST && compareAddressesSimple(l1Token, TOKEN_SYMBOLS_MAP.DAI.addresses[hubChainId]) From f69a7a8daad37bb4ad631764d571db45bb164794 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 23 Oct 2024 21:42:55 +0200 Subject: [PATCH 21/41] chore: Support rebalancing POOL to Scroll (#1861) Has been tested manually; it gets routed via the same contracts as the other supported ERC20s. --- src/common/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 404f054b1..05d0a447e 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -358,7 +358,7 @@ export const SUPPORTED_TOKENS: { [chainId: number]: string[] } = { [CHAIN_IDs.OPTIMISM]: ["DAI", "SNX", "BAL", "WETH", "USDC", "POOL", "USDT", "WBTC", "UMA", "ACX"], [CHAIN_IDs.POLYGON]: ["USDC", "USDT", "WETH", "DAI", "WBTC", "UMA", "BAL", "ACX", "POOL"], [CHAIN_IDs.REDSTONE]: ["WETH"], - [CHAIN_IDs.SCROLL]: ["WETH", "USDC", "USDT", "WBTC"], + [CHAIN_IDs.SCROLL]: ["WETH", "USDC", "USDT", "WBTC", "POOL"], [CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC", "USDC"], [CHAIN_IDs.ZK_SYNC]: ["USDC", "USDT", "WETH", "WBTC", "DAI"], [CHAIN_IDs.ZORA]: ["USDC", "WETH"], From 1038b27be57a6ce77495aebbee4b8911bb207247 Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Thu, 24 Oct 2024 12:56:06 -0500 Subject: [PATCH 22/41] improve: force nonce reset across all chains (#1874) Signed-off-by: bennett Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- src/utils/TransactionUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index eedadef5e..6670bdcce 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -66,7 +66,7 @@ export async function runTransaction( const { provider } = contract; const { chainId } = await provider.getNetwork(); - if (process.env[`ACROSS_RESET_NONCE_${chainId}`] && !nonceReset[chainId]) { + if (!nonceReset[chainId]) { nonce = await provider.getTransactionCount(await contract.signer.getAddress()); nonceReset[chainId] = true; } From e59ad7aa355b7ceeb07df484ad528cdf0d311602 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:55:37 +0200 Subject: [PATCH 23/41] improve(relayer): Make HubPoolClient update conditional (#1875) This skips an RPC call when the block number has not incremented beyond the last update cycle. --- src/relayer/Relayer.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index c2c29b43b..7049ebb83 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -119,7 +119,9 @@ export class Relayer { tokenClient.clearTokenData(); await configStoreClient.update(); - await hubPoolClient.update(); + if (configStoreClient.latestBlockSearched > hubPoolClient.latestBlockSearched) { + await hubPoolClient.update(); + } } await updateSpokePoolClients(spokePoolClients, [ From e683053364d96d737e8c20f57b0a722ab7adc067 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Fri, 25 Oct 2024 11:59:42 -0400 Subject: [PATCH 24/41] feat: Create generic AtomicWethDepositor (#1696) * feat: Create generic AtomicWethDepositor Signed-off-by: nicholaspai --------- Signed-off-by: nicholaspai Co-authored-by: bmzig <57361391+bmzig@users.noreply.github.com> Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- contracts/AtomicWethDepositor.sol | 197 ++++++++++++------------------ 1 file changed, 75 insertions(+), 122 deletions(-) diff --git a/contracts/AtomicWethDepositor.sol b/contracts/AtomicWethDepositor.sol index 2808bf81d..437cb2a11 100644 --- a/contracts/AtomicWethDepositor.sol +++ b/contracts/AtomicWethDepositor.sol @@ -1,144 +1,97 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.0; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@uma/core/contracts/common/implementation/MultiCaller.sol"; +import "@uma/core/contracts/common/implementation/Lockable.sol"; + interface Weth { function withdraw(uint256 _wad) external; function transferFrom(address _from, address _to, uint256 _wad) external; } -interface OvmL1Bridge { - function depositETHTo(address _to, uint32 _l2Gas, bytes calldata _data) external payable; -} +/** + * @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain + * bridges for chains that only support bridging of ETH not WETH. + * @dev This contract is ownable so that the owner can update whitelisted bridge addresses and function selectors. + */ +contract AtomicWethDepositor is Ownable, MultiCaller, Lockable { + // The Bridge used to send ETH to another chain. Only the function selector can be used when + // calling the bridge contract. + struct Bridge { + address bridge; + bytes4 funcSelector; + } -interface PolygonL1Bridge { - function depositEtherFor(address _to) external payable; -} + Weth public immutable WETH = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); -interface ZkSyncL1Bridge { - function requestL2Transaction( - address _contractL2, - uint256 _l2Value, - bytes calldata _calldata, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit, - bytes[] calldata _factoryDeps, - address _refundRecipient - ) external payable; - - function l2TransactionBaseCost( - uint256 _gasPrice, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit - ) external pure returns (uint256); -} + /** + * @notice Mapping of chain ID to whitelisted bridge addresses and function selectors + * that can be called by this contract. + */ + mapping(uint256 => Bridge) public whitelistedBridgeFunctions; -interface LineaL1MessageService { - function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable; -} + /////////////////////////////// + // Events // + /////////////////////////////// -/** - * @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain - * bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH, - * not WETH. - */ + event AtomicWethDepositInitiated(address indexed from, uint256 indexed chainId, uint256 amount); -contract AtomicWethDepositor { - Weth public immutable weth = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); - OvmL1Bridge public immutable optimismL1Bridge = OvmL1Bridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1); - OvmL1Bridge public immutable modeL1Bridge = OvmL1Bridge(0x735aDBbE72226BD52e818E7181953f42E3b0FF21); - OvmL1Bridge public immutable bobaL1Bridge = OvmL1Bridge(0xdc1664458d2f0B6090bEa60A8793A4E66c2F1c00); - OvmL1Bridge public immutable baseL1Bridge = OvmL1Bridge(0x3154Cf16ccdb4C6d922629664174b904d80F2C35); - OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08); - OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69); - OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524); - OvmL1Bridge public immutable worldChainL1Bridge = OvmL1Bridge(0x470458C91978D2d929704489Ad730DC3E3001113); - OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631); - PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77); - ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324); - LineaL1MessageService public immutable lineaL1MessageService = - LineaL1MessageService(0xd19d4B5d358258f05D7B411E21A1460D11B0876F); - - event ZkSyncEthDepositInitiated(address indexed from, address indexed to, uint256 amount); - event LineaEthDepositInitiated(address indexed from, address indexed to, uint256 amount); - event OvmEthDepositInitiated(uint256 indexed chainId, address indexed from, address indexed to, uint256 amount); - - function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public { - weth.transferFrom(msg.sender, address(this), amount); - weth.withdraw(amount); - - if (chainId == 10) { - optimismL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 8453) { - baseL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 34443) { - modeL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 480) { - worldChainL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 1135) { - liskL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 81457) { - blastL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 690) { - redstoneL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 7777777) { - zoraL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else if (chainId == 288) { - bobaL1Bridge.depositETHTo{ value: amount }(to, l2Gas, ""); - } else { - revert("Invalid OVM chainId"); - } - - emit OvmEthDepositInitiated(chainId, msg.sender, to, amount); - } + /////////////////////////////// + // Errors // + /////////////////////////////// + + error InvalidBridgeFunction(); + + /////////////////////////////// + // Internal Functions // + /////////////////////////////// - function bridgeWethToPolygon(address to, uint256 amount) public { - weth.transferFrom(msg.sender, address(this), amount); - weth.withdraw(amount); - polygonL1Bridge.depositEtherFor{ value: amount }(to); + /** + * @notice Transfers WETH to this contract and withdraws it to ETH. + * @param amount The amount of WETH to withdraw. + */ + function _withdrawWeth(uint256 amount) internal { + WETH.transferFrom(msg.sender, address(this), amount); + WETH.withdraw(amount); } - function bridgeWethToLinea(address to, uint256 amount) public payable { - weth.transferFrom(msg.sender, address(this), amount); - weth.withdraw(amount); - lineaL1MessageService.sendMessage{ value: amount + msg.value }(to, msg.value, ""); - // Emit an event that we can easily track in the Linea-related adapters/finalizers - emit LineaEthDepositInitiated(msg.sender, to, amount); + /////////////////////////////// + // Admin Functions // + /////////////////////////////// + + /** + * @notice Whitelists function selector and bridge contract for chain. + * @param chainId The chain ID of the bridge. + * @param bridge The address of the bridge contract to call to bridge ETH to the chain. + * @param funcSelector The function selector of the bridge contract. + */ + function whitelistBridge(uint256 chainId, address bridge, bytes4 funcSelector) public onlyOwner { + whitelistedBridgeFunctions[chainId] = Bridge({ bridge: bridge, funcSelector: funcSelector }); } - function bridgeWethToZkSync( - address to, - uint256 amount, - uint256 l2GasLimit, - uint256 l2GasPerPubdataByteLimit, - address refundRecipient - ) public { - // The ZkSync Mailbox contract checks that the msg.value of the transaction is enough to cover the transaction base - // cost. The transaction base cost can be queried from the Mailbox by passing in an L1 "executed" gas price, - // which is the priority fee plus base fee. This is the same as calling tx.gasprice on-chain as the Mailbox - // contract does here: - // https://github.com/matter-labs/era-contracts/blob/3a4506522aaef81485d8abb96f5a6394bd2ba69e/ethereum/contracts/zksync/facets/Mailbox.sol#L287 - uint256 l2TransactionBaseCost = zkSyncL1Bridge.l2TransactionBaseCost( - tx.gasprice, - l2GasLimit, - l2GasPerPubdataByteLimit - ); - uint256 valueToSubmitXChainMessage = l2TransactionBaseCost + amount; - weth.transferFrom(msg.sender, address(this), valueToSubmitXChainMessage); - weth.withdraw(valueToSubmitXChainMessage); - zkSyncL1Bridge.requestL2Transaction{ value: valueToSubmitXChainMessage }( - to, - amount, - "", - l2GasLimit, - l2GasPerPubdataByteLimit, - new bytes[](0), - refundRecipient - ); - - // Emit an event that we can easily track in the ZkSyncAdapter because otherwise there is no easy event to - // track ETH deposit initiations. - emit ZkSyncEthDepositInitiated(msg.sender, to, amount); + /////////////////////////////// + // Public Functions // + /////////////////////////////// + + /** + * @notice Initiates a WETH deposit to a whitelisted bridge for a specified chain with user calldata. + * @dev Requires that the owner of this contract has whitelisted the bridge contract and function + * selector for the chainId that the user wants to send ETH to. + * @param value The amount of WETH to deposit. + * @param chainId The chain to send ETH to. + * @param bridgeCallData The calldata to pass to the bridge contract. The first 4 bytes should be equal + * to the whitelisted function selector of the bridge contract. + */ + function bridgeWeth(uint256 chainId, uint256 value, bytes calldata bridgeCallData) public nonReentrant { + _withdrawWeth(value); + Bridge memory bridge = whitelistedBridgeFunctions[chainId]; + if (bridge.funcSelector != bytes4(bridgeCallData)) revert InvalidBridgeFunction(); + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory result) = bridge.bridge.call{ value: value }(bridgeCallData); + require(success, string(result)); + emit AtomicWethDepositInitiated(msg.sender, chainId, value); } fallback() external payable {} From bdf41c757c44a84562dbd6102d3e9cd1e0deb71d Mon Sep 17 00:00:00 2001 From: Matt Rice Date: Fri, 25 Oct 2024 17:54:18 -0400 Subject: [PATCH 25/41] feat: add machine-readable JSON report to reporter (#1867) * feat: add machine-readable JSON report to reporter Signed-off-by: Matt Rice * lint Signed-off-by: Matt Rice --------- Signed-off-by: Matt Rice --- src/monitor/Monitor.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index e2004fc5f..2cf4155ab 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -8,6 +8,8 @@ import { L1Token, RelayerBalanceReport, RelayerBalanceTable, + RelayerBalanceColumns, + RelayerBalanceCell, TokenTransfer, } from "../interfaces"; import { @@ -46,6 +48,8 @@ import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; import { MonitorConfig } from "./MonitorConfig"; import { CombinedRefunds } from "../dataworker/DataworkerUtils"; +import lodash from "lodash"; + // 60 minutes, which is the length of the challenge window, so if a rebalance takes longer than this to finalize, // then its finalizing after the subsequent challenge period has started, which is sub-optimal. export const REBALANCE_FINALIZE_GRACE_PERIOD = Number(process.env.REBALANCE_FINALIZE_GRACE_PERIOD ?? 60 * 60); @@ -293,6 +297,25 @@ export class Monitor { message: `Balance report for ${relayer} 📖`, mrkdwn, }); + + // Note: types are here for clarity, not necessity. + const machineReadableReport = lodash.mapValues(reports, (table: RelayerBalanceTable) => + lodash.mapValues(table, (columns: RelayerBalanceColumns, tokenSymbol: string) => { + const decimals = allL1Tokens.find((token) => token.symbol === tokenSymbol)?.decimals; + if (!decimals) { + throw new Error(`No decimals found for ${tokenSymbol}`); + } + return lodash.mapValues(columns, (cell: RelayerBalanceCell) => + lodash.mapValues(cell, (balance: BigNumber) => convertFromWei(balance.toString(), decimals)) + ); + }) + ); + + this.logger.debug({ + at: "Monitor#reportRelayerBalances", + message: "Machine-readable balance report", + report: machineReadableReport, + }); } } From d44d140feaf42a64a575d1aac35499fd19825383 Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Mon, 28 Oct 2024 08:31:27 -0500 Subject: [PATCH 26/41] improve(finalizer): make finalizer logs more human-readable (#1871) * improve(finalizer): make finalizer logs more human-readable Signed-off-by: bennett --------- Signed-off-by: bennett Co-authored-by: Matt Rice --- src/finalizer/utils/cctp/l1ToL2.ts | 3 ++- src/finalizer/utils/cctp/l2ToL1.ts | 3 ++- src/finalizer/utils/scroll.ts | 12 ++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index e80c786ec..aca9992b1 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -19,6 +19,7 @@ import { isDefined, paginatedEventQuery, winston, + convertFromWei, } from "../../../utils"; import { CCTPMessageStatus, DecodedCCTPMessage, resolveCCTPRelatedTxns } from "../../../utils/CCTPUtils"; import { FinalizerPromise, CrossChainMessage } from "../../types"; @@ -147,7 +148,7 @@ async function generateDepositData( ): Promise { return messages.map((message) => ({ l1TokenSymbol: "USDC", // Always USDC b/c that's the only token we support on CCTP - amount: formatUnitsForToken("USDC", message.amount), // Format out to 6 decimal places for USDC + amount: convertFromWei(message.amount, TOKEN_SYMBOLS_MAP.USDC.decimals), // Format out to 6 decimal places for USDC type: "deposit", originationChainId, destinationChainId, diff --git a/src/finalizer/utils/cctp/l2ToL1.ts b/src/finalizer/utils/cctp/l2ToL1.ts index 2c4cbdece..7d369a096 100644 --- a/src/finalizer/utils/cctp/l2ToL1.ts +++ b/src/finalizer/utils/cctp/l2ToL1.ts @@ -16,6 +16,7 @@ import { groupObjectCountsByProp, isDefined, winston, + convertFromWei, } from "../../../utils"; import { CCTPMessageStatus, DecodedCCTPMessage, resolveCCTPRelatedTxns } from "../../../utils/CCTPUtils"; import { FinalizerPromise, CrossChainMessage } from "../../types"; @@ -127,7 +128,7 @@ async function generateWithdrawalData( ): Promise { return messages.map((message) => ({ l1TokenSymbol: "USDC", // Always USDC b/c that's the only token we support on CCTP - amount: message.amount, + amount: convertFromWei(message.amount, TOKEN_SYMBOLS_MAP.USDC.decimals), type: "withdrawal", originationChainId, destinationChainId, diff --git a/src/finalizer/utils/scroll.ts b/src/finalizer/utils/scroll.ts index adddeca01..98ce04e3a 100644 --- a/src/finalizer/utils/scroll.ts +++ b/src/finalizer/utils/scroll.ts @@ -4,7 +4,15 @@ import { TransactionRequest } from "@ethersproject/abstract-provider"; import axios from "axios"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { CONTRACT_ADDRESSES, Multicall2Call } from "../../common"; -import { Contract, Signer, getBlockForTimestamp, getCurrentTime, getRedisCache, winston } from "../../utils"; +import { + Contract, + Signer, + getBlockForTimestamp, + getCurrentTime, + getRedisCache, + winston, + convertFromWei, +} from "../../utils"; import { FinalizerPromise, CrossChainMessage } from "../types"; // The highest amount of pending finalizations the scroll API can return. @@ -169,7 +177,7 @@ function populateClaimWithdrawal( return { originationChainId: l2ChainId, l1TokenSymbol: l1Token.symbol, - amount: claim.tokenAmount, + amount: convertFromWei(claim.tokenAmount, l1Token.decimals), type: "withdrawal", destinationChainId: hubPoolClient.chainId, // Always on L1 }; From 6892f7e26fc08061903be7169e819a7e134c0bc5 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:23:35 -0400 Subject: [PATCH 27/41] feat(InventoryClient): Wrap ETH on Mainnet (#1882) * feat(InventoryClient): Wrap ETH on Mainnet Adds lightweight Ethereum adapter that inventory client can use to wrap ETH. Has no other side effects * lint * Update src/adapter/BaseChainAdapter.ts Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --------- Co-authored-by: Paul <108695806+pxrl@users.noreply.github.com> --- src/adapter/BaseChainAdapter.ts | 7 ++++++- src/clients/bridges/AdapterManager.ts | 12 ++++++++++-- src/clients/bridges/EthereumAdapter.ts | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 src/clients/bridges/EthereumAdapter.ts diff --git a/src/adapter/BaseChainAdapter.ts b/src/adapter/BaseChainAdapter.ts index 4ef899f3b..d72b83fd6 100644 --- a/src/adapter/BaseChainAdapter.ts +++ b/src/adapter/BaseChainAdapter.ts @@ -208,7 +208,12 @@ export class BaseChainAdapter { } const value = ethBalance.sub(target); - this.log("Wrapping ETH", { threshold, target, value, ethBalance }, "debug", "wrapEthIfAboveThreshold"); + this.log( + `Wrapping ETH on chain ${getNetworkName(this.chainId)}`, + { threshold, target, value, ethBalance }, + "debug", + "wrapEthIfAboveThreshold" + ); const method = "deposit"; const formatFunc = createFormatFunction(2, 4, false, 18); const mrkdwn = diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index e232d5089..3e9866064 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -13,6 +13,7 @@ import { ArbitrumAdapter, PolygonAdapter, ZKSyncAdapter, LineaAdapter, ScrollAda import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; import { BaseChainAdapter } from "../../adapter"; +import { EthereumAdapter } from "./EthereumAdapter"; export class AdapterManager { public adapters: { [chainId: number]: BaseChainAdapter } = {}; @@ -21,7 +22,7 @@ export class AdapterManager { // receiving ETH that needs to be wrapped on the L2. This array contains the chainIds of the chains that this // manager will attempt to wrap ETH on into WETH. This list also includes chains like Arbitrum where the relayer is // expected to receive ETH as a gas refund from an L1 to L2 deposit that was intended to rebalance inventory. - public chainsToWrapEtherOn = [...spokesThatHoldEthAndWeth, CHAIN_IDs.ARBITRUM]; + private chainsToWrapEtherOn = [...spokesThatHoldEthAndWeth, CHAIN_IDs.ARBITRUM, CHAIN_IDs.MAINNET]; constructor( readonly logger: winston.Logger, @@ -232,7 +233,14 @@ export class AdapterManager { wrapThreshold.gte(wrapTarget), `wrapEtherThreshold ${wrapThreshold.toString()} must be >= wrapEtherTarget ${wrapTarget.toString()}` ); - await this.adapters[chainId].wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode); + if (chainId === CHAIN_IDs.MAINNET) { + // For mainnet, construct one-off adapter to wrap ETH, because Ethereum is typically not a chain + // that we have an adapter for. + const ethAdapter = new EthereumAdapter(this.logger, this.spokePoolClients); + await ethAdapter.wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode); + } else { + await this.adapters[chainId].wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode); + } } ); } diff --git a/src/clients/bridges/EthereumAdapter.ts b/src/clients/bridges/EthereumAdapter.ts new file mode 100644 index 000000000..03f4787a3 --- /dev/null +++ b/src/clients/bridges/EthereumAdapter.ts @@ -0,0 +1,25 @@ +import { DEFAULT_GAS_MULTIPLIER } from "../../common"; +import { CHAIN_IDs, winston } from "../../utils"; +import { SpokePoolClient } from "../SpokePoolClient"; +import { BaseChainAdapter } from "../../adapter/BaseChainAdapter"; + +// This adapter is only used by the AdapterManager to wrap ETH on Mainnet, so we don't pass in any supported +// tokens or bridges. +export class EthereumAdapter extends BaseChainAdapter { + constructor(logger: winston.Logger, readonly spokePoolClients: { [chainId: number]: SpokePoolClient }) { + const { MAINNET } = CHAIN_IDs; + const bridges = {}; + const supportedTokens = []; + const monitoredAddresses = []; + super( + spokePoolClients, + MAINNET, + MAINNET, + monitoredAddresses, + logger, + supportedTokens, + bridges, + DEFAULT_GAS_MULTIPLIER[MAINNET] ?? 1 + ); + } +} From 6395d1782c3d99d7779a15410d517427c736b72d Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:35:25 -0500 Subject: [PATCH 28/41] fix: define OptimismPortal2 and DisputeGameFactory for Base (#1883) Signed-off-by: bennett --- src/common/Constants.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 05d0a447e..654fcad38 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -540,6 +540,22 @@ export const EXPECTED_L1_TO_L2_MESSAGE_TIME = { }; export const OPSTACK_CONTRACT_OVERRIDES = { + [CHAIN_IDs.BASE]: { + // https://github.com/ethereum-optimism/ecosystem/blob/8df6ab1afcf49312dc7e89ed079f910843d74427/packages/sdk/src/utils/chain-constants.ts#L252 + l1: { + AddressManager: "0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2", + L1CrossDomainMessenger: "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa", + L1StandardBridge: CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET].ovmStandardBridge_8453.address, + StateCommitmentChain: ZERO_ADDRESS, + CanonicalTransactionChain: ZERO_ADDRESS, + BondManager: ZERO_ADDRESS, + OptimismPortal: "0x49048044D57e1C92A77f79988d21Fa8fAF74E97e", + L2OutputOracle: "0x56315b90c40730925ec5485cf004d835058518A0", + OptimismPortal2: "0x49048044D57e1C92A77f79988d21Fa8fAF74E97e", + DisputeGameFactory: "0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e", + }, + l2: DEFAULT_L2_CONTRACT_ADDRESSES, + }, [CHAIN_IDs.BLAST]: { l1: { AddressManager: "0xE064B565Cf2A312a3e66Fe4118890583727380C0", From 6bc6acbf5156c4a6d6ce3441e42804011b346740 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:09:09 -0400 Subject: [PATCH 29/41] improve: Remove FundsDeposited and FilledRelay mentions (#1884) Same idea as across-protocol/sdk#762 --- src/dataworker/README.md | 41 +++++++++++++++------------- test/Dataworker.loadData.fill.ts | 16 +++++------ test/Dataworker.loadData.slowFill.ts | 2 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/dataworker/README.md b/src/dataworker/README.md index 37eb36e12..265ca7671 100644 --- a/src/dataworker/README.md +++ b/src/dataworker/README.md @@ -123,41 +123,44 @@ flowchart TD ### Validating fills -A fill must match a deposit on every shared parameter that they have in common. The matched deposit does not have to be in the same bundle as the fill. A fill contains the following [event parameter](https://github.com/across-protocol/contracts/blob/master/contracts/SpokePool.sol#L139)'s: +A fill must match a deposit on every shared parameter that they have in common. The matched deposit does not have to be in the same bundle as the fill. A fill contains the following [event parameter](https://github.com/across-protocol/contracts/blob/a663586e8619bc74cb1da2375107bd5eef0f3144/contracts/interfaces/V3SpokePoolInterface.sol#L124)'s: ```solidity -event FilledRelay( - uint256 amount, - uint256 totalFilledAmount, - uint256 fillAmount, +event FilledV3Relay( + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, uint256 repaymentChainId, uint256 indexed originChainId, - uint256 destinationChainId, - int64 relayerFeePct, - int64 realizedLpFeePct, uint32 indexed depositId, - address destinationToken, - address relayer, - address indexed depositor, + uint32 fillDeadline, + uint32 exclusivityDeadline, + address exclusiveRelayer, + address indexed relayer, + address depositor, address recipient, bytes message, - RelayExecutionInfo updatableRelayData + V3RelayExecutionEventInfo relayExecutionInfo ); ``` -A [deposit](https://github.com/across-protocol/contracts/blob/master/contracts/SpokePool.sol#L119) contains: +A [deposit](https://github.com/across-protocol/contracts/blob/a663586e8619bc74cb1da2375107bd5eef0f3144/contracts/interfaces/V3SpokePoolInterface.sol#L99) contains: ```solidity -event FundsDeposited( - uint256 amount, - uint256 originChainId, +event V3FundsDeposited( + address inputToken, + address outputToken, + uint256 inputAmount, + uint256 outputAmount, uint256 indexed destinationChainId, - int64 relayerFeePct, uint32 indexed depositId, uint32 quoteTimestamp, - address originToken, - address recipient, + uint32 fillDeadline, + uint32 exclusivityDeadline, address indexed depositor, + address recipient, + address exclusiveRelayer, bytes message ); ``` diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 6d0d4c66d..d5e2a0a30 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -231,7 +231,7 @@ describe("Dataworker: Load data used in all functions", async function () { // Send expired deposit const expiredDeposits = [generateV3Deposit({ fillDeadline: bundleBlockTimestamps[destinationChainId][1] - 1 })]; const depositEvents = [...unexpiredDeposits, ...expiredDeposits]; - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients @@ -258,7 +258,7 @@ describe("Dataworker: Load data used in all functions", async function () { const originChainBlockRange = [deposits[1].blockNumber - 1, deposits[1].blockNumber + 1]; // Substitute origin chain bundle block range. const bundleBlockRanges = [originChainBlockRange].concat(getDefaultBlockRange(5).slice(1)); - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); expect(mockOriginSpokePoolClient.getDeposits().length).to.equal(deposits.length); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleDepositsV3[originChainId][erc20_1.address].length).to.equal(1); @@ -299,7 +299,7 @@ describe("Dataworker: Load data used in all functions", async function () { depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); // Fill deposits from different relayers @@ -307,7 +307,7 @@ describe("Dataworker: Load data used in all functions", async function () { fillV3Events.push(generateV3FillFromDeposit(deposits[0])); fillV3Events.push(generateV3FillFromDeposit(deposits[1])); fillV3Events.push(generateV3FillFromDeposit(deposits[2], {}, relayer2)); - await mockDestinationSpokePoolClient.update(["FilledV3Relay", "FilledRelay"]); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients @@ -361,7 +361,7 @@ describe("Dataworker: Load data used in all functions", async function () { depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); depositV3Events.push(generateV3Deposit({ outputToken: randomAddress() })); - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); // Fill deposits from different relayers @@ -369,7 +369,7 @@ describe("Dataworker: Load data used in all functions", async function () { fillV3Events.push(generateV3FillFromDeposit(deposits[0])); fillV3Events.push(generateV3FillFromDeposit(deposits[1])); fillV3Events.push(generateV3FillFromDeposit(deposits[2], {}, relayer2)); - await mockDestinationSpokePoolClient.update(["FilledV3Relay", "FilledRelay"]); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData( getDefaultBlockRange(5), spokePoolClients @@ -548,7 +548,7 @@ describe("Dataworker: Load data used in all functions", async function () { generateV3Deposit({ outputToken: randomAddress() }); generateV3Deposit({ outputToken: randomAddress() }); generateV3Deposit({ outputToken: randomAddress() }); - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); const fills = [ @@ -569,7 +569,7 @@ describe("Dataworker: Load data used in all functions", async function () { const destinationChainIndex = dataworkerInstance.chainIdListForBundleEvaluationBlockNumbers.indexOf(destinationChainId); bundleBlockRanges[destinationChainIndex] = destinationChainBlockRange; - await mockDestinationSpokePoolClient.update(["FilledV3Relay", "FilledRelay"]); + await mockDestinationSpokePoolClient.update(["FilledV3Relay"]); expect(mockDestinationSpokePoolClient.getFills().length).to.equal(fills.length); const data1 = await dataworkerInstance.clients.bundleDataClient.loadData(bundleBlockRanges, spokePoolClients); expect(data1.bundleFillsV3[repaymentChainId][l1Token_1.address].fills.length).to.equal(1); diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index 8ae55d4f0..1fb54e715 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -428,7 +428,7 @@ describe("BundleDataClient: Slow fill handling & validation", async function () generateV3Deposit({ outputToken: erc20_2.address }); generateV3Deposit({ outputToken: erc20_2.address }); generateV3Deposit({ outputToken: erc20_2.address }); - await mockOriginSpokePoolClient.update(["FundsDeposited", "V3FundsDeposited"]); + await mockOriginSpokePoolClient.update(["V3FundsDeposited"]); const deposits = mockOriginSpokePoolClient.getDeposits(); const events = [ From 5f37034039dc323b986e2970c806194ece5fa948 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 1 Nov 2024 22:26:33 +0100 Subject: [PATCH 30/41] chore: Bump SDK (#1889) Relayer performance should improve with an optimisation in the SDK provider layer. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6d074695c..278bd9fe8 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.16", "@across-protocol/contracts": "^3.0.11", - "@across-protocol/sdk": "^3.2.7", + "@across-protocol/sdk": "^3.2.11", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/yarn.lock b/yarn.lock index effb7bb56..7f13b4ab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -45,10 +45,10 @@ axios "^1.7.4" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.2.7": - version "3.2.7" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.7.tgz#c0d2ad49065a33d05f01f846e328989c4e6585c2" - integrity sha512-oUZ8uIw10/y+ZsRjlZTivJnK3AGpwCJbt3VLMHhv6TUYGNcQW/Vr71LFhyeieKfoY4+lGZq/UV4QIqsNSWiSZA== +"@across-protocol/sdk@^3.2.11": + version "3.2.11" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.11.tgz#8d5190863240e9a5062a38a03406f8e3b0390d84" + integrity sha512-eNUKYlV1ClzVHFHwDDRKaK2u/eFCTrZrCTglATtuQVJCUlpLlIR369qx/kquVbH6E5rRjSZm2Lo9hiuUJ3oH2Q== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.16" From f1ce2097d7dd96cd2a9f914dfb853479253d80ee Mon Sep 17 00:00:00 2001 From: Dong-Ha Kim Date: Mon, 4 Nov 2024 10:54:55 +0100 Subject: [PATCH 31/41] fix: error log stringify (#1880) --- index.ts | 17 +++++++++++++---- src/clients/TransactionClient.ts | 3 ++- src/utils/LogUtils.ts | 14 ++++++++++++++ src/utils/TransactionUtils.ts | 5 +++-- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/index.ts b/index.ts index 1fa60dae5..01dd29a63 100644 --- a/index.ts +++ b/index.ts @@ -1,5 +1,15 @@ import minimist from "minimist"; -import { config, delay, exit, retrieveSignerFromCLIArgs, help, Logger, usage, waitForLogger } from "./src/utils"; +import { + config, + delay, + exit, + retrieveSignerFromCLIArgs, + help, + Logger, + usage, + waitForLogger, + stringifyThrownValue, +} from "./src/utils"; import { runRelayer } from "./src/relayer"; import { runDataworker } from "./src/dataworker"; import { runMonitor } from "./src/monitor"; @@ -61,12 +71,11 @@ if (require.main === module) { run(args) .catch(async (error) => { exitCode = 1; + const stringifiedError = stringifyThrownValue(error); logger.error({ at: cmd ?? "unknown process", message: "There was an execution error!", - reason: error, - e: error, - error, + error: stringifiedError, args, notificationPath: "across-error", }); diff --git a/src/clients/TransactionClient.ts b/src/clients/TransactionClient.ts index ffb38b1f5..3c8aacc86 100644 --- a/src/clients/TransactionClient.ts +++ b/src/clients/TransactionClient.ts @@ -11,6 +11,7 @@ import { TransactionResponse, TransactionSimulationResult, willSucceed, + stringifyThrownValue, } from "../utils"; export interface AugmentedTransaction { @@ -100,7 +101,7 @@ export class TransactionClient { mrkdwn, // @dev `error` _sometimes_ doesn't decode correctly (especially on Polygon), so fish for the reason. errorMessage: isError(error) ? (error as Error).message : undefined, - error, + error: stringifyThrownValue(error), notificationPath: "across-error", }); return txnResponses; diff --git a/src/utils/LogUtils.ts b/src/utils/LogUtils.ts index b84973e95..0161fbbc9 100644 --- a/src/utils/LogUtils.ts +++ b/src/utils/LogUtils.ts @@ -1 +1,15 @@ export type DefaultLogLevels = "debug" | "info" | "warn" | "error"; + +export function stringifyThrownValue(value: unknown): string { + if (value instanceof Error) { + const errToString = value.toString(); + return value.stack || value.message || errToString !== "[object Object]" + ? errToString + : "could not extract error from 'Error' instance"; + } else if (value instanceof Object) { + const objStringified = JSON.stringify(value); + return objStringified !== "{}" ? objStringified : "could not extract error from 'Object' instance"; + } else { + return `ThrownValue: ${value.toString()}`; + } +} diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index 6670bdcce..6db601be4 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -16,6 +16,7 @@ import { Signer, toBNWei, winston, + stringifyThrownValue, } from "../utils"; dotenv.config(); @@ -107,7 +108,7 @@ export async function runTransaction( logger.debug({ at: "TxUtil#runTransaction", message: "Retrying txn due to expected error", - error: JSON.stringify(error), + error: stringifyThrownValue(error), retriesRemaining, }); @@ -143,7 +144,7 @@ export async function runTransaction( } else { logger[txnRetryable(error) ? "warn" : "error"]({ ...commonFields, - error: JSON.stringify(error), + error: stringifyThrownValue(error), }); } throw error; From 30b7003bac89168dd0849a419d696f8ca92e8edb Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Mon, 4 Nov 2024 16:27:17 -0500 Subject: [PATCH 32/41] improve(README): Remove more legacy references (#1886) * improve: Remove FundsDeposited and FilledRelay mentions * improve(README): Remove more legacy references forgotten commit from 7802c9420b27ea8fcce1c9dfce95dd76f80c2359 * Update README.md * Update src/dataworker/README.md * Update src/dataworker/README.md * Update src/dataworker/README.md --- src/dataworker/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dataworker/README.md b/src/dataworker/README.md index 265ca7671..575190851 100644 --- a/src/dataworker/README.md +++ b/src/dataworker/README.md @@ -2,6 +2,8 @@ This document explains how the Dataworker constructs "root bundles", which instruct the Across system on how to reallocate LP capital to keep the system functioning and providing an effective bridging service. +The following README is a simplified summary of the exact protocol rules described in [UMIP-179](https://github.com/UMAprotocol/UMIPs/blob/2046d3cc228cacfb27688fc02e9674aef13445c9/UMIPs/umip-179.md) and its predecessor [UMIP-157](https://github.com/UMAprotocol/UMIPs/blob/92abcfcb676b0c24690811751521cd0782af5372/UMIPs/umip-157). These UMIP's describe the explicit rules that the Dataworker implements. In other words, the "Dataworker" code is an implementation of the UMIP-179 "interface". + ## Why does Across need to move capital around? There are two types of capital pools in Across: liquidity provider capital in the `HubPool` ("LP capital") and capital held in the `SpokePools` ("SpokePool balance"). @@ -165,9 +167,9 @@ event V3FundsDeposited( ); ``` -Therefore, `amount`, `originChainId`, `destinationChainId`, `relayerFeePct`, `depositId`, `destinationToken`, `message`, and `recipient` must match. In addition, the fill's matched deposit must have been emitted from the SpokePool at the `originChainId` of the fill. The fill must have been emitted from the SpokePool on the `destinationChainId`. +All of the shared event properties must match. In addition, the fill's matched deposit must have been emitted from the SpokePool at the `originChainId` of the fill. The fill must have been emitted from the SpokePool on the `destinationChainId`. -Finally, the fill's `realizedLpFeePct` must be correct. Currently this is deterministically linked with the deposit's `quoteTimestamp`: there is a correct `realizedLpFeePct` for each `quoteTimestamp`, which is computed by querying the HubPool's "utilization" at the Ethereum block height corresponding to the `quoteTimestamp`. This is described in the [UMIP here](https://github.com/UMAprotocol/UMIPs/blob/e3198578b1d339914afa5243a80e3ac8055fba34/UMIPs/umip-157.md#validating-realizedlpfeepct). +The only exception to the above rule is if `outputToken` is equal to the zero address, then the "equivalent" token address should be substituted in as described in [UMIP-179](https://github.com/UMAprotocol/UMIPs/blob/2046d3cc228cacfb27688fc02e9674aef13445c9/UMIPs/umip-179.md#finding-valid-relays). ## Incorporating slow fills @@ -234,7 +236,7 @@ This is everything that the Dataworker needs to construct a root bundle! All tha Root bundle merkle leaf formats -- [PoolRebalanceLeaf](https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/HubPoolInterface.sol#L11): One per chain -- [RelayerRefundLeaf](https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/SpokePoolInterface.sol#L9) One per token per chain -- [SlowFillLeaf](https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/SpokePoolInterface.sol#L29) One per unfilled deposit -- [RootBundle](https://github.com/across-protocol/contracts/blob/master/contracts/interfaces/HubPoolInterface.sol#L53) how the Dataworker's proposal is stored in the HubPool throughout its pending challenge window +- [PoolRebalanceLeaf](https://github.com/across-protocol/contracts/blob/95c4f923932d597d3e63449718bba5c674b084eb/contracts/interfaces/HubPoolInterface.sol#L11): One per chain +- [RelayerRefundLeaf](https://github.com/across-protocol/contracts/blob/95c4f923932d597d3e63449718bba5c674b084eb/contracts/interfaces/SpokePoolInterface.sol#L9) One per token per chain +- [SlowFillLeaf](https://github.com/across-protocol/contracts/blob/95c4f923932d597d3e63449718bba5c674b084eb/contracts/interfaces/V3SpokePoolInterface.sol#L66) One per unfilled deposit +- [RootBundle](https://github.com/across-protocol/contracts/blob/95c4f923932d597d3e63449718bba5c674b084eb/contracts/interfaces/HubPoolInterface.sol#L53) how the Dataworker's proposal is stored in the HubPool throughout its pending challenge window From 10b8cdb6e3018791483b84cea9cd24af593ee80d Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:00:30 -0500 Subject: [PATCH 33/41] feat: handle Polygon finalizer edge case when CCTP limit is breached (#1897) * feat: handle Polygon finalizer edge case when CCTP limit is breached There is a bug in the Polygon finalizer where a single transaction contains multiple token withdrawals from Spoke to Hub in which more than 1 million USDC are withdrawn, resulting in a single `TokensBridged` event getting emitted but multiple USDC "burns" happening (due to the CCTP 1mil withdrawal limit). This messes up the `logIndex` computation math for any withdrawals that take place after the USDC withdrawals. This logic isn't needed in other chains with CCTP like Arbitrum or Optimism because the `logIndex` is relative to all other non-CCTP withdrawals, whereas in Polygon it seems that CCTP withdrawals also count towards the `logIndex` of non CCTP withdrawals * Update polygon.ts --- src/finalizer/utils/polygon.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/finalizer/utils/polygon.ts b/src/finalizer/utils/polygon.ts index c0c3d6f28..8a81bc8b3 100644 --- a/src/finalizer/utils/polygon.ts +++ b/src/finalizer/utils/polygon.ts @@ -16,6 +16,8 @@ import { compareAddressesSimple, TOKEN_SYMBOLS_MAP, CHAIN_IDs, + sortEventsAscending, + toBNWei, } from "../../utils"; import { EthersError, TokensBridged } from "../../interfaces"; import { HubPoolClient, SpokePoolClient } from "../../clients"; @@ -36,6 +38,9 @@ enum POLYGON_MESSAGE_STATUS { // canonical bridge. Do not change. const BURN_SIG = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; +// We should ideally read this limit from a contract call, but for now we'll hardcode it. +const CCTP_WITHDRAWAL_LIMIT_WEI = toBNWei(1_000_000, 6); + export interface PolygonTokensBridged extends TokensBridged { payload: string; } @@ -61,7 +66,26 @@ export async function polygonFinalizer( // Unlike the rollups, withdrawals process very quickly on polygon, so we can conservatively remove any events // that are older than 1 day old: - const recentTokensBridgedEvents = spokePoolClient.getTokensBridged().filter((e) => e.blockNumber >= fromBlock); + let recentTokensBridgedEvents = spokePoolClient.getTokensBridged().filter((e) => e.blockNumber >= fromBlock); + + // The SpokePool emits one TokensBridged event even if the token is USDC and it gets withdrawn in two separate + // CCTP events. We can't filter out these USDC events here (see comment below in `getFinalizableTransactions()`) + // but we do need to add in more TokensBridged events so that the call to `getUniqueLogIndex` will work. + recentTokensBridgedEvents.forEach((e) => { + if ( + compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_ID]) && + e.amountToReturn.gt(CCTP_WITHDRAWAL_LIMIT_WEI) + ) { + // Inject one TokensBridged event for each CCTP withdrawal that needs to be processed. + const numberOfEventsToAdd = Math.ceil(e.amountToReturn.div(CCTP_WITHDRAWAL_LIMIT_WEI).toNumber()); + for (let i = 0; i < numberOfEventsToAdd; i++) { + recentTokensBridgedEvents.push({ + ...e, + }); + } + } + }); + recentTokensBridgedEvents = sortEventsAscending(recentTokensBridgedEvents); return multicallPolygonFinalizations(recentTokensBridgedEvents, posClient, signer, hubPoolClient, logger); } From ba9a76da445ad4f78e8deaa207f2271affd70618 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:14:48 +0100 Subject: [PATCH 34/41] improve(finalizer): Don't globally suppress unused variables (#1894) --- src/finalizer/utils/cctp/l1ToL2.ts | 6 ++---- src/finalizer/utils/cctp/l2ToL1.ts | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index aca9992b1..fa7985b8a 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -1,15 +1,13 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { TransactionReceipt, TransactionRequest } from "@ethersproject/abstract-provider"; import { ethers } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; -import { CHAIN_MAX_BLOCK_LOOKBACK, CONTRACT_ADDRESSES, Multicall2Call, chainIdsToCctpDomains } from "../../../common"; +import { CHAIN_MAX_BLOCK_LOOKBACK, CONTRACT_ADDRESSES, Multicall2Call } from "../../../common"; import { Contract, EventSearchConfig, Signer, TOKEN_SYMBOLS_MAP, assert, - formatUnitsForToken, getBlockForTimestamp, getCachedProvider, getCurrentTime, @@ -28,7 +26,7 @@ import { uniqWith } from "lodash"; export async function cctpL1toL2Finalizer( logger: winston.Logger, - signer: Signer, + _signer: Signer, hubPoolClient: HubPoolClient, spokePoolClient: SpokePoolClient, l1ToL2AddressesToFinalize: string[] diff --git a/src/finalizer/utils/cctp/l2ToL1.ts b/src/finalizer/utils/cctp/l2ToL1.ts index 7d369a096..337f53b4c 100644 --- a/src/finalizer/utils/cctp/l2ToL1.ts +++ b/src/finalizer/utils/cctp/l2ToL1.ts @@ -1,8 +1,7 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ import { TransactionRequest } from "@ethersproject/abstract-provider"; import { ethers } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; -import { CONTRACT_ADDRESSES, Multicall2Call, chainIdsToCctpDomains } from "../../../common"; +import { CONTRACT_ADDRESSES, Multicall2Call } from "../../../common"; import { Contract, Signer, @@ -23,7 +22,7 @@ import { FinalizerPromise, CrossChainMessage } from "../../types"; export async function cctpL2toL1Finalizer( logger: winston.Logger, - signer: Signer, + _signer: Signer, hubPoolClient: HubPoolClient, spokePoolClient: SpokePoolClient ): Promise { From e2fa91f5a1dad94a1d02962703d500f4e0c1642f Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:36:13 +0100 Subject: [PATCH 35/41] fix(relayer): Don't fast fill any deposit made by a slow depositor (#1899) The existing logic incorrectly only short-circuits when the fill status is unfilled. Once this has happened, the fill status changes to SlowFillRequested and the relayer will proceed to evaluate it as per its normal fill logic. --- src/relayer/Relayer.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 7049ebb83..08d2b67a2 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -628,13 +628,15 @@ export class Relayer { } // If depositor is on the slow deposit list, then send a zero fill to initiate a slow relay and return early. - if (slowDepositors?.includes(depositor) && fillStatus === FillStatus.Unfilled) { - this.logger.debug({ - at: "Relayer::evaluateFill", - message: "Initiating slow fill for grey listed depositor", - depositor, - }); - this.requestSlowFill(deposit); + if (slowDepositors?.includes(depositor)) { + if (fillStatus === FillStatus.Unfilled) { + this.logger.debug({ + at: "Relayer::evaluateFill", + message: "Initiating slow fill for grey listed depositor", + depositor, + }); + this.requestSlowFill(deposit); + } return; } From 80b6bad19dbc9b822ad9849ebafa4495de2f18ef Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:45:25 +0100 Subject: [PATCH 36/41] fix: Use CacheProvider in script utils (#1900) This ensures that providers have cache and fallback behaviour. --- scripts/spokepool.ts | 11 ++++++----- scripts/utils.ts | 25 +++---------------------- 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/scripts/spokepool.ts b/scripts/spokepool.ts index 4d4d1b056..9f597984b 100644 --- a/scripts/spokepool.ts +++ b/scripts/spokepool.ts @@ -12,6 +12,7 @@ import { formatFeePct, getDeploymentBlockNumber, getNetworkName, + getProvider, getSigner, isDefined, resolveTokenSymbols, @@ -194,7 +195,7 @@ async function deposit(args: Record, signer: Signer): P const tokenSymbol = token.symbol.toUpperCase(); const amount = ethers.utils.parseUnits(baseAmount.toString(), args.decimals ? 0 : token.decimals); - const provider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(fromChainId)); + const provider = await getProvider(fromChainId); signer = signer.connect(provider); const spokePool = (await utils.getSpokePoolContract(fromChainId)).connect(signer); @@ -246,7 +247,7 @@ async function fillDeposit(args: Record, sign throw new Error(`Missing or malformed transaction hash: ${txnHash}`); } - const originProvider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(originChainId)); + const originProvider = await getProvider(originChainId); const originSpokePool = await utils.getSpokePoolContract(originChainId); const spokePools: { [chainId: number]: Contract } = {}; @@ -321,7 +322,7 @@ async function fillDeposit(args: Record, sign } const sender = await signer.getAddress(); - const destProvider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(destinationChainId)); + const destProvider = await getProvider(destinationChainId); const destSigner = signer.connect(destProvider); const erc20 = new Contract(outputToken, ERC20.abi, destSigner); @@ -348,7 +349,7 @@ async function dumpConfig(args: Record, _signer: Signer const _spokePool = await utils.getSpokePoolContract(chainId); const hubChainId = utils.resolveHubChainId(chainId); - const spokeProvider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(chainId)); + const spokeProvider = await getProvider(chainId); const spokePool = _spokePool.connect(spokeProvider); const [spokePoolChainId, hubPool, crossDomainAdmin, weth, _currentTime] = await Promise.all([ @@ -436,7 +437,7 @@ async function fetchTxn(args: Record, _signer: Signer): return false; } - const provider = new ethers.providers.StaticJsonRpcProvider(utils.getProviderUrl(chainId)); + const provider = await getProvider(chainId); const spokePool = (await utils.getSpokePoolContract(chainId)).connect(provider); let deposits: Log[] = []; diff --git a/scripts/utils.ts b/scripts/utils.ts index 701620b03..2018077c9 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -1,9 +1,9 @@ import assert from "assert"; -import { Contract, ethers, utils as ethersUtils, Signer } from "ethers"; +import { Contract, utils as ethersUtils, Signer } from "ethers"; import readline from "readline"; import * as contracts from "@across-protocol/contracts"; import { utils as sdkUtils } from "@across-protocol/sdk"; -import { getDeployedContract, getNodeUrlList, CHAIN_IDs } from "../src/utils"; +import { getDeployedContract, getProvider, CHAIN_IDs } from "../src/utils"; // https://nodejs.org/api/process.html#exit-codes export const NODE_SUCCESS = 0; @@ -16,12 +16,6 @@ export type ERC20 = { symbol: string; }; -// Public RPC endpoints to be used if preferred providers are not defined in the environment. -const fallbackProviders: { [chainId: number]: string } = { - [CHAIN_IDs.MAINNET]: "https://eth.llamarpc.com", - [CHAIN_IDs.SEPOLIA]: "https://sepolia.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161", -}; - async function askQuestion(query: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); @@ -81,19 +75,6 @@ export function validateChainIds(chainIds: number[]): boolean { ); } -/** - * @description Resolve a default provider URL. - * @param chainId Chain ID for the provider to select. - * @returns URL of the provider endpoint. - */ -export function getProviderUrl(chainId: number): string { - try { - return getNodeUrlList(chainId, 1)[0]; - } catch { - return fallbackProviders[chainId]; - } -} - /** * @description For a SpokePool chain ID, resolve its corresponding HubPool chain ID. * @param spokeChainId Chain ID of the SpokePool. @@ -116,7 +97,7 @@ export function resolveHubChainId(spokeChainId: number): number { */ export async function getContract(chainId: number, contractName: string): Promise { const contract = getDeployedContract(contractName, chainId); - const provider = new ethers.providers.StaticJsonRpcProvider(getProviderUrl(chainId)); + const provider = await getProvider(chainId); return contract.connect(provider); } From c5229ba23c35a8a1d9fb5db951fe2258362d6dbc Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Wed, 13 Nov 2024 02:23:51 +0100 Subject: [PATCH 37/41] refactor: Use sdk multicall3 implementation (#1881) * refactor: Use sdk multicall3 implementation * lint * Cleanup --------- Co-authored-by: nicholaspai <9457025+nicholaspai@users.noreply.github.com> --- src/clients/MultiCallerClient.ts | 3 ++- src/clients/TokenClient.ts | 2 +- src/common/Constants.ts | 33 +----------------------------- src/finalizer/index.ts | 2 +- src/finalizer/types.ts | 3 +-- src/finalizer/utils/arbitrum.ts | 3 ++- src/finalizer/utils/cctp/l1ToL2.ts | 3 ++- src/finalizer/utils/cctp/l2ToL1.ts | 3 ++- src/finalizer/utils/opStack.ts | 3 ++- src/finalizer/utils/polygon.ts | 2 +- src/finalizer/utils/scroll.ts | 5 ++--- src/finalizer/utils/zkSync.ts | 3 ++- src/utils/TransactionUtils.ts | 12 ++++++----- 13 files changed, 26 insertions(+), 51 deletions(-) diff --git a/src/clients/MultiCallerClient.ts b/src/clients/MultiCallerClient.ts index 42bbbe26b..851fbe3cc 100644 --- a/src/clients/MultiCallerClient.ts +++ b/src/clients/MultiCallerClient.ts @@ -1,5 +1,5 @@ import { utils as sdkUtils } from "@across-protocol/sdk"; -import { DEFAULT_MULTICALL_CHUNK_SIZE, Multicall2Call } from "../common"; +import { DEFAULT_MULTICALL_CHUNK_SIZE } from "../common"; import { BigNumber, winston, @@ -14,6 +14,7 @@ import { Signer, getMultisender, getProvider, + Multicall2Call, assert, } from "../utils"; import { AugmentedTransaction, TransactionClient } from "./TransactionClient"; diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index 8f3e1bd4c..f2d2b68e8 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -211,7 +211,7 @@ export class TokenClient { ): Promise> { const { spokePool } = this.spokePoolClients[chainId]; - const multicall3 = await sdkUtils.getMulticall3(chainId, spokePool.provider); + const multicall3 = sdkUtils.getMulticall3(chainId, spokePool.provider); if (!isDefined(multicall3)) { return this.fetchTokenData(chainId, hubPoolTokens); } diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 654fcad38..853db265d 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -1,4 +1,4 @@ -import { CHAIN_IDs, TOKEN_SYMBOLS_MAP, ethers, Signer, Provider, ZERO_ADDRESS, bnUint32Max } from "../utils"; +import { CHAIN_IDs, TOKEN_SYMBOLS_MAP, Signer, Provider, ZERO_ADDRESS, bnUint32Max } from "../utils"; import { BaseBridgeAdapter, OpStackDefaultERC20Bridge, @@ -281,37 +281,6 @@ export const BLOCK_NUMBER_TTL = 60; export const PROVIDER_CACHE_TTL = 3600; export const PROVIDER_CACHE_TTL_MODIFIER = 0.15; -// Multicall3 Constants: -export const multicall3Addresses = { - [CHAIN_IDs.ARBITRUM]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.BASE]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.BLAST]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.BOBA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.LINEA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.LISK]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.MAINNET]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.MODE]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.OPTIMISM]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.POLYGON]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.REDSTONE]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.SCROLL]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.WORLD_CHAIN]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.ZK_SYNC]: "0xF9cda624FBC7e059355ce98a31693d299FACd963", - [CHAIN_IDs.ZORA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - // Testnet: - [CHAIN_IDs.POLYGON_AMOY]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.BASE_SEPOLIA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.BLAST_SEPOLIA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.POLYGON_AMOY]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.SCROLL_SEPOLIA]: "0xcA11bde05977b3631167028862bE2a173976CA11", - [CHAIN_IDs.SEPOLIA]: "0xcA11bde05977b3631167028862bE2a173976CA11", -}; - -export type Multicall2Call = { - callData: ethers.utils.BytesLike; - target: string; -}; - // These are the spokes that can hold both ETH and WETH, so they should be added together when calculating whether // a bundle execution is possible with the funds in the pool. export const spokesThatHoldEthAndWeth = [ diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index baeb3e720..87a4f30c7 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -8,7 +8,6 @@ import { CONTRACT_ADDRESSES, Clients, FINALIZER_TOKENBRIDGE_LOOKBACK, - Multicall2Call, ProcessEnv, constructClients, constructSpokePoolClientsWithLookback, @@ -25,6 +24,7 @@ import { disconnectRedisClients, getMultisender, getNetworkName, + Multicall2Call, processEndPollingLoop, startupLogLevel, winston, diff --git a/src/finalizer/types.ts b/src/finalizer/types.ts index b4138264e..69f0edabe 100644 --- a/src/finalizer/types.ts +++ b/src/finalizer/types.ts @@ -1,7 +1,6 @@ import { Signer } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../clients"; -import { Multicall2Call } from "../common"; -import { winston } from "../utils"; +import { Multicall2Call, winston } from "../utils"; /** * A cross-chain message is a message sent from one chain to another. This can be a token withdrawal from L2 to L1, diff --git a/src/finalizer/utils/arbitrum.ts b/src/finalizer/utils/arbitrum.ts index 0f4877329..b660ed3f3 100644 --- a/src/finalizer/utils/arbitrum.ts +++ b/src/finalizer/utils/arbitrum.ts @@ -11,13 +11,14 @@ import { getRedisCache, getBlockForTimestamp, getL1TokenInfo, + Multicall2Call, compareAddressesSimple, TOKEN_SYMBOLS_MAP, CHAIN_IDs, } from "../../utils"; import { TokensBridged } from "../../interfaces"; import { HubPoolClient, SpokePoolClient } from "../../clients"; -import { CONTRACT_ADDRESSES, Multicall2Call } from "../../common"; +import { CONTRACT_ADDRESSES } from "../../common"; import { FinalizerPromise, CrossChainMessage } from "../types"; const CHAIN_ID = CHAIN_IDs.ARBITRUM; diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index fa7985b8a..276553a0f 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -1,7 +1,7 @@ import { TransactionReceipt, TransactionRequest } from "@ethersproject/abstract-provider"; import { ethers } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; -import { CHAIN_MAX_BLOCK_LOOKBACK, CONTRACT_ADDRESSES, Multicall2Call } from "../../../common"; +import { CHAIN_MAX_BLOCK_LOOKBACK, CONTRACT_ADDRESSES } from "../../../common"; import { Contract, EventSearchConfig, @@ -15,6 +15,7 @@ import { getRedisCache, groupObjectCountsByProp, isDefined, + Multicall2Call, paginatedEventQuery, winston, convertFromWei, diff --git a/src/finalizer/utils/cctp/l2ToL1.ts b/src/finalizer/utils/cctp/l2ToL1.ts index 337f53b4c..2695915de 100644 --- a/src/finalizer/utils/cctp/l2ToL1.ts +++ b/src/finalizer/utils/cctp/l2ToL1.ts @@ -1,7 +1,7 @@ import { TransactionRequest } from "@ethersproject/abstract-provider"; import { ethers } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; -import { CONTRACT_ADDRESSES, Multicall2Call } from "../../../common"; +import { CONTRACT_ADDRESSES } from "../../../common"; import { Contract, Signer, @@ -13,6 +13,7 @@ import { getNetworkName, getRedisCache, groupObjectCountsByProp, + Multicall2Call, isDefined, winston, convertFromWei, diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 3d4e82883..3504f51fc 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -22,9 +22,10 @@ import { chainIsProd, Contract, ethers, + Multicall2Call, paginatedEventQuery, } from "../../utils"; -import { CONTRACT_ADDRESSES, Multicall2Call, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; +import { CONTRACT_ADDRESSES, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; import { FinalizerPromise, CrossChainMessage } from "../types"; const { utils } = ethers; diff --git a/src/finalizer/utils/polygon.ts b/src/finalizer/utils/polygon.ts index 8a81bc8b3..2ed1e39f3 100644 --- a/src/finalizer/utils/polygon.ts +++ b/src/finalizer/utils/polygon.ts @@ -14,6 +14,7 @@ import { getBlockForTimestamp, getL1TokenInfo, compareAddressesSimple, + Multicall2Call, TOKEN_SYMBOLS_MAP, CHAIN_IDs, sortEventsAscending, @@ -21,7 +22,6 @@ import { } from "../../utils"; import { EthersError, TokensBridged } from "../../interfaces"; import { HubPoolClient, SpokePoolClient } from "../../clients"; -import { Multicall2Call } from "../../common"; import { FinalizerPromise, CrossChainMessage } from "../types"; // Note!!: This client will only work for PoS tokens. Matic also has Plasma tokens which have a different finalization diff --git a/src/finalizer/utils/scroll.ts b/src/finalizer/utils/scroll.ts index 98ce04e3a..2446d739f 100644 --- a/src/finalizer/utils/scroll.ts +++ b/src/finalizer/utils/scroll.ts @@ -1,15 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ import { utils as sdkUtils } from "@across-protocol/sdk"; -import { TransactionRequest } from "@ethersproject/abstract-provider"; import axios from "axios"; import { HubPoolClient, SpokePoolClient } from "../../clients"; -import { CONTRACT_ADDRESSES, Multicall2Call } from "../../common"; +import { CONTRACT_ADDRESSES } from "../../common"; import { Contract, Signer, getBlockForTimestamp, getCurrentTime, getRedisCache, + Multicall2Call, winston, convertFromWei, } from "../../utils"; @@ -57,7 +57,6 @@ export async function scrollFinalizer( // Why are we breaking with the existing pattern--is it faster? // Scroll takes up to 4 hours with finalize a withdrawal so lets search // up to 12 hours for withdrawals. - const redis = await getRedisCache(logger); logger.debug({ at: "Finalizer#ScrollFinalizer", message: "Scroll TokensBridged event filter", diff --git a/src/finalizer/utils/zkSync.ts b/src/finalizer/utils/zkSync.ts index 2afc1e829..48a970865 100644 --- a/src/finalizer/utils/zkSync.ts +++ b/src/finalizer/utils/zkSync.ts @@ -3,7 +3,7 @@ import { Contract, Wallet, Signer } from "ethers"; import { groupBy } from "lodash"; import { Provider as zksProvider, Wallet as zkWallet } from "zksync-ethers"; import { HubPoolClient, SpokePoolClient } from "../../clients"; -import { CONTRACT_ADDRESSES, Multicall2Call } from "../../common"; +import { CONTRACT_ADDRESSES } from "../../common"; import { convertFromWei, getBlockForTimestamp, @@ -12,6 +12,7 @@ import { getL1TokenInfo, getRedisCache, getUniqueLogIndex, + Multicall2Call, winston, zkSync as zkSyncUtils, } from "../../utils"; diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index 6db601be4..a8b0fa1ea 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -2,7 +2,7 @@ import { gasPriceOracle, typeguards, utils as sdkUtils } from "@across-protocol/ import { FeeData } from "@ethersproject/abstract-provider"; import dotenv from "dotenv"; import { AugmentedTransaction } from "../clients"; -import { DEFAULT_GAS_FEE_SCALERS, multicall3Addresses } from "../common"; +import { DEFAULT_GAS_FEE_SCALERS } from "../common"; import { EthersError } from "../interfaces"; import { BigNumber, @@ -29,6 +29,11 @@ export type TransactionSimulationResult = { const { isError, isEthersError } = typeguards; +export type Multicall2Call = { + callData: ethers.utils.BytesLike; + target: string; +}; + const nonceReset: { [chainId: number]: boolean } = {}; const txnRetryErrors = new Set(["INSUFFICIENT_FUNDS", "NONCE_EXPIRED", "REPLACEMENT_UNDERPRICED"]); @@ -46,10 +51,7 @@ export function getNetworkError(err: unknown): string { } export async function getMultisender(chainId: number, baseSigner: Signer): Promise { - if (!multicall3Addresses[chainId] || !baseSigner) { - return undefined; - } - return new Contract(multicall3Addresses[chainId], await sdkUtils.getABI("Multicall3"), baseSigner); + return sdkUtils.getMulticall3(chainId, baseSigner); } // Note that this function will throw if the call to the contract on method for given args reverts. Implementers From 668e051aeb22fe5c546cb4165dde5082664d0115 Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Wed, 13 Nov 2024 09:02:55 -0600 Subject: [PATCH 38/41] improve: track slow fills in the monitor (#1888) * improve: track slow fills in the monitor --------- Signed-off-by: bennett Co-authored-by: nicholaspai <9457025+nicholaspai@users.noreply.github.com> --- src/monitor/Monitor.ts | 143 +++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 34 deletions(-) diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index 2cf4155ab..34ab55801 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -29,6 +29,7 @@ import { getNetworkName, getUnfilledDeposits, mapAsync, + getEndBlockBuffers, parseUnits, providers, toBN, @@ -42,6 +43,7 @@ import { isDefined, resolveTokenDecimals, sortEventsDescending, + getWidestPossibleExpectedBlockRange, } from "../utils"; import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; @@ -629,6 +631,8 @@ export class Monitor { const hubPoolClient = this.clients.hubPoolClient; const monitoredTokenSymbols = this.monitorConfig.monitoredTokenSymbols; + + // Define the chain IDs in the same order as `enabledChainIds` so that block range ordering is preserved. const chainIds = this.monitorConfig.monitoredSpokePoolChains.length !== 0 ? this.monitorChains.filter((chain) => this.monitorConfig.monitoredSpokePoolChains.includes(chain)) @@ -637,14 +641,91 @@ export class Monitor { const l2TokenForChain = (chainId: number, symbol: string) => { return TOKEN_SYMBOLS_MAP[symbol]?.addresses[chainId]; }; - const currentSpokeBalances = {}; const pendingRelayerRefunds = {}; const pendingRebalanceRoots = {}; - // Get the pool rebalance leaves of the currently outstanding proposed root bundle. - const poolRebalanceRoot = await this.clients.bundleDataClient.getLatestPoolRebalanceRoot(); + // Take the validated bundles from the hub pool client. + const validatedBundles = sortEventsDescending(hubPoolClient.getValidatedRootBundles()).slice( + 0, + this.monitorConfig.bundlesCount + ); + + // Fetch the data from the latest root bundle. + const bundle = hubPoolClient.getLatestProposedRootBundle(); + // If there is an outstanding root bundle, then add it to the bundles to check. Otherwise, ignore it. + const bundlesToCheck = validatedBundles + .map((validatedBundle) => validatedBundle.transactionHash) + .includes(bundle.transactionHash) + ? validatedBundles + : [...validatedBundles, bundle]; + + const nextBundleMainnetStartBlock = hubPoolClient.getNextBundleStartBlockNumber( + this.clients.bundleDataClient.chainIdListForBundleEvaluationBlockNumbers, + hubPoolClient.latestBlockSearched, + hubPoolClient.chainId + ); + const enabledChainIds = this.clients.configStoreClient.getChainIdIndicesForBlock(nextBundleMainnetStartBlock); + + const slowFillBlockRange = getWidestPossibleExpectedBlockRange( + enabledChainIds, + this.clients.spokePoolClients, + getEndBlockBuffers(enabledChainIds, this.clients.bundleDataClient.blockRangeEndBlockBuffer), + this.clients, + hubPoolClient.latestBlockSearched, + this.clients.configStoreClient.getEnabledChains(hubPoolClient.latestBlockSearched) + ); + const blockRangeTail = bundle.bundleEvaluationBlockNumbers.map((endBlockForChain, idx) => { + const endBlockNumber = Number(endBlockForChain); + const spokeLatestBlockSearched = this.clients.spokePoolClients[enabledChainIds[idx]]?.latestBlockSearched ?? 0; + return spokeLatestBlockSearched === 0 + ? [endBlockNumber, endBlockNumber] + : [endBlockNumber + 1, spokeLatestBlockSearched > endBlockNumber ? spokeLatestBlockSearched : endBlockNumber]; + }); + + // Do all async tasks in parallel. We want to know about the pool rebalances, slow fills in the most recent proposed bundle, refunds + // from the last `n` bundles, pending refunds which have not been made official via a root bundle proposal, and the current balances of + // all the spoke pools. + const [ + poolRebalanceRoot, + currentBundleData, + accountedBundleRefunds, + unaccountedBundleRefunds, + currentSpokeBalances, + ] = await Promise.all([ + this.clients.bundleDataClient.getLatestPoolRebalanceRoot(), + this.clients.bundleDataClient.loadData(slowFillBlockRange, this.clients.spokePoolClients, false), + mapAsync(bundlesToCheck, async (proposedBundle) => + this.clients.bundleDataClient.getPendingRefundsFromBundle(proposedBundle) + ), + this.clients.bundleDataClient.getApproximateRefundsForBlockRange(enabledChainIds, blockRangeTail), + Object.fromEntries( + await mapAsync(chainIds, async (chainId) => { + const spokePool = this.clients.spokePoolClients[chainId].spokePool.address; + const l2TokenAddresses = monitoredTokenSymbols + .map((symbol) => l2TokenForChain(chainId, symbol)) + .filter(isDefined); + const balances = Object.fromEntries( + await mapAsync(l2TokenAddresses, async (l2Token) => [ + l2Token, + ( + await this._getBalances([ + { + token: l2Token, + chainId: chainId, + account: spokePool, + }, + ]) + )[0], + ]) + ); + return [chainId, balances]; + }) + ), + ]); + const poolRebalanceLeaves = poolRebalanceRoot.root.leaves; + // Get the pool rebalance leaf amounts. const enabledTokens = [...hubPoolClient.getL1Tokens()]; for (const leaf of poolRebalanceLeaves) { if (!chainIds.includes(leaf.chainId)) { @@ -658,29 +739,14 @@ export class Monitor { }); } - // Take the validated bundles from the hub pool client. - const validatedBundles = sortEventsDescending(hubPoolClient.getValidatedRootBundles()).slice( - 0, - this.monitorConfig.bundlesCount - ); - const previouslyValidatedBundleRefunds: CombinedRefunds[] = await mapAsync( - validatedBundles, - async (bundle) => await this.clients.bundleDataClient.getPendingRefundsFromBundle(bundle) - ); - - // Here are the current outstanding refunds. - const nextBundleRefunds = await this.clients.bundleDataClient.getNextBundleRefunds(); - - // Calculate the pending refunds and the spoke pool balances in parallel. + // Calculate the pending refunds. for (const chainId of chainIds) { - const spokePool = this.clients.spokePoolClients[chainId].spokePool.address; const l2TokenAddresses = monitoredTokenSymbols .map((symbol) => l2TokenForChain(chainId, symbol)) .filter(isDefined); - currentSpokeBalances[chainId] = {}; pendingRelayerRefunds[chainId] = {}; - void (await mapAsync(l2TokenAddresses, async (l2Token) => { - const pendingValidatedDeductions = previouslyValidatedBundleRefunds + l2TokenAddresses.forEach((l2Token) => { + const pendingValidatedDeductions = accountedBundleRefunds .map((refund) => refund[chainId]?.[l2Token]) .filter(isDefined) .reduce( @@ -691,7 +757,7 @@ export class Monitor { ), bnZero ); - const nextBundleDeductions = nextBundleRefunds + const nextBundleDeductions = [unaccountedBundleRefunds] .map((refund) => refund[chainId]?.[l2Token]) .filter(isDefined) .reduce( @@ -702,20 +768,29 @@ export class Monitor { ), bnZero ); - const totalObligations = pendingValidatedDeductions.add(nextBundleDeductions); - currentSpokeBalances[chainId][l2Token] = ( - await this._getBalances([ - { - token: l2Token, - chainId: chainId, - account: spokePool, - }, - ]) - )[0]; - pendingRelayerRefunds[chainId][l2Token] = totalObligations; - })); + pendingRelayerRefunds[chainId][l2Token] = pendingValidatedDeductions.add(nextBundleDeductions); + }); } + // Get the slow fill amounts. Only do this step if there were slow fills in the most recent root bundle. + Object.entries(currentBundleData.bundleSlowFillsV3) + .filter(([chainId]) => chainIds.includes(+chainId)) + .map(([chainId, bundleSlowFills]) => { + const l2TokenAddresses = monitoredTokenSymbols + .map((symbol) => l2TokenForChain(+chainId, symbol)) + .filter(isDefined); + Object.entries(bundleSlowFills) + .filter(([l2Token]) => l2TokenAddresses.includes(l2Token)) + .map(([l2Token, fills]) => { + const pendingSlowFillAmounts = fills + .map((fill) => fill.outputAmount) + .filter(isDefined) + .reduce((totalAmounts, outputAmount) => totalAmounts.add(outputAmount), bnZero); + pendingRelayerRefunds[chainId][l2Token] = + pendingRelayerRefunds[chainId][l2Token].sub(pendingSlowFillAmounts); + }); + }); + // Print the output: The current spoke pool balance, the amount of refunds to payout, the pending pool rebalances, and then the sum of the three. let tokenMarkdown = "Token amounts: current, pending relayer refunds, pool rebalances, adjusted spoke pool balance\n"; From 28cffcd9fc6224944a6fbd09154c485c5ca03316 Mon Sep 17 00:00:00 2001 From: Melisa Guevara Date: Wed, 13 Nov 2024 14:19:47 -0500 Subject: [PATCH 39/41] feat: Support AlephZero (#1887) * Support AlephZero * change time based constants * define gasToken in BaseBridgeAdapter * set aleph zero min deposit confirmations equal to arbitrum values * fix typo * feat(alephzero): make arb finalizer generic (#1895) Signed-off-by: james-a-morris * Delete duplicated multicall address * update across constants repo * Change contract addresses name pattern * Fix arbitrum outbox definition * Rename orbit contract addresses entries * Update contracts and sdk, delete deprecated events * fix tests * fix test * use native token symbol when deciding to unwrap eth or not * improve: bump sdk (#1904) * refactor: arbitrum orbit bridge (#1903) * refactor: aleph zero bridge inherits arbitrum one bridge Signed-off-by: bennett * make arbitrum bridge generic Signed-off-by: bennett * add bridge Signed-off-by: bennett * native token Signed-off-by: bennett * build Signed-off-by: bennett --------- Signed-off-by: bennett * fix: change contract constant name Signed-off-by: james-a-morris * Rebasing and removing DATAWORKER_FAST_LOOKBACK as it is not used anymore * feat: support custom gas token bridges in the rebalancer (#1873) * feat: support custom gas token bridges in the rebalancer Signed-off-by: bennett * Support AlephZero * Support AlephZero * feat(alephzero): add multicall3 (#1893) Signed-off-by: james-a-morris * feat(alephzero): make arb finalizer generic (#1895) Signed-off-by: james-a-morris * fix: dedup approvals and encode proper outbound data Signed-off-by: bennett * remove await Signed-off-by: bennett * custom azero gas values Signed-off-by: bennett * fix package.json * Delete unused file --------- Signed-off-by: bennett Signed-off-by: james-a-morris Co-authored-by: Melisa Guevara Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> --------- Signed-off-by: james-a-morris Signed-off-by: bennett Co-authored-by: James Morris, MS <96435344+james-a-morris@users.noreply.github.com> Co-authored-by: nicholaspai Co-authored-by: bmzig <57361391+bmzig@users.noreply.github.com> Co-authored-by: james-a-morris --- package.json | 6 +- src/adapter/BaseChainAdapter.ts | 32 +- ...rumOneBridge.ts => ArbitrumOrbitBridge.ts} | 55 +- src/adapter/bridges/BaseBridgeAdapter.ts | 1 + src/adapter/bridges/index.ts | 2 +- src/clients/InventoryClient.ts | 7 +- src/clients/ProfitClient.ts | 1 + src/clients/bridges/AdapterManager.ts | 15 +- src/common/Constants.ts | 79 +-- src/common/ContractAddresses.ts | 26 +- src/common/abi/ArbitrumErc20GatewayL1.json | 14 + .../abi/ArbitrumErc20GatewayRouterL1.json | 12 - src/finalizer/index.ts | 8 +- .../utils/{arbitrum.ts => arbStack.ts} | 44 +- src/finalizer/utils/index.ts | 2 +- src/interfaces/index.ts | 1 - test/AdapterManager.SendTokensCrossChain.ts | 4 +- test/Relayer.BasicFill.ts | 34 +- .../AdapterManager.SendTokensCrossChain.ts | 4 +- yarn.lock | 553 +++++++++++++++++- 20 files changed, 739 insertions(+), 161 deletions(-) rename src/adapter/bridges/{ArbitrumOneBridge.ts => ArbitrumOrbitBridge.ts} (55%) create mode 100644 src/common/abi/ArbitrumErc20GatewayL1.json rename src/finalizer/utils/{arbitrum.ts => arbStack.ts} (87%) diff --git a/package.json b/package.json index 278bd9fe8..126c8ca02 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,9 @@ "node": ">=20" }, "dependencies": { - "@across-protocol/constants": "^3.1.16", - "@across-protocol/contracts": "^3.0.11", - "@across-protocol/sdk": "^3.2.11", + "@across-protocol/constants": "^3.1.19", + "@across-protocol/contracts": "^3.0.16", + "@across-protocol/sdk": "^3.2.13", "@arbitrum/sdk": "^3.1.3", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/src/adapter/BaseChainAdapter.ts b/src/adapter/BaseChainAdapter.ts index d72b83fd6..a7c5fc7eb 100644 --- a/src/adapter/BaseChainAdapter.ts +++ b/src/adapter/BaseChainAdapter.ts @@ -106,8 +106,10 @@ export class BaseChainAdapter { async checkTokenApprovals(l1Tokens: string[]): Promise { const unavailableTokens: string[] = []; - const tokensToApprove = ( - await mapAsync( + // Approve tokens to bridges. This includes the tokens we want to send over a bridge as well as the custom gas tokens + // each bridge supports (if applicable). + const [bridgeTokensToApprove, gasTokensToApprove] = await Promise.all([ + mapAsync( l1Tokens.map((token) => [token, this.bridges[token]?.l1Gateways] as [string, string[]]), async ([l1Token, bridges]) => { const erc20 = ERC20.connect(l1Token, this.getSigner(this.hubChainId)); @@ -126,8 +128,30 @@ export class BaseChainAdapter { }); return { token: erc20, bridges: bridgesToApprove }; } - ) - ).filter(({ bridges }) => bridges.length > 0); + ), + mapAsync( + Object.values(this.bridges).filter((bridge) => isDefined(bridge.gasToken)), + async (bridge) => { + const gasToken = bridge.gasToken; + const erc20 = ERC20.connect(gasToken, this.getSigner(this.hubChainId)); + const bridgesToApprove = await filterAsync(bridge.l1Gateways, async (gateway) => { + const senderAddress = await erc20.signer.getAddress(); + const cachedResult = await getTokenAllowanceFromCache(gasToken, senderAddress, gateway); + const allowance = cachedResult ?? (await erc20.allowance(senderAddress, gateway)); + if (!isDefined(cachedResult) && aboveAllowanceThreshold(allowance)) { + await setTokenAllowanceInCache(gasToken, senderAddress, gateway, allowance); + } + return !aboveAllowanceThreshold(allowance); + }); + return { token: erc20, bridges: bridgesToApprove }; + } + ), + ]); + // Dedup the `gasTokensToApprove` array so that we don't approve the same bridge to send the same token multiple times. + const tokensToApprove = gasTokensToApprove + .filter(({ token, bridges }, idx) => gasTokensToApprove.indexOf({ token, bridges }) === idx) + .concat(bridgeTokensToApprove) + .filter(({ bridges }) => bridges.length > 0); if (unavailableTokens.length > 0) { this.log("Some tokens do not have a bridge contract", { unavailableTokens }); } diff --git a/src/adapter/bridges/ArbitrumOneBridge.ts b/src/adapter/bridges/ArbitrumOrbitBridge.ts similarity index 55% rename from src/adapter/bridges/ArbitrumOneBridge.ts rename to src/adapter/bridges/ArbitrumOrbitBridge.ts index 0dd573d6d..89d045a16 100644 --- a/src/adapter/bridges/ArbitrumOneBridge.ts +++ b/src/adapter/bridges/ArbitrumOrbitBridge.ts @@ -7,24 +7,35 @@ import { Provider, toBN, toWei, + TOKEN_SYMBOLS_MAP, + isDefined, + ethers, + bnZero, + CHAIN_IDs, } from "../../utils"; -import { CONTRACT_ADDRESSES, CUSTOM_ARBITRUM_GATEWAYS } from "../../common"; +import { CONTRACT_ADDRESSES, CUSTOM_ARBITRUM_GATEWAYS, DEFAULT_ARBITRUM_GATEWAY } from "../../common"; import { BridgeTransactionDetails, BaseBridgeAdapter, BridgeEvents } from "./BaseBridgeAdapter"; import { processEvent } from "../utils"; +import { PRODUCTION_NETWORKS } from "@across-protocol/constants"; -const DEFAULT_ERC20_GATEWAY = { - l1: "0xa3A7B6F88361F48403514059F1F16C8E78d60EeC", - l2: "0x09e9222E96E7B4AE2a407B98d48e330053351EEe", +const bridgeSubmitValue: { [chainId: number]: BigNumber } = { + [CHAIN_IDs.ARBITRUM]: toWei(0.013), + [CHAIN_IDs.ALEPH_ZERO]: toWei(0.45), }; -export class ArbitrumOneBridge extends BaseBridgeAdapter { - protected l1Gateway: Contract; +const maxFeePerGas: { [chainId: number]: BigNumber } = { + [CHAIN_IDs.ARBITRUM]: toBN(20e9), + [CHAIN_IDs.ALEPH_ZERO]: toBN(24e10), +}; + +export class ArbitrumOrbitBridge extends BaseBridgeAdapter { + protected l1GatewayRouter: Contract; private readonly transactionSubmissionData = "0x000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000"; private readonly l2GasLimit = toBN(150000); - private readonly l2GasPrice = toBN(20e9); - private readonly l1SubmitValue = toWei(0.013); + private readonly l2GasPrice; + private readonly l1SubmitValue; constructor( l2chainId: number, @@ -33,15 +44,25 @@ export class ArbitrumOneBridge extends BaseBridgeAdapter { l2SignerOrProvider: Signer | Provider, l1Token: string ) { - const { address: gatewayAddress, abi: l1Abi } = CONTRACT_ADDRESSES[hubChainId].arbitrumErc20GatewayRouter; - const { l1: l1Address, l2: l2Address } = CUSTOM_ARBITRUM_GATEWAYS[l1Token] ?? DEFAULT_ERC20_GATEWAY; + const { address: gatewayAddress, abi: gatewayRouterAbi } = + CONTRACT_ADDRESSES[hubChainId][`orbitErc20GatewayRouter_${l2chainId}`]; + const { l1: l1Address, l2: l2Address } = + CUSTOM_ARBITRUM_GATEWAYS[l2chainId]?.[l1Token] ?? DEFAULT_ARBITRUM_GATEWAY[l2chainId]; + const l1Abi = CONTRACT_ADDRESSES[hubChainId][`orbitErc20Gateway_${l2chainId}`].abi; const l2Abi = CONTRACT_ADDRESSES[l2chainId].erc20Gateway.abi; super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [l1Address]); + const nativeToken = PRODUCTION_NETWORKS[l2chainId].nativeToken; + // Only set nonstandard gas tokens. + if (nativeToken !== "ETH") { + this.gasToken = TOKEN_SYMBOLS_MAP[nativeToken].addresses[hubChainId]; + } + this.l1SubmitValue = bridgeSubmitValue[l2chainId]; + this.l2GasPrice = maxFeePerGas[l2chainId]; this.l1Bridge = new Contract(l1Address, l1Abi, l1Signer); this.l2Bridge = new Contract(l2Address, l2Abi, l2SignerOrProvider); - this.l1Gateway = new Contract(gatewayAddress, l1Abi, l1Signer); + this.l1GatewayRouter = new Contract(gatewayAddress, gatewayRouterAbi, l1Signer); } async constructL1ToL2Txn( @@ -50,12 +71,18 @@ export class ArbitrumOneBridge extends BaseBridgeAdapter { l2Token: string, amount: BigNumber ): Promise { - const { l1Gateway, l2GasLimit, l2GasPrice, transactionSubmissionData, l1SubmitValue } = this; + const { l1GatewayRouter, l2GasLimit, l2GasPrice, l1SubmitValue } = this; + const transactionSubmissionData = isDefined(this.gasToken) + ? ethers.utils.defaultAbiCoder.encode( + ["uint256", "bytes", "uint256"], + [l1SubmitValue, "0x", l2GasLimit.mul(l2GasPrice).add(l1SubmitValue)] + ) + : this.transactionSubmissionData; return Promise.resolve({ - contract: l1Gateway, + contract: l1GatewayRouter, method: "outboundTransfer", args: [l1Token, toAddress, amount, l2GasLimit, l2GasPrice, transactionSubmissionData], - value: l1SubmitValue, + value: isDefined(this.gasToken) ? bnZero : l1SubmitValue, }); } diff --git a/src/adapter/bridges/BaseBridgeAdapter.ts b/src/adapter/bridges/BaseBridgeAdapter.ts index a54dbc395..d9f4320c9 100644 --- a/src/adapter/bridges/BaseBridgeAdapter.ts +++ b/src/adapter/bridges/BaseBridgeAdapter.ts @@ -28,6 +28,7 @@ export type BridgeEvents = { [l2Token: string]: BridgeEvent[] }; export abstract class BaseBridgeAdapter { protected l1Bridge: Contract; protected l2Bridge: Contract; + public gasToken: string | undefined; constructor( protected l2chainId: number, diff --git a/src/adapter/bridges/index.ts b/src/adapter/bridges/index.ts index ef672f619..8c3a6a87c 100644 --- a/src/adapter/bridges/index.ts +++ b/src/adapter/bridges/index.ts @@ -3,7 +3,7 @@ export * from "./SnxOptimismBridge"; export * from "./BaseBridgeAdapter"; export * from "./UsdcTokenSplitterBridge"; export * from "./OpStackWethBridge"; -export * from "./ArbitrumOneBridge"; +export * from "./ArbitrumOrbitBridge"; export * from "./LineaBridge"; export * from "./LineaUSDCBridge"; export * from "./PolygonERC20Bridge"; diff --git a/src/clients/InventoryClient.ts b/src/clients/InventoryClient.ts index d02f0204a..1e25e19aa 100644 --- a/src/clients/InventoryClient.ts +++ b/src/clients/InventoryClient.ts @@ -25,6 +25,7 @@ import { assert, compareAddressesSimple, getUsdcSymbol, + getNativeTokenSymbol, } from "../utils"; import { HubPoolClient, TokenClient, BundleDataClient } from "."; import { Deposit } from "../interfaces"; @@ -981,7 +982,11 @@ export class InventoryClient { const { unwrapWethThreshold, unwrapWethTarget } = tokenConfig; // Ignore chains where ETH isn't the native gas token. Returning null will result in these being filtered. - if (chainId === CHAIN_IDs.POLYGON || unwrapWethThreshold === undefined || unwrapWethTarget === undefined) { + if ( + getNativeTokenSymbol(chainId) !== "ETH" || + unwrapWethThreshold === undefined || + unwrapWethTarget === undefined + ) { return; } const weth = TOKEN_SYMBOLS_MAP.WETH.addresses[chainId]; diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index ec5907e52..9bd93c325 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -587,6 +587,7 @@ export class ProfitClient { // The relayer _cannot_ be the recipient because the SpokePool skips the ERC20 transfer. Instead, use // the main RL address because it has all supported tokens and approvals in place on all chains. const testSymbols = { + [CHAIN_IDs.ALEPH_ZERO]: "USDT", // USDC is not yet supported on AlephZero, so revert to USDT. @todo: Update. [CHAIN_IDs.BLAST]: "USDB", [CHAIN_IDs.LISK]: "USDT", // USDC is not yet supported on Lisk, so revert to USDT. @todo: Update. [CHAIN_IDs.REDSTONE]: "WETH", // Redstone only supports WETH. diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index 3e9866064..8d83fc6c4 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -46,7 +46,8 @@ export class AdapterManager { ); }; - const { OPTIMISM, ARBITRUM, POLYGON, ZK_SYNC, BASE, MODE, LINEA, LISK, BLAST, REDSTONE, SCROLL, ZORA } = CHAIN_IDs; + const { OPTIMISM, ARBITRUM, POLYGON, ZK_SYNC, BASE, MODE, LINEA, LISK, BLAST, REDSTONE, SCROLL, ZORA, ALEPH_ZERO } = + CHAIN_IDs; const hubChainId = hubPoolClient.chainId; const l1Signer = spokePoolClients[hubChainId].spokePool.signer; const constructBridges = (chainId: number) => { @@ -171,6 +172,18 @@ export class AdapterManager { DEFAULT_GAS_MULTIPLIER[ZORA] ?? 1 ); } + if (this.spokePoolClients[ALEPH_ZERO] !== undefined) { + this.adapters[ALEPH_ZERO] = new BaseChainAdapter( + spokePoolClients, + ALEPH_ZERO, + hubChainId, + filterMonitoredAddresses(ALEPH_ZERO), + logger, + SUPPORTED_TOKENS[ALEPH_ZERO], + constructBridges(ALEPH_ZERO), + DEFAULT_GAS_MULTIPLIER[ALEPH_ZERO] ?? 1 + ); + } logger.debug({ at: "AdapterManager#constructor", diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 853db265d..90c0225a4 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -10,7 +10,7 @@ import { PolygonERC20Bridge, ZKSyncBridge, ZKSyncWethBridge, - ArbitrumOneBridge, + ArbitrumOrbitBridge, LineaBridge, LineaUSDCBridge, LineaWethBridge, @@ -38,25 +38,6 @@ export const INFINITE_FILL_DEADLINE = bnUint32Max; // Target ~4 hours export const MAX_RELAYER_DEPOSIT_LOOK_BACK = 4 * 60 * 60; -// Target ~4 days per chain. Should cover all events needed to construct pending bundle. -export const DATAWORKER_FAST_LOOKBACK: { [chainId: number]: number } = { - [CHAIN_IDs.ARBITRUM]: 1382400, - [CHAIN_IDs.BASE]: 172800, // Same as Optimism. - [CHAIN_IDs.BLAST]: 172800, - [CHAIN_IDs.BOBA]: 11520, - [CHAIN_IDs.LINEA]: 115200, // 1 block every 3 seconds - [CHAIN_IDs.LISK]: 172800, // Same as Optimism. - [CHAIN_IDs.MAINNET]: 28800, - [CHAIN_IDs.MODE]: 172800, // Same as Optimism. - [CHAIN_IDs.OPTIMISM]: 172800, // 1 block every 2 seconds after bedrock - [CHAIN_IDs.POLYGON]: 138240, - [CHAIN_IDs.REDSTONE]: 172800, // OP stack - [CHAIN_IDs.SCROLL]: 115200, // 4 * 24 * 20 * 60, - [CHAIN_IDs.WORLD_CHAIN]: 172800, // OP stack - [CHAIN_IDs.ZK_SYNC]: 345600, // 4 * 24 * 60 * 60, - [CHAIN_IDs.ZORA]: 172800, // OP stack -}; - // Target ~14 days per chain. Should cover all events that could be finalized, so 2x the optimistic // rollup challenge period seems safe. export const FINALIZER_TOKENBRIDGE_LOOKBACK = 14 * 24 * 60 * 60; @@ -70,7 +51,7 @@ export const FINALIZER_TOKENBRIDGE_LOOKBACK = 14 * 24 * 60 * 60; // The key of the following dictionary is used as the USD threshold to determine the MDC: // - Searching from highest USD threshold to lowest // - If the key is >= deposited USD amount, then use the MDC associated with the key for the origin chain -// - If no keys are >= depostied USD amount, ignore the deposit. +// - If no keys are >= deposited USD amount, ignore the deposit. // To see the latest block reorg events go to: // - Ethereum: https://etherscan.io/blocks_forked // - Polygon: https://polygonscan.com/blocks_forked @@ -79,6 +60,7 @@ export const FINALIZER_TOKENBRIDGE_LOOKBACK = 14 * 24 * 60 * 60; // anything under 7 days. export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chainId: number]: number } } = { 10000: { + [CHAIN_IDs.ALEPH_ZERO]: 0, [CHAIN_IDs.ARBITRUM]: 0, [CHAIN_IDs.BASE]: 120, [CHAIN_IDs.BLAST]: 120, @@ -95,6 +77,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain [CHAIN_IDs.ZORA]: 120, }, 1000: { + [CHAIN_IDs.ALEPH_ZERO]: 0, [CHAIN_IDs.ARBITRUM]: 0, [CHAIN_IDs.BASE]: 60, [CHAIN_IDs.BLAST]: 60, @@ -111,6 +94,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain [CHAIN_IDs.ZORA]: 60, }, 100: { + [CHAIN_IDs.ALEPH_ZERO]: 0, [CHAIN_IDs.ARBITRUM]: 0, [CHAIN_IDs.BASE]: 60, [CHAIN_IDs.BLAST]: 60, @@ -136,6 +120,7 @@ export const REDIS_URL_DEFAULT = "redis://localhost:6379"; // if the RPC provider allows it. This is why the user should override these lookbacks if they are not using // Quicknode for example. export const CHAIN_MAX_BLOCK_LOOKBACK = { + [CHAIN_IDs.ALEPH_ZERO]: 10000, [CHAIN_IDs.ARBITRUM]: 10000, [CHAIN_IDs.BASE]: 10000, [CHAIN_IDs.BLAST]: 10000, @@ -167,6 +152,7 @@ export const CHAIN_MAX_BLOCK_LOOKBACK = { // can be matched with a deposit on the origin chain, so something like // ~1-2 mins per chain. export const BUNDLE_END_BLOCK_BUFFERS = { + [CHAIN_IDs.ALEPH_ZERO]: 240, // Same as Arbitrum [CHAIN_IDs.ARBITRUM]: 240, // ~0.25s/block. Arbitrum is a centralized sequencer [CHAIN_IDs.BASE]: 60, // 2s/block. Same finality profile as Optimism [CHAIN_IDs.BLAST]: 60, @@ -212,6 +198,7 @@ export const IGNORED_HUB_EXECUTED_BUNDLES: number[] = []; // Provider caching will not be allowed for queries whose responses depend on blocks closer than this many blocks. // This is intended to be conservative. export const CHAIN_CACHE_FOLLOW_DISTANCE: { [chainId: number]: number } = { + [CHAIN_IDs.ALEPH_ZERO]: 60, [CHAIN_IDs.ARBITRUM]: 32, [CHAIN_IDs.BASE]: 120, [CHAIN_IDs.BLAST]: 120, @@ -242,6 +229,7 @@ export const CHAIN_CACHE_FOLLOW_DISTANCE: { [chainId: number]: number } = { // These are all intended to be roughly 2 days of blocks for each chain. // blocks = 172800 / avg_block_time export const DEFAULT_NO_TTL_DISTANCE: { [chainId: number]: number } = { + [CHAIN_IDs.ALEPH_ZERO]: 691200, [CHAIN_IDs.ARBITRUM]: 691200, [CHAIN_IDs.BASE]: 86400, [CHAIN_IDs.BLAST]: 86400, @@ -318,6 +306,7 @@ export const chainIdsToCctpDomains: { [chainId: number]: number } = { // A mapping of L2 chain IDs to an array of tokens Across supports on that chain. export const SUPPORTED_TOKENS: { [chainId: number]: string[] } = { + [CHAIN_IDs.ALEPH_ZERO]: ["USDT", "WETH"], [CHAIN_IDs.ARBITRUM]: ["USDC", "USDT", "WETH", "DAI", "WBTC", "UMA", "BAL", "ACX", "POOL"], [CHAIN_IDs.BASE]: ["BAL", "DAI", "ETH", "WETH", "USDC", "POOL"], [CHAIN_IDs.BLAST]: ["DAI", "WBTC", "WETH"], @@ -371,7 +360,8 @@ export const CANONICAL_BRIDGE: { ): BaseBridgeAdapter; }; } = { - [CHAIN_IDs.ARBITRUM]: ArbitrumOneBridge, + [CHAIN_IDs.ALEPH_ZERO]: ArbitrumOrbitBridge, + [CHAIN_IDs.ARBITRUM]: ArbitrumOrbitBridge, [CHAIN_IDs.BASE]: OpStackDefaultERC20Bridge, [CHAIN_IDs.BLAST]: OpStackDefaultERC20Bridge, [CHAIN_IDs.LINEA]: LineaBridge, @@ -458,22 +448,38 @@ export const DEFAULT_ARWEAVE_GATEWAY = { url: "arweave.net", port: 443, protocol // relayer to unintentionally overdraw the HubPool's available reserves. export const SLOW_WITHDRAWAL_CHAINS = [CHAIN_IDs.ARBITRUM, CHAIN_IDs.BASE, CHAIN_IDs.OPTIMISM, CHAIN_IDs.BLAST]; -export const CUSTOM_ARBITRUM_GATEWAYS: { [chainId: number]: { l1: string; l2: string } } = { - [TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET]]: { - l1: "0xcEe284F754E854890e311e3280b767F80797180d", // USDT - l2: "0x096760F208390250649E3e8763348E783AEF5562", - }, - [TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: { - l1: "0xcEe284F754E854890e311e3280b767F80797180d", // USDC - l2: "0x096760F208390250649E3e8763348E783AEF5562", // If we want to bridge to USDC.e, we need to specify a unique Arbitrum Gateway. +// Arbitrum Orbit chains may have custom gateways for certain tokens. These gateways need to be specified since token approvals are directed at the +// gateway, while function calls are directed at the gateway router. +export const CUSTOM_ARBITRUM_GATEWAYS: { [chainId: number]: { [address: string]: { l1: string; l2: string } } } = { + [CHAIN_IDs.ARBITRUM]: { + [TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET]]: { + l1: "0xcEe284F754E854890e311e3280b767F80797180d", // USDT + l2: "0x096760F208390250649E3e8763348E783AEF5562", + }, + [TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: { + l1: "0xcEe284F754E854890e311e3280b767F80797180d", // USDC + l2: "0x096760F208390250649E3e8763348E783AEF5562", // If we want to bridge to USDC.e, we need to specify a unique Arbitrum Gateway. + }, + [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: { + l1: "0xd92023E9d9911199a6711321D1277285e6d4e2db", // WETH + l2: "0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B", + }, + [TOKEN_SYMBOLS_MAP.DAI.addresses[CHAIN_IDs.MAINNET]]: { + l1: "0xD3B5b60020504bc3489D6949d545893982BA3011", // DAI + l2: "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65", + }, }, - [TOKEN_SYMBOLS_MAP.WETH.addresses[CHAIN_IDs.MAINNET]]: { - l1: "0xd92023E9d9911199a6711321D1277285e6d4e2db", // WETH - l2: "0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B", +}; + +// The default ERC20 gateway is the generic gateway used by Arbitrum Orbit chains to mint tokens which do not have a custom gateway set. +export const DEFAULT_ARBITRUM_GATEWAY: { [chainId: number]: { l1: string; l2: string } } = { + [CHAIN_IDs.ALEPH_ZERO]: { + l1: "0xccaF21F002EAF230c9Fa810B34837a3739B70F7B", + l2: "0x2A5a79061b723BBF453ef7E07c583C750AFb9BD6", }, - [TOKEN_SYMBOLS_MAP.DAI.addresses[CHAIN_IDs.MAINNET]]: { - l1: "0xD3B5b60020504bc3489D6949d545893982BA3011", // DAI - l2: "0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65", + [CHAIN_IDs.ARBITRUM]: { + l1: "0xa3A7B6F88361F48403514059F1F16C8E78d60EeC", + l2: "0x09e9222E96E7B4AE2a407B98d48e330053351EEe", }, }; @@ -493,6 +499,7 @@ export const SCROLL_CUSTOM_GATEWAY: { [chainId: number]: { l1: string; l2: strin // Expected worst-case time for message from L1 to propogate to L2 in seconds export const EXPECTED_L1_TO_L2_MESSAGE_TIME = { + [CHAIN_IDs.ALEPH_ZERO]: 20 * 60, [CHAIN_IDs.ARBITRUM]: 20 * 60, [CHAIN_IDs.BASE]: 20 * 60, [CHAIN_IDs.BLAST]: 20 * 60, diff --git a/src/common/ContractAddresses.ts b/src/common/ContractAddresses.ts index 4433ed226..cdd9473ee 100644 --- a/src/common/ContractAddresses.ts +++ b/src/common/ContractAddresses.ts @@ -19,6 +19,7 @@ import ZK_SYNC_DEFAULT_ERC20_BRIDGE_L1_ABI from "./abi/ZkSyncDefaultErc20BridgeL import ZK_SYNC_DEFAULT_ERC20_BRIDGE_L2_ABI from "./abi/ZkSyncDefaultErc20BridgeL2.json"; import ZK_SYNC_MAILBOX_ABI from "./abi/ZkSyncMailbox.json"; import ARBITRUM_ERC20_GATEWAY_ROUTER_L1_ABI from "./abi/ArbitrumErc20GatewayRouterL1.json"; +import ARBITRUM_ERC20_GATEWAY_L1_ABI from "./abi/ArbitrumErc20GatewayL1.json"; import ARBITRUM_ERC20_GATEWAY_L2_ABI from "./abi/ArbitrumErc20GatewayL2.json"; import ARBITRUM_OUTBOX_ABI from "./abi/ArbitrumOutbox.json"; import LINEA_MESSAGE_SERVICE_ABI from "./abi/LineaMessageService.json"; @@ -127,10 +128,24 @@ export const CONTRACT_ADDRESSES: { address: "0x8484Ef722627bf18ca5Ae6BcF031c23E6e922B30", abi: POLYGON_BRIDGE_ABI, }, - arbitrumErc20GatewayRouter: { + orbitOutbox_42161: { + address: "0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840", + abi: ARBITRUM_OUTBOX_ABI, + }, + orbitErc20GatewayRouter_42161: { address: "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", abi: ARBITRUM_ERC20_GATEWAY_ROUTER_L1_ABI, }, + orbitErc20Gateway_42161: { + abi: ARBITRUM_ERC20_GATEWAY_L1_ABI, + }, + orbitErc20GatewayRouter_41455: { + address: "0xeBb17f398ed30d02F2e8733e7c1e5cf566e17812", + abi: ARBITRUM_ERC20_GATEWAY_ROUTER_L1_ABI, + }, + orbitErc20Gateway_41455: { + abi: ARBITRUM_ERC20_GATEWAY_L1_ABI, + }, VotingV2: { address: "0x004395edb43EFca9885CEdad51EC9fAf93Bd34ac", abi: VOTING_V2_ABI, @@ -308,10 +323,6 @@ export const CONTRACT_ADDRESSES: { erc20Gateway: { abi: ARBITRUM_ERC20_GATEWAY_L2_ABI, }, - outbox: { - address: "0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840", - abi: ARBITRUM_OUTBOX_ABI, - }, cctpMessageTransmitter: { address: "0xC30362313FBBA5cf9163F0bb16a0e01f01A896ca", abi: CCTP_MESSAGE_TRANSMITTER_ABI, @@ -321,6 +332,11 @@ export const CONTRACT_ADDRESSES: { abi: CCTP_TOKEN_MESSENGER_ABI, }, }, + 41455: { + erc20Gateway: { + abi: ARBITRUM_ERC20_GATEWAY_L2_ABI, + }, + }, 59144: { l2MessageService: { address: "0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec", diff --git a/src/common/abi/ArbitrumErc20GatewayL1.json b/src/common/abi/ArbitrumErc20GatewayL1.json new file mode 100644 index 000000000..47f31c802 --- /dev/null +++ b/src/common/abi/ArbitrumErc20GatewayL1.json @@ -0,0 +1,14 @@ +[ + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "l1Token", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "_from", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "_to", "type": "address" }, + { "indexed": true, "internalType": "uint256", "name": "_sequenceNumber", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "_amount", "type": "uint256" } + ], + "name": "DepositInitiated", + "type": "event" + } +] diff --git a/src/common/abi/ArbitrumErc20GatewayRouterL1.json b/src/common/abi/ArbitrumErc20GatewayRouterL1.json index a1526e9d4..28029092c 100644 --- a/src/common/abi/ArbitrumErc20GatewayRouterL1.json +++ b/src/common/abi/ArbitrumErc20GatewayRouterL1.json @@ -12,17 +12,5 @@ "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], "stateMutability": "payable", "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": false, "internalType": "address", "name": "l1Token", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "_from", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "_to", "type": "address" }, - { "indexed": true, "internalType": "uint256", "name": "_sequenceNumber", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "_amount", "type": "uint256" } - ], - "name": "DepositInitiated", - "type": "event" } ] diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index 87a4f30c7..8b648dcf9 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -32,7 +32,7 @@ import { } from "../utils"; import { ChainFinalizer, CrossChainMessage } from "./types"; import { - arbitrumOneFinalizer, + arbStackFinalizer, cctpL1toL2Finalizer, cctpL2toL1Finalizer, lineaL1ToL2Finalizer, @@ -78,8 +78,12 @@ const chainFinalizers: { [chainId: number]: { finalizeOnL2: ChainFinalizer[]; fi finalizeOnL1: [opStackFinalizer, cctpL2toL1Finalizer], finalizeOnL2: [cctpL1toL2Finalizer], }, + [CHAIN_IDs.ALEPH_ZERO]: { + finalizeOnL1: [arbStackFinalizer], + finalizeOnL2: [], + }, [CHAIN_IDs.ARBITRUM]: { - finalizeOnL1: [arbitrumOneFinalizer, cctpL2toL1Finalizer], + finalizeOnL1: [arbStackFinalizer, cctpL2toL1Finalizer], finalizeOnL2: [cctpL1toL2Finalizer], }, [CHAIN_IDs.LINEA]: { diff --git a/src/finalizer/utils/arbitrum.ts b/src/finalizer/utils/arbStack.ts similarity index 87% rename from src/finalizer/utils/arbitrum.ts rename to src/finalizer/utils/arbStack.ts index b660ed3f3..0373c0cf3 100644 --- a/src/finalizer/utils/arbitrum.ts +++ b/src/finalizer/utils/arbStack.ts @@ -13,17 +13,15 @@ import { getL1TokenInfo, Multicall2Call, compareAddressesSimple, - TOKEN_SYMBOLS_MAP, CHAIN_IDs, + TOKEN_SYMBOLS_MAP, } from "../../utils"; import { TokensBridged } from "../../interfaces"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { CONTRACT_ADDRESSES } from "../../common"; import { FinalizerPromise, CrossChainMessage } from "../types"; -const CHAIN_ID = CHAIN_IDs.ARBITRUM; - -export async function arbitrumOneFinalizer( +export async function arbStackFinalizer( logger: winston.Logger, signer: Signer, hubPoolClient: HubPoolClient, @@ -49,25 +47,26 @@ export async function arbitrumOneFinalizer( (e) => e.blockNumber <= latestBlockToFinalize && // USDC withdrawals for Arbitrum should be finalized via the CCTP Finalizer. - !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[CHAIN_ID]) + !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[chainId]) ); - return await multicallArbitrumFinalizations(olderTokensBridgedEvents, signer, hubPoolClient, logger); + return await multicallArbitrumFinalizations(olderTokensBridgedEvents, signer, hubPoolClient, logger, chainId); } async function multicallArbitrumFinalizations( tokensBridged: TokensBridged[], hubSigner: Signer, hubPoolClient: HubPoolClient, - logger: winston.Logger + logger: winston.Logger, + chainId: number ): Promise { - const finalizableMessages = await getFinalizableMessages(logger, tokensBridged, hubSigner); - const callData = await Promise.all(finalizableMessages.map((message) => finalizeArbitrum(message.message))); + const finalizableMessages = await getFinalizableMessages(logger, tokensBridged, hubSigner, chainId); + const callData = await Promise.all(finalizableMessages.map((message) => finalizeArbitrum(message.message, chainId))); const crossChainTransfers = finalizableMessages.map(({ info: { l2TokenAddress, amountToReturn } }) => { - const l1TokenInfo = getL1TokenInfo(l2TokenAddress, CHAIN_ID); + const l1TokenInfo = getL1TokenInfo(l2TokenAddress, chainId); const amountFromWei = convertFromWei(amountToReturn.toString(), l1TokenInfo.decimals); const withdrawal: CrossChainMessage = { - originationChainId: CHAIN_ID, + originationChainId: chainId, l1TokenSymbol: l1TokenInfo.symbol, amount: amountFromWei, type: "withdrawal", @@ -82,10 +81,10 @@ async function multicallArbitrumFinalizations( }; } -async function finalizeArbitrum(message: L2ToL1MessageWriter): Promise { - const l2Provider = getCachedProvider(CHAIN_ID, true); +async function finalizeArbitrum(message: L2ToL1MessageWriter, chainId: number): Promise { + const l2Provider = getCachedProvider(chainId, true); const proof = await message.getOutboxProof(l2Provider); - const { address, abi } = CONTRACT_ADDRESSES[CHAIN_ID].outbox; + const { address, abi } = CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET][`orbitOutbox_${chainId}`]; const outbox = new Contract(address, abi); // eslint-disable-next-line @typescript-eslint/no-explicit-any const eventData = (message as any).nitroWriter.event; // nitroWriter is a private property on the @@ -111,7 +110,8 @@ async function finalizeArbitrum(message: L2ToL1MessageWriter): Promise { - const allMessagesWithStatuses = await getAllMessageStatuses(tokensBridged, logger, l1Signer); + const allMessagesWithStatuses = await getAllMessageStatuses(tokensBridged, logger, l1Signer, chainId); const statusesGrouped = groupObjectCountsByProp( allMessagesWithStatuses, (message: { status: string }) => message.status @@ -135,7 +135,8 @@ async function getFinalizableMessages( async function getAllMessageStatuses( tokensBridged: TokensBridged[], logger: winston.Logger, - mainnetSigner: Signer + mainnetSigner: Signer, + chainId: number ): Promise< { info: TokensBridged; @@ -148,7 +149,9 @@ async function getAllMessageStatuses( const logIndexesForMessage = getUniqueLogIndex(tokensBridged); return ( await Promise.all( - tokensBridged.map((e, i) => getMessageOutboxStatusAndProof(logger, e, mainnetSigner, logIndexesForMessage[i])) + tokensBridged.map((e, i) => + getMessageOutboxStatusAndProof(logger, e, mainnetSigner, logIndexesForMessage[i], chainId) + ) ) ) .map((result, i) => { @@ -164,12 +167,13 @@ async function getMessageOutboxStatusAndProof( logger: winston.Logger, event: TokensBridged, l1Signer: Signer, - logIndex: number + logIndex: number, + chainId: number ): Promise<{ message: L2ToL1MessageWriter; status: string; }> { - const l2Provider = getCachedProvider(CHAIN_ID, true); + const l2Provider = getCachedProvider(chainId, true); const receipt = await l2Provider.getTransactionReceipt(event.transactionHash); const l2Receipt = new L2TransactionReceipt(receipt); diff --git a/src/finalizer/utils/index.ts b/src/finalizer/utils/index.ts index b3d8d45c3..4e93f4533 100644 --- a/src/finalizer/utils/index.ts +++ b/src/finalizer/utils/index.ts @@ -1,5 +1,5 @@ export * from "./polygon"; -export * from "./arbitrum"; +export * from "./arbStack"; export * from "./opStack"; export * from "./zkSync"; export * from "./scroll"; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 5cbdeac7d..8993ad9c8 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -53,7 +53,6 @@ export type PendingRootBundle = interfaces.PendingRootBundle; // SpokePool interfaces export type RelayData = interfaces.RelayData; -export type FundsDepositedEvent = interfaces.FundsDepositedEvent; export type Deposit = interfaces.Deposit; export type DepositWithBlock = interfaces.DepositWithBlock; export type Fill = interfaces.Fill; diff --git a/test/AdapterManager.SendTokensCrossChain.ts b/test/AdapterManager.SendTokensCrossChain.ts index f6bd5e77f..a75541918 100644 --- a/test/AdapterManager.SendTokensCrossChain.ts +++ b/test/AdapterManager.SendTokensCrossChain.ts @@ -427,8 +427,8 @@ async function constructChainSpecificFakes() { // Arbitrum contracts l1ArbitrumBridge = await makeFake( - "arbitrumErc20GatewayRouter", - CONTRACT_ADDRESSES[1].arbitrumErc20GatewayRouter.address + "orbitErc20GatewayRouter_42161", + CONTRACT_ADDRESSES[1].orbitErc20GatewayRouter_42161.address ); // zkSync contracts diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 19cb1b314..f9d322cc4 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -1,4 +1,5 @@ import { clients, constants, utils as sdkUtils } from "@across-protocol/sdk"; +import hre from "hardhat"; import { AcrossApiClient, ConfigStoreClient, MultiCallerClient, TokenClient } from "../src/clients"; import { FillStatus, Deposit, RelayData } from "../src/interfaces"; import { CONFIG_STORE_VERSION } from "../src/common"; @@ -510,9 +511,9 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { }); it("Correctly defers destination chain fills", async function () { - let { average: avgBlockTime } = await averageBlockTime(spokePool_2.provider); - avgBlockTime = Math.ceil(avgBlockTime); - const minFillTime = 4 * avgBlockTime; // Fill after deposit has aged 4 blocks. + const { average: avgBlockTime } = await averageBlockTime(spokePool_2.provider); + const minDepositAgeBlocks = 4; // Fill after deposit has aged this # of blocks. + const minFillTime = Math.ceil(minDepositAgeBlocks * avgBlockTime); relayerInstance = new Relayer( relayer.address, @@ -537,15 +538,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { } as unknown as RelayerConfig ); - const deposit = await depositV3( - spokePool_1, - destinationChainId, - depositor, - inputToken, - inputAmount, - outputToken, - outputAmount - ); + await depositV3(spokePool_1, destinationChainId, depositor, inputToken, inputAmount, outputToken, outputAmount); await updateAllClients(); let txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); for (const receipts of Object.values(txnReceipts)) { @@ -553,19 +546,10 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { } expect(lastSpyLogIncludes(spy, "due to insufficient fill time for")).to.be.true; - // SpokePool time is overridden and does not increment; it must be cranked manually. - const startTime = Number(await spokePool_2.getCurrentTime()); - let nextTime: number; - do { - await fillV3Relay( - spokePool_2, - { ...deposit, depositId: deposit.depositId + 1, outputAmount: bnZero, recipient: randomAddress() }, - relayer - ); - nextTime = Number(await spokePool_2.getCurrentTime()) + avgBlockTime; - await spokePool_2.setCurrentTime(nextTime); - } while (startTime + minFillTime > nextTime); - + // Mine enough blocks such that the deposit has aged sufficiently. + for (let i = 0; i < minFillTime * minDepositAgeBlocks * 10; i++) { + await hre.network.provider.send("evm_mine"); + } await updateAllClients(); txnReceipts = await relayerInstance.checkForUnfilledDepositsAndFill(); const receipts = await txnReceipts[destinationChainId]; diff --git a/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts b/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts index 21890b602..9bf7e797c 100644 --- a/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts +++ b/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts @@ -428,8 +428,8 @@ async function constructChainSpecificFakes() { // Arbitrum contracts l1ArbitrumBridge = await makeFake( - "arbitrumErc20GatewayRouter", - CONTRACT_ADDRESSES[1].arbitrumErc20GatewayRouter.address + "orbitErc20GatewayRouter_42161", + CONTRACT_ADDRESSES[1].orbitErc20GatewayRouter_42161.address ); // zkSync contracts diff --git a/yarn.lock b/yarn.lock index 7f13b4ab5..959ce422c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,10 +11,10 @@ "@uma/common" "^2.17.0" hardhat "^2.9.3" -"@across-protocol/constants@^3.1.16": - version "3.1.16" - resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.16.tgz#c126085d29d4d051fd02a04c833d804d37c3c219" - integrity sha512-+U+AecGWnfY4b4sSfKBvsDj/+yXKEqpTXcZgI8GVVmUTkUhs1efA0kN4q3q10yy5TXI5TtagaG7R9yZg1zgKKg== +"@across-protocol/constants@^3.1.19": + version "3.1.19" + resolved "https://registry.yarnpkg.com/@across-protocol/constants/-/constants-3.1.19.tgz#3c29b52ec5f2eece93a6abd50d580668b03dd7b3" + integrity sha512-XOFF+o64TDn57xNfUB38kWy8lYyE9lB7PBdyoMOadsXx00HC3KMznFi/paLRKT1iZ50vDwHp00tNZbr7Z7umzA== "@across-protocol/contracts@^0.1.4": version "0.1.4" @@ -25,12 +25,13 @@ "@openzeppelin/contracts" "4.1.0" "@uma/core" "^2.18.0" -"@across-protocol/contracts@^3.0.11": - version "3.0.11" - resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-3.0.11.tgz#d010e2a1a44a7ac8184848a54bb9c7b2d41875b0" - integrity sha512-T2C8jOetkcqFDbp8fqI894Dd9qm7D9X7h1kqsI7rYu9paXdaqAUVSR/XcMTq2aHhNAVgb0OlKY/do982ujd0xw== +"@across-protocol/contracts@^3.0.16": + version "3.0.16" + resolved "https://registry.yarnpkg.com/@across-protocol/contracts/-/contracts-3.0.16.tgz#22eb0c1dcdb01e8ca504dc2351d46513d9f71cc6" + integrity sha512-vwg+PmWaenlrx7kTHZdjDTTj1PwXWFU3rMlFyfKM8xBXbPWhIfMQCKCYOwFrGmZw2nRTYgoyhoKN/f6rUs/snw== dependencies: - "@across-protocol/constants" "^3.1.16" + "@across-protocol/constants" "^3.1.19" + "@coral-xyz/anchor" "^0.30.1" "@defi-wonderland/smock" "^2.3.4" "@eth-optimism/contracts" "^0.5.40" "@ethersproject/abstract-provider" "5.7.0" @@ -39,20 +40,27 @@ "@openzeppelin/contracts" "4.9.6" "@openzeppelin/contracts-upgradeable" "4.9.6" "@scroll-tech/contracts" "^0.1.0" + "@solana-developers/helpers" "^2.4.0" + "@solana/spl-token" "^0.4.6" + "@solana/web3.js" "^1.31.0" + "@types/yargs" "^17.0.33" "@uma/common" "^2.34.0" "@uma/contracts-node" "^0.4.17" "@uma/core" "^2.56.0" axios "^1.7.4" + bs58 "^6.0.0" + prettier-plugin-rust "^0.1.9" + yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@^3.2.11": - version "3.2.11" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.11.tgz#8d5190863240e9a5062a38a03406f8e3b0390d84" - integrity sha512-eNUKYlV1ClzVHFHwDDRKaK2u/eFCTrZrCTglATtuQVJCUlpLlIR369qx/kquVbH6E5rRjSZm2Lo9hiuUJ3oH2Q== +"@across-protocol/sdk@^3.2.13": + version "3.2.13" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-3.2.13.tgz#8f7fd14dabdd7da0be2a548f9a3b0b49c7f19eba" + integrity sha512-lyzP7bgaABygeIwWISQtBWmzlxyZVvlmp0Au518W8TZ1vkagt7sZa24SV4do8TP9z4JhfsRJVnKGqQJIAWd5hQ== dependencies: "@across-protocol/across-token" "^1.0.0" - "@across-protocol/constants" "^3.1.16" - "@across-protocol/contracts" "^3.0.11" + "@across-protocol/constants" "^3.1.19" + "@across-protocol/contracts" "^3.0.16" "@eth-optimism/sdk" "^3.3.1" "@ethersproject/bignumber" "^5.7.0" "@pinata/sdk" "^2.1.0" @@ -257,6 +265,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.25.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -357,6 +372,40 @@ typeorm-naming-strategies "^4.1.0" winston "^3.9.0" +"@coral-xyz/anchor-errors@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor-errors/-/anchor-errors-0.30.1.tgz#bdfd3a353131345244546876eb4afc0e125bec30" + integrity sha512-9Mkradf5yS5xiLWrl9WrpjqOrAV+/W2RQHDlbnAZBivoGpOs1ECjoDCkVk4aRG8ZdiFiB8zQEVlxf+8fKkmSfQ== + +"@coral-xyz/anchor@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/anchor/-/anchor-0.30.1.tgz#17f3e9134c28cd0ea83574c6bab4e410bcecec5d" + integrity sha512-gDXFoF5oHgpriXAaLpxyWBHdCs8Awgf/gLHIo6crv7Aqm937CNdY+x+6hoj7QR5vaJV7MxWSQ0NGFzL3kPbWEQ== + dependencies: + "@coral-xyz/anchor-errors" "^0.30.1" + "@coral-xyz/borsh" "^0.30.1" + "@noble/hashes" "^1.3.1" + "@solana/web3.js" "^1.68.0" + bn.js "^5.1.2" + bs58 "^4.0.1" + buffer-layout "^1.2.2" + camelcase "^6.3.0" + cross-fetch "^3.1.5" + crypto-hash "^1.3.0" + eventemitter3 "^4.0.7" + pako "^2.0.3" + snake-case "^3.0.4" + superstruct "^0.15.4" + toml "^3.0.0" + +"@coral-xyz/borsh@^0.30.1": + version "0.30.1" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.30.1.tgz#869d8833abe65685c72e9199b8688477a4f6b0e3" + integrity sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1503,7 +1552,7 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== -"@noble/curves@1.6.0", "@noble/curves@^1.4.0", "@noble/curves@~1.6.0": +"@noble/curves@1.6.0", "@noble/curves@^1.4.0", "@noble/curves@^1.4.2", "@noble/curves@~1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.6.0.tgz#be5296ebcd5a1730fccea4786d420f87abfeb40b" integrity sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ== @@ -1515,7 +1564,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg== -"@noble/hashes@1.5.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": +"@noble/hashes@1.5.0", "@noble/hashes@^1.3.1", "@noble/hashes@^1.4.0", "@noble/hashes@~1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== @@ -2542,6 +2591,143 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@solana-developers/helpers@^2.4.0": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@solana-developers/helpers/-/helpers-2.5.6.tgz#2af7613ea6848ce087c0dec7cf38e6f172abcbd4" + integrity sha512-NPWZblVMl4LuVVSJOZG0ZF0VYnrMUjCyMNTiGwNUXPK2WWYJCqpuDyzs/PMqwvM4gMTjk4pEToBX8N2UxDvZkQ== + dependencies: + "@solana/spl-token" "^0.4.8" + "@solana/spl-token-metadata" "^0.1.4" + "@solana/web3.js" "^1.95.2" + bs58 "^6.0.0" + dotenv "^16.4.5" + +"@solana/buffer-layout-utils@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout-utils/-/buffer-layout-utils-0.2.0.tgz#b45a6cab3293a2eb7597cceb474f229889d875ca" + integrity sha512-szG4sxgJGktbuZYDg2FfNmkMi0DYQoVjN2h7ta1W1hPrwzarcFLBq9UpX1UjNXsNpT9dn+chgprtWGioUAr4/g== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/web3.js" "^1.32.0" + bigint-buffer "^1.1.5" + bignumber.js "^9.0.1" + +"@solana/buffer-layout@^4.0.0", "@solana/buffer-layout@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" + integrity sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA== + dependencies: + buffer "~6.0.3" + +"@solana/codecs-core@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz#1a2d76b9c7b9e7b7aeb3bd78be81c2ba21e3ce22" + integrity sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ== + dependencies: + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-data-structures@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz#d47b2363d99fb3d643f5677c97d64a812982b888" + integrity sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-numbers@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz#f34978ddf7ea4016af3aaed5f7577c1d9869a614" + integrity sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs-strings@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz#e1d9167075b8c5b0b60849f8add69c0f24307018" + integrity sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/codecs@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-rc.1.tgz#146dc5db58bd3c28e04b4c805e6096c2d2a0a875" + integrity sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/options" "2.0.0-rc.1" + +"@solana/errors@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-rc.1.tgz#3882120886eab98a37a595b85f81558861b29d62" + integrity sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ== + dependencies: + chalk "^5.3.0" + commander "^12.1.0" + +"@solana/options@2.0.0-rc.1": + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-rc.1.tgz#06924ba316dc85791fc46726a51403144a85fc4d" + integrity sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA== + dependencies: + "@solana/codecs-core" "2.0.0-rc.1" + "@solana/codecs-data-structures" "2.0.0-rc.1" + "@solana/codecs-numbers" "2.0.0-rc.1" + "@solana/codecs-strings" "2.0.0-rc.1" + "@solana/errors" "2.0.0-rc.1" + +"@solana/spl-token-group@^0.0.7": + version "0.0.7" + resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.7.tgz#83c00f0cd0bda33115468cd28b89d94f8ec1fee4" + integrity sha512-V1N/iX7Cr7H0uazWUT2uk27TMqlqedpXHRqqAbVO2gvmJyT0E0ummMEAVQeXZ05ZhQ/xF39DLSdBp90XebWEug== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + +"@solana/spl-token-metadata@^0.1.4", "@solana/spl-token-metadata@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.6.tgz#d240947aed6e7318d637238022a7b0981b32ae80" + integrity sha512-7sMt1rsm/zQOQcUWllQX9mD2O6KhSAtY1hFR2hfFwgqfFWzSY9E9GDvFVNYUI1F0iQKcm6HmePU9QbKRXTEBiA== + dependencies: + "@solana/codecs" "2.0.0-rc.1" + +"@solana/spl-token@^0.4.6", "@solana/spl-token@^0.4.8": + version "0.4.9" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.9.tgz#24d032d2935f237925c3b058ba6bb1e1ece5428c" + integrity sha512-g3wbj4F4gq82YQlwqhPB0gHFXfgsC6UmyGMxtSLf/BozT/oKd59465DbnlUK8L8EcimKMavxsVAMoLcEdeCicg== + dependencies: + "@solana/buffer-layout" "^4.0.0" + "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-group" "^0.0.7" + "@solana/spl-token-metadata" "^0.1.6" + buffer "^6.0.3" + +"@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.95.2": + version "1.95.4" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.4.tgz#771603f60d75cf7556ad867e1fd2efae32f9ad09" + integrity sha512-sdewnNEA42ZSMxqkzdwEWi6fDgzwtJHaQa5ndUGEJYtoOnM6X5cvPmjoTUp7/k7bRrVAxfBgDnvQQHD6yhlLYw== + dependencies: + "@babel/runtime" "^7.25.0" + "@noble/curves" "^1.4.2" + "@noble/hashes" "^1.4.0" + "@solana/buffer-layout" "^4.0.1" + agentkeepalive "^4.5.0" + bigint-buffer "^1.1.5" + bn.js "^5.2.1" + borsh "^0.7.0" + bs58 "^4.0.1" + buffer "6.0.3" + fast-stable-stringify "^1.0.0" + jayson "^4.1.1" + node-fetch "^2.7.0" + rpc-websockets "^9.0.2" + superstruct "^2.0.2" + "@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1": version "0.14.1" resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.1.tgz#179afb29f4e295a77cc141151f26b3848abc3c46" @@ -2561,6 +2747,13 @@ resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== +"@swc/helpers@^0.5.11": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c" + integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== + dependencies: + tslib "^2.4.0" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" @@ -2904,6 +3097,13 @@ dependencies: "@types/node" "*" +"@types/connect@^3.4.33": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + "@types/ethereum-protocol@^1.0.0": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/ethereum-protocol/-/ethereum-protocol-1.0.2.tgz#e765d4c6f4b5ebe906932bd20333e307c56a9bc7" @@ -3060,6 +3260,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b" integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw== +"@types/node@^12.12.54": + version "12.20.55" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" + integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== + "@types/node@^12.12.6": version "12.20.48" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.48.tgz#55f70bd432b6515828c0298689776861b90ca4fa" @@ -3134,11 +3339,30 @@ resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== +"@types/uuid@^8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + "@types/validator@^13.11.8": version "13.11.8" resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.8.tgz#bb1162ec0fe6f87c95ca812f15b996fcc5e1e2dc" integrity sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ== +"@types/ws@^7.4.4": + version "7.4.7" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" + integrity sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww== + dependencies: + "@types/node" "*" + +"@types/ws@^8.2.2": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + "@types/ws@^8.5.5": version "8.5.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.7.tgz#1ca585074fe5d2c81dec7a3d451f244a2a6d83cb" @@ -3146,6 +3370,18 @@ dependencies: "@types/node" "*" +"@types/yargs-parser@*": + version "21.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" + integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== + +"@types/yargs@^17.0.33": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^4.29.1": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" @@ -3436,6 +3672,14 @@ resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abbrev@1, abbrev@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -3607,6 +3851,13 @@ agentkeepalive@^4.1.3: depd "^1.1.2" humanize-ms "^1.2.1" +agentkeepalive@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -4092,6 +4343,11 @@ base-x@^3.0.2, base-x@^3.0.8: dependencies: safe-buffer "^5.0.1" +base-x@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/base-x/-/base-x-5.0.0.tgz#6d835ceae379130e1a4cb846a70ac4746f28ea9b" + integrity sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ== + base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -4137,6 +4393,13 @@ big.js@^6.0.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.1.1.tgz#63b35b19dc9775c94991ee5db7694880655d5537" integrity sha512-1vObw81a8ylZO5ePrtMay0n018TcftpTA5HFKDaSuiUDBo8biRBtjIobw60OpwuvrGk+FsxKamqN4cnmj/eXdg== +bigint-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/bigint-buffer/-/bigint-buffer-1.1.5.tgz#d038f31c8e4534c1f8d0015209bf34b4fa6dd442" + integrity sha512-trfYco6AoZ+rKhKnxA0hgX0HAbVP/s808/EuDSe2JDzUnCp/xAsli35Orvk67UrTEcwuxZqYZDmfA2RXJgxVvA== + dependencies: + bindings "^1.3.0" + bigint-crypto-utils@^3.0.23: version "3.1.7" resolved "https://registry.yarnpkg.com/bigint-crypto-utils/-/bigint-crypto-utils-3.1.7.tgz#c4c1b537c7c1ab7aadfaecf3edfd45416bf2c651" @@ -4186,7 +4449,7 @@ binary-extensions@^2.0.0, binary-extensions@^2.2.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -4287,6 +4550,15 @@ borc@^2.1.2: json-text-sequence "~0.1.0" readable-stream "^3.6.0" +borsh@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.7.0.tgz#6e9560d719d86d90dc589bca60ffc8a6c51fec2a" + integrity sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA== + dependencies: + bn.js "^5.2.0" + bs58 "^4.0.0" + text-encoding-utf-8 "^1.0.2" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -4411,6 +4683,13 @@ bs58@^4.0.0, bs58@^4.0.1: dependencies: base-x "^3.0.2" +bs58@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/bs58/-/bs58-6.0.0.tgz#a2cda0130558535dd281a2f8697df79caaf425d8" + integrity sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw== + dependencies: + base-x "^5.0.0" + bs58check@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" @@ -4435,6 +4714,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-layout@^1.2.0, buffer-layout@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/buffer-layout/-/buffer-layout-1.2.2.tgz#b9814e7c7235783085f9ca4966a0cfff112259d5" + integrity sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA== + buffer-reverse@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-reverse/-/buffer-reverse-1.0.1.tgz#49283c8efa6f901bc01fa3304d06027971ae2f60" @@ -4471,7 +4755,7 @@ buffer@4.9.2: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@6.0.3, buffer@^6.0.1, buffer@^6.0.3: +buffer@6.0.3, buffer@^6.0.1, buffer@^6.0.3, buffer@~6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -4630,7 +4914,7 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0: +camelcase@^6.0.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -4743,6 +5027,11 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + change-case@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.0.2.tgz#fd48746cce02f03f0a672577d1d3a8dc2eceb037" @@ -5174,7 +5463,12 @@ commander@^10.0.0: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== -commander@^2.15.0, commander@^2.19.0: +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^2.15.0, commander@^2.19.0, commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -5400,6 +5694,13 @@ cross-fetch@^3.0.6, cross-fetch@^3.1.4: dependencies: node-fetch "2.6.7" +cross-fetch@^3.1.5: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5444,6 +5745,11 @@ crypto-browserify@3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-hash@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247" + integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== + crypto-js@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" @@ -5640,6 +5946,11 @@ define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4, de has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delay@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/delay/-/delay-5.0.0.tgz#137045ef1b96e5071060dd5be60bf9334436bd1d" + integrity sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw== + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -5837,6 +6148,14 @@ dot-case@^2.1.0: dependencies: no-case "^2.2.0" +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + dot-prop@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" @@ -5854,6 +6173,11 @@ dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + dotenv@^9.0.0: version "9.0.2" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-9.0.2.tgz#dacc20160935a37dea6364aa1bef819fb9b6ab05" @@ -6163,11 +6487,18 @@ es6-iterator@^2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@4.2.8, es6-promise@^4.2.8: +es6-promise@4.2.8, es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ== + dependencies: + es6-promise "^4.0.3" + es6-symbol@^3.1.1, es6-symbol@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -6880,6 +7211,16 @@ eventemitter3@4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventid@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/eventid/-/eventid-2.0.1.tgz#574e860149457a79a2efe788c459f0c3062d02ec" @@ -6979,6 +7320,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ== + fake-merkle-patricia-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" @@ -7039,6 +7385,11 @@ fast-safe-stringify@^2.0.6: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== +fast-stable-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz#5c5543462b22aeeefd36d05b34e51c78cb86d313" + integrity sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag== + fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" @@ -9074,6 +9425,11 @@ isomorphic-unfetch@^3.0.0: node-fetch "^2.6.1" unfetch "^4.2.0" +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + isows@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" @@ -9179,6 +9535,29 @@ jackspeak@^2.3.5: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +jayson@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" + integrity sha512-5nzMWDHy6f+koZOuYsArh2AXs73NfWYVlFyJJuCedr93GpY+Ku8qq10ropSXVfHK+H0T6paA88ww+/dV+1fBNA== + dependencies: + "@types/connect" "^3.4.33" + "@types/node" "^12.12.54" + "@types/ws" "^7.4.4" + JSONStream "^1.3.5" + commander "^2.20.3" + delay "^5.0.0" + es6-promisify "^5.0.0" + eyes "^0.1.8" + isomorphic-ws "^4.0.1" + json-stringify-safe "^5.0.1" + uuid "^8.3.2" + ws "^7.5.10" + +jinx-rust@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/jinx-rust/-/jinx-rust-0.1.6.tgz#c7bce55d97bfbad76a9b930c01fe6a8629a170d7" + integrity sha512-qP+wtQL1PrDDFwtPKhNGtjWOmijCrKdfUHWTV2G/ikxfjrh+cjdvkQTmny9RAsVF0jiui9m+F0INWu4cuRcZeQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -9339,7 +9718,7 @@ json-stringify-nice@^1.1.4: resolved "https://registry.yarnpkg.com/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -9386,10 +9765,10 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonparse@^1.3.1: +jsonparse@^1.2.0, jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== jsonschema@^1.2.4: version "1.4.0" @@ -10018,6 +10397,13 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -10949,6 +11335,14 @@ no-case@^2.2.0, no-case@^2.3.2: dependencies: lower-case "^1.1.1" +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + node-abi@^3.3.0: version "3.54.0" resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" @@ -10990,7 +11384,7 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@^2.6.9: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -11616,6 +12010,11 @@ pacote@^11.1.11, pacote@^11.2.6, pacote@^11.3.0, pacote@^11.3.1, pacote@^11.3.5: ssri "^8.0.1" tar "^6.1.0" +pako@^2.0.3: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + param-case@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" @@ -12011,6 +12410,14 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" +prettier-plugin-rust@^0.1.9: + version "0.1.9" + resolved "https://registry.yarnpkg.com/prettier-plugin-rust/-/prettier-plugin-rust-0.1.9.tgz#1a93b035743fa02a006b4980a1035a260ea9e501" + integrity sha512-n1DTTJQaHMdnoG/+nKUvBm3EKsMVWsYES2UPCiOPiZdBrmuAO/pX++m7L3+Hz3uuhtddpH0HRKHB2F3jbtJBOQ== + dependencies: + jinx-rust "0.1.6" + prettier "^2.7.1" + prettier-plugin-solidity@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.3.tgz#9a35124f578404caf617634a8cab80862d726cba" @@ -12020,7 +12427,7 @@ prettier-plugin-solidity@^1.1.3: semver "^7.3.8" solidity-comments-extractor "^0.0.7" -prettier@^2.3.1, prettier@^2.8.3, prettier@^2.8.8: +prettier@^2.3.1, prettier@^2.7.1, prettier@^2.8.3, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -12622,6 +13029,11 @@ regenerator-runtime@^0.13.11: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.4.3: version "1.5.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" @@ -12922,6 +13334,22 @@ rlp@^2.0.0, rlp@^2.2.3, rlp@^2.2.4, rlp@^2.2.7: dependencies: bn.js "^5.2.0" +rpc-websockets@^9.0.2: + version "9.0.4" + resolved "https://registry.yarnpkg.com/rpc-websockets/-/rpc-websockets-9.0.4.tgz#9d8ee82533b5d1e13d9ded729e3e38d0d8fa083f" + integrity sha512-yWZWN0M+bivtoNLnaDbtny4XchdAIF5Q4g/ZsC5UC61Ckbp0QczwO8fg44rV3uYmY4WHd+EZQbn90W1d8ojzqQ== + dependencies: + "@swc/helpers" "^0.5.11" + "@types/uuid" "^8.3.4" + "@types/ws" "^8.2.2" + buffer "^6.0.3" + eventemitter3 "^5.0.1" + uuid "^8.3.2" + ws "^8.5.0" + optionalDependencies: + bufferutil "^4.0.1" + utf-8-validate "^5.0.2" + run-parallel-limit@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.1.0.tgz#be80e936f5768623a38a963262d6bef8ff11e7ba" @@ -13371,6 +13799,14 @@ snake-case@^2.1.0: dependencies: no-case "^2.2.0" +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + socks-proxy-agent@^6.0.0: version "6.2.0" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz#f6b5229cc0cbd6f2f202d9695f09d871e951c85e" @@ -13668,7 +14104,7 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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== @@ -13694,6 +14130,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, 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@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -13763,7 +14208,7 @@ stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -"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== @@ -13791,6 +14236,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.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -13852,6 +14304,11 @@ superstruct@^1.0.3: resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-1.0.3.tgz#de626a5b49c6641ff4d37da3c7598e7a87697046" integrity sha512-8iTn3oSS8nRGn+C2pgXSKPI3jmpm6FExNazNpjvqS6ZUJQCej3PUXEKM8NjHBOs54ExM+LPW/FBRhymrdcCiSg== +superstruct@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-2.0.2.tgz#3f6d32fbdc11c357deff127d591a39b996300c54" + integrity sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A== + supports-color@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" @@ -14027,6 +14484,11 @@ testrpc@0.0.1: resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== +text-encoding-utf-8@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13" + integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg== + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" @@ -14068,6 +14530,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +"through@>=2.2.7 <3": + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -14130,6 +14597,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + tough-cookie@>=2.3.3: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -14239,6 +14711,11 @@ tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.3, tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.1, tslib@^2.5.0, tslib@^2.6.1, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -15373,7 +15850,7 @@ workerpool@6.2.1: 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== @@ -15399,6 +15876,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.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.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -15428,7 +15914,7 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@8.18.0: +ws@8.18.0, ws@^8.5.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -15454,6 +15940,11 @@ ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== +ws@^7.5.10: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + ws@^8.13.0: version "8.14.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" From f644828099a2d3b865325a138f2fd556d247086a Mon Sep 17 00:00:00 2001 From: bmzig <57361391+bmzig@users.noreply.github.com> Date: Wed, 13 Nov 2024 15:52:46 -0600 Subject: [PATCH 40/41] fix: dedup bridges to approve properly (#1908) * fix: dedup bridges to approve properly Signed-off-by: bennett --------- Signed-off-by: bennett --- src/adapter/BaseChainAdapter.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/adapter/BaseChainAdapter.ts b/src/adapter/BaseChainAdapter.ts index a7c5fc7eb..d2a110e07 100644 --- a/src/adapter/BaseChainAdapter.ts +++ b/src/adapter/BaseChainAdapter.ts @@ -148,8 +148,12 @@ export class BaseChainAdapter { ), ]); // Dedup the `gasTokensToApprove` array so that we don't approve the same bridge to send the same token multiple times. + const tokenBridgePairs = gasTokensToApprove.map(({ token, bridges }) => `${token.address}_${bridges.join("_")}`); const tokensToApprove = gasTokensToApprove - .filter(({ token, bridges }, idx) => gasTokensToApprove.indexOf({ token, bridges }) === idx) + .filter(({ token, bridges }, idx) => { + const tokenBridgePair = `${token.address}_${bridges.join("_")}`; + return tokenBridgePairs.indexOf(tokenBridgePair) === idx; + }) .concat(bridgeTokensToApprove) .filter(({ bridges }) => bridges.length > 0); if (unavailableTokens.length > 0) { From 326c287feb7abba2e5e9023b5dc51f843875aa77 Mon Sep 17 00:00:00 2001 From: Sameer Kumar Subudhi Date: Thu, 14 Nov 2024 10:35:24 +0100 Subject: [PATCH 41/41] :gear: Run fork sync once a week on Mondays --- .github/workflows/fork-sync.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fork-sync.yaml b/.github/workflows/fork-sync.yaml index ee136d55c..0c23e2805 100644 --- a/.github/workflows/fork-sync.yaml +++ b/.github/workflows/fork-sync.yaml @@ -2,7 +2,7 @@ name: Sync Fork on: schedule: - - cron: "0 0 * * *" # once a day + - cron: "0 0 * * 1" # once a week on Monday workflow_dispatch: # on button click jobs: