From cec5aab190d9faaafcd40e359062db3013c58684 Mon Sep 17 00:00:00 2001 From: bennett Date: Fri, 9 May 2025 15:31:27 -0500 Subject: [PATCH 01/20] chore: bump sdk Signed-off-by: bennett --- scripts/zkSyncDemo.ts | 5 +++- src/adapter/BaseChainAdapter.ts | 22 +++++++++--------- src/adapter/bridges/BinanceCEXBridge.ts | 4 ++-- src/adapter/bridges/LineaWethBridge.ts | 8 +++---- src/adapter/bridges/ZKStackBridge.ts | 5 +++- src/adapter/l2Bridges/BinanceCEXBridge.ts | 2 +- src/clients/ConfigStoreClient.ts | 6 ++--- src/clients/HubPoolClient.ts | 2 +- src/clients/InventoryClient.ts | 6 ++--- src/clients/SpokePoolClient.ts | 12 +++++----- src/clients/bridges/AdapterManager.ts | 12 +++++----- src/common/ClientHelper.ts | 14 ++++++------ src/dataworker/Dataworker.ts | 28 +++++++++++------------ src/dataworker/DataworkerUtils.ts | 8 +++---- src/finalizer/utils/arbStack.ts | 10 ++++---- src/finalizer/utils/binance.ts | 2 +- src/finalizer/utils/cctp/l1ToL2.ts | 6 ++--- src/finalizer/utils/cctp/l2ToL1.ts | 6 ++--- src/finalizer/utils/helios.ts | 22 +++++++++--------- src/finalizer/utils/linea/l1ToL2.ts | 6 ++--- src/finalizer/utils/linea/l2ToL1.ts | 22 +++++++++--------- src/finalizer/utils/opStack.ts | 4 ++-- src/libexec/util/evm/util.ts | 2 +- src/monitor/Monitor.ts | 22 +++++++++--------- src/relayer/Relayer.ts | 18 +++++++-------- src/relayer/RelayerClientHelper.ts | 4 ++-- src/scripts/validateRunningBalances.ts | 16 ++++++------- src/utils/SDKUtils.ts | 2 ++ src/utils/TransactionUtils.ts | 5 ++-- src/utils/UmaUtils.ts | 2 +- 30 files changed, 146 insertions(+), 137 deletions(-) diff --git a/scripts/zkSyncDemo.ts b/scripts/zkSyncDemo.ts index 5edb3e5c79..6cbc6cac53 100644 --- a/scripts/zkSyncDemo.ts +++ b/scripts/zkSyncDemo.ts @@ -10,6 +10,7 @@ import { Contract, isDefined, TransactionSimulationResult, + type EvmGasPriceEstimate, } from "../src/utils"; import { askYesNoQuestion } from "./utils"; import minimist from "minimist"; @@ -62,7 +63,9 @@ export async function run(): Promise { connectedSigner ); const l2PubdataByteLimit = zksync.utils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT; - const l1GasPriceData = await gasPriceOracle.getGasPriceEstimate(l1Provider, { chainId: l1ChainId }); + const l1GasPriceData = (await gasPriceOracle.getGasPriceEstimate(l1Provider, { + chainId: l1ChainId, + })) as EvmGasPriceEstimate; const estimatedL1GasPrice = l1GasPriceData.maxPriorityFeePerGas.add(l1GasPriceData.maxFeePerGas); // 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, diff --git a/src/adapter/BaseChainAdapter.ts b/src/adapter/BaseChainAdapter.ts index 64150e9dba..cd23bcffe2 100644 --- a/src/adapter/BaseChainAdapter.ts +++ b/src/adapter/BaseChainAdapter.ts @@ -42,8 +42,8 @@ export type SupportedL1Token = EvmAddress; export type SupportedTokenSymbol = string; export class BaseChainAdapter { - protected baseL1SearchConfig: MakeOptional; - protected baseL2SearchConfig: MakeOptional; + protected baseL1SearchConfig: MakeOptional; + protected baseL2SearchConfig: MakeOptional; private transactionClient: TransactionClient; constructor( @@ -71,7 +71,7 @@ export class BaseChainAdapter { this.logger[level]({ at: name, message, ...data }); } - protected getSearchConfig(chainId: number): MakeOptional { + protected getSearchConfig(chainId: number): MakeOptional { return { ...this.spokePoolClients[chainId].eventSearchConfig }; } @@ -81,19 +81,19 @@ export class BaseChainAdapter { // Note: this must be called after the SpokePoolClients are updated. public getUpdatedSearchConfigs(): { l1SearchConfig: EventSearchConfig; l2SearchConfig: EventSearchConfig } { - const l1LatestBlock = this.spokePoolClients[this.hubChainId].latestBlockSearched; - const l2LatestBlock = this.spokePoolClients[this.chainId].latestBlockSearched; + const l1LatestBlock = this.spokePoolClients[this.hubChainId].latestHeightSearched; + const l2LatestBlock = this.spokePoolClients[this.chainId].latestHeightSearched; if (l1LatestBlock === 0 || l2LatestBlock === 0) { throw new Error("One or more SpokePoolClients have not been updated"); } return { l1SearchConfig: { ...this.baseL1SearchConfig, - toBlock: this.baseL1SearchConfig?.toBlock ?? l1LatestBlock, + to: this.baseL1SearchConfig?.to ?? l1LatestBlock, }, l2SearchConfig: { ...this.baseL2SearchConfig, - toBlock: this.baseL2SearchConfig?.toBlock ?? l2LatestBlock, + to: this.baseL2SearchConfig?.to ?? l2LatestBlock, }, }; } @@ -221,12 +221,12 @@ export class BaseChainAdapter { getBlockForTimestamp(this.chainId, getCurrentTime() - lookbackPeriodSeconds), ]); const l1EventSearchConfig: EventSearchConfig = { - fromBlock: l1SearchFromBlock, - toBlock: this.baseL1SearchConfig.toBlock, + from: l1SearchFromBlock, + to: this.baseL1SearchConfig.to, }; const l2EventSearchConfig: EventSearchConfig = { - fromBlock: l2SearchFromBlock, - toBlock: this.baseL2SearchConfig.toBlock, + from: l2SearchFromBlock, + to: this.baseL2SearchConfig.to, }; return await this.l2Bridges[l1Token].getL2PendingWithdrawalAmount( l2EventSearchConfig, diff --git a/src/adapter/bridges/BinanceCEXBridge.ts b/src/adapter/bridges/BinanceCEXBridge.ts index 06259da606..be86baae13 100644 --- a/src/adapter/bridges/BinanceCEXBridge.ts +++ b/src/adapter/bridges/BinanceCEXBridge.ts @@ -85,7 +85,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { return {}; } assert(l1Token.toAddress() === this.getL1Bridge().address); - const fromTimestamp = (await getTimestampForBlock(this.getL1Bridge().provider, eventConfig.fromBlock)) * 1_000; // Convert timestamp to ms. + const fromTimestamp = (await getTimestampForBlock(this.getL1Bridge().provider, eventConfig.from)) * 1_000; // Convert timestamp to ms. const binanceApiClient = await this.getBinanceClient(); // Fetch the deposit address from the binance API. @@ -142,7 +142,7 @@ export class BinanceCEXBridge extends BaseBridgeAdapter { ? (this.l2SignerOrProvider as ethers.providers.Provider) : (this.l2SignerOrProvider as ethers.Signer).provider; assert(l1Token.toAddress() === this.getL1Bridge().address); - const fromTimestamp = (await getTimestampForBlock(l2Provider, eventConfig.fromBlock)) * 1_000; // Convert timestamp to ms. + const fromTimestamp = (await getTimestampForBlock(l2Provider, eventConfig.from)) * 1_000; // Convert timestamp to ms. const binanceApiClient = await this.getBinanceClient(); // Fetch the deposit address from the binance API. diff --git a/src/adapter/bridges/LineaWethBridge.ts b/src/adapter/bridges/LineaWethBridge.ts index e819e7d7d8..2c66d748cc 100644 --- a/src/adapter/bridges/LineaWethBridge.ts +++ b/src/adapter/bridges/LineaWethBridge.ts @@ -81,8 +81,8 @@ export class LineaWethBridge extends BaseBridgeAdapter { const l2Provider = this.getL2Bridge().provider; const [fromBlock, toBlock] = await Promise.all([ - l2Provider.getBlock(eventConfig.fromBlock), - l2Provider.getBlock(eventConfig.toBlock), + l2Provider.getBlock(eventConfig.from), + l2Provider.getBlock(eventConfig.to), ]); const [l1FromBlock, l1ToBlock] = [ @@ -90,8 +90,8 @@ export class LineaWethBridge extends BaseBridgeAdapter { await getBlockForTimestamp(this.hubChainId, toBlock.timestamp, this.blockFinder), ]; const l1SearchConfig = { - fromBlock: l1FromBlock, - toBlock: l1ToBlock, + from: l1FromBlock, + to: l1ToBlock, }; const initiatedQueryResult = await paginatedEventQuery( this.getL1Bridge(), diff --git a/src/adapter/bridges/ZKStackBridge.ts b/src/adapter/bridges/ZKStackBridge.ts index fa50fa9364..87ab2c2525 100644 --- a/src/adapter/bridges/ZKStackBridge.ts +++ b/src/adapter/bridges/ZKStackBridge.ts @@ -13,6 +13,7 @@ import { isDefined, bnZero, ZERO_BYTES, + EvmGasPriceEstimate, } from "../../utils"; import { processEvent, matchL2EthDepositAndWrapEvents } from "../utils"; import { CONTRACT_ADDRESSES } from "../../common"; @@ -255,7 +256,9 @@ export class ZKStackBridge extends BaseBridgeAdapter { } async _txBaseCost(): Promise { - const l1GasPriceData = await gasPriceOracle.getGasPriceEstimate(this.getL1Bridge().provider!); + const l1GasPriceData = (await gasPriceOracle.getGasPriceEstimate( + this.getL1Bridge().provider! + )) as EvmGasPriceEstimate; // Similar to the ZkSyncBridge types, we must calculate the l2 gas cost by querying a system contract. In this case, // the system contract to query is the bridge hub contract. diff --git a/src/adapter/l2Bridges/BinanceCEXBridge.ts b/src/adapter/l2Bridges/BinanceCEXBridge.ts index 4177ae6d42..22a42dab0c 100644 --- a/src/adapter/l2Bridges/BinanceCEXBridge.ts +++ b/src/adapter/l2Bridges/BinanceCEXBridge.ts @@ -93,7 +93,7 @@ export class BinanceCEXBridge extends BaseL2BridgeAdapter { ): Promise { const binanceApiClient = await this.getBinanceClient(); const l2TokenInfo = getTokenInfo(l2Token.toAddress(), this.l2chainId); - const fromTimestamp = (await getTimestampForBlock(this.l2Bridge.provider, l2EventConfig.fromBlock)) * 1_000; + const fromTimestamp = (await getTimestampForBlock(this.l2Bridge.provider, l2EventConfig.from)) * 1_000; const [_depositHistory, _withdrawHistory] = await Promise.all([ binanceApiClient.depositHistory({ coin: this.l1TokenInfo.symbol, diff --git a/src/clients/ConfigStoreClient.ts b/src/clients/ConfigStoreClient.ts index 1601e90425..7aebf8dcdc 100644 --- a/src/clients/ConfigStoreClient.ts +++ b/src/clients/ConfigStoreClient.ts @@ -14,7 +14,7 @@ export class ConfigStoreClient extends clients.AcrossConfigStoreClient { constructor( readonly logger: winston.Logger, readonly configStore: Contract, - readonly eventSearchConfig: MakeOptional = { fromBlock: 0, maxBlockLookBack: 0 }, + readonly eventSearchConfig: MakeOptional = { from: 0, maxLookBack: 0 }, readonly configStoreVersion: number = CONFIG_STORE_VERSION ) { super(logger, configStore, eventSearchConfig, configStoreVersion); @@ -66,10 +66,10 @@ export class ConfigStoreClient extends clients.AcrossConfigStoreClient { if (isDefined(this.injectedChain)) { const { chainId: injectedChainId, blockNumber: injectedBlockNumber } = this.injectedChain; // Sanity check to ensure that this event doesn't happen in the future - if (injectedBlockNumber > this.latestBlockSearched) { + if (injectedBlockNumber > this.latestHeightSearched) { this.logger.debug({ at: "ConfigStore[Relayer]#update", - message: `Injected block number ${injectedBlockNumber} is greater than the latest block number ${this.latestBlockSearched}`, + message: `Injected block number ${injectedBlockNumber} is greater than the latest block number ${this.latestHeightSearched}`, }); return; } diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index eacdbc973e..7f70b11c28 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -23,7 +23,7 @@ export class HubPoolClient extends clients.HubPoolClient { configStoreClient: clients.AcrossConfigStoreClient, deploymentBlock?: number, chainId = CHAIN_IDs.MAINNET, - eventSearchConfig: MakeOptional = { fromBlock: 0, maxBlockLookBack: 0 }, + eventSearchConfig: MakeOptional = { from: 0, maxLookBack: 0 }, cachingMechanism?: interfaces.CachingMechanismInterface, timeToCache?: number ) { diff --git a/src/clients/InventoryClient.ts b/src/clients/InventoryClient.ts index a156db4649..3c86a0e6ad 100644 --- a/src/clients/InventoryClient.ts +++ b/src/clients/InventoryClient.ts @@ -435,7 +435,7 @@ export class InventoryClient { if (depositForcesOriginChainRepayment(deposit, this.hubPoolClient)) { return false; } - const hubPoolBlock = this.hubPoolClient.latestBlockSearched; + const hubPoolBlock = this.hubPoolClient.latestHeightSearched; if (!this.hubPoolClient.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId, hubPoolBlock)) { return false; } @@ -705,7 +705,7 @@ export class InventoryClient { // We need to find the latest validated running balance for this chain and token. const lastValidatedRunningBalance = this.hubPoolClient.getRunningBalanceBeforeBlockForChain( - this.hubPoolClient.latestBlockSearched, + this.hubPoolClient.latestHeightSearched, chainId, l1Token ).runningBalance; @@ -714,7 +714,7 @@ export class InventoryClient { // - minus total deposit amount on chain since the latest validated end block // - plus total refund amount on chain since the latest validated end block const latestValidatedBundle = this.hubPoolClient.getLatestExecutedRootBundleContainingL1Token( - this.hubPoolClient.latestBlockSearched, + this.hubPoolClient.latestHeightSearched, chainId, l1Token ); diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 8e9434e4fa..c82e6a67a9 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -60,9 +60,9 @@ export class IndexedSpokePoolClient extends clients.EVMSpokePoolClient { readonly hubPoolClient: clients.HubPoolClient | null, readonly chainId: number, public deploymentBlock: number, - eventSearchConfig: MakeOptional = { - fromBlock: deploymentBlock, - maxBlockLookBack: CHAIN_MAX_BLOCK_LOOKBACK[chainId], + eventSearchConfig: MakeOptional = { + from: deploymentBlock, + maxLookBack: CHAIN_MAX_BLOCK_LOOKBACK[chainId], }, readonly opts: IndexerOpts ) { @@ -85,10 +85,10 @@ export class IndexedSpokePoolClient extends clients.EVMSpokePoolClient { */ protected startWorker(): void { const { - eventSearchConfig: { fromBlock, maxBlockLookBack: blockrange }, + eventSearchConfig: { from, maxLookBack: blockrange }, spokePool: { address: spokepool }, } = this; - const opts = { spokepool, blockrange, lookback: `@${fromBlock}` }; + const opts = { spokepool, blockrange, lookback: `@${from}` }; const args = Object.entries(opts) .map(([k, v]) => [`--${k}`, `${v}`]) @@ -285,7 +285,7 @@ export class IndexedSpokePoolClient extends clients.EVMSpokePoolClient { this.pendingEvents[eventIdx] = []; pendingEvents.forEach(({ removed }) => assert(!removed)); - return pendingEvents; + return pendingEvents.map(spreadEventWithBlockNumber); }); return { diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index 59ad10712e..1c89dd7aaa 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -104,7 +104,7 @@ export class AdapterManager { spokePoolClients, chainId, hubChainId, - filterMonitoredAddresses(chainId).map((address) => toAddressType(address, chainId)), + filterMonitoredAddresses(chainId).map((address) => toAddressType(address)), logger, SUPPORTED_TOKENS[chainId] ?? [], constructBridges(chainId), @@ -163,9 +163,9 @@ export class AdapterManager { this.logger.debug({ at: "AdapterManager", message: "Sending token cross-chain", chainId, l1Token, amount }); l2Token ??= this.l2TokenForL1Token(l1Token, chainId); return this.adapters[chainId].sendTokenToTargetChain( - toAddressType(address, chainId), + toAddressType(address), EvmAddress.from(l1Token), - toAddressType(l2Token, chainId), + toAddressType(l2Token), amount, simMode ); @@ -188,7 +188,7 @@ export class AdapterManager { }); const txnReceipts = this.adapters[chainId].withdrawTokenFromL2( EvmAddress.from(address), - toAddressType(l2Token, chainId), + toAddressType(l2Token), amount, simMode ); @@ -204,8 +204,8 @@ export class AdapterManager { chainId = Number(chainId); return await this.adapters[chainId].getL2PendingWithdrawalAmount( lookbackPeriodSeconds, - toAddressType(fromAddress, chainId), - toAddressType(l2Token, chainId) + toAddressType(fromAddress), + toAddressType(l2Token) ); } diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index a6ff304662..597128c9cf 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -219,7 +219,7 @@ export async function constructSpokePoolClientsWithStartBlocks( // Explicitly set toBlocks for all chains so we can re-use them in other clients to make sure they all query // state to the same "latest" block per chain. - const hubPoolBlock = await hubPoolClient.hubPool.provider.getBlock(hubPoolClient.latestBlockSearched); + const hubPoolBlock = await hubPoolClient.hubPool.provider.getBlock(hubPoolClient.latestHeightSearched); const latestBlocksForChain: Record = Object.fromEntries( await Promise.all( enabledChains.map(async (chainId) => { @@ -278,9 +278,9 @@ export function getSpokePoolClientsForContract( }); } const spokePoolClientSearchSettings = { - fromBlock: fromBlocks[chainId] ? Math.max(fromBlocks[chainId], registrationBlock) : registrationBlock, - toBlock: toBlocks[chainId], - maxBlockLookBack: config.maxBlockLookBack[chainId], + from: fromBlocks[chainId] ? Math.max(fromBlocks[chainId], registrationBlock) : registrationBlock, + to: toBlocks[chainId], + maxLookBack: config.maxBlockLookBack[chainId], }; spokePoolClients[chainId] = new SpokePoolClient( logger, @@ -313,9 +313,9 @@ export async function constructClients( const latestMainnetBlock = await hubPoolProvider.getBlockNumber(); const rateModelClientSearchSettings = { - fromBlock: Number(getDeploymentBlockNumber("AcrossConfigStore", config.hubPoolChainId)), - toBlock: config.toBlockOverride[config.hubPoolChainId] ?? latestMainnetBlock, - maxBlockLookBack: config.maxBlockLookBack[config.hubPoolChainId], + from: Number(getDeploymentBlockNumber("AcrossConfigStore", config.hubPoolChainId)), + to: config.toBlockOverride[config.hubPoolChainId] ?? latestMainnetBlock, + maxLookBack: config.maxBlockLookBack[config.hubPoolChainId], }; const configStore = getDeployedContract("AcrossConfigStore", config.hubPoolChainId, hubSigner); diff --git a/src/dataworker/Dataworker.ts b/src/dataworker/Dataworker.ts index feeb26bb49..3281050cc9 100644 --- a/src/dataworker/Dataworker.ts +++ b/src/dataworker/Dataworker.ts @@ -220,7 +220,7 @@ export class Dataworker { // are executed so we want to make sure that these are all older than the mainnet bundle end block which is // sometimes treated as the "latest" mainnet block. const mostRecentProposedRootBundle = this.clients.hubPoolClient.getLatestFullyExecutedRootBundle( - this.clients.hubPoolClient.latestBlockSearched + this.clients.hubPoolClient.latestHeightSearched ); // If there has never been a validated root bundle, then we can always propose a new one: @@ -344,7 +344,7 @@ export class Dataworker { const hubPoolClient = this.clients.hubPoolClient; return hubPoolClient.getNextBundleStartBlockNumber( chainIdList, - hubPoolClient.latestBlockSearched, + hubPoolClient.latestHeightSearched, hubPoolClient.chainId ); } @@ -372,7 +372,7 @@ export class Dataworker { return; } - const { chainId: hubPoolChainId, latestBlockSearched } = this.clients.hubPoolClient; + const { chainId: hubPoolChainId, latestHeightSearched } = this.clients.hubPoolClient; const mainnetBundleEndBlock = blockRangesForProposal[0][1]; this.logger.debug({ @@ -384,7 +384,7 @@ export class Dataworker { const rootBundleData = await this._proposeRootBundle( blockRangesForProposal, spokePoolClients, - latestBlockSearched, + latestHeightSearched, false, // Don't load data from arweave when proposing. logData ); @@ -1020,7 +1020,7 @@ export class Dataworker { Object.entries(spokePoolClients).map(async ([_chainId, client]) => { const chainId = Number(_chainId); let rootBundleRelays = sortEventsDescending(client.getRootBundleRelays()).filter( - (rootBundle) => rootBundle.blockNumber >= client.eventSearchConfig.fromBlock + (rootBundle) => rootBundle.blockNumber >= client.eventSearchConfig.from ); // Filter out roots that are not in the latest N root bundles. This assumes that @@ -1044,7 +1044,7 @@ export class Dataworker { const followingBlockNumber = this.clients.hubPoolClient.getFollowingRootBundle(bundle)?.blockNumber || - this.clients.hubPoolClient.latestBlockSearched; + this.clients.hubPoolClient.latestHeightSearched; if (!followingBlockNumber) { return false; @@ -1232,7 +1232,7 @@ export class Dataworker { // @dev check if there's been a duplicate leaf execution and if so, then exit early. // Since this function is happening near the end of the dataworker run and leaf executions are // relatively infrequent, the additional RPC latency and cost is acceptable. - const fillStatus = await client.relayFillStatus(slowFill.relayData, "latest"); + const fillStatus = await client.relayFillStatus(slowFill.relayData); if (fillStatus === FillStatus.Filled) { this.logger.debug({ at: "Dataworker#executeSlowRelayLeaves", @@ -1456,7 +1456,7 @@ export class Dataworker { const executedLeaves = this.clients.hubPoolClient.getExecutedLeavesForRootBundle( this.clients.hubPoolClient.getLatestProposedRootBundle(), - this.clients.hubPoolClient.latestBlockSearched + this.clients.hubPoolClient.latestHeightSearched ); // Filter out previously executed leaves. @@ -2071,7 +2071,7 @@ export class Dataworker { for (const [_chainId, client] of Object.entries(spokePoolClients)) { const chainId = Number(_chainId); let rootBundleRelays = sortEventsDescending(client.getRootBundleRelays()).filter( - (rootBundle) => rootBundle.blockNumber >= client.eventSearchConfig.fromBlock + (rootBundle) => rootBundle.blockNumber >= client.eventSearchConfig.from ); // Filter out roots that are not in the latest N root bundles. This assumes that @@ -2094,7 +2094,7 @@ export class Dataworker { return false; } const followingBlockNumber = - hubPoolClient.getFollowingRootBundle(bundle)?.blockNumber || hubPoolClient.latestBlockSearched; + hubPoolClient.getFollowingRootBundle(bundle)?.blockNumber || hubPoolClient.latestHeightSearched; if (followingBlockNumber === undefined) { return false; @@ -2237,9 +2237,9 @@ export class Dataworker { leaf.leafId ); const searchConfig = { - maxBlockLookBack: client.eventSearchConfig.maxBlockLookBack, - fromBlock: client.latestBlockSearched - client.eventSearchConfig.maxBlockLookBack, - toBlock: await client.spokePool.provider.getBlockNumber(), + maxLookBack: client.eventSearchConfig.maxLookBack, + from: client.latestHeightSearched - client.eventSearchConfig.maxLookBack, + to: await client.spokePool.provider.getBlockNumber(), }; const duplicateEvents = await sdkUtils.paginatedEventQuery(client.spokePool, eventFilter, searchConfig); if (duplicateEvents.length > 0) { @@ -2573,7 +2573,7 @@ export class Dataworker { spokePoolClients, getEndBlockBuffers(chainIds, this.blockRangeEndBlockBuffer), this.clients, - this.clients.hubPoolClient.latestBlockSearched, + this.clients.hubPoolClient.latestHeightSearched, // We only want to count enabled chains at the same time that we are loading chain ID indices. this.clients.configStoreClient.getEnabledChains(mainnetBundleStartBlock) ); diff --git a/src/dataworker/DataworkerUtils.ts b/src/dataworker/DataworkerUtils.ts index 3b9e9a6d96..41ae675732 100644 --- a/src/dataworker/DataworkerUtils.ts +++ b/src/dataworker/DataworkerUtils.ts @@ -91,7 +91,7 @@ export async function blockRangesAreInvalidForSpokeClients( }; } - const clientLastBlockQueried = spokePoolClient.latestBlockSearched; + const clientLastBlockQueried = spokePoolClient.latestHeightSearched; const earliestValidBundleStartBlockForChain = earliestValidBundleStartBlock?.[chainId] ?? spokePoolClient.deploymentBlock; @@ -125,19 +125,19 @@ export async function blockRangesAreInvalidForSpokeClients( const conservativeBundleFrequencySeconds = Number( process.env.CONSERVATIVE_BUNDLE_FREQUENCY_SECONDS ?? CONSERVATIVE_BUNDLE_FREQUENCY_SECONDS ); - if (spokePoolClient.eventSearchConfig.fromBlock > spokePoolClient.deploymentBlock) { + if (spokePoolClient.eventSearchConfig.from > spokePoolClient.deploymentBlock) { // @dev The maximum lookback window we need to evaluate expired deposits is the max fill deadline buffer, // which captures all deposits that newly expired, plus the bundle time (e.g. 1 hour) to account for the // maximum time it takes for a newly expired deposit to be included in a bundle. A conservative value for // this bundle time is 3 hours. This `conservativeBundleFrequencySeconds` buffer also ensures that all deposits // that are technically "expired", but have fills in the bundle, are also included. This can happen if a fill // is sent pretty late into the deposit's expiry period. - const oldestTime = await spokePoolClient.getTimeAt(spokePoolClient.eventSearchConfig.fromBlock); + const oldestTime = await spokePoolClient.getTimeAt(spokePoolClient.eventSearchConfig.from); const expiryWindow = endBlockTimestamps[chainId] - oldestTime; const safeExpiryWindow = maxFillDeadlineBufferInBlockRange + conservativeBundleFrequencySeconds; if (expiryWindow < safeExpiryWindow) { return { - reason: `cannot evaluate all possible expired deposits; endBlockTimestamp ${endBlockTimestamps[chainId]} - spokePoolClient.eventSearchConfig.fromBlock timestamp ${oldestTime} < maxFillDeadlineBufferInBlockRange ${maxFillDeadlineBufferInBlockRange} + conservativeBundleFrequencySeconds ${conservativeBundleFrequencySeconds}`, + reason: `cannot evaluate all possible expired deposits; endBlockTimestamp ${endBlockTimestamps[chainId]} - spokePoolClient.eventSearchConfig.from timestamp ${oldestTime} < maxFillDeadlineBufferInBlockRange ${maxFillDeadlineBufferInBlockRange} + conservativeBundleFrequencySeconds ${conservativeBundleFrequencySeconds}`, chainId, }; } diff --git a/src/finalizer/utils/arbStack.ts b/src/finalizer/utils/arbStack.ts index 8d9e4a8428..8ad03d4481 100644 --- a/src/finalizer/utils/arbStack.ts +++ b/src/finalizer/utils/arbStack.ts @@ -82,7 +82,7 @@ export async function arbStackFinalizer( _l1SpokePoolClient: SpokePoolClient, recipientAddresses: string[] ): Promise { - LATEST_MAINNET_BLOCK = hubPoolClient.latestBlockSearched; + LATEST_MAINNET_BLOCK = hubPoolClient.latestHeightSearched; const hubPoolProvider = await getProvider(hubPoolClient.chainId, logger); MAINNET_BLOCK_TIME = (await averageBlockTime(hubPoolProvider)).average; // Now that we know the L1 block time, we can calculate the confirmPeriodBlocks. @@ -114,7 +114,7 @@ export async function arbStackFinalizer( logger.debug({ at: `Finalizer#${networkName}Finalizer`, message: `${networkName} TokensBridged event filter`, - toBlock: latestBlockToFinalize, + to: latestBlockToFinalize, }); const withdrawalEvents: TokensBridged[] = []; @@ -137,7 +137,7 @@ export async function arbStackFinalizer( ), { ...spokePoolClient.eventSearchConfig, - toBlock: latestBlockToFinalize, + to: latestBlockToFinalize, } ); const uniqueGateways = new Set(transferRoutedEvents.map((e) => e.args.gateway)); @@ -157,7 +157,7 @@ export async function arbStackFinalizer( ), { ...spokePoolClient.eventSearchConfig, - toBlock: latestBlockToFinalize, + to: latestBlockToFinalize, } ); }) @@ -170,7 +170,7 @@ export async function arbStackFinalizer( ), { ...spokePoolClient.eventSearchConfig, - toBlock: latestBlockToFinalize, + to: latestBlockToFinalize, } ); const _withdrawalEvents = [ diff --git a/src/finalizer/utils/binance.ts b/src/finalizer/utils/binance.ts index 1552ddf8e4..419c89f64f 100644 --- a/src/finalizer/utils/binance.ts +++ b/src/finalizer/utils/binance.ts @@ -87,7 +87,7 @@ export async function binanceFinalizer( const [binanceApi, _fromTimestamp] = await Promise.all([ getBinanceApiClient(process.env["BINANCE_API_BASE"]), - getTimestampForBlock(hubSigner.provider, l1EventSearchConfig.fromBlock), + getTimestampForBlock(hubSigner.provider, l1EventSearchConfig.from), ]); const fromTimestamp = _fromTimestamp * 1_000; diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index 60a13b423c..1d18817922 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -30,9 +30,9 @@ export async function cctpL1toL2Finalizer( senderAddresses: string[] ): Promise { const searchConfig: EventSearchConfig = { - fromBlock: l1SpokePoolClient.eventSearchConfig.fromBlock, - toBlock: l1SpokePoolClient.latestBlockSearched, - maxBlockLookBack: l1SpokePoolClient.eventSearchConfig.maxBlockLookBack, + from: l1SpokePoolClient.eventSearchConfig.from, + to: l1SpokePoolClient.latestHeightSearched, + maxLookBack: l1SpokePoolClient.eventSearchConfig.maxLookBack, }; const outstandingDeposits = await getAttestationsForCCTPDepositEvents( senderAddresses, diff --git a/src/finalizer/utils/cctp/l2ToL1.ts b/src/finalizer/utils/cctp/l2ToL1.ts index de9fd41b36..6d2d6b5ff6 100644 --- a/src/finalizer/utils/cctp/l2ToL1.ts +++ b/src/finalizer/utils/cctp/l2ToL1.ts @@ -30,9 +30,9 @@ export async function cctpL2toL1Finalizer( senderAddresses: string[] ): Promise { const searchConfig: EventSearchConfig = { - fromBlock: spokePoolClient.eventSearchConfig.fromBlock, - toBlock: spokePoolClient.latestBlockSearched, - maxBlockLookBack: spokePoolClient.eventSearchConfig.maxBlockLookBack, + from: spokePoolClient.eventSearchConfig.from, + to: spokePoolClient.latestHeightSearched, + maxLookBack: spokePoolClient.eventSearchConfig.maxLookBack, }; const outstandingDeposits = await getAttestationsForCCTPDepositEvents( senderAddresses, diff --git a/src/finalizer/utils/helios.ts b/src/finalizer/utils/helios.ts index 5a4ea8e6db..135121a35e 100644 --- a/src/finalizer/utils/helios.ts +++ b/src/finalizer/utils/helios.ts @@ -224,12 +224,12 @@ async function getRelevantL1Events( * @dev We artificially shorten the lookback time peiod for L1 events by a factor of 2. We want to avoid race conditions where * we see an old event on L1, but not look back far enough on L2 to see that the event has been executed successfully. */ - const toBlock = l1SpokePoolClient.latestBlockSearched; - const fromBlock = Math.floor((l1SpokePoolClient.eventSearchConfig.fromBlock + toBlock) / 2); + const toBlock = l1SpokePoolClient.latestHeightSearched; + const fromBlock = Math.floor((l1SpokePoolClient.eventSearchConfig.from + toBlock) / 2); const l1SearchConfig: EventSearchConfig = { - fromBlock: fromBlock, - toBlock: toBlock, - maxBlockLookBack: l1SpokePoolClient.eventSearchConfig.maxBlockLookBack, + from: fromBlock, + to: toBlock, + maxLookBack: l1SpokePoolClient.eventSearchConfig.maxLookBack, }; const storedCallDataFilter = hubPoolStoreContract.filters.StoredCallData(); @@ -256,9 +256,9 @@ async function getL2VerifiedSlotsMap( const sp1HeliosContract = getSp1HeliosContract(l2ChainId, l2Provider); const l2SearchConfig: EventSearchConfig = { - fromBlock: l2SpokePoolClient.eventSearchConfig.fromBlock, - toBlock: l2SpokePoolClient.latestBlockSearched, - maxBlockLookBack: l2SpokePoolClient.eventSearchConfig.maxBlockLookBack, + from: l2SpokePoolClient.eventSearchConfig.from, + to: l2SpokePoolClient.latestHeightSearched, + maxLookBack: l2SpokePoolClient.eventSearchConfig.maxLookBack, }; const storageVerifiedFilter = sp1HeliosContract.filters.StorageSlotVerified(); @@ -292,9 +292,9 @@ async function getL2RelayedNonces(l2SpokePoolClient: SpokePoolClient): PromiseL1 messages are claimable after 6 - 32 hours const { fromBlock: l2FromBlock, toBlock: l2ToBlock } = await getBlockRangeByHoursOffsets(l2ChainId, 24 * 8, 6); const l1SearchConfig = { - fromBlock: l1FromBlock, - toBlock: l1ToBlock, - maxBlockLookBack: CHAIN_MAX_BLOCK_LOOKBACK[l1ChainId] || 10_000, + from: l1FromBlock, + to: l1ToBlock, + maxLookBack: CHAIN_MAX_BLOCK_LOOKBACK[l1ChainId] || 10_000, }; const l2SearchConfig = { - fromBlock: l2FromBlock, - toBlock: l2ToBlock, - maxBlockLookBack: CHAIN_MAX_BLOCK_LOOKBACK[l2ChainId] || 5_000, + from: l2FromBlock, + to: l2ToBlock, + maxLookBack: CHAIN_MAX_BLOCK_LOOKBACK[l2ChainId] || 5_000, }; const l2Provider = await getProvider(l2ChainId); const l1Provider = await getProvider(l1ChainId); @@ -285,7 +285,7 @@ export async function lineaL2ToL1Finalizer( at: "Finalizer#LineaL2ToL1Finalizer", message: "Linea L2->L1 message statuses", averageBlockTimeSeconds, - latestSpokePoolBlock: spokePoolClient.latestBlockSearched, + latestSpokePoolBlock: spokePoolClient.latestHeightSearched, statuses: { claimed: claimed.length, claimable: claimable.length, @@ -297,7 +297,7 @@ export async function lineaL2ToL1Finalizer( txnHash: message.txHash, withdrawalBlock, maturedHours: - (averageBlockTimeSeconds.average * (spokePoolClient.latestBlockSearched - withdrawalBlock)) / 60 / 60, + (averageBlockTimeSeconds.average * (spokePoolClient.latestHeightSearched - withdrawalBlock)) / 60 / 60, }; }), }); diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 1c0bc7fe55..2935c178f4 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -145,7 +145,7 @@ export async function opStackFinalizer( ), { ...spokePoolClient.eventSearchConfig, - toBlock: spokePoolClient.latestBlockSearched, + to: spokePoolClient.latestHeightSearched, } ) ).map((event) => { @@ -164,7 +164,7 @@ export async function opStackFinalizer( ), { ...spokePoolClient.eventSearchConfig, - toBlock: spokePoolClient.latestBlockSearched, + to: spokePoolClient.latestHeightSearched, } ) ).map((event) => { diff --git a/src/libexec/util/evm/util.ts b/src/libexec/util/evm/util.ts index 56fae63463..6a6413ddbd 100644 --- a/src/libexec/util/evm/util.ts +++ b/src/libexec/util/evm/util.ts @@ -57,7 +57,7 @@ export async function scrapeEvents( const fromBlock = Math.max(toBlock - (lookback ?? deploymentBlock), deploymentBlock); assert(toBlock > fromBlock, `${toBlock} > ${fromBlock}`); - const searchConfig = { fromBlock, toBlock, maxBlockLookBack: maxBlockRange }; + const searchConfig = { from: fromBlock, to: toBlock, maxLookBack: maxBlockRange }; const mark = profiler.start("paginatedEventQuery"); const filter = getEventFilter(spokePool, eventName, filterArgs[eventName]); diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index bcf38c6c01..6fbd14aa23 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -115,9 +115,9 @@ export class Monitor { Object.entries(this.spokePoolsBlocks).map(([chainId, config]) => [ chainId, { - fromBlock: config.startingBlock, - toBlock: config.endingBlock, - maxBlockLookBack: 0, + from: config.startingBlock, + to: config.endingBlock, + maxLookBack: 0, }, ]) ); @@ -684,7 +684,7 @@ export class Monitor { const nextBundleMainnetStartBlock = hubPoolClient.getNextBundleStartBlockNumber( this.clients.bundleDataClient.chainIdListForBundleEvaluationBlockNumbers, - hubPoolClient.latestBlockSearched, + hubPoolClient.latestHeightSearched, hubPoolClient.chainId ); const enabledChainIds = this.clients.configStoreClient.getChainIdIndicesForBlock(nextBundleMainnetStartBlock); @@ -701,12 +701,12 @@ export class Monitor { this.clients.spokePoolClients, getEndBlockBuffers(enabledChainIds, this.clients.bundleDataClient.blockRangeEndBlockBuffer), this.clients, - hubPoolClient.latestBlockSearched, - this.clients.configStoreClient.getEnabledChains(hubPoolClient.latestBlockSearched) + hubPoolClient.latestHeightSearched, + this.clients.configStoreClient.getEnabledChains(hubPoolClient.latestHeightSearched) ); const blockRangeTail = bundle.bundleEvaluationBlockNumbers.map((endBlockForChain, idx) => { const endBlockNumber = Number(endBlockForChain); - const spokeLatestBlockSearched = this.clients.spokePoolClients[enabledChainIds[idx]]?.latestBlockSearched ?? 0; + const spokeLatestBlockSearched = this.clients.spokePoolClients[enabledChainIds[idx]]?.latestHeightSearched ?? 0; return spokeLatestBlockSearched === 0 ? [endBlockNumber, endBlockNumber] : [endBlockNumber + 1, spokeLatestBlockSearched > endBlockNumber ? spokeLatestBlockSearched : endBlockNumber]; @@ -916,8 +916,8 @@ export class Monitor { // should stay unstuck for longer than one bundle. async checkStuckRebalances(): Promise { const hubPoolClient = this.clients.hubPoolClient; - const { currentTime, latestBlockSearched } = hubPoolClient; - const lastFullyExecutedBundle = hubPoolClient.getLatestFullyExecutedRootBundle(latestBlockSearched); + const { currentTime, latestHeightSearched } = hubPoolClient; + const lastFullyExecutedBundle = hubPoolClient.getLatestFullyExecutedRootBundle(latestHeightSearched); // This case shouldn't happen outside of tests as Across V2 has already launched. if (lastFullyExecutedBundle === undefined) { return; @@ -927,7 +927,7 @@ export class Monitor { const allL1Tokens = this.clients.hubPoolClient.getL1Tokens(); const poolRebalanceLeaves = this.clients.hubPoolClient.getExecutedLeavesForRootBundle( lastFullyExecutedBundle, - latestBlockSearched + latestHeightSearched ); for (const chainId of this.crossChainAdapterSupportedChains) { // Exit early if there were no pool rebalance leaves for this chain executed in the last bundle. @@ -1258,7 +1258,7 @@ export class Monitor { // is now aware of those executions. await new Contract(token, ERC20.abi, this.clients.spokePoolClients[chainId].spokePool.provider).balanceOf( account, - { blockTag: this.clients.spokePoolClients[chainId].latestBlockSearched } + { blockTag: this.clients.spokePoolClients[chainId].latestHeightSearched } ); if (!this.balanceCache[chainId]) { this.balanceCache[chainId] = {}; diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 505a8b2e1b..5b0a9f1478 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -132,7 +132,7 @@ export class Relayer { tokenClient.clearTokenData(); await configStoreClient.update(); - if (configStoreClient.latestBlockSearched > hubPoolClient.latestBlockSearched) { + if (configStoreClient.latestHeightSearched > hubPoolClient.latestHeightSearched) { await hubPoolClient.update(); } } @@ -362,14 +362,14 @@ export class Relayer { const { minConfirmations } = minDepositConfirmations[originChainId].find(({ usdThreshold }) => usdThreshold.gte(fillAmountUsd) ) ?? { minConfirmations: 100_000 }; - const { latestBlockSearched } = spokePoolClients[originChainId]; - if (latestBlockSearched - blockNumber < minConfirmations) { + const { latestHeightSearched } = spokePoolClients[originChainId]; + if (latestHeightSearched - blockNumber < minConfirmations) { this.logger.debug({ at: "Relayer::filterDeposit", message: `Skipping ${srcChain} deposit due to insufficient deposit confirmations.`, depositId: depositId.toString(), blockNumber, - confirmations: latestBlockSearched - blockNumber, + confirmations: latestHeightSearched - blockNumber, minConfirmations, txnRef: deposit.txnRef, }); @@ -545,7 +545,7 @@ export class Relayer { const originSpoke = this.clients.spokePoolClients[chainId]; let totalCommitment = bnZero; - let toBlock = originSpoke.latestBlockSearched; + let toBlock = originSpoke.latestHeightSearched; // For each deposit confirmation tier (lookback), sum all outstanding commitments back to head. const limits = mdcs.map(({ usdThreshold, minConfirmations }) => { @@ -571,7 +571,7 @@ export class Relayer { overCommitment: limit.abs(), usdThreshold: mdcs[i].usdThreshold, fromBlock, - toBlock: originSpoke.latestBlockSearched, + toBlock: originSpoke.latestHeightSearched, }); break; } @@ -712,7 +712,7 @@ export class Relayer { if (minFillTime > 0 && deposit.exclusiveRelayer !== this.relayerAddress) { const originSpoke = spokePoolClients[originChainId]; const { average: avgBlockTime } = await averageBlockTime(originSpoke.spokePool.provider); - const depositAge = Math.floor(avgBlockTime * (originSpoke.latestBlockSearched - deposit.blockNumber)); + const depositAge = Math.floor(avgBlockTime * (originSpoke.latestHeightSearched - deposit.blockNumber)); if (minFillTime > depositAge) { this.logger.debug({ @@ -945,9 +945,9 @@ export class Relayer { const mdcPerChain = this.computeRequiredDepositConfirmations(unfilledDeposits, destinationChainId); const maxBlockNumbers = Object.fromEntries( - Object.values(spokePoolClients).map(({ chainId, latestBlockSearched }) => [ + Object.values(spokePoolClients).map(({ chainId, latestHeightSearched }) => [ chainId, - latestBlockSearched - mdcPerChain[chainId], + latestHeightSearched - mdcPerChain[chainId], ]) ); await this.evaluateFills(unfilledDeposits, lpFees, maxBlockNumbers, sendSlowRelays); diff --git a/src/relayer/RelayerClientHelper.ts b/src/relayer/RelayerClientHelper.ts index 174f9f2848..98816b273e 100644 --- a/src/relayer/RelayerClientHelper.ts +++ b/src/relayer/RelayerClientHelper.ts @@ -46,7 +46,7 @@ async function indexedSpokePoolClient( const blockFinder = undefined; const redis = await getRedisCache(hubPoolClient.logger); - const [activationBlock, fromBlock] = await Promise.all([ + const [activationBlock, from] = await Promise.all([ resolveSpokePoolActivationBlock(chainId, hubPoolClient), getBlockForTimestamp(chainId, getCurrentTime() - opts.lookback, blockFinder, redis), ]); @@ -57,7 +57,7 @@ async function indexedSpokePoolClient( hubPoolClient, chainId, activationBlock, - { fromBlock, maxBlockLookBack: opts.blockRange }, + { from, maxLookBack: opts.blockRange }, opts ); diff --git a/src/scripts/validateRunningBalances.ts b/src/scripts/validateRunningBalances.ts index 1131669484..ab0549490d 100644 --- a/src/scripts/validateRunningBalances.ts +++ b/src/scripts/validateRunningBalances.ts @@ -138,7 +138,7 @@ export async function runScript(baseSigner: Signer): Promise { const bundleBlockRanges = _getBundleBlockRanges(mostRecentValidatedBundle, spokePoolClients); const followingBlockNumber = clients.hubPoolClient.getFollowingRootBundle(mostRecentValidatedBundle)?.blockNumber || - clients.hubPoolClient.latestBlockSearched; + clients.hubPoolClient.latestHeightSearched; const poolRebalanceLeaves = clients.hubPoolClient.getExecutedLeavesForRootBundle( mostRecentValidatedBundle, followingBlockNumber @@ -227,7 +227,7 @@ export async function runScript(baseSigner: Signer): Promise { if (leaf.chainId !== clients.hubPoolClient.chainId) { const _followingBlockNumber = clients.hubPoolClient.getFollowingRootBundle(previousValidatedBundle)?.blockNumber || - clients.hubPoolClient.latestBlockSearched; + clients.hubPoolClient.latestHeightSearched; const previousBundlePoolRebalanceLeaves = clients.hubPoolClient.getExecutedLeavesForRootBundle( previousValidatedBundle, _followingBlockNumber @@ -265,9 +265,9 @@ export async function runScript(baseSigner: Signer): Promise { clients.hubPoolClient.hubPool.address // from ), { - fromBlock: previousBundleEndBlockForChain.toNumber(), - toBlock: bundleEndBlockForChain.toNumber(), - maxBlockLookBack: config.maxBlockLookBack[leaf.chainId], + from: previousBundleEndBlockForChain.toNumber(), + to: bundleEndBlockForChain.toNumber(), + maxLookBack: config.maxBlockLookBack[leaf.chainId], } ) ).filter((e) => e.args._amount.eq(previousNetSendAmount) && e.args._to === spokePoolAddress); @@ -279,9 +279,9 @@ export async function runScript(baseSigner: Signer): Promise { l2TokenContract, l2TokenContract.filters.Transfer(undefined, spokePoolAddress), { - fromBlock: previousBundleEndBlockForChain.toNumber(), - toBlock: bundleEndBlockForChain.toNumber(), - maxBlockLookBack: config.maxBlockLookBack[leaf.chainId], + from: previousBundleEndBlockForChain.toNumber(), + to: bundleEndBlockForChain.toNumber(), + maxLookBack: config.maxBlockLookBack[leaf.chainId], } ) ).filter((e) => e.args.value.eq(previousNetSendAmount)); diff --git a/src/utils/SDKUtils.ts b/src/utils/SDKUtils.ts index 90ff69712f..0d012d79e0 100644 --- a/src/utils/SDKUtils.ts +++ b/src/utils/SDKUtils.ts @@ -13,6 +13,8 @@ export class Address extends sdk.utils.Address {} export class EvmAddress extends sdk.utils.EvmAddress {} export class SvmAddress extends sdk.utils.SvmAddress {} +export type EvmGasPriceEstimate = sdk.gasPriceOracle.EvmGasPriceEstimate; + export const { fillStatusArray, populateV3Relay, relayFillStatus, getTimestampForBlock } = sdk.arch.evm; export const { diff --git a/src/utils/TransactionUtils.ts b/src/utils/TransactionUtils.ts index a0bb308b40..cfc269d1cb 100644 --- a/src/utils/TransactionUtils.ts +++ b/src/utils/TransactionUtils.ts @@ -17,6 +17,7 @@ import { winston, stringifyThrownValue, CHAIN_IDs, + EvmGasPriceEstimate, } from "../utils"; dotenv.config(); @@ -179,12 +180,12 @@ export async function getGasPrice( priorityScaler = Math.max(1, priorityScaler); const { chainId } = await provider.getNetwork(); // Pass in unsignedTx here for better Linea gas price estimations via the Linea Viem provider. - const feeData = await gasPriceOracle.getGasPriceEstimate(provider, { + const feeData = (await gasPriceOracle.getGasPriceEstimate(provider, { chainId, baseFeeMultiplier: toBNWei(maxFeePerGasScaler), priorityFeeMultiplier: toBNWei(priorityScaler), unsignedTx: transactionObject, - }); + })) as EvmGasPriceEstimate; // Default to EIP-1559 (type 2) pricing. If gasPriceOracle is using a legacy adapter for this chain then // the priority fee will be 0. diff --git a/src/utils/UmaUtils.ts b/src/utils/UmaUtils.ts index 141e617dba..cfca2b6041 100644 --- a/src/utils/UmaUtils.ts +++ b/src/utils/UmaUtils.ts @@ -31,7 +31,7 @@ export async function getDisputeForTimestamp( ? disputeRequestBlock : await getBlockForTimestamp(hubPoolClient.chainId, disputeRequestTimestamp); - const eventSearchConfig = { fromBlock: priceRequestBlock, toBlock: priceRequestBlock }; + const eventSearchConfig = { from: priceRequestBlock, to: priceRequestBlock }; const disputes = await paginatedEventQuery(dvm, filter, eventSearchConfig); const dispute = disputes.find(({ args }) => args.time.toString() === disputeRequestTimestamp.toString()); return dispute ? spreadEventWithBlockNumber(dispute) : undefined; From 0fe58f342d63cf68817ef7a3e277a76f5cfc3314 Mon Sep 17 00:00:00 2001 From: bennett Date: Mon, 12 May 2025 12:53:24 -0500 Subject: [PATCH 02/20] update tests Signed-off-by: bennett --- package.json | 2 +- src/common/ClientHelper.ts | 2 +- test/Dataworker.blockRangeUtils.ts | 56 +++++++++---------- test/Dataworker.loadData.deposit.ts | 14 ++--- test/Dataworker.loadData.fill.ts | 28 +++++----- test/Dataworker.loadData.prefill.ts | 12 ++-- test/Dataworker.loadData.slowFill.ts | 36 ++++++------ ...ataworker.loadData.unexecutableSlowFill.ts | 16 +++--- test/Dataworker.validateRootBundle.ts | 4 +- test/DataworkerUtils.ts | 8 +-- test/EventUtils.ts | 32 +++++------ test/InventoryClient.InventoryRebalance.ts | 2 +- test/Relayer.BasicFill.ts | 6 +- test/Relayer.IndexedSpokePoolClient.ts | 2 +- test/Relayer.SlowFill.ts | 2 +- test/Relayer.TokenShortfall.ts | 2 +- test/Relayer.UnfilledDeposits.ts | 2 +- test/TokenClient.Approval.ts | 2 +- test/TokenClient.BalanceAlowance.ts | 2 +- test/fixtures/Dataworker.Fixture.ts | 2 +- test/generic-adapters/Arbitrum.ts | 12 ++-- test/generic-adapters/Linea.ts | 16 +++--- test/generic-adapters/OpStack.ts | 12 ++-- test/generic-adapters/Polygon.ts | 10 ++-- test/generic-adapters/Scroll.ts | 12 ++-- test/generic-adapters/zkSync.ts | 8 +-- test/mocks/MockBaseChainAdapter.ts | 4 +- test/mocks/MockConfigStoreClient.ts | 2 +- yarn.lock | 20 +++++-- 29 files changed, 166 insertions(+), 162 deletions(-) diff --git a/package.json b/package.json index 9de86eedff..6d396c8628 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.63", "@across-protocol/contracts": "^4.0.9", - "@across-protocol/sdk": "4.1.62", + "@across-protocol/sdk": "4.1.63-beta.0", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@defi-wonderland/smock": "^2.3.5", diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index 597128c9cf..4ee3f24122 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -331,7 +331,7 @@ export async function constructClients( const fromBlock = isDefined(hubPoolLookback) ? Math.max(latestMainnetBlock - hubPoolLookback / avgMainnetBlockTime, hubPoolDeploymentBlock) : hubPoolDeploymentBlock; - const hubPoolClientSearchSettings = { ...rateModelClientSearchSettings, fromBlock }; + const hubPoolClientSearchSettings = { ...rateModelClientSearchSettings, from: fromBlock }; // Create contract instances for each chain for each required contract. const hubPool = getDeployedContract("HubPool", config.hubPoolChainId, hubSigner); diff --git a/test/Dataworker.blockRangeUtils.ts b/test/Dataworker.blockRangeUtils.ts index d47a2a05bc..d0899d6d83 100644 --- a/test/Dataworker.blockRangeUtils.ts +++ b/test/Dataworker.blockRangeUtils.ts @@ -47,7 +47,7 @@ describe("Dataworker block range-related utility methods", async function () { ) ) ); - const latestMainnetBlock = hubPoolClient.latestBlockSearched; + const latestMainnetBlock = hubPoolClient.latestHeightSearched; const startingWidestBlocks = getWidestPossibleExpectedBlockRange( chainIdListForBundleEvaluationBlockNumbers, spokePoolClients, @@ -106,7 +106,7 @@ describe("Dataworker block range-related utility methods", async function () { // - Buffers are 0: let defaultEndBlockBuffers = Array(chainIdListForBundleEvaluationBlockNumbers.length).fill(0); chainIdListForBundleEvaluationBlockNumbers.forEach((_chainId) => { - mockHubPoolClient.setLatestBundleEndBlockForChain(_chainId, spokePoolClients[_chainId].latestBlockSearched); + mockHubPoolClient.setLatestBundleEndBlockForChain(_chainId, spokePoolClients[_chainId].latestHeightSearched); }); expect( getWidestPossibleExpectedBlockRange( @@ -122,8 +122,8 @@ describe("Dataworker block range-related utility methods", async function () { ) ).to.deep.equal( chainIdListForBundleEvaluationBlockNumbers.map((_chainId) => [ - spokePoolClients[_chainId].latestBlockSearched, - spokePoolClients[_chainId].latestBlockSearched, + spokePoolClients[_chainId].latestHeightSearched, + spokePoolClients[_chainId].latestHeightSearched, ]) ); @@ -143,8 +143,8 @@ describe("Dataworker block range-related utility methods", async function () { ) ).to.deep.equal( chainIdListForBundleEvaluationBlockNumbers.map((_chainId) => [ - spokePoolClients[_chainId].latestBlockSearched, - spokePoolClients[_chainId].latestBlockSearched, + spokePoolClients[_chainId].latestHeightSearched, + spokePoolClients[_chainId].latestHeightSearched, ]) ); }); @@ -160,7 +160,7 @@ describe("Dataworker block range-related utility methods", async function () { // Block ranges are invalid if any spoke pool client for a chain is undefined result = await blockRangesAreInvalidForSpokeClients( {}, - [[0, spokePoolClients[chainId].latestBlockSearched]], + [[0, spokePoolClients[chainId].latestHeightSearched]], chainIds, {} ); @@ -189,16 +189,16 @@ describe("Dataworker block range-related utility methods", async function () { if (mainnetDeploymentBlock === 0) { throw new Error("Mainnet SpokePoolClient has not been updated"); } - if (spokePoolClients[chainId].latestBlockSearched === 0) { + if (spokePoolClients[chainId].latestHeightSearched === 0) { throw new Error(`Chain ${spokePoolClients[1].chainId} SpokePoolClient has not been updated`); - } else if (spokePoolClients[originChainId].latestBlockSearched === 0) { + } else if (spokePoolClients[originChainId].latestHeightSearched === 0) { throw new Error(`Chain ${originChainId} SpokePoolClient has not been updated`); } // Does not error if earliest block range object is empty: result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[0, spokePoolClients[chainId].latestBlockSearched]], + [[0, spokePoolClients[chainId].latestHeightSearched]], chainIds, {} ); @@ -215,7 +215,7 @@ describe("Dataworker block range-related utility methods", async function () { // that block ranges can be validated by spoke pool clients. result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestBlockSearched]], + [[mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestHeightSearched]], chainIds, { [chainId]: mainnetDeploymentBlock + 2 } ); @@ -223,7 +223,7 @@ describe("Dataworker block range-related utility methods", async function () { // Set block range toBlock > client's last block queried. Clients can no longer validate this block range. result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestBlockSearched + 3]], + [[mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestHeightSearched + 3]], chainIds, { [chainId]: mainnetDeploymentBlock + 2 } ); @@ -234,7 +234,7 @@ describe("Dataworker block range-related utility methods", async function () { // latest invalid bundle start blocks below, so block ranges can't be validated by clients. result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[mainnetDeploymentBlock + 1, spokePoolClients[chainId].latestBlockSearched]], + [[mainnetDeploymentBlock + 1, spokePoolClients[chainId].latestHeightSearched]], chainIds, { [chainId]: mainnetDeploymentBlock + 2 } ); @@ -247,8 +247,8 @@ describe("Dataworker block range-related utility methods", async function () { result = await blockRangesAreInvalidForSpokeClients( { [chainId]: spokePoolClients[chainId], [10]: spokePoolClients[originChainId] }, [ - [mainnetDeploymentBlock + 1, spokePoolClients[chainId].latestBlockSearched], - [optimismDeploymentBlock + 3, spokePoolClients[originChainId].latestBlockSearched], + [mainnetDeploymentBlock + 1, spokePoolClients[chainId].latestHeightSearched], + [optimismDeploymentBlock + 3, spokePoolClients[originChainId].latestHeightSearched], ], [chainId, 10], // hub chain start block is higher than block range from block passed in for hub chain above @@ -262,8 +262,8 @@ describe("Dataworker block range-related utility methods", async function () { result = await blockRangesAreInvalidForSpokeClients( { [chainId]: spokePoolClients[chainId], [10]: spokePoolClients[originChainId] }, [ - [mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestBlockSearched], - [optimismDeploymentBlock + 3, spokePoolClients[originChainId].latestBlockSearched], + [mainnetDeploymentBlock + 3, spokePoolClients[chainId].latestHeightSearched], + [optimismDeploymentBlock + 3, spokePoolClients[originChainId].latestHeightSearched], ], [chainId, 10], { [chainId]: mainnetDeploymentBlock + 2, [10]: optimismDeploymentBlock + 2 } @@ -275,7 +275,7 @@ describe("Dataworker block range-related utility methods", async function () { // that don't have early enough data for the first bundle, which started at the deployment block height. result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[0, spokePoolClients[chainId].latestBlockSearched]], + [[0, spokePoolClients[chainId].latestHeightSearched]], chainIds, { [chainId]: mainnetDeploymentBlock + 2, @@ -288,7 +288,7 @@ describe("Dataworker block range-related utility methods", async function () { // This time, the deployment block is higher than the earliestValidBundleStartBlockForChain so the range is valid. result = await blockRangesAreInvalidForSpokeClients( _spokePoolClients, - [[0, spokePoolClients[chainId].latestBlockSearched]], + [[0, spokePoolClients[chainId].latestHeightSearched]], chainIds, { [chainId]: mainnetDeploymentBlock - 1, @@ -310,11 +310,11 @@ describe("Dataworker block range-related utility methods", async function () { originSpokePoolClient.logger, fakeSpokePool as unknown as Contract, originSpokePoolClient.chainId, - originSpokePoolClient.eventSearchConfig.fromBlock - 1 // Set deployment block less than eventSearchConfig.fromBlock + originSpokePoolClient.eventSearchConfig.from - 1 // Set deployment block less than eventSearchConfig.fromBlock // to force blockRangesAreInvalidForSpokeClients to compare the client's oldestTime() with its // fill deadline buffer. ); - const blockRanges = [[mainnetDeploymentBlock + 1, mockSpokePoolClient.latestBlockSearched]]; + const blockRanges = [[mainnetDeploymentBlock + 1, mockSpokePoolClient.latestHeightSearched]]; const endBlockTimestamps = await getTimestampsForBundleEndBlocks( { [originChainId]: mockSpokePoolClient as SpokePoolClient }, blockRanges, @@ -322,12 +322,12 @@ describe("Dataworker block range-related utility methods", async function () { ); // override oldest spoke pool client's oldest time searched to be realistic (i.e. not zero) mockSpokePoolClient.setBlockTimestamp( - mockSpokePoolClient.eventSearchConfig.fromBlock, + mockSpokePoolClient.eventSearchConfig.from, endBlockTimestamps[originChainId] - 1 ); const expectedTimeBetweenOldestAndEndBlockTimestamp = endBlockTimestamps[originChainId] - - (await mockSpokePoolClient.getTimeAt(mockSpokePoolClient.eventSearchConfig.fromBlock)); + (await mockSpokePoolClient.getTimeAt(mockSpokePoolClient.eventSearchConfig.from)); assert( expectedTimeBetweenOldestAndEndBlockTimestamp > 0, "unrealistic time between oldest and end block timestamp" @@ -364,10 +364,7 @@ describe("Dataworker block range-related utility methods", async function () { const oldestBlockTimestampOverride = endBlockTimestamps[originChainId] - fillDeadlineOverride - CONSERVATIVE_BUNDLE_FREQUENCY_SECONDS - 1; assert(oldestBlockTimestampOverride > 0, "unrealistic oldest block timestamp"); - mockSpokePoolClient.setBlockTimestamp( - mockSpokePoolClient.eventSearchConfig.fromBlock, - oldestBlockTimestampOverride - ); + mockSpokePoolClient.setBlockTimestamp(mockSpokePoolClient.eventSearchConfig.from, oldestBlockTimestampOverride); result = await blockRangesAreInvalidForSpokeClients( { [originChainId]: mockSpokePoolClient as SpokePoolClient }, blockRanges, @@ -381,10 +378,7 @@ describe("Dataworker block range-related utility methods", async function () { // Finally, reset fill deadline buffer in contracts and reset the override in the mock to test that // the client calls from the contracts. - mockSpokePoolClient.setBlockTimestamp( - mockSpokePoolClient.eventSearchConfig.fromBlock, - oldestBlockTimestampOverride - ); + mockSpokePoolClient.setBlockTimestamp(mockSpokePoolClient.eventSearchConfig.from, oldestBlockTimestampOverride); mockSpokePoolClient.setMaxFillDeadlineOverride(undefined); fakeSpokePool.fillDeadlineBuffer.returns(expectedTimeBetweenOldestAndEndBlockTimestamp); // This should be same // length as time between oldest time and end block timestamp so it should be a valid block range. diff --git a/test/Dataworker.loadData.deposit.ts b/test/Dataworker.loadData.deposit.ts index 9f3fd05c17..1ffdb9b49b 100644 --- a/test/Dataworker.loadData.deposit.ts +++ b/test/Dataworker.loadData.deposit.ts @@ -127,8 +127,8 @@ describe("Dataworker: Load bundle data", async function () { quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, - blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.DepositWithBlock); } @@ -148,8 +148,8 @@ describe("Dataworker: Load bundle data", async function () { updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -174,8 +174,8 @@ describe("Dataworker: Load bundle data", async function () { updatedOutputAmount: updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -547,7 +547,7 @@ describe("Dataworker: Load bundle data", async function () { await bundleDataClient.getUpcomingDepositAmount( originChainId, erc20_1.address, - spokePoolClient_1.latestBlockSearched // block higher than the deposit + spokePoolClient_1.latestHeightSearched // block higher than the deposit ) ).to.equal(0); expect( diff --git a/test/Dataworker.loadData.fill.ts b/test/Dataworker.loadData.fill.ts index 65f79bf884..059f9ca8b0 100644 --- a/test/Dataworker.loadData.fill.ts +++ b/test/Dataworker.loadData.fill.ts @@ -182,8 +182,8 @@ describe("Dataworker: Load bundle data", async function () { quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, - blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.DepositWithBlock); } @@ -204,8 +204,8 @@ describe("Dataworker: Load bundle data", async function () { updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -230,8 +230,8 @@ describe("Dataworker: Load bundle data", async function () { updatedOutputAmount: updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -605,8 +605,8 @@ describe("Dataworker: Load bundle data", async function () { const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -652,8 +652,8 @@ describe("Dataworker: Load bundle data", async function () { bundleBlockRanges[originChainIndex] = [depositBlock - 2, depositBlock - 1]; // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -693,8 +693,8 @@ describe("Dataworker: Load bundle data", async function () { ); const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -754,8 +754,8 @@ describe("Dataworker: Load bundle data", async function () { const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); diff --git a/test/Dataworker.loadData.prefill.ts b/test/Dataworker.loadData.prefill.ts index e03fad6bdf..b1c41cda16 100644 --- a/test/Dataworker.loadData.prefill.ts +++ b/test/Dataworker.loadData.prefill.ts @@ -161,8 +161,8 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, - blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.DepositWithBlock); } @@ -183,8 +183,8 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -196,8 +196,8 @@ describe("Dataworker: Load bundle data: Pre-fill and Pre-Slow-Fill request logic const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.SlowFillRequestWithBlock); } diff --git a/test/Dataworker.loadData.slowFill.ts b/test/Dataworker.loadData.slowFill.ts index e35b3e2499..a979690067 100644 --- a/test/Dataworker.loadData.slowFill.ts +++ b/test/Dataworker.loadData.slowFill.ts @@ -61,8 +61,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, - blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.DepositWithBlock); } @@ -83,8 +83,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -96,8 +96,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.SlowFillRequestWithBlock); } @@ -314,8 +314,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -362,8 +362,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () bundleBlockRanges[originChainIndex] = [depositBlock - 2, depositBlock - 1]; // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -462,8 +462,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -505,8 +505,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); const deposits = spokePoolClient_1.getDeposits(); expect(deposits.length).to.equal(0); @@ -665,8 +665,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); expect(mockConfigStore.liteChainIndicesUpdates.length).to.equal(1); expect(mockConfigStore.liteChainIndicesUpdates[0].timestamp).to.be.lt(depositObject.quoteTimestamp); @@ -713,8 +713,8 @@ describe("Dataworker: Load bundle data: Computing slow fills", async function () const depositBlock = await spokePool_1.provider.getBlockNumber(); // Construct a spoke pool client with a small search range that would not include the deposit. - spokePoolClient_1.firstBlockToSearch = depositBlock + 1; - spokePoolClient_1.eventSearchConfig.fromBlock = spokePoolClient_1.firstBlockToSearch; + spokePoolClient_1.firstHeightToSearch = depositBlock + 1; + spokePoolClient_1.eventSearchConfig.from = spokePoolClient_1.firstHeightToSearch; await spokePoolClient_1.update(); expect(mockConfigStore.liteChainIndicesUpdates.length).to.equal(1); expect(mockConfigStore.liteChainIndicesUpdates[0].timestamp).to.be.lt(depositObject.quoteTimestamp); diff --git a/test/Dataworker.loadData.unexecutableSlowFill.ts b/test/Dataworker.loadData.unexecutableSlowFill.ts index 2b7922e781..d2e5142445 100644 --- a/test/Dataworker.loadData.unexecutableSlowFill.ts +++ b/test/Dataworker.loadData.unexecutableSlowFill.ts @@ -57,8 +57,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn quoteTimestamp: eventOverride?.quoteTimestamp ?? getCurrentTime() - 10, fillDeadline: eventOverride?.fillDeadline ?? getCurrentTime() + 14400, destinationChainId, - blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: eventOverride?.blockNumber ?? spokePoolClient_1.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.DepositWithBlock); } @@ -79,8 +79,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn updatedOutputAmount: fillObject.relayExecutionInfo.updatedOutputAmount, fillType, }, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.FillWithBlock); } @@ -92,8 +92,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn const { relayer, repaymentChainId, relayExecutionInfo, ...relayData } = fillObject; return mockDestinationSpokePoolClient.requestV3SlowFill({ ...relayData, - blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestBlockSearched, // @dev use latest block searched from non-mocked client - // so that mocked client's latestBlockSearched gets set to the same value. + blockNumber: fillEventOverride?.blockNumber ?? spokePoolClient_2.latestHeightSearched, // @dev use latest block searched from non-mocked client + // so that mocked client's latestHeightSearched gets set to the same value. } as interfaces.SlowFillRequestWithBlock); } @@ -234,8 +234,8 @@ describe("Dataworker: Load bundle data: Computing unexecutable slow fills", asyn await fillV3Relay(spokePool_2, deposits[2], relayer, repaymentChainId); // Construct a spoke pool client with a small search range that would not include the first fill. - spokePoolClient_2.firstBlockToSearch = missingSlowFillRequestBlock + 1; - spokePoolClient_2.eventSearchConfig.fromBlock = spokePoolClient_2.firstBlockToSearch; + spokePoolClient_2.firstHeightToSearch = missingSlowFillRequestBlock + 1; + spokePoolClient_2.eventSearchConfig.from = spokePoolClient_2.firstHeightToSearch; // There should be one "missing" slow fill request. await spokePoolClient_2.update(); diff --git a/test/Dataworker.validateRootBundle.ts b/test/Dataworker.validateRootBundle.ts index 8f63fe1352..68fcce1b26 100644 --- a/test/Dataworker.validateRootBundle.ts +++ b/test/Dataworker.validateRootBundle.ts @@ -198,7 +198,7 @@ describe("Dataworker: Validate pending root bundle", async function () { await hubPool.proposeRootBundle( // Since the dataworker sets the end block to latest minus buffer, setting the bundle end blocks to HEAD // should fall within buffer. - Object.keys(spokePoolClients).map((chainId) => spokePoolClients[chainId].latestBlockSearched), + Object.keys(spokePoolClients).map((chainId) => spokePoolClients[chainId].latestHeightSearched), expectedPoolRebalanceRoot4.leaves.length, expectedPoolRebalanceRoot4.tree.getHexRoot(), expectedRelayerRefundRoot4.tree.getHexRoot(), @@ -219,7 +219,7 @@ describe("Dataworker: Validate pending root bundle", async function () { await updateAllClients(); await hubPool.proposeRootBundle( Object.keys(spokePoolClients).map( - (chainId) => spokePoolClients[chainId].latestBlockSearched + BUNDLE_END_BLOCK_BUFFER + 1 + (chainId) => spokePoolClients[chainId].latestHeightSearched + BUNDLE_END_BLOCK_BUFFER + 1 ), expectedPoolRebalanceRoot4.leaves.length, expectedPoolRebalanceRoot4.tree.getHexRoot(), diff --git a/test/DataworkerUtils.ts b/test/DataworkerUtils.ts index 9102215502..7d78ec4645 100644 --- a/test/DataworkerUtils.ts +++ b/test/DataworkerUtils.ts @@ -184,8 +184,8 @@ describe("PoolRebalanceLeaf utils", function () { }); it("Produces empty pool rebalance leaf for chains with only refunds and no running balances", async function () { const { leaves } = _buildPoolRebalanceRoot( - mockHubPoolClient.latestBlockSearched, - mockHubPoolClient.latestBlockSearched, + mockHubPoolClient.latestHeightSearched, + mockHubPoolClient.latestHeightSearched, // To make this test simpler, create the minimum object that won't break the tested function: {}, { @@ -220,8 +220,8 @@ describe("PoolRebalanceLeaf utils", function () { }); it("Inserts empty pool rebalance leaf in correct order", async function () { const { leaves } = _buildPoolRebalanceRoot( - mockHubPoolClient.latestBlockSearched, - mockHubPoolClient.latestBlockSearched, + mockHubPoolClient.latestHeightSearched, + mockHubPoolClient.latestHeightSearched, // To make this test simpler, create the minimum object that won't break the tested function: { [originChainId]: { diff --git a/test/EventUtils.ts b/test/EventUtils.ts index 29b04bc42f..ecaab35b81 100644 --- a/test/EventUtils.ts +++ b/test/EventUtils.ts @@ -6,42 +6,42 @@ import { getPaginatedBlockRanges, getUniqueLogIndex } from "../src/utils/EventUt describe("EventUtils", async function () { it("getPaginatedBlockRanges", async function () { // Undefined lookback returns full range - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 4, maxBlockLookBack: undefined })).to.deep.equal([[0, 4]]); + expect(getPaginatedBlockRanges({ from: 0, to: 4, maxLookBack: undefined })).to.deep.equal([[0, 4]]); // zero lookback throws error - expect(() => getPaginatedBlockRanges({ fromBlock: 0, toBlock: 4, maxBlockLookBack: 0 })).to.throw(); + expect(() => getPaginatedBlockRanges({ from: 0, to: 4, maxLookBack: 0 })).to.throw(); - // toBlock > fromBlock returns an empty array. - expect(getPaginatedBlockRanges({ fromBlock: 5, toBlock: 4, maxBlockLookBack: 2 })).to.deep.equal([]); + // to > from returns an empty array. + expect(getPaginatedBlockRanges({ from: 5, to: 4, maxLookBack: 2 })).to.deep.equal([]); // Lookback larger than range returns full range - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 4, maxBlockLookBack: 6 })).to.deep.equal([[0, 4]]); + expect(getPaginatedBlockRanges({ from: 0, to: 4, maxLookBack: 6 })).to.deep.equal([[0, 4]]); - // Range of 0 returns a range that covers both the fromBlock and toBlock as expected. - expect(getPaginatedBlockRanges({ fromBlock: 1, toBlock: 1, maxBlockLookBack: 3 })).to.deep.equal([[0, 1]]); + // Range of 0 returns a range that covers both the from and to as expected. + expect(getPaginatedBlockRanges({ from: 1, to: 1, maxLookBack: 3 })).to.deep.equal([[0, 1]]); - // Range of 0 returns a range that covers both the fromBlock and toBlock as expected. - expect(getPaginatedBlockRanges({ fromBlock: 3, toBlock: 3, maxBlockLookBack: 3 })).to.deep.equal([[3, 3]]); + // Range of 0 returns a range that covers both the from and to as expected. + expect(getPaginatedBlockRanges({ from: 3, to: 3, maxLookBack: 3 })).to.deep.equal([[3, 3]]); // Lookback of 1 - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 4, maxBlockLookBack: 2 })).to.deep.equal([ + expect(getPaginatedBlockRanges({ from: 0, to: 4, maxLookBack: 2 })).to.deep.equal([ [0, 1], [2, 3], [4, 4], ]); // Lookback equal to range returns full range - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 4, maxBlockLookBack: 5 })).to.deep.equal([[0, 4]]); + expect(getPaginatedBlockRanges({ from: 0, to: 4, maxLookBack: 5 })).to.deep.equal([[0, 4]]); // Range evenly divided by max block lookback: - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 100, maxBlockLookBack: 50 })).to.deep.equal([ + expect(getPaginatedBlockRanges({ from: 0, to: 100, maxLookBack: 50 })).to.deep.equal([ [0, 49], [50, 99], [100, 100], ]); // Range divided by max block lookback with remainder: - expect(getPaginatedBlockRanges({ fromBlock: 0, toBlock: 100, maxBlockLookBack: 30 })).to.deep.equal([ + expect(getPaginatedBlockRanges({ from: 0, to: 100, maxLookBack: 30 })).to.deep.equal([ [0, 29], [30, 59], [60, 89], @@ -49,7 +49,7 @@ describe("EventUtils", async function () { ]); // Range divided by max block lookback with remainder: - expect(getPaginatedBlockRanges({ fromBlock: 172, toBlock: 200, maxBlockLookBack: 12 })).to.deep.equal([ + expect(getPaginatedBlockRanges({ from: 172, to: 200, maxLookBack: 12 })).to.deep.equal([ [168, 179], [180, 191], [192, 200], @@ -58,9 +58,7 @@ describe("EventUtils", async function () { // Consistent range (for caching purposes) for many sub-ranges for (let i = 0; i < 1000; i++) { const start = Math.floor(Math.random() * 100 + 100); - expect(getPaginatedBlockRanges({ fromBlock: start, toBlock: 199, maxBlockLookBack: 100 })).to.deep.equal([ - [100, 199], - ]); + expect(getPaginatedBlockRanges({ from: start, to: 199, maxLookBack: 100 })).to.deep.equal([[100, 199]]); } }); it("getUniqueLogIndex", async function () { diff --git a/test/InventoryClient.InventoryRebalance.ts b/test/InventoryClient.InventoryRebalance.ts index d9d6049ed2..7bfbec1d28 100644 --- a/test/InventoryClient.InventoryRebalance.ts +++ b/test/InventoryClient.InventoryRebalance.ts @@ -103,7 +103,7 @@ describe("InventoryClient: Rebalancing inventory", async function () { const { hubPool, dai: l1Token } = await hubPoolFixture(); const { configStore } = await deployConfigStore(owner, [l1Token]); - const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, 0); + const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { from: 0 }, 0); await configStoreClient.update(); hubPoolClient = new MockHubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 2b78b3cc1f..723515f1e7 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -120,7 +120,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { configStoreClient = new MockConfigStoreClient( spyLogger, configStore, - { fromBlock: 0 }, + { from: 0 }, CONFIG_STORE_VERSION, [originChainId, destinationChainId], originChainId, @@ -765,7 +765,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Make an invalid fills to crank the chain forward until the initial deposit has enough confirmations. while ( originChainConfirmations[0].minConfirmations > - spokePoolClient_2.latestBlockSearched - deposit1.blockNumber + spokePoolClient_2.latestHeightSearched - deposit1.blockNumber ) { await fillV3Relay( spokePool_2, @@ -795,7 +795,7 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { // Make an invalid fills to crank the chain forward until the 2nd deposit has enough confirmations. while ( originChainConfirmations[0].minConfirmations > - spokePoolClient_2.latestBlockSearched - deposit2.blockNumber + spokePoolClient_2.latestHeightSearched - deposit2.blockNumber ) { await fillV3Relay( spokePool_2, diff --git a/test/Relayer.IndexedSpokePoolClient.ts b/test/Relayer.IndexedSpokePoolClient.ts index 18dd731746..01239d7014 100644 --- a/test/Relayer.IndexedSpokePoolClient.ts +++ b/test/Relayer.IndexedSpokePoolClient.ts @@ -134,7 +134,7 @@ describe("IndexedSpokePoolClient: Update", async function () { postEvents(blockNumber, currentTime, events); await spokePoolClient.update(); - expect(spokePoolClient.latestBlockSearched).to.equal(blockNumber); + expect(spokePoolClient.latestHeightSearched).to.equal(blockNumber); const deposits = spokePoolClient.getDeposits(); expect(deposits.length).to.equal(events.length); diff --git a/test/Relayer.SlowFill.ts b/test/Relayer.SlowFill.ts index 910c4e1552..b01555a0b1 100644 --- a/test/Relayer.SlowFill.ts +++ b/test/Relayer.SlowFill.ts @@ -104,7 +104,7 @@ describe("Relayer: Initiates slow fill requests", async function () { CHAIN_ID_TEST_LIST )); - configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, CONFIG_STORE_VERSION); + configStoreClient = new ConfigStoreClient(spyLogger, configStore, { from: 0 }, CONFIG_STORE_VERSION); await configStoreClient.update(); hubPoolClient = new SimpleMockHubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/Relayer.TokenShortfall.ts b/test/Relayer.TokenShortfall.ts index 42cfe3cb1c..b014c8d2f5 100644 --- a/test/Relayer.TokenShortfall.ts +++ b/test/Relayer.TokenShortfall.ts @@ -104,7 +104,7 @@ describe("Relayer: Token balance shortfall", async function () { undefined, CHAIN_ID_TEST_LIST )); - configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, CONFIG_STORE_VERSION); + configStoreClient = new ConfigStoreClient(spyLogger, configStore, { from: 0 }, CONFIG_STORE_VERSION); await configStoreClient.update(); hubPoolClient = new SimpleMockHubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/Relayer.UnfilledDeposits.ts b/test/Relayer.UnfilledDeposits.ts index fe7047b618..0610fb9afc 100644 --- a/test/Relayer.UnfilledDeposits.ts +++ b/test/Relayer.UnfilledDeposits.ts @@ -123,7 +123,7 @@ describe("Relayer: Unfilled Deposits", async function () { hubPoolClient, destinationChainId, spokePool2DeploymentBlock, - { fromBlock: 0, toBlock: undefined, maxBlockLookBack: 0 } + { from: 0, to: undefined, maxLookBack: 0 } ); spokePoolClients = { [originChainId]: spokePoolClient_1, [destinationChainId]: spokePoolClient_2 }; diff --git a/test/TokenClient.Approval.ts b/test/TokenClient.Approval.ts index 1f37caeaf5..119d6597fe 100644 --- a/test/TokenClient.Approval.ts +++ b/test/TokenClient.Approval.ts @@ -84,7 +84,7 @@ describe("TokenClient: Origin token approval", async function () { const spokePoolClients = { [originChainId]: spokePoolClient_1, [destinationChainId]: spokePoolClient_2 }; const { configStore } = await deployConfigStore(owner, [l1Token_1, l1Token_2]); - const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, 0); + const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { from: 0 }, 0); await configStoreClient.update(); hubPoolClient = new MockHubPoolClient(createSpyLogger().spyLogger, hubPool, configStoreClient); diff --git a/test/TokenClient.BalanceAlowance.ts b/test/TokenClient.BalanceAlowance.ts index fda786e717..e47cacfa2e 100644 --- a/test/TokenClient.BalanceAlowance.ts +++ b/test/TokenClient.BalanceAlowance.ts @@ -62,7 +62,7 @@ describe("TokenClient: Balance and Allowance", async function () { ZERO_ADDRESS ); const { configStore } = await deployConfigStore(owner, [hubERC20, hubWeth]); - const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { fromBlock: 0 }, 0); + const configStoreClient = new ConfigStoreClient(spyLogger, configStore, { from: 0 }, 0); await configStoreClient.update(); hubPoolClient = new MockHubPoolClient(spyLogger, hubPool, configStoreClient); diff --git a/test/fixtures/Dataworker.Fixture.ts b/test/fixtures/Dataworker.Fixture.ts index ba5ffec6d1..051c338b86 100644 --- a/test/fixtures/Dataworker.Fixture.ts +++ b/test/fixtures/Dataworker.Fixture.ts @@ -52,7 +52,7 @@ async function _constructSpokePoolClientsWithLookback( hubPoolClient, spokePoolChains[i], deploymentBlocks?.[spokePoolChains[i]] ?? 0, - lookbackForAllChains === undefined ? undefined : { fromBlock: latestBlocks[i] - lookbackForAllChains } + lookbackForAllChains === undefined ? undefined : { from: latestBlocks[i] - lookbackForAllChains } ); }); } diff --git a/test/generic-adapters/Arbitrum.ts b/test/generic-adapters/Arbitrum.ts index f912d8dbb8..523ac445b0 100644 --- a/test/generic-adapters/Arbitrum.ts +++ b/test/generic-adapters/Arbitrum.ts @@ -9,8 +9,8 @@ import { getCctpDomainForChainId, EvmAddress } from "../../src/utils"; const logger = createSpyLogger().spyLogger; const searchConfig = { - fromBlock: 1, - toBlock: 1_000_000, + from: 1, + to: 1_000_000, }; const monitoredEoa = randomAddress(); @@ -55,10 +55,10 @@ describe("Cross Chain Adapter: Arbitrum", async function () { cctpBridgeContract = await (await getContractFactory("CctpTokenMessenger", deployer)).deploy(); const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, null, CHAIN_IDs.ARBITRUM, 0, { - fromBlock: 0, + from: 0, }); const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, null, CHAIN_IDs.MAINNET, 0, { - fromBlock: 0, + from: 0, }); const l1Signer = l1SpokePoolClient.spokePool.signer; @@ -95,8 +95,8 @@ describe("Cross Chain Adapter: Arbitrum", async function () { adapter.setCCTPL2Bridge(l1UsdcAddress, cctpBridgeContract); // Required to pass checks in `BaseAdapter.getUpdatedSearchConfigs` - l2SpokePoolClient.latestBlockSearched = searchConfig.toBlock; - l1SpokePoolClient.latestBlockSearched = searchConfig.toBlock; + l2SpokePoolClient.latestHeightSearched = searchConfig.to; + l1SpokePoolClient.latestHeightSearched = searchConfig.to; }); describe("ERC20", () => { diff --git a/test/generic-adapters/Linea.ts b/test/generic-adapters/Linea.ts index b24c873b84..0afdcd9de1 100644 --- a/test/generic-adapters/Linea.ts +++ b/test/generic-adapters/Linea.ts @@ -25,8 +25,8 @@ describe("Cross Chain Adapter: Linea", async function () { }; beforeEach(async function () { searchConfig = { - fromBlock: 0, - toBlock: 1_000_000, + from: 0, + to: 1_000_000, }; const [deployer] = await ethers.getSigners(); @@ -45,10 +45,10 @@ describe("Cross Chain Adapter: Linea", async function () { const spokePool = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); const l2SpokePoolClient = new SpokePoolClient(null, spokePool, null, l2ChainId, 0, { - fromBlock: 0, + from: 0, }); const l1SpokePoolClient = new SpokePoolClient(null, spokePool, null, hubChainId, 0, { - fromBlock: 0, + from: 0, }); wethBridgeContract = await (await getContractFactory("LineaWethBridge", deployer)).deploy(); @@ -100,8 +100,8 @@ describe("Cross Chain Adapter: Linea", async function () { adapter.setTargetL2Bridge(l1Token, erc20BridgeContract); // Required to pass checks in `BaseAdapter.getUpdatedSearchConfigs` - l2SpokePoolClient.latestBlockSearched = searchConfig.toBlock; - l1SpokePoolClient.latestBlockSearched = searchConfig.toBlock; + l2SpokePoolClient.latestHeightSearched = searchConfig.to; + l1SpokePoolClient.latestHeightSearched = searchConfig.to; }); describe("WETH", function () { @@ -299,7 +299,7 @@ class MockBaseChainAdapter extends BaseChainAdapter { // Since we are simulating getting outstanding transfers, we need to manually overwrite the config in // the adapter so that getOutstandingCrossChainTransfers won't throw an error. const blockNumber = await this.spokePoolClients[this.hubChainId].spokePool.provider.getBlockNumber(); - this.spokePoolClients[this.hubChainId].latestBlockSearched = blockNumber; - this.spokePoolClients[this.chainId].latestBlockSearched = blockNumber; + this.spokePoolClients[this.hubChainId].latestHeightSearched = blockNumber; + this.spokePoolClients[this.chainId].latestHeightSearched = blockNumber; } } diff --git a/test/generic-adapters/OpStack.ts b/test/generic-adapters/OpStack.ts index 33b4998078..c8d9942a54 100644 --- a/test/generic-adapters/OpStack.ts +++ b/test/generic-adapters/OpStack.ts @@ -71,8 +71,8 @@ describe("Cross Chain Adapter: OP Stack", async function () { }; beforeEach(async function () { searchConfig = { - fromBlock: 0, - toBlock: 1_000_000, + from: 0, + to: 1_000_000, }; const [deployer] = await ethers.getSigners(); @@ -109,10 +109,10 @@ describe("Cross Chain Adapter: OP Stack", async function () { spokePoolContract = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); const l2SpokePoolClient = new SpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.OPTIMISM, 0, { - fromBlock: 0, + from: 0, }); const l1SpokePoolClient = new SpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.MAINNET, 0, { - fromBlock: 0, + from: 0, }); adapter = new TestBaseChainAdapter( @@ -137,8 +137,8 @@ describe("Cross Chain Adapter: OP Stack", async function () { adapter.setL2Weth(l1WethAddress, wethContract); // Required to pass checks in `BaseAdapter.getUpdatedSearchConfigs` - l2SpokePoolClient.latestBlockSearched = searchConfig.toBlock; - l1SpokePoolClient.latestBlockSearched = searchConfig.toBlock; + l2SpokePoolClient.latestHeightSearched = searchConfig.to; + l1SpokePoolClient.latestHeightSearched = searchConfig.to; }); describe("WETH", function () { diff --git a/test/generic-adapters/Polygon.ts b/test/generic-adapters/Polygon.ts index a43e80fc37..7fd9873bf4 100644 --- a/test/generic-adapters/Polygon.ts +++ b/test/generic-adapters/Polygon.ts @@ -79,12 +79,12 @@ describe("Cross Chain Adapter: Polygon", async function () { const hubPoolClient = null; const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, POLYGON, deploymentBlock, { - fromBlock: deploymentBlock, + from: deploymentBlock, }); const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { - fromBlock: deploymentBlock, + from: deploymentBlock, }); - searchConfig = { fromBlock: deploymentBlock, toBlock: 1_000_000 }; + searchConfig = { from: deploymentBlock, to: 1_000_000 }; const l1Signer = l1SpokePoolClient.spokePool.signer; const l2Signer = l2SpokePoolClient.spokePool.signer; @@ -130,8 +130,8 @@ describe("Cross Chain Adapter: Polygon", async function () { l2Weth = adapter.bridges[l1Weth].resolveL2TokenAddress(toAddress(l1Weth)); // Required to pass checks in `BaseAdapter.getUpdatedSearchConfigs` - l2SpokePoolClient.latestBlockSearched = searchConfig.toBlock; - l1SpokePoolClient.latestBlockSearched = searchConfig.toBlock; + l2SpokePoolClient.latestHeightSearched = searchConfig.to; + l1SpokePoolClient.latestHeightSearched = searchConfig.to; }); describe("WETH bridge", function () { diff --git a/test/generic-adapters/Scroll.ts b/test/generic-adapters/Scroll.ts index 95ea3f7626..abd6dce28a 100644 --- a/test/generic-adapters/Scroll.ts +++ b/test/generic-adapters/Scroll.ts @@ -24,8 +24,8 @@ describe("Cross Chain Adapter: Scroll", async function () { const bnOne = utils.bnOne; beforeEach(async function () { searchConfig = { - fromBlock: 0, - toBlock: 1_000_000, + from: 0, + to: 1_000_000, }; const [deployer] = await ethers.getSigners(); @@ -42,10 +42,10 @@ describe("Cross Chain Adapter: Scroll", async function () { const spokePool = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); const l2SpokePoolClient = new SpokePoolClient(null, spokePool, null, l2ChainId, 0, { - fromBlock: 0, + from: 0, }); const l1SpokePoolClient = new SpokePoolClient(null, spokePool, null, hubChainId, 0, { - fromBlock: 0, + from: 0, }); spokeAddress = l2SpokePoolClient.spokePool.address; // For the purposes of this test, we will treat the hub pool as an EOA. @@ -249,7 +249,7 @@ class MockBaseChainAdapter extends BaseChainAdapter { // Since we are simulating getting outstanding transfers, we need to manually overwrite the config in // the adapter so that getOutstandingCrossChainTransfers won't throw an error. const blockNumber = await this.spokePoolClients[this.hubChainId].spokePool.provider.getBlockNumber(); - this.spokePoolClients[this.hubChainId].latestBlockSearched = blockNumber; - this.spokePoolClients[this.chainId].latestBlockSearched = blockNumber; + this.spokePoolClients[this.hubChainId].latestHeightSearched = blockNumber; + this.spokePoolClients[this.chainId].latestHeightSearched = blockNumber; } } diff --git a/test/generic-adapters/zkSync.ts b/test/generic-adapters/zkSync.ts index adb3039df0..0dbebe0b1c 100644 --- a/test/generic-adapters/zkSync.ts +++ b/test/generic-adapters/zkSync.ts @@ -135,12 +135,12 @@ describe("Cross Chain Adapter: zkSync", async function () { const hubPoolClient = null; const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, ZK_SYNC, deploymentBlock, { - fromBlock: deploymentBlock, + from: deploymentBlock, }); const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { - fromBlock: deploymentBlock, + from: deploymentBlock, }); - searchConfig = { fromBlock: deploymentBlock, toBlock: 1_000_000 }; + searchConfig = { from: deploymentBlock, to: 1_000_000 }; const l1Signer = l1SpokePoolClient.spokePool.signer; const l2Signer = l2SpokePoolClient.spokePool.signer; @@ -1034,7 +1034,7 @@ describe("Cross Chain Adapter: zkSync", async function () { const hubPoolClient = null; const deploymentBlock = spokePool.deployTransaction.blockNumber!; const lensSpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, LENS, deploymentBlock, { - fromBlock: deploymentBlock, + from: deploymentBlock, }); const l1Signer = adapter.spokePoolClients[MAINNET].spokePool.signer; diff --git a/test/mocks/MockBaseChainAdapter.ts b/test/mocks/MockBaseChainAdapter.ts index 16d1a38b39..6fadda1800 100644 --- a/test/mocks/MockBaseChainAdapter.ts +++ b/test/mocks/MockBaseChainAdapter.ts @@ -18,8 +18,8 @@ export class MockBaseChainAdapter extends BaseChainAdapter { 0 ); } - getSearchConfig(): MakeOptional { - return { fromBlock: 0 }; + getSearchConfig(): MakeOptional { + return { from: 0 }; } isSupportedL2Bridge(): boolean { return true; diff --git a/test/mocks/MockConfigStoreClient.ts b/test/mocks/MockConfigStoreClient.ts index 2b7a930a21..555b98cab7 100644 --- a/test/mocks/MockConfigStoreClient.ts +++ b/test/mocks/MockConfigStoreClient.ts @@ -10,7 +10,7 @@ export class MockConfigStoreClient extends clients.mocks.MockConfigStoreClient { constructor( logger: winston.Logger, configStore: Contract, - eventSearchConfig: MakeOptional = { fromBlock: 0, maxBlockLookBack: 0 }, + eventSearchConfig: MakeOptional = { from: 0, maxLookBack: 0 }, configStoreVersion = DEFAULT_CONFIG_STORE_VERSION, enabledChainIds = CHAIN_ID_TEST_LIST, chainId = 1, diff --git a/yarn.lock b/yarn.lock index 9438d66790..7d395a7fdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -59,10 +59,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@4.1.62": - version "4.1.62" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.1.62.tgz#d9c3855cef5ad657e7431a167a3cb3825da01376" - integrity sha512-bE20Sze19ZH8DjwTW2JeEWhWJdfXVQ8aH0y1TlvseW+Skf1Oc2IW7FNBiEhdIopFTIDagcGU9uwKNurMmC1wmw== +"@across-protocol/sdk@4.1.63-beta.0": + version "4.1.63-beta.0" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.1.63-beta.0.tgz#434a10705976b9fc5fd5758c55512ae7e246f3c3" + integrity sha512-YVLdJ5vn+8wzL7k4k8uDt4/dWc+6PPG1KYgweoEtfSL7pnBvxjdsWF1D2/JLjyxu0YIL0LZs5/k/hCQnZXll1g== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.64" @@ -71,6 +71,8 @@ "@eth-optimism/sdk" "^3.3.1" "@ethersproject/bignumber" "^5.7.0" "@pinata/sdk" "^2.1.0" + "@solana-program/system" "^0.7.0" + "@solana-program/token-2022" "^0.4.0" "@solana/kit" "^2.1.0" "@solana/web3.js" "^1.31.0" "@types/mocha" "^10.0.1" @@ -2734,6 +2736,16 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@solana-program/system@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@solana-program/system/-/system-0.7.0.tgz#3e21c9fb31d3795eb65ba5cb663947c19b305bad" + integrity sha512-FKTBsKHpvHHNc1ATRm7SlC5nF/VdJtOSjldhcyfMN9R7xo712Mo2jHIzvBgn8zQO5Kg0DcWuKB7268Kv1ocicw== + +"@solana-program/token-2022@^0.4.0": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@solana-program/token-2022/-/token-2022-0.4.1.tgz#4060407f68db7dda7cd919d7c867cfd72b172145" + integrity sha512-zIjdwUwirmvvF1nb95I/88+76d9HPCmAIcjjM4NpznDFjZpPItpfKIbTyp/uxeVOzi7d5LmvuvXRr373gpdGCg== + "@solana-program/token@^0.5.1": version "0.5.1" resolved "https://registry.yarnpkg.com/@solana-program/token/-/token-0.5.1.tgz#10e327df23f05a7f892fd33a9b6418f17dd62296" From 2e38dd0a7c85eabc0fffffa5f8a7fae2ec92af20 Mon Sep 17 00:00:00 2001 From: bennett Date: Mon, 12 May 2025 17:14:12 -0500 Subject: [PATCH 03/20] feat: Solana rebalancing Signed-off-by: bennett --- src/adapter/bridges/SolanaUsdcCCTPBridge.ts | 131 ++++++++++++++++++++ src/adapter/bridges/index.ts | 1 + src/common/Constants.ts | 10 +- src/common/ContractAddresses.ts | 8 ++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/adapter/bridges/SolanaUsdcCCTPBridge.ts diff --git a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts new file mode 100644 index 0000000000..d7d4ed8361 --- /dev/null +++ b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts @@ -0,0 +1,131 @@ +import { Contract, Signer } from "ethers"; +import { l2SignerOrProvider } from "../../common"; +import { BridgeTransactionDetails, BaseBridgeAdapter, BridgeEvents } from "./BaseBridgeAdapter"; +import { + BigNumber, + EventSearchConfig, + TOKEN_SYMBOLS_MAP, + compareAddressesSimple, + assert, + toBN, + getCctpDomainForChainId, + Address, + EvmAddress, + SvmAddress, + paginatedEventQuery, + ZERO_BYTES, +} from "../../utils"; +import { processEvent } from "../utils"; +import { getCctpTokenMessenger, isCctpV2L2ChainId } from "../../utils/CCTPUtils"; +import { CCTP_NO_DOMAIN } from "@across-protocol/constants"; +import { arch } from "@across-protocol/sdk"; +import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; +import { Rpc, SolanaRpcApi } from "@solana/kit"; + +export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { + private CCTP_MAX_SEND_AMOUNT = toBN(1_000_000_000_000); // 1MM USDC. + private IS_CCTP_V2 = false; + private readonly l1UsdcTokenAddress: EvmAddress; + private readonly solanaMessageTransmitter: SvmAddress; + // We need the constructor to operate in a synchronous context, but the call to construct an event client is asynchronous, so + // this bridge holds onto the client promise and lazily evaluates it for when it needs to use it (in `queryL2BridgeFinalizationEvents`). + private readonly solanaEventsClientPromise: Promise; + private solanaEventsClient: arch.svm.SvmCpiEventsClient; + + constructor(l2chainId: number, hubChainId: number, l1Signer: Signer, l2SignerOrProvider: l2SignerOrProvider) { + super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [ + EvmAddress.from(getCctpTokenMessenger(l2chainId, hubChainId).address), + ]); + assert( + getCctpDomainForChainId(l2chainId) !== CCTP_NO_DOMAIN && getCctpDomainForChainId(hubChainId) !== CCTP_NO_DOMAIN, + "Unknown CCTP domain ID" + ); + this.IS_CCTP_V2 = isCctpV2L2ChainId(l2chainId); + + const { address: l1Address, abi: l1Abi } = getCctpTokenMessenger(l2chainId, hubChainId); + this.l1Bridge = new Contract(l1Address, l1Abi, l1Signer); + + const { address: l2Address } = getCctpTokenMessenger(l2chainId, l2chainId); + this.solanaMessageTransmitter = SvmAddress.from(l2Address); + this.solanaEventsClientPromise = arch.svm.SvmCpiEventsClient.createFor( + l2SignerOrProvider as Rpc, + l2Address, + TokenMessengerMinterIdl + ); + + this.l1UsdcTokenAddress = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId]); + } + + private get l2DestinationDomain(): number { + return getCctpDomainForChainId(this.l2chainId); + } + + protected resolveL2TokenAddress(l1Token: EvmAddress): string { + l1Token; + return TOKEN_SYMBOLS_MAP.USDC.addresses[this.l2chainId]; + } + + async constructL1ToL2Txn( + toAddress: Address, + l1Token: EvmAddress, + _l2Token: Address, + amount: BigNumber + ): Promise { + assert(compareAddressesSimple(l1Token.toAddress(), 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", + args: this.IS_CCTP_V2 + ? [ + amount, + this.l2DestinationDomain, + toAddress.toBytes32(), + this.l1UsdcTokenAddress.toAddress(), + ZERO_BYTES, // Anyone can finalize the message on domain when this is set to bytes32(0) + 0, // maxFee set to 0 so this will be a "standard" speed transfer + 2000, // Hardcoded minFinalityThreshold value for standard transfer + ] + : [amount, this.l2DestinationDomain, toAddress.toBytes32(), this.l1UsdcTokenAddress.toAddress()], + }); + } + + async queryL1BridgeInitiationEvents( + l1Token: EvmAddress, + fromAddress: EvmAddress, + toAddress: Address, + eventConfig: EventSearchConfig + ): Promise { + assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); + const eventFilterArgs = this.IS_CCTP_V2 + ? [this.l1UsdcTokenAddress.toAddress(), undefined, fromAddress.toAddress()] + : [undefined, this.l1UsdcTokenAddress.toAddress(), undefined, fromAddress.toAddress()]; + const eventFilter = this.getL1Bridge().filters.DepositForBurn(...eventFilterArgs); + const events = (await paginatedEventQuery(this.getL1Bridge(), eventFilter, eventConfig)).filter((event) => + compareAddressesSimple(event.args.mintRecipient, toAddress.toBytes32()) + ); + return { + [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "amount")), + }; + } + + async queryL2BridgeFinalizationEvents( + l1Token: EvmAddress, + fromAddress: EvmAddress, + toAddress: Address, + eventConfig: EventSearchConfig + ): Promise { + // Lazily evaluate the events client. + this.solanaEventsClient ??= await this.solanaEventsClientPromise; + assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); + const l2FinalizationEvents = await this.solanaEventsClient.queryEvents( + "mintAndWithdraw", + this.solanaMessageTransmitter.toV2Address(), + BigInt(eventConfig.from), + BigInt(eventConfig.to) + ); + return { + [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "amount")), // TODO + }; + } +} diff --git a/src/adapter/bridges/index.ts b/src/adapter/bridges/index.ts index 6c2c96024d..ad779f54b8 100644 --- a/src/adapter/bridges/index.ts +++ b/src/adapter/bridges/index.ts @@ -18,3 +18,4 @@ export * from "./UsdcCCTPBridge"; export * from "./ZKStackBridge"; export * from "./ZKStackUSDCBridge"; export * from "./ZKStackWethBridge"; +export * from "./SolanaCCTPUsdcBridge"; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 37e5a9900f..c42ebe458a 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -31,6 +31,7 @@ import { ZKStackWethBridge, BinanceCEXBridge, BinanceCEXNativeBridge, + SolanaUsdcCCTPBridge, } from "../adapter/bridges"; import { BaseL2BridgeAdapter, @@ -40,6 +41,7 @@ import { BinanceCEXBridge as L2BinanceCEXBridge, } from "../adapter/l2Bridges"; import { CONTRACT_ADDRESSES } from "./ContractAddresses"; +import { Rpc, SolanaRpcApi } from "@solana/kit"; /** * Note: When adding new chains, it's preferred to retain alphabetical ordering of CHAIN_IDs in Object mappings. @@ -144,6 +146,7 @@ export const CHAIN_MAX_BLOCK_LOOKBACK = { [CHAIN_IDs.REDSTONE]: 10000, [CHAIN_IDs.SCROLL]: 10000, [CHAIN_IDs.SONEIUM]: 10000, + [CHAIN_IDs.SOLANA]: 10000, [CHAIN_IDs.WORLD_CHAIN]: 10000, [CHAIN_IDs.ZK_SYNC]: 10000, [CHAIN_IDs.ZORA]: 10000, @@ -184,6 +187,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.SOLANA]: 150, // ~400ms/block [CHAIN_IDs.SONEIUM]: 60, // 2s/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 @@ -240,6 +244,7 @@ export const CHAIN_CACHE_FOLLOW_DISTANCE: { [chainId: number]: number } = { [CHAIN_IDs.REDSTONE]: 120, [CHAIN_IDs.SONEIUM]: 120, [CHAIN_IDs.SCROLL]: 100, + [CHAIN_IDs.SOLANA]: 512, [CHAIN_IDs.WORLD_CHAIN]: 120, [CHAIN_IDs.ZK_SYNC]: 512, [CHAIN_IDs.ZORA]: 120, @@ -278,6 +283,7 @@ export const DEFAULT_NO_TTL_DISTANCE: { [chainId: number]: number } = { [CHAIN_IDs.POLYGON]: 86400, [CHAIN_IDs.REDSTONE]: 86400, [CHAIN_IDs.SCROLL]: 57600, + [CHAIN_IDs.SOLANA]: 432000, [CHAIN_IDs.SONEIUM]: 86400, [CHAIN_IDs.WORLD_CHAIN]: 86400, [CHAIN_IDs.ZK_SYNC]: 172800, @@ -342,6 +348,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", "POOL"], + [CHAIN_IDs.SOLANA]: ["USDC"], [CHAIN_IDs.SONEIUM]: ["WETH", "USDC"], [CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC", "USDC", "WLD", "POOL"], [CHAIN_IDs.ZK_SYNC]: ["USDC", "USDT", "WETH", "WBTC", "DAI"], @@ -449,7 +456,7 @@ export const CUSTOM_BRIDGE: { l2chainId: number, hubChainId: number, l1Signer: Signer, - l2SignerOrProvider: Signer | Provider, + l2SignerOrProvider: L2SignerOrProvider, l1Token?: EvmAddress ): BaseBridgeAdapter; }; @@ -659,6 +666,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.SOLANA]: 60 * 30, [CHAIN_IDs.SONEIUM]: 20 * 60, [CHAIN_IDs.WORLD_CHAIN]: 20 * 60, [CHAIN_IDs.ZK_SYNC]: 60 * 60, diff --git a/src/common/ContractAddresses.ts b/src/common/ContractAddresses.ts index d35214d83b..a9ece8737f 100644 --- a/src/common/ContractAddresses.ts +++ b/src/common/ContractAddresses.ts @@ -318,6 +318,14 @@ export const CONTRACT_ADDRESSES: { address: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000", }, }, + [CHAIN_IDs.SOLANA]: { + cctpTokenMessenger: { + address: "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", + }, + cctpMessageTransmitter: { + address: "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd", + }, + }, [CHAIN_IDs.SONEIUM]: { opUSDCBridge: { address: "0x8be79275FCfD08A931087ECf70Ba8a99aee3AC59", From ec65a9d51f70235692324389cee275f704065214 Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 13 May 2025 12:56:51 -0500 Subject: [PATCH 04/20] update with new bridge type Signed-off-by: bennett --- src/adapter/bridges/SolanaUsdcCCTPBridge.ts | 15 ++++++--------- src/adapter/bridges/index.ts | 2 +- src/common/Constants.ts | 2 +- src/utils/SDKUtils.ts | 1 + 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts index d7d4ed8361..adfb4d719f 100644 --- a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts +++ b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts @@ -1,5 +1,4 @@ import { Contract, Signer } from "ethers"; -import { l2SignerOrProvider } from "../../common"; import { BridgeTransactionDetails, BaseBridgeAdapter, BridgeEvents } from "./BaseBridgeAdapter"; import { BigNumber, @@ -14,13 +13,13 @@ import { SvmAddress, paginatedEventQuery, ZERO_BYTES, + SVMProvider, } from "../../utils"; import { processEvent } from "../utils"; import { getCctpTokenMessenger, isCctpV2L2ChainId } from "../../utils/CCTPUtils"; import { CCTP_NO_DOMAIN } from "@across-protocol/constants"; import { arch } from "@across-protocol/sdk"; import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; -import { Rpc, SolanaRpcApi } from "@solana/kit"; export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { private CCTP_MAX_SEND_AMOUNT = toBN(1_000_000_000_000); // 1MM USDC. @@ -32,10 +31,8 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { private readonly solanaEventsClientPromise: Promise; private solanaEventsClient: arch.svm.SvmCpiEventsClient; - constructor(l2chainId: number, hubChainId: number, l1Signer: Signer, l2SignerOrProvider: l2SignerOrProvider) { - super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [ - EvmAddress.from(getCctpTokenMessenger(l2chainId, hubChainId).address), - ]); + constructor(l2chainId: number, hubChainId: number, l1Signer: Signer, l2Provider: SVMProvider) { + super(l2chainId, hubChainId, l1Signer, [EvmAddress.from(getCctpTokenMessenger(l2chainId, hubChainId).address)]); assert( getCctpDomainForChainId(l2chainId) !== CCTP_NO_DOMAIN && getCctpDomainForChainId(hubChainId) !== CCTP_NO_DOMAIN, "Unknown CCTP domain ID" @@ -48,7 +45,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { const { address: l2Address } = getCctpTokenMessenger(l2chainId, l2chainId); this.solanaMessageTransmitter = SvmAddress.from(l2Address); this.solanaEventsClientPromise = arch.svm.SvmCpiEventsClient.createFor( - l2SignerOrProvider as Rpc, + l2Provider, l2Address, TokenMessengerMinterIdl ); @@ -118,14 +115,14 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { // Lazily evaluate the events client. this.solanaEventsClient ??= await this.solanaEventsClientPromise; assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); - const l2FinalizationEvents = await this.solanaEventsClient.queryEvents( + const l2FinalizationEvents = await this.solanaEventsClient.queryDerivedAddressEvents( "mintAndWithdraw", this.solanaMessageTransmitter.toV2Address(), BigInt(eventConfig.from), BigInt(eventConfig.to) ); return { - [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "amount")), // TODO + [this.resolveL2TokenAddress(l1Token)]: [], }; } } diff --git a/src/adapter/bridges/index.ts b/src/adapter/bridges/index.ts index ad779f54b8..fe3dbab06e 100644 --- a/src/adapter/bridges/index.ts +++ b/src/adapter/bridges/index.ts @@ -18,4 +18,4 @@ export * from "./UsdcCCTPBridge"; export * from "./ZKStackBridge"; export * from "./ZKStackUSDCBridge"; export * from "./ZKStackWethBridge"; -export * from "./SolanaCCTPUsdcBridge"; +export * from "./SolanaUsdcCCTPBridge"; diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 020405ddf5..e91f453795 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -41,7 +41,6 @@ import { BinanceCEXBridge as L2BinanceCEXBridge, } from "../adapter/l2Bridges"; import { CONTRACT_ADDRESSES } from "./ContractAddresses"; -import { Rpc, SolanaRpcApi } from "@solana/kit"; /** * Note: When adding new chains, it's preferred to retain alphabetical ordering of CHAIN_IDs in Object mappings. @@ -404,6 +403,7 @@ export const CANONICAL_BRIDGE: Record Date: Mon, 19 May 2025 11:29:54 -0500 Subject: [PATCH 05/20] svm Signed-off-by: bennett --- src/common/ClientHelper.ts | 2 ++ src/utils/BlockUtils.ts | 24 ++++++++++++++++-------- src/utils/ProviderUtils.ts | 21 ++++++++++++++++++++- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index b4e84d9c4b..c0e79c9c7a 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -13,6 +13,7 @@ import { isDefined, getRedisCache, getArweaveJWKSigner, + getBlockFinder, } from "../utils"; import { HubPoolClient, MultiCallerClient, ConfigStoreClient, SpokePoolClient } from "../clients"; import { CommonConfig } from "./Config"; @@ -128,6 +129,7 @@ export async function constructSpokePoolClientsWithLookback( if (chainId === hubPoolChainId) { return [chainId, fromBlock_1]; } else { + const blockFinder = await getBlockFinder(chainId); return [chainId, await getBlockForTimestamp(chainId, lookback, blockFinder, redis)]; } }) diff --git a/src/utils/BlockUtils.ts b/src/utils/BlockUtils.ts index 28d4a670e8..ebc0054369 100644 --- a/src/utils/BlockUtils.ts +++ b/src/utils/BlockUtils.ts @@ -1,11 +1,12 @@ import { interfaces, utils } from "@across-protocol/sdk"; import { isDefined } from "./"; -import { BlockFinderHints, EVMBlockFinder } from "./SDKUtils"; -import { getProvider } from "./ProviderUtils"; +import { BlockFinderHints, EVMBlockFinder, SVMBlockFinder, chainIsEvm } from "./SDKUtils"; +import { getProvider, getSvmProvider } from "./ProviderUtils"; import { getRedisCache } from "./RedisUtils"; import { SpokePoolClientsByChain } from "../interfaces/SpokePool"; const evmBlockFinders: { [chainId: number]: EVMBlockFinder } = {}; +let svmBlockFinder: SVMBlockFinder; /** * @notice Return block finder for chain. Loads from in memory blockFinder cache if this function was called before @@ -13,12 +14,19 @@ const evmBlockFinders: { [chainId: number]: EVMBlockFinder } = {}; * @param chainId * @returns */ -export async function getBlockFinder(chainId: number): Promise { - if (!isDefined(evmBlockFinders[chainId])) { - const providerForChain = await getProvider(chainId); - evmBlockFinders[chainId] = new EVMBlockFinder(providerForChain); +export async function getBlockFinder(chainId: number): Promise> { + if (chainIsEvm(chainId)) { + if (!isDefined(evmBlockFinders[chainId])) { + const providerForChain = await getProvider(chainId); + evmBlockFinders[chainId] = new EVMBlockFinder(providerForChain); + } + return evmBlockFinders[chainId]; } - return evmBlockFinders[chainId]; + const provider = await getSvmProvider(); + if (!isDefined(svmBlockFinder)) { + svmBlockFinder = new SVMBlockFinder(provider); + } + return svmBlockFinder; } /** @@ -33,7 +41,7 @@ export async function getBlockFinder(chainId: number): Promise { export async function getBlockForTimestamp( chainId: number, timestamp: number, - blockFinder?: EVMBlockFinder, + blockFinder?: utils.BlockFinder, redisCache?: interfaces.CachingMechanismInterface, hints: BlockFinderHints = {} ): Promise { diff --git a/src/utils/ProviderUtils.ts b/src/utils/ProviderUtils.ts index b504cdabfa..4356c408d5 100644 --- a/src/utils/ProviderUtils.ts +++ b/src/utils/ProviderUtils.ts @@ -3,7 +3,7 @@ import { providers as sdkProviders } from "@across-protocol/sdk"; import { ethers } from "ethers"; import winston from "winston"; import { CHAIN_CACHE_FOLLOW_DISTANCE, DEFAULT_NO_TTL_DISTANCE } from "../common"; -import { delay, getOriginFromURL, Logger } from "./"; +import { delay, getOriginFromURL, Logger, SVMProvider } from "./"; import { getRedisCache } from "./RedisUtils"; import { isDefined } from "./TypeGuards"; import * as viem from "viem"; @@ -222,6 +222,25 @@ export async function getProvider( return provider; } +/** + * @notice Returns a cached SVMProvider. + */ +export async function getSvmProvider(): Promise { + const nodeUrlList = getNodeUrlList(MAINNET_CHAIN_IDs.SOLANA); + const redis = await getRedisCache(); + const namespace = process.env["NODE_PROVIDER_CACHE_NAMESPACE"] ?? "default_svm_provider"; + const providerFactory = new sdkProviders.CachedSolanaRpcFactory( + namespace, + redis, + 10, + 0, + undefined, + Object.values(nodeUrlList)[0], + MAINNET_CHAIN_IDs.SOLANA + ); + return providerFactory.createRpcClient(); +} + /** * @notice Returns a Viem custom transport that can be used to create a Viem client from our customized Ethers * provider. This allows us to send requests through our RetryProvider that need to be handled by Viem SDK's. From bc3acf49c165098919c8299b903bcc6ef0daca4d Mon Sep 17 00:00:00 2001 From: bennett Date: Mon, 19 May 2025 12:29:45 -0500 Subject: [PATCH 06/20] block finder Signed-off-by: bennett --- src/utils/BlockUtils.ts | 2 +- src/utils/ProviderUtils.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/BlockUtils.ts b/src/utils/BlockUtils.ts index ebc0054369..52e5f948f9 100644 --- a/src/utils/BlockUtils.ts +++ b/src/utils/BlockUtils.ts @@ -22,7 +22,7 @@ export async function getBlockFinder(chainId: number): Promise { +export function getSvmProvider(): SVMProvider { const nodeUrlList = getNodeUrlList(MAINNET_CHAIN_IDs.SOLANA); - const redis = await getRedisCache(); const namespace = process.env["NODE_PROVIDER_CACHE_NAMESPACE"] ?? "default_svm_provider"; const providerFactory = new sdkProviders.CachedSolanaRpcFactory( namespace, - redis, + undefined, 10, 0, undefined, From 2af255f6e927d755196b6a5a219846f621b3b28b Mon Sep 17 00:00:00 2001 From: bennett Date: Mon, 19 May 2025 13:34:01 -0500 Subject: [PATCH 07/20] wip Signed-off-by: bennett --- src/clients/index.ts | 2 +- src/common/ClientHelper.ts | 65 ++++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/clients/index.ts b/src/clients/index.ts index 376241e05d..05f14b0eef 100644 --- a/src/clients/index.ts +++ b/src/clients/index.ts @@ -2,7 +2,7 @@ import { clients } from "@across-protocol/sdk"; export type SpokePoolClient = clients.EVMSpokePoolClient; export type SpokePoolUpdate = clients.SpokePoolUpdate; -export const { EVMSpokePoolClient: SpokePoolClient } = clients; +export const { EVMSpokePoolClient: SpokePoolClient, SvmSpokePoolClient } = clients; export { IndexedSpokePoolClient, SpokePoolClientMessage } from "./SpokePoolClient"; export class BundleDataClient extends clients.BundleDataClient.BundleDataClient {} diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index c0e79c9c7a..5f4e1fd6d6 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -14,8 +14,13 @@ import { getRedisCache, getArweaveJWKSigner, getBlockFinder, + chainIsEvm, + SvmAddress, + getSvmProvider, + getDeployedAddress, + forEachAsync, } from "../utils"; -import { HubPoolClient, MultiCallerClient, ConfigStoreClient, SpokePoolClient } from "../clients"; +import { HubPoolClient, MultiCallerClient, ConfigStoreClient, SpokePoolClient, SvmSpokePoolClient } from "../clients"; import { CommonConfig } from "./Config"; import { SpokePoolClientsByChain } from "../interfaces"; import { caching, clients, arch } from "@across-protocol/sdk"; @@ -206,16 +211,23 @@ export async function constructSpokePoolClientsWithStartBlocks( const spokePoolSigners = await getSpokePoolSigners(baseSigner, enabledChains); const spokePools = await Promise.all( enabledChains.map(async (chainId) => { - const spokePoolAddr = hubPoolClient.getSpokePoolForBlock(chainId, toBlockOverride[1]); - // TODO: initialize using typechain factory after V3.5 migration. - // const spokePoolContract = SpokePool.connect(spokePoolAddr, spokePoolSigners[chainId]); - const spokePoolContract = new ethers.Contract( - spokePoolAddr, - [...SpokePool.abi, ...V3_SPOKE_POOL_ABI], - spokePoolSigners[chainId] - ); const registrationBlock = await resolveSpokePoolActivationBlock(chainId, hubPoolClient, toBlockOverride[1]); - return { chainId, contract: spokePoolContract, registrationBlock }; + if (chainIsEvm(chainId)) { + const spokePoolAddr = hubPoolClient.getSpokePoolForBlock(chainId, toBlockOverride[1]); + // TODO: initialize using typechain factory after V3.5 migration. + // const spokePoolContract = SpokePool.connect(spokePoolAddr, spokePoolSigners[chainId]); + const spokePoolContract = new ethers.Contract( + spokePoolAddr, + [...SpokePool.abi, ...V3_SPOKE_POOL_ABI], + spokePoolSigners[chainId] + ); + return { chainId, contract: spokePoolContract, registrationBlock }; + } else { + // The hub pool client can only return the truncated address of the SVM spoke pool, so if the chain is non-evm, then fallback + // to the definitions in the contracts repository. + const spokePoolAddr = getDeployedAddress("SpokePool", chainId); + return { chainId, contract: SvmAddress.from(spokePoolAddr), registrationBlock }; + } }) ); @@ -249,7 +261,7 @@ export async function constructSpokePoolClientsWithStartBlocks( * @param toBlocks Mapping of chainId to toBlocks per chain to set in SpokePoolClients. * @returns Mapping of chainId to SpokePoolClient */ -export function getSpokePoolClientsForContract( +export async function getSpokePoolClientsForContract( logger: winston.Logger, hubPoolClient: HubPoolClient, config: CommonConfig, @@ -265,7 +277,9 @@ export function getSpokePoolClientsForContract( }); const spokePoolClients: SpokePoolClientsByChain = {}; - spokePools.forEach(({ chainId, contract, registrationBlock }) => { + forEachAsync( + spokePools, + async ({ chainId, contract, registrationBlock }) => { if (!isDefined(fromBlocks[chainId])) { logger.debug({ at: "ClientHelper#getSpokePoolClientsForContract", @@ -284,14 +298,25 @@ export function getSpokePoolClientsForContract( to: toBlocks[chainId], maxLookBack: config.maxBlockLookBack[chainId], }; - spokePoolClients[chainId] = new SpokePoolClient( - logger, - contract, - hubPoolClient, - chainId, - registrationBlock, - spokePoolClientSearchSettings - ); + if (chainIsEvm(chainId)) { + spokePoolClients[chainId] = new SpokePoolClient( + logger, + contract as Contract, + hubPoolClient, + chainId, + registrationBlock, + spokePoolClientSearchSettings + ); + } else { + spokePoolClients[chainId] = await SvmSpokePoolClient.create( + logger, + hubPoolClient, + chainId, + BigInt(registrationBlock), + spokePoolClientSearchSettings, + getSvmProvider() + ); + } }); return spokePoolClients; From 90b147eb1314a87b02175f974067d105bb8b8dad Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 20 May 2025 14:53:49 -0500 Subject: [PATCH 08/20] improve: Lax assumption of EVMSpokePoolClients Signed-off-by: bennett --- package.json | 2 +- src/adapter/BaseChainAdapter.ts | 5 +- src/clients/InventoryClient.ts | 19 +++-- src/clients/ProfitClient.ts | 20 +++-- src/clients/TokenClient.ts | 45 +++++++---- src/clients/bridges/AdapterManager.ts | 19 +++-- src/clients/index.ts | 5 +- src/common/ClientHelper.ts | 56 ++++++++++---- src/dataworker/Dataworker.ts | 66 ++++++++-------- src/finalizer/index.ts | 12 +-- src/finalizer/utils/arbStack.ts | 3 + src/finalizer/utils/binance.ts | 3 + src/finalizer/utils/cctp/l1ToL2.ts | 2 + src/finalizer/utils/helios.ts | 28 ++++--- src/finalizer/utils/linea/l1ToL2.ts | 3 + src/finalizer/utils/linea/l2ToL1.ts | 3 + src/finalizer/utils/opStack.ts | 6 +- src/finalizer/utils/scroll.ts | 3 + src/finalizer/utils/zkSync.ts | 3 + src/monitor/Monitor.ts | 102 ++++++++++++++----------- src/monitor/MonitorClientHelper.ts | 10 ++- src/relayer/Relayer.ts | 82 ++++++++++++-------- src/scripts/validateRunningBalances.ts | 4 +- src/utils/BlockUtils.ts | 8 +- src/utils/ProviderUtils.ts | 20 ++++- src/utils/SDKUtils.ts | 1 + yarn.lock | 8 +- 27 files changed, 348 insertions(+), 190 deletions(-) diff --git a/package.json b/package.json index fe43b3e559..f2ffd77335 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.66", "@across-protocol/contracts": "^4.0.9", - "@across-protocol/sdk": "4.2.1", + "@across-protocol/sdk": "4.2.2", "@arbitrum/sdk": "^4.0.2", "@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 bc3592fd29..27dc809faa 100644 --- a/src/adapter/BaseChainAdapter.ts +++ b/src/adapter/BaseChainAdapter.ts @@ -31,6 +31,7 @@ import { getWrappedNativeTokenAddress, stringifyThrownValue, ZERO_BYTES, + isEVMSpokePoolClient, } from "../utils"; import { AugmentedTransaction, TransactionClient } from "../clients/TransactionClient"; import { approveTokens, getTokenAllowanceFromCache, aboveAllowanceThreshold, setTokenAllowanceInCache } from "./utils"; @@ -77,7 +78,9 @@ export class BaseChainAdapter { } protected getSigner(chainId: number): Signer { - return this.spokePoolClients[chainId].spokePool.signer; + const spokePoolClient = this.spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return spokePoolClient.spokePool.signer; } // Note: this must be called after the SpokePoolClients are updated. diff --git a/src/clients/InventoryClient.ts b/src/clients/InventoryClient.ts index 3c86a0e6ad..3ba354effd 100644 --- a/src/clients/InventoryClient.ts +++ b/src/clients/InventoryClient.ts @@ -29,6 +29,7 @@ import { depositForcesOriginChainRepayment, getRemoteTokenForL1Token, getTokenInfo, + isEVMSpokePoolClient, } from "../utils"; import { HubPoolClient, TokenClient, BundleDataClient } from "."; import { Deposit, ProposedRootBundle } from "../interfaces"; @@ -1119,12 +1120,14 @@ export class InventoryClient { // This filters out all nulls, which removes any chains that are meant to be ignored. .filter(isDefined) // This map adds the ETH balance to the object. - .map(async (chainInfo) => ({ - ...chainInfo, - balance: await this.tokenClient.spokePoolClients[chainInfo.chainId].spokePool.provider.getBalance( - this.relayer - ), - })) + .map(async (chainInfo) => { + const spokePoolClient = this.tokenClient.spokePoolClients[chainInfo.chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return { + ...chainInfo, + balance: await spokePoolClient.spokePool.provider.getBalance(this.relayer), + }; + }) ); this.log("Checking WETH unwrap thresholds for chains with thresholds set", { chains }); @@ -1455,7 +1458,9 @@ export class InventoryClient { } _unwrapWeth(chainId: number, _l2Weth: string, amount: BigNumber): Promise { - const l2Signer = this.tokenClient.spokePoolClients[chainId].spokePool.signer; + const spokePoolClient = this.tokenClient.spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + const l2Signer = spokePoolClient.spokePool.signer; const l2Weth = new Contract(_l2Weth, WETH_ABI, l2Signer); this.log("Unwrapping WETH", { amount: amount.toString() }); return runTransaction(this.logger, l2Weth, "withdraw", [amount]); diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index 705fc263a2..9f4ae7d94a 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -32,6 +32,9 @@ import { getRemoteTokenForL1Token, getTokenInfo, dedupArray, + SVMProvider, + isEVMSpokePoolClient, + isSVMSpokePoolClient, } from "../utils"; import { Deposit, DepositWithBlock, L1Token, SpokePoolClientsByChain } from "../interfaces"; import { getAcrossHost } from "./AcrossAPIClient"; @@ -142,10 +145,14 @@ export class ProfitClient { ]); for (const chainId of this.enabledChainIds) { - this.relayerFeeQueries[chainId] = this.constructRelayerFeeQuery( - chainId, - spokePoolClients[chainId].spokePool.provider - ); + const spokePoolClient = spokePoolClients[chainId]; + let provider; + if (isEVMSpokePoolClient(spokePoolClient)) { + provider = spokePoolClient.spokePool.provider; + } else if (isSVMSpokePoolClient(spokePoolClient)) { + provider = spokePoolClient.svmEventsClient.rpc; + } + this.relayerFeeQueries[chainId] = this.constructRelayerFeeQuery(chainId, provider); } this.isTestnet = this.hubPoolClient.chainId !== CHAIN_IDs.MAINNET; @@ -713,7 +720,10 @@ export class ProfitClient { return dedupArray([...hubPoolTokens, ...additionalL1Tokens]); } - private constructRelayerFeeQuery(chainId: number, provider: Provider): relayFeeCalculator.QueryInterface { + private constructRelayerFeeQuery( + chainId: number, + provider: Provider | SVMProvider + ): relayFeeCalculator.QueryInterface { // Fallback to Coingecko's free API for now. // TODO: Add support for Coingecko Pro. const coingeckoProApiKey = undefined; diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index d27edd781f..61c00f0936 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -21,6 +21,8 @@ import { TOKEN_SYMBOLS_MAP, getRemoteTokenForL1Token, getTokenInfo, + isEVMSpokePoolClient, + assert, } from "../utils"; export type TokenDataType = { [chainId: number]: { [token: string]: { balance: BigNumber; allowance: BigNumber } } }; @@ -146,13 +148,16 @@ export class TokenClient { let mrkdwn = "*Approval transactions:* \n"; for (const { token, chainId } of tokensToApprove) { - const targetSpokePool = this.spokePoolClients[chainId].spokePool; - const contract = new Contract(token, ERC20.abi, targetSpokePool.signer); - const tx = await runTransaction(this.logger, contract, "approve", [targetSpokePool.address, MAX_UINT_VAL]); - mrkdwn += - ` - Approved SpokePool ${blockExplorerLink(targetSpokePool.address, chainId)} ` + - `to spend ${await contract.symbol()} ${blockExplorerLink(token, chainId)} on ${getNetworkName(chainId)}. ` + - `tx: ${blockExplorerLink(tx.hash, chainId)}\n`; + const targetSpokePoolClient = this.spokePoolClients[chainId]; + if (isEVMSpokePoolClient(targetSpokePoolClient)) { + const targetSpokePool = targetSpokePoolClient.spokePool; + const contract = new Contract(token, ERC20.abi, targetSpokePool.signer); + const tx = await runTransaction(this.logger, contract, "approve", [targetSpokePool.address, MAX_UINT_VAL]); + mrkdwn += + ` - Approved SpokePool ${blockExplorerLink(targetSpokePool.address, chainId)} ` + + `to spend ${await contract.symbol()} ${blockExplorerLink(token, chainId)} on ${getNetworkName(chainId)}. ` + + `tx: ${blockExplorerLink(tx.hash, chainId)}\n`; + } } this.logger.info({ at: "TokenBalanceClient", message: "Approved whitelisted tokens! 💰", mrkdwn }); } @@ -178,7 +183,9 @@ export class TokenClient { } resolveRemoteTokens(chainId: number, hubPoolTokens: L1Token[]): Contract[] { - const { signer } = this.spokePoolClients[chainId].spokePool; + const spokePoolClient = this.spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + const { signer } = spokePoolClient.spokePool; if (chainId === this.hubPoolClient.chainId) { return hubPoolTokens.map(({ address }) => new Contract(address, ERC20.abi, signer)); @@ -215,9 +222,10 @@ export class TokenClient { chainId: number, hubPoolTokens: L1Token[] ): Promise> { - const { spokePool } = this.spokePoolClients[chainId]; + const spokePoolClient = this.spokePoolClients[chainId]; - const multicall3 = sdkUtils.getMulticall3(chainId, spokePool.provider); + assert(isEVMSpokePoolClient(spokePoolClient)); + const multicall3 = sdkUtils.getMulticall3(chainId, spokePoolClient.spokePool.provider); if (!isDefined(multicall3)) { return this.fetchTokenData(chainId, hubPoolTokens); } @@ -227,7 +235,11 @@ export class TokenClient { const allowances: sdkUtils.Call3[] = []; this.resolveRemoteTokens(chainId, hubPoolTokens).forEach((token) => { balances.push({ contract: token, method: "balanceOf", args: [relayerAddress] }); - allowances.push({ contract: token, method: "allowance", args: [relayerAddress, spokePool.address] }); + allowances.push({ + contract: token, + method: "allowance", + args: [relayerAddress, spokePoolClient.spokePoolAddress.toEvmAddress()], + }); }); const calls = [...balances, ...allowances]; @@ -299,8 +311,10 @@ export class TokenClient { } private _getAllowanceCacheKey(spokePoolClient: SpokePoolClient, originToken: string): string { - const { chainId, spokePool } = spokePoolClient; - return `l2TokenAllowance_${chainId}_${originToken}_${this.relayerAddress}_targetContract:${spokePool.address}`; + const { chainId } = spokePoolClient; + return `l2TokenAllowance_${chainId}_${originToken}_${ + this.relayerAddress + }_targetContract:${spokePoolClient.spokePoolAddress.toEvmAddress()}`; } private async _getAllowance(spokePoolClient: SpokePoolClient, token: Contract): Promise { @@ -312,7 +326,10 @@ export class TokenClient { return toBN(result); } } - const allowance: BigNumber = await token.allowance(this.relayerAddress, spokePoolClient.spokePool.address); + const allowance: BigNumber = await token.allowance( + this.relayerAddress, + spokePoolClient.spokePoolAddress.toEvmAddress() + ); if (allowance.gte(MAX_SAFE_ALLOWANCE) && redis) { // Save allowance in cache with no TTL as these should be exhausted. await redis.set(key, MAX_SAFE_ALLOWANCE); diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index 335d9c5aed..dde43b314f 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -23,6 +23,7 @@ import { TOKEN_EQUIVALENCE_REMAPPING, getRemoteTokenForL1Token, getTokenInfo, + isEVMSpokePoolClient, } from "../../utils"; import { SpokePoolClient, HubPoolClient } from "../"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; @@ -47,7 +48,7 @@ export class AdapterManager { if (!spokePoolClients) { return; } - const spokePoolAddresses = Object.values(spokePoolClients).map((client) => client.spokePool.address); + const spokePoolAddresses = Object.values(spokePoolClients).map((client) => client.spokePoolAddress.toEvmAddress()); // The adapters are only set up to monitor EOA's and the HubPool and SpokePool address, so remove // spoke pool addresses from other chains. @@ -55,20 +56,22 @@ export class AdapterManager { return monitoredAddresses.filter( (address) => this.hubPoolClient.hubPool.address === address || - this.spokePoolClients[chainId].spokePool.address === address || + this.spokePoolClients[chainId].spokePoolAddress.toEvmAddress() === address || !spokePoolAddresses.includes(address) ); }; const hubChainId = hubPoolClient.chainId; - const l1Signer = spokePoolClients[hubChainId].spokePool.signer; + const l1Signer = hubPoolClient.hubPool.signer; const constructBridges = (chainId: number) => { if (chainId === hubChainId) { return {}; } // Special case for the EthereumAdapter return Object.fromEntries( SUPPORTED_TOKENS[chainId]?.map((symbol) => { - const l2Signer = spokePoolClients[chainId].spokePool.signer; + const spokePoolClient = spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + const l2Signer = spokePoolClient.spokePool.signer; const l1Token = TOKEN_SYMBOLS_MAP[symbol].addresses[hubChainId]; const bridgeConstructor = CUSTOM_BRIDGE[chainId]?.[l1Token] ?? CANONICAL_BRIDGE[chainId]; const bridge = new bridgeConstructor(chainId, hubChainId, l1Signer, l2Signer, EvmAddress.from(l1Token)); @@ -80,7 +83,9 @@ export class AdapterManager { if (chainId === hubChainId) { return {}; } - const l2Signer = spokePoolClients[chainId].spokePool.signer; + const spokePoolClient = spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + const l2Signer = spokePoolClient.spokePool.signer; return Object.fromEntries( SUPPORTED_TOKENS[chainId] ?.map((symbol) => { @@ -231,7 +236,9 @@ export class AdapterManager { } getSigner(chainId: number): Signer { - return this.spokePoolClients[chainId].spokePool.signer; + const spokePoolClient = this.spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return spokePoolClient.spokePool.signer; } l2TokenForL1Token(l1Token: string, chainId: number): string { diff --git a/src/clients/index.ts b/src/clients/index.ts index 376241e05d..a085ad25ca 100644 --- a/src/clients/index.ts +++ b/src/clients/index.ts @@ -1,8 +1,9 @@ import { clients } from "@across-protocol/sdk"; -export type SpokePoolClient = clients.EVMSpokePoolClient; +export type SpokePoolClient = clients.SpokePoolClient; +export type EVMSpokePoolClient = clients.EVMSpokePoolClient; export type SpokePoolUpdate = clients.SpokePoolUpdate; -export const { EVMSpokePoolClient: SpokePoolClient } = clients; +export const { EVMSpokePoolClient, SpokePoolClient, SVMSpokePoolClient } = clients; export { IndexedSpokePoolClient, SpokePoolClientMessage } from "./SpokePoolClient"; export class BundleDataClient extends clients.BundleDataClient.BundleDataClient {} diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index b4e84d9c4b..7a4a735627 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -13,8 +13,19 @@ import { isDefined, getRedisCache, getArweaveJWKSigner, + chainIsEvm, + forEachAsync, + isEVMSpokePoolClient, + getSvmProvider, } from "../utils"; -import { HubPoolClient, MultiCallerClient, ConfigStoreClient, SpokePoolClient } from "../clients"; +import { + HubPoolClient, + MultiCallerClient, + ConfigStoreClient, + EVMSpokePoolClient, + SVMSpokePoolClient, + SpokePoolClient, +} from "../clients"; import { CommonConfig } from "./Config"; import { SpokePoolClientsByChain } from "../interfaces"; import { caching, clients, arch } from "@across-protocol/sdk"; @@ -247,14 +258,14 @@ export async function constructSpokePoolClientsWithStartBlocks( * @param toBlocks Mapping of chainId to toBlocks per chain to set in SpokePoolClients. * @returns Mapping of chainId to SpokePoolClient */ -export function getSpokePoolClientsForContract( +export async function getSpokePoolClientsForContract( logger: winston.Logger, hubPoolClient: HubPoolClient, config: CommonConfig, spokePools: { chainId: number; contract: Contract; registrationBlock: number }[], fromBlocks: { [chainId: number]: number }, toBlocks: { [chainId: number]: number } -): SpokePoolClientsByChain { +): Promise { logger.debug({ at: "ClientHelper#getSpokePoolClientsForContract", message: "Constructing SpokePoolClients", @@ -263,7 +274,7 @@ export function getSpokePoolClientsForContract( }); const spokePoolClients: SpokePoolClientsByChain = {}; - spokePools.forEach(({ chainId, contract, registrationBlock }) => { + await forEachAsync(spokePools, async ({ chainId, contract, registrationBlock }) => { if (!isDefined(fromBlocks[chainId])) { logger.debug({ at: "ClientHelper#getSpokePoolClientsForContract", @@ -282,14 +293,25 @@ export function getSpokePoolClientsForContract( to: toBlocks[chainId], maxLookBack: config.maxBlockLookBack[chainId], }; - spokePoolClients[chainId] = new SpokePoolClient( - logger, - contract, - hubPoolClient, - chainId, - registrationBlock, - spokePoolClientSearchSettings - ); + if (chainIsEvm(chainId)) { + spokePoolClients[chainId] = new EVMSpokePoolClient( + logger, + contract, + hubPoolClient, + chainId, + registrationBlock, + spokePoolClientSearchSettings + ); + } else { + spokePoolClients[chainId] = await SVMSpokePoolClient.create( + logger, + hubPoolClient, + chainId, + BigInt(registrationBlock), + spokePoolClientSearchSettings, + getSvmProvider() + ); + } }); return spokePoolClients; @@ -374,10 +396,12 @@ export function spokePoolClientsToProviders(spokePoolClients: { [chainId: number } { return Object.fromEntries( Object.entries(spokePoolClients) - .map(([chainId, client]): [number, ethers.providers.Provider] => [ - Number(chainId), - client.spokePool.signer.provider, - ]) + .map(([chainId, client]): [number, ethers.providers.Provider] => { + if (isEVMSpokePoolClient(client)) { + return [Number(chainId), client.spokePool.signer.provider]; + } + return [Number(chainId), undefined]; + }) .filter(([, provider]) => !!provider) ); } diff --git a/src/dataworker/Dataworker.ts b/src/dataworker/Dataworker.ts index 3281050cc9..66aa25e1bd 100644 --- a/src/dataworker/Dataworker.ts +++ b/src/dataworker/Dataworker.ts @@ -20,6 +20,8 @@ import { _buildPoolRebalanceRoot, ERC20, getTokenInfo, + isEVMSpokePoolClient, + isSVMSpokePoolClient, } from "../utils"; import { ProposedRootBundle, @@ -1254,7 +1256,7 @@ export class Dataworker { const success = await balanceAllocator.requestBalanceAllocation( destinationChainId, l2TokensToCountTowardsSpokePoolLeafExecutionCapital(outputToken, destinationChainId), - client.spokePool.address, + client.spokePoolAddress.toEvmAddress(), amountRequired ); @@ -1273,7 +1275,7 @@ export class Dataworker { balanceAllocator, destinationChainId, outputToken, - client.spokePool.address + client.spokePoolAddress.toEvmAddress() ), }); } @@ -1300,6 +1302,7 @@ export class Dataworker { `amount: ${outputAmount.toString()}`; if (submitExecution) { + assert(isEVMSpokePoolClient(client)); const { method, args } = this.encodeSlowFillLeaf(slowRelayTree, rootBundleId, leaf); this.clients.multiCallerClient.enqueueTransaction({ @@ -1742,7 +1745,7 @@ export class Dataworker { balanceAllocator.addUsed( leaf.chainId, leaf.l1Tokens[i], - spokePoolClients[leaf.chainId].spokePool.address, + spokePoolClients[leaf.chainId].spokePoolAddress.toEvmAddress(), amount.mul(-1) ); } @@ -2224,6 +2227,9 @@ export class Dataworker { if (leaf.chainId !== chainId) { throw new Error("Leaf chainId does not match input chainId"); } + if (!isEVMSpokePoolClient(client)) { + throw new Error("Dataworker does not support non-evm chains"); + } const symbol = this.getTokenInfo(leaf.l2TokenAddress, chainId); // @dev check if there's been a duplicate leaf execution and if so, then exit early. // Since this function is happening near the end of the dataworker run and leaf executions are @@ -2256,7 +2262,7 @@ export class Dataworker { { chainId: leaf.chainId, tokens: l2TokensToCountTowardsSpokePoolLeafExecutionCapital(leaf.l2TokenAddress, leaf.chainId), - holder: client.spokePool.address, + holder: client.spokePoolAddress.toEvmAddress(), amount: totalSent, }, ]; @@ -2265,7 +2271,7 @@ export class Dataworker { // If we have to pass ETH via the payable function, then we need to add a balance request for the signer // to ensure that it has enough ETH to send. // NOTE: this is ETH required separately from the amount required to send the tokens - if (isDefined(valueToPassViaPayable)) { + if (isDefined(valueToPassViaPayable) && isEVMSpokePoolClient(client)) { balanceRequestsToQuery.push({ chainId: leaf.chainId, tokens: [ZERO_ADDRESS], // ZERO_ADDRESS is used to represent ETH. @@ -2289,16 +2295,9 @@ export class Dataworker { balanceAllocator, leaf.chainId, leaf.l2TokenAddress, - client.spokePool.address + client.spokePoolAddress.toEvmAddress() ), requiredEthValue: valueToPassViaPayable, - senderEthValue: - valueToPassViaPayable && - (await balanceAllocator.getBalanceSubUsed( - leaf.chainId, - ZERO_ADDRESS, - await client.spokePool.signer.getAddress() - )), }); } else { // If mainnet leaf, then allocate balance to the HubPool since it will be atomically transferred. @@ -2324,22 +2323,26 @@ export class Dataworker { leaf.leafId }\nchainId: ${chainId}\ntoken: ${symbol}\namount: ${leaf.amountToReturn.toString()}`; if (submitExecution) { - const valueToPassViaPayable = getMsgValue(leaf); - this.clients.multiCallerClient.enqueueTransaction({ - value: valueToPassViaPayable, - contract: client.spokePool, - chainId: Number(chainId), - method: "executeRelayerRefundLeaf", - args: [rootBundleId, leaf, relayerRefundTree.getHexProof(leaf)], - message: "Executed RelayerRefundLeaf 🌿!", - mrkdwn, - // If mainnet, send through Multicall3 so it can be batched with PoolRebalanceLeaf executions, otherwise - // SpokePool.multicall() is fine. - unpermissioned: Number(chainId) === this.clients.hubPoolClient.chainId, - // If simulating mainnet execution, can fail as it may require funds to be sent from - // pool rebalance leaf. - canFailInSimulation: leaf.chainId === this.clients.hubPoolClient.chainId, - }); + if (isEVMSpokePoolClient(client)) { + const valueToPassViaPayable = getMsgValue(leaf); + this.clients.multiCallerClient.enqueueTransaction({ + value: valueToPassViaPayable, + contract: client.spokePool, + chainId: Number(chainId), + method: "executeRelayerRefundLeaf", + args: [rootBundleId, leaf, relayerRefundTree.getHexProof(leaf)], + message: "Executed RelayerRefundLeaf 🌿!", + mrkdwn, + // If mainnet, send through Multicall3 so it can be batched with PoolRebalanceLeaf executions, otherwise + // SpokePool.multicall() is fine. + unpermissioned: Number(chainId) === this.clients.hubPoolClient.chainId, + // If simulating mainnet execution, can fail as it may require funds to be sent from + // pool rebalance leaf. + canFailInSimulation: leaf.chainId === this.clients.hubPoolClient.chainId, + }); + } else if (isSVMSpokePoolClient(client)) { + throw new Error("Not implemented"); + } } else { this.logger.debug({ at: "Dataworker#_executeRelayerRefundLeaves", message: mrkdwn }); } @@ -2521,7 +2524,10 @@ export class Dataworker { */ _getRequiredEthForLineaRelayLeafExecution(client: SpokePoolClient): Promise { // You should *only* call this method on Linea chains. - assert(sdkUtils.chainIsLinea(client.chainId), "This method should only be called on Linea chains!"); + assert( + sdkUtils.chainIsLinea(client.chainId) && isEVMSpokePoolClient(client), + "This method should only be called on Linea chains!" + ); // Resolve and sanitize the L2MessageService contract ABI and address. const l2MessageABI = CONTRACT_ADDRESSES[client.chainId]?.l2MessageService?.abi; const l2MessageAddress = CONTRACT_ADDRESSES[client.chainId]?.l2MessageService?.address; diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index 90c75e75d9..cb74b9a9c2 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -31,6 +31,7 @@ import { CHAIN_IDs, Profiler, stringifyThrownValue, + isEVMSpokePoolClient, } from "../utils"; import { ChainFinalizer, CrossChainMessage, isAugmentedTransaction } from "./types"; import { @@ -240,7 +241,7 @@ export async function finalize( : []; const addressesToFinalize = [ hubPoolClient.hubPool.address, - spokePoolClients[chainId].spokePool.address, + spokePoolClients[chainId].spokePoolAddress.toEvmAddress(), CONTRACT_ADDRESSES[hubChainId]?.atomicDepositor?.address, ...userSpecifiedAddresses, ].map(getAddress); @@ -292,10 +293,11 @@ export async function finalize( // since any L2 -> L1 transfers will be finalized on the hub chain. hubChainId, ...configuredChainIds, - ]).map( - async (chainId) => - [chainId, await getMultisender(chainId, spokePoolClients[chainId].spokePool.signer)] as [number, Contract] - ) + ]).map(async (chainId) => { + const spokePoolClient = spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return [chainId, await getMultisender(chainId, spokePoolClient.spokePool.signer)] as [number, Contract]; + }) ) ); // Assert that no multicall2Lookup is undefined diff --git a/src/finalizer/utils/arbStack.ts b/src/finalizer/utils/arbStack.ts index c6b9c78ef1..a404f0f5b3 100644 --- a/src/finalizer/utils/arbStack.ts +++ b/src/finalizer/utils/arbStack.ts @@ -25,6 +25,8 @@ import { getL2TokenAddresses, getNativeTokenSymbol, getTokenInfo, + assert, + isEVMSpokePoolClient, } from "../../utils"; import { TokensBridged } from "../../interfaces"; import { HubPoolClient, SpokePoolClient } from "../../clients"; @@ -81,6 +83,7 @@ export async function arbStackFinalizer( _l1SpokePoolClient: SpokePoolClient, recipientAddresses: string[] ): Promise { + assert(isEVMSpokePoolClient(spokePoolClient)); LATEST_MAINNET_BLOCK = hubPoolClient.latestHeightSearched; const hubPoolProvider = await getProvider(hubPoolClient.chainId, logger); MAINNET_BLOCK_TIME = (await arch.evm.averageBlockTime(hubPoolProvider)).average; diff --git a/src/finalizer/utils/binance.ts b/src/finalizer/utils/binance.ts index 419c89f64f..c701ccc9dc 100644 --- a/src/finalizer/utils/binance.ts +++ b/src/finalizer/utils/binance.ts @@ -14,6 +14,8 @@ import { getTokenInfo, ethers, groupObjectCountsByProp, + isEVMSpokePoolClient, + assert, } from "../../utils"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { FinalizerPromise } from "../types"; @@ -81,6 +83,7 @@ export async function binanceFinalizer( l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { + assert(isEVMSpokePoolClient(l1SpokePoolClient) && isEVMSpokePoolClient(l2SpokePoolClient)); const chainId = l2SpokePoolClient.chainId; const hubChainId = l1SpokePoolClient.chainId; const l1EventSearchConfig = l1SpokePoolClient.eventSearchConfig; diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index 2533b02d12..ea756c0b78 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -12,6 +12,7 @@ import { Multicall2Call, winston, convertFromWei, + isEVMSpokePoolClient, } from "../../../utils"; import { AttestedCCTPDepositEvent, @@ -29,6 +30,7 @@ export async function cctpL1toL2Finalizer( l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { + assert(isEVMSpokePoolClient(l1SpokePoolClient) && isEVMSpokePoolClient(l2SpokePoolClient)); const searchConfig: EventSearchConfig = { from: l1SpokePoolClient.eventSearchConfig.from, to: l1SpokePoolClient.latestHeightSearched, diff --git a/src/finalizer/utils/helios.ts b/src/finalizer/utils/helios.ts index fb988ad30c..ddbd451804 100644 --- a/src/finalizer/utils/helios.ts +++ b/src/finalizer/utils/helios.ts @@ -1,4 +1,4 @@ -import { HubPoolClient, SpokePoolClient, AugmentedTransaction } from "../../clients"; +import { HubPoolClient, SpokePoolClient, AugmentedTransaction, EVMSpokePoolClient } from "../../clients"; import { EventSearchConfig, Signer, @@ -8,6 +8,8 @@ import { ethers, BigNumber, groupObjectCountsByProp, + isEVMSpokePoolClient, + assert, } from "../../utils"; import { spreadEventWithBlockNumber } from "../../utils/EventUtils"; import { FinalizerPromise, CrossChainMessage } from "../types"; @@ -62,6 +64,10 @@ export async function heliosL1toL2Finalizer( // eslint-disable-next-line @typescript-eslint/no-unused-vars _senderAddresses: string[] ): Promise { + assert( + isEVMSpokePoolClient(l2SpokePoolClient) && isEVMSpokePoolClient(l1SpokePoolClient), + "Cannot use helios finalizer on non-evm chains" + ); const l1ChainId = hubPoolClient.chainId; const l2ChainId = l2SpokePoolClient.chainId; const { sp1HeliosHead, sp1HeliosHeader } = await getSp1HeliosHeadData(l2SpokePoolClient); @@ -117,7 +123,7 @@ interface Sp1HeliosHeadData { sp1HeliosHeader: string; } -async function getSp1HeliosHeadData(l2SpokePoolClient: SpokePoolClient): Promise { +async function getSp1HeliosHeadData(l2SpokePoolClient: EVMSpokePoolClient): Promise { const sp1HeliosL2 = getSp1HeliosContract(l2SpokePoolClient.chainId, l2SpokePoolClient.spokePool.provider); const sp1HeliosHeadBn: ethers.BigNumber = await sp1HeliosL2.head(); const sp1HeliosHead = sp1HeliosHeadBn.toNumber(); @@ -135,8 +141,8 @@ async function getSp1HeliosHeadData(l2SpokePoolClient: SpokePoolClient): Promise async function identifyRequiredActions( logger: winston.Logger, hubPoolClient: HubPoolClient, - l1SpokePoolClient: SpokePoolClient, - l2SpokePoolClient: SpokePoolClient, + l1SpokePoolClient: EVMSpokePoolClient, + l2SpokePoolClient: EVMSpokePoolClient, l1ChainId: number, l2ChainId: number ): Promise { @@ -214,7 +220,7 @@ async function identifyRequiredActions( async function shouldGenerateKeepAliveAction( logger: winston.Logger, - l2SpokePoolClient: SpokePoolClient, + l2SpokePoolClient: EVMSpokePoolClient, l2ChainId: number ): Promise { const twentyFourHoursInSeconds = 24 * 60 * 60; // 24 hours @@ -264,8 +270,8 @@ async function shouldGenerateKeepAliveAction( async function enrichHeliosActions( logger: winston.Logger, actions: HeliosAction[], - l2SpokePoolClient: SpokePoolClient, - l1SpokePoolClient: SpokePoolClient, + l2SpokePoolClient: EVMSpokePoolClient, + l1SpokePoolClient: EVMSpokePoolClient, currentL2HeliosHeadNumber: number, currentL2HeliosHeader: string ): Promise { @@ -394,7 +400,7 @@ async function enrichHeliosActions( async function getRelevantL1Events( _logger: winston.Logger, hubPoolClient: HubPoolClient, - l1SpokePoolClient: SpokePoolClient, + l1SpokePoolClient: EVMSpokePoolClient, l1ChainId: number, _l2ChainId: number, l2SpokePoolAddress: string @@ -431,7 +437,7 @@ async function getRelevantL1Events( /** Query L2 Verification Events and return verified slots map */ async function getL2VerifiedSlotsMap( - l2SpokePoolClient: SpokePoolClient, + l2SpokePoolClient: EVMSpokePoolClient, l2ChainId: number ): Promise> { const l2Provider = l2SpokePoolClient.spokePool.provider; @@ -468,7 +474,7 @@ async function getL2VerifiedSlotsMap( } /** --- Query L2 Execution Events (RelayedCallData) */ -async function getL2RelayedNonces(l2SpokePoolClient: SpokePoolClient): Promise> { +async function getL2RelayedNonces(l2SpokePoolClient: EVMSpokePoolClient): Promise> { const l2Provider = l2SpokePoolClient.spokePool.provider; const l2SpokePoolAddress = l2SpokePoolClient.spokePool.address; const universalSpokePoolContract = new ethers.Contract(l2SpokePoolAddress, UNIVERSAL_SPOKE_ABI, l2Provider); @@ -540,7 +546,7 @@ async function generateTxnsForHeliosActions( readyActions: HeliosAction[], l1ChainId: number, l2ChainId: number, - l2SpokePoolClient: SpokePoolClient + l2SpokePoolClient: EVMSpokePoolClient ): Promise { const transactions: AugmentedTransaction[] = []; const crossChainMessages: CrossChainMessage[] = []; diff --git a/src/finalizer/utils/linea/l1ToL2.ts b/src/finalizer/utils/linea/l1ToL2.ts index f0fbf2691f..7f3d5cf348 100644 --- a/src/finalizer/utils/linea/l1ToL2.ts +++ b/src/finalizer/utils/linea/l1ToL2.ts @@ -13,6 +13,8 @@ import { ethers, BigNumber, getTokenInfo, + assert, + isEVMSpokePoolClient, } from "../../../utils"; import { CrossChainMessage, FinalizerPromise } from "../../types"; import { @@ -50,6 +52,7 @@ export async function lineaL1ToL2Finalizer( l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { + assert(isEVMSpokePoolClient(l1SpokePoolClient) && isEVMSpokePoolClient(l2SpokePoolClient)); const [l1ChainId] = [hubPoolClient.chainId, hubPoolClient.hubPool.address]; if (l1ChainId !== CHAIN_IDs.MAINNET) { throw new Error("Finalizations for Linea testnet is not supported."); diff --git a/src/finalizer/utils/linea/l2ToL1.ts b/src/finalizer/utils/linea/l2ToL1.ts index ded401f257..e023d88ce3 100644 --- a/src/finalizer/utils/linea/l2ToL1.ts +++ b/src/finalizer/utils/linea/l2ToL1.ts @@ -16,6 +16,8 @@ import { BigNumber, TOKEN_SYMBOLS_MAP, getTokenInfo, + assert, + isEVMSpokePoolClient, } from "../../../utils"; import { FinalizerPromise, CrossChainMessage } from "../../types"; import { TokensBridged } from "../../../interfaces"; @@ -162,6 +164,7 @@ export async function lineaL2ToL1Finalizer( hubPoolClient: HubPoolClient, spokePoolClient: SpokePoolClient ): Promise { + assert(isEVMSpokePoolClient(spokePoolClient)); const [l1ChainId, l2ChainId] = [hubPoolClient.chainId, spokePoolClient.chainId]; const lineaSdk = initLineaSdk(l1ChainId, l2ChainId); const l2Contract = lineaSdk.getL2Contract(); diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 19f337928a..db705c93a6 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -40,6 +40,7 @@ import { getTokenInfo, compareAddressesSimple, getCctpDomainForChainId, + isEVMSpokePoolClient, } from "../../utils"; import { CONTRACT_ADDRESSES, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; import OPStackPortalL1 from "../../common/abi/OpStackPortalL1.json"; @@ -101,6 +102,7 @@ export async function opStackFinalizer( _l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { + assert(isEVMSpokePoolClient(spokePoolClient)); const { chainId } = spokePoolClient; assert(chainIsOPStack(chainId), `Unsupported OP Stack chain ID: ${chainId}`); const networkName = getNetworkName(chainId); @@ -160,7 +162,7 @@ export async function opStackFinalizer( await paginatedEventQuery( ovmStandardBridge, ovmStandardBridge.filters.ETHBridgeInitiated( - senderAddresses.filter((sender) => sender !== spokePoolClient.spokePool.address) // from, filter out + senderAddresses.filter((sender) => sender !== spokePoolClient.spokePoolAddress.toEvmAddress()) // from, filter out // SpokePool as sender since we query for it previously using the TokensBridged event query. ), { @@ -180,7 +182,7 @@ export async function opStackFinalizer( ovmStandardBridge.filters.ERC20BridgeInitiated( null, // localToken null, // remoteToken - senderAddresses.filter((sender) => sender !== spokePoolClient.spokePool.address) // from, filter out + senderAddresses.filter((sender) => sender !== spokePoolClient.spokePoolAddress.toEvmAddress()) // from, filter out // SpokePool as sender since we query for it previously using the TokensBridged event query. ), { diff --git a/src/finalizer/utils/scroll.ts b/src/finalizer/utils/scroll.ts index 9475908fb2..1da8885183 100644 --- a/src/finalizer/utils/scroll.ts +++ b/src/finalizer/utils/scroll.ts @@ -13,6 +13,8 @@ import { winston, convertFromWei, getTokenInfo, + assert, + isEVMSpokePoolClient, } from "../../utils"; import { FinalizerPromise, CrossChainMessage } from "../types"; @@ -46,6 +48,7 @@ export async function scrollFinalizer( hubPoolClient: HubPoolClient, spokePoolClient: SpokePoolClient ): Promise { + assert(isEVMSpokePoolClient(spokePoolClient)); const [l1ChainId, l2ChainId, targetAddress] = [ hubPoolClient.chainId, spokePoolClient.chainId, diff --git a/src/finalizer/utils/zkSync.ts b/src/finalizer/utils/zkSync.ts index 7783595df7..3ba843c34b 100644 --- a/src/finalizer/utils/zkSync.ts +++ b/src/finalizer/utils/zkSync.ts @@ -14,6 +14,8 @@ import { Multicall2Call, winston, zkSync as zkSyncUtils, + assert, + isEVMSpokePoolClient, } from "../../utils"; import { FinalizerPromise, CrossChainMessage } from "../types"; @@ -36,6 +38,7 @@ export async function zkSyncFinalizer( hubPoolClient: HubPoolClient, spokePoolClient: SpokePoolClient ): Promise { + assert(isEVMSpokePoolClient(spokePoolClient)); const { chainId: l1ChainId } = hubPoolClient; const { chainId: l2ChainId } = spokePoolClient; diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index 6fbd14aa23..023fec1e08 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -45,6 +45,8 @@ import { getTokenInfo, ConvertDecimals, getL1TokenAddress, + isEVMSpokePoolClient, + isSVMSpokePoolClient, } from "../utils"; import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; import { MonitorConfig } from "./MonitorConfig"; @@ -458,13 +460,7 @@ export class Monitor { if (trippedThreshold !== null) { const gasTokenAddressForChain = getNativeTokenAddressForChain(chainId); const symbol = - token === gasTokenAddressForChain - ? getNativeTokenSymbol(chainId) - : await new Contract( - token, - ERC20.abi, - this.clients.spokePoolClients[chainId].spokePool.provider - ).symbol(); + token === gasTokenAddressForChain ? getNativeTokenSymbol(chainId) : getTokenInfo(token, chainId).symbol; return { level: trippedThreshold.level, text: ` ${getNetworkName(chainId)} ${symbol} balance for ${blockExplorerLink( @@ -531,16 +527,18 @@ export class Monitor { signerAddress, deficit ); + const spokePoolClient = this.clients.spokePoolClients[chainId]; // If token is gas token, try unwrapping deficit amount of WETH into ETH to have available for refill. if ( !canRefill && token === getNativeTokenAddressForChain(chainId) && - getNativeTokenSymbol(chainId) === "ETH" + getNativeTokenSymbol(chainId) === "ETH" && + isEVMSpokePoolClient(spokePoolClient) ) { const weth = new Contract( TOKEN_SYMBOLS_MAP.WETH.addresses[chainId], WETH9.abi, - this.clients.spokePoolClients[chainId].spokePool.signer + spokePoolClient.spokePool.signer ); const wethBalance = await weth.balanceOf(signerAddress); if (wethBalance.gte(deficit)) { @@ -569,7 +567,7 @@ export class Monitor { return; } } - if (canRefill) { + if (canRefill && isEVMSpokePoolClient(spokePoolClient)) { this.logger.debug({ at: "Monitor#refillBalances", message: "Balance below trigger and can refill to target", @@ -598,10 +596,10 @@ export class Monitor { }); } else { // Note: We don't multicall sending ETH as its not a contract call. - const gas = await getGasPrice(this.clients.spokePoolClients[chainId].spokePool.provider); + const gas = await getGasPrice(spokePoolClient.spokePool.provider); const nativeSymbolForChain = getNativeTokenSymbol(chainId); const tx = await ( - await this.clients.spokePoolClients[chainId].spokePool.signer + await spokePoolClient.spokePool.signer ).sendTransaction({ to: account, value: deficit, ...gas }); const receipt = await tx.wait(); this.logger.info({ @@ -744,7 +742,7 @@ export class Monitor { this.clients.bundleDataClient.getApproximateRefundsForBlockRange(enabledChainIds, blockRangeTail), Object.fromEntries( await mapAsync(chainIds, async (chainId) => { - const spokePool = this.clients.spokePoolClients[chainId].spokePool.address; + const spokePool = this.clients.spokePoolClients[chainId].spokePoolAddress.toEvmAddress(); const l2TokenAddresses = monitoredTokenSymbols .map((symbol) => l2TokenForChain(chainId, symbol)) .filter(isDefined); @@ -974,7 +972,7 @@ export class Monitor { }); } - const spokePoolAddress = this.clients.spokePoolClients[chainId].spokePool.address; + const spokePoolAddress = this.clients.spokePoolClients[chainId].spokePoolAddress.toEvmAddress(); for (const l1Token of allL1Tokens) { // Outstanding transfers are mapped to either the spoke pool or the hub pool, depending on which // chain events are queried. Some only allow us to index on the fromAddress, the L1 originator or the @@ -1199,14 +1197,26 @@ export class Monitor { private async computeSpokePoolsBlocks() { for (const chainId of this.monitorChains) { - const { startingBlock, endingBlock } = await this.computeStartingAndEndingBlock( - this.clients.spokePoolClients[chainId].spokePool.provider, - this.monitorConfig.spokePoolsBlocks[chainId]?.startingBlock, - this.monitorConfig.spokePoolsBlocks[chainId]?.endingBlock - ); - - this.spokePoolsBlocks[chainId].startingBlock = startingBlock; - this.spokePoolsBlocks[chainId].endingBlock = endingBlock; + const spokePoolClient = this.clients.spokePoolClients[chainId]; + if (isEVMSpokePoolClient(spokePoolClient)) { + const { startingBlock, endingBlock } = await this.computeStartingAndEndingBlock( + spokePoolClient.spokePool.provider, + this.monitorConfig.spokePoolsBlocks[chainId]?.startingBlock, + this.monitorConfig.spokePoolsBlocks[chainId]?.endingBlock + ); + this.spokePoolsBlocks[chainId].startingBlock = startingBlock; + this.spokePoolsBlocks[chainId].endingBlock = endingBlock; + } else if (isSVMSpokePoolClient(spokePoolClient)) { + const latestSlot = Number(await spokePoolClient.svmEventsClient.rpc.getSlot().send()); + const endingBlock = this.monitorConfig.spokePoolsBlocks[chainId]?.endingBlock; + this.monitorConfig.spokePoolsBlocks[chainId] ??= { startingBlock: undefined, endingBlock: undefined }; + if (this.monitorConfig.pollingDelay === 0) { + this.monitorConfig.spokePoolsBlocks[chainId].startingBlock ??= latestSlot; + } else { + this.monitorConfig.spokePoolsBlocks[chainId].startingBlock = endingBlock; + } + this.monitorConfig.spokePoolsBlocks[chainId].endingBlock = latestSlot; + } } } @@ -1248,26 +1258,30 @@ export class Monitor { if (this.balanceCache[chainId]?.[token]?.[account]) { return this.balanceCache[chainId][token][account]; } - const gasTokenAddressForChain = getNativeTokenAddressForChain(chainId); - const balance = - token === gasTokenAddressForChain - ? await this.clients.spokePoolClients[chainId].spokePool.provider.getBalance(account) - : // Use the latest block number the SpokePoolClient is aware of to query balances. - // This prevents double counting when there are very recent refund leaf executions that the SpokePoolClients - // missed (the provider node did not see those events yet) but when the balanceOf calls are made, the node - // is now aware of those executions. - await new Contract(token, ERC20.abi, this.clients.spokePoolClients[chainId].spokePool.provider).balanceOf( - account, - { blockTag: this.clients.spokePoolClients[chainId].latestHeightSearched } - ); - if (!this.balanceCache[chainId]) { - this.balanceCache[chainId] = {}; - } - if (!this.balanceCache[chainId][token]) { - this.balanceCache[chainId][token] = {}; + const spokePoolClient = this.clients.spokePoolClients[chainId]; + if (isEVMSpokePoolClient(spokePoolClient)) { + const gasTokenAddressForChain = getNativeTokenAddressForChain(chainId); + const balance = + token === gasTokenAddressForChain + ? await spokePoolClient.spokePool.provider.getBalance(account) + : // Use the latest block number the SpokePoolClient is aware of to query balances. + // This prevents double counting when there are very recent refund leaf executions that the SpokePoolClients + // missed (the provider node did not see those events yet) but when the balanceOf calls are made, the node + // is now aware of those executions. + await new Contract(token, ERC20.abi, spokePoolClient.spokePool.provider).balanceOf(account, { + blockTag: spokePoolClient.latestHeightSearched, + }); + if (!this.balanceCache[chainId]) { + this.balanceCache[chainId] = {}; + } + if (!this.balanceCache[chainId][token]) { + this.balanceCache[chainId][token] = {}; + } + this.balanceCache[chainId][token][account] = balance; + return balance; } - this.balanceCache[chainId][token][account] = balance; - return balance; + // @todo _getBalances is unimplemented for SVM. + return bnZero; }) ); } @@ -1282,11 +1296,7 @@ export class Monitor { if (this.decimals[chainId]?.[token]) { return this.decimals[chainId][token]; } - const decimals: number = await new Contract( - token, - ERC20.abi, - this.clients.spokePoolClients[chainId].spokePool.provider - ).decimals(); + const { decimals } = getTokenInfo(token, chainId); if (!this.decimals[chainId]) { this.decimals[chainId] = {}; } diff --git a/src/monitor/MonitorClientHelper.ts b/src/monitor/MonitorClientHelper.ts index fbcc6d94d0..07ccf299ed 100644 --- a/src/monitor/MonitorClientHelper.ts +++ b/src/monitor/MonitorClientHelper.ts @@ -1,5 +1,5 @@ import { MonitorConfig } from "./MonitorConfig"; -import { Signer, winston } from "../utils"; +import { Signer, winston, assert, isEVMSpokePoolClient } from "../utils"; import { BundleDataClient, HubPoolClient, TokenTransferClient } from "../clients"; import { Clients, @@ -50,7 +50,7 @@ export async function constructMonitorClients( ); // Need to update HubPoolClient to get latest tokens. - const spokePoolAddresses = Object.values(spokePoolClients).map((client) => client.spokePool.address); + const spokePoolAddresses = Object.values(spokePoolClients).map((client) => client.spokePoolAddress.toEvmAddress()); // Cross-chain transfers will originate from the HubPool's address and target SpokePool addresses, so // track both. @@ -61,7 +61,11 @@ export async function constructMonitorClients( ]); const spokePoolChains = Object.keys(spokePoolClients).map((chainId) => Number(chainId)); const providerPerChain = Object.fromEntries( - spokePoolChains.map((chainId) => [chainId, spokePoolClients[chainId].spokePool.provider]) + spokePoolChains.map((chainId) => { + const spokePoolClient = spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return [chainId, spokePoolClient.spokePool.provider]; + }) ); const tokenTransferClient = new TokenTransferClient(logger, providerPerChain, config.monitoredRelayers); diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index c222455379..ea15c3faf4 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -22,6 +22,8 @@ import { formatGwei, toBytes32, depositForcesOriginChainRepayment, + isEVMSpokePoolClient, + isSVMSpokePoolClient, } from "../utils"; import { RelayerClients } from "./RelayerClientHelper"; import { RelayerConfig } from "./RelayerConfig"; @@ -710,7 +712,14 @@ export class Relayer { const minFillTime = this.config.minFillTime?.[destinationChainId] ?? 0; if (minFillTime > 0 && deposit.exclusiveRelayer !== this.relayerAddress) { const originSpoke = spokePoolClients[originChainId]; - const { average: avgBlockTime } = await arch.evm.averageBlockTime(originSpoke.spokePool.provider); + let avgBlockTime; + if (isEVMSpokePoolClient(originSpoke)) { + const { average } = await arch.evm.averageBlockTime(originSpoke.spokePool.provider); + avgBlockTime = average; + } else if (isSVMSpokePoolClient(originSpoke)) { + const { average } = await arch.svm.averageBlockTime(); + avgBlockTime = average; + } const depositAge = Math.floor(avgBlockTime * (originSpoke.latestHeightSearched - deposit.blockNumber)); if (minFillTime > depositAge) { @@ -1029,15 +1038,16 @@ export class Relayer { }; this.logger.debug({ at: "Relayer::requestSlowFill", message: "Enqueuing slow fill request.", deposit }); - multiCallerClient.enqueueTransaction({ - chainId: destinationChainId, - contract: spokePoolClient.spokePool, - method: "requestSlowFill", - args: [convertRelayDataParamsToBytes32(deposit)], - message: "Requested slow fill for deposit.", - mrkdwn: formatSlowFillRequestMarkdown(), - }); - + if (isEVMSpokePoolClient(spokePoolClient)) { + multiCallerClient.enqueueTransaction({ + chainId: destinationChainId, + contract: spokePoolClient.spokePool, + method: "requestSlowFill", + args: [convertRelayDataParamsToBytes32(deposit)], + message: "Requested slow fill for deposit.", + mrkdwn: formatSlowFillRequestMarkdown(), + }); + } this.setFillStatus(deposit, FillStatus.RequestedSlowFill); } @@ -1072,28 +1082,36 @@ export class Relayer { realizedLpFeePct, }); - const [method, messageModifier, args] = !isDepositSpedUp(deposit) - ? ["fillRelay", "", [convertRelayDataParamsToBytes32(deposit), repaymentChainId, toBytes32(this.relayerAddress)]] - : [ - "fillRelayWithUpdatedDeposit", - " with updated parameters ", - [ - convertRelayDataParamsToBytes32(deposit), - repaymentChainId, - toBytes32(this.relayerAddress), - deposit.updatedOutputAmount, - toBytes32(deposit.updatedRecipient), - deposit.updatedMessage, - deposit.speedUpSignature, - ], - ]; - - const message = `Filled v3 deposit ${messageModifier}🚀`; - const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct, gasPrice); - const contract = spokePoolClients[deposit.destinationChainId].spokePool; - const chainId = deposit.destinationChainId; - const multiCallerClient = this.getMulticaller(chainId); - multiCallerClient.enqueueTransaction({ contract, chainId, method, args, gasLimit, message, mrkdwn }); + const spokePoolClient = spokePoolClients[deposit.destinationChainId]; + if (isEVMSpokePoolClient(spokePoolClient)) { + const [method, messageModifier, args] = !isDepositSpedUp(deposit) + ? [ + "fillRelay", + "", + [convertRelayDataParamsToBytes32(deposit), repaymentChainId, toBytes32(this.relayerAddress)], + ] + : [ + "fillRelayWithUpdatedDeposit", + " with updated parameters ", + [ + convertRelayDataParamsToBytes32(deposit), + repaymentChainId, + toBytes32(this.relayerAddress), + deposit.updatedOutputAmount, + toBytes32(deposit.updatedRecipient), + deposit.updatedMessage, + deposit.speedUpSignature, + ], + ]; + + const message = `Filled v3 deposit ${messageModifier}🚀`; + const mrkdwn = this.constructRelayFilledMrkdwn(deposit, repaymentChainId, realizedLpFeePct, gasPrice); + + const contract = spokePoolClient.spokePool; + const chainId = deposit.destinationChainId; + const multiCallerClient = this.getMulticaller(chainId); + multiCallerClient.enqueueTransaction({ contract, chainId, method, args, gasLimit, message, mrkdwn }); + } this.setFillStatus(deposit, FillStatus.Filled); } diff --git a/src/scripts/validateRunningBalances.ts b/src/scripts/validateRunningBalances.ts index ab0549490d..bb692edaef 100644 --- a/src/scripts/validateRunningBalances.ts +++ b/src/scripts/validateRunningBalances.ts @@ -179,7 +179,7 @@ export async function runScript(baseSigner: Signer): Promise { ]; logs.push(`- Bundle end block for chain: ${bundleEndBlockForChain.toNumber()}`); let tokenBalanceAtBundleEndBlock = await l2TokenContract.balanceOf( - spokePoolClients[leaf.chainId].spokePool.address, + spokePoolClients[leaf.chainId].spokePoolAddress.toEvmAddress(), { blockTag: bundleEndBlockForChain.toNumber(), } @@ -244,7 +244,7 @@ export async function runScript(baseSigner: Signer): Promise { previousPoolRebalanceLeaf.netSendAmounts[previousPoolRebalanceLeaf.l1Tokens.indexOf(l1Token)]; logs.push(`- Previous net send amount: ${fromWei(previousNetSendAmount.toString(), decimals)}`); if (previousNetSendAmount.gt(bnZero)) { - const spokePoolAddress = spokePoolClients[leaf.chainId].spokePool.address; + const spokePoolAddress = spokePoolClients[leaf.chainId].spokePoolAddress.toEvmAddress(); let depositsToSpokePool: Log[]; // Handle the case that L1-->L2 deposits for some chains for ETH do not emit Transfer events, but // emit other events instead. This is the case for OpStack chains which emit DepositFinalized events diff --git a/src/utils/BlockUtils.ts b/src/utils/BlockUtils.ts index 28d4a670e8..fa093a21aa 100644 --- a/src/utils/BlockUtils.ts +++ b/src/utils/BlockUtils.ts @@ -1,6 +1,6 @@ import { interfaces, utils } from "@across-protocol/sdk"; import { isDefined } from "./"; -import { BlockFinderHints, EVMBlockFinder } from "./SDKUtils"; +import { BlockFinderHints, EVMBlockFinder, isEVMSpokePoolClient, isSVMSpokePoolClient } from "./SDKUtils"; import { getProvider } from "./ProviderUtils"; import { getRedisCache } from "./RedisUtils"; import { SpokePoolClientsByChain } from "../interfaces/SpokePool"; @@ -55,7 +55,11 @@ export async function getTimestampsForBundleEndBlocks( if (spokePoolClient === undefined) { return; } - return [chainId, (await spokePoolClient.spokePool.getCurrentTime({ blockTag: endBlock })).toNumber()]; + if (isEVMSpokePoolClient(spokePoolClient)) { + return [chainId, (await spokePoolClient.spokePool.getCurrentTime({ blockTag: endBlock })).toNumber()]; + } else if (isSVMSpokePoolClient(spokePoolClient)) { + return [chainId, Number(await spokePoolClient.svmEventsClient.rpc.getBlockTime(BigInt(endBlock)).send())]; + } }) ).filter(isDefined) ); diff --git a/src/utils/ProviderUtils.ts b/src/utils/ProviderUtils.ts index b504cdabfa..876544a828 100644 --- a/src/utils/ProviderUtils.ts +++ b/src/utils/ProviderUtils.ts @@ -3,7 +3,7 @@ import { providers as sdkProviders } from "@across-protocol/sdk"; import { ethers } from "ethers"; import winston from "winston"; import { CHAIN_CACHE_FOLLOW_DISTANCE, DEFAULT_NO_TTL_DISTANCE } from "../common"; -import { delay, getOriginFromURL, Logger } from "./"; +import { delay, getOriginFromURL, Logger, SVMProvider } from "./"; import { getRedisCache } from "./RedisUtils"; import { isDefined } from "./TypeGuards"; import * as viem from "viem"; @@ -259,6 +259,24 @@ export function getWSProviders(chainId: number, quorum?: number): ethers.provide return Object.values(urls).map((url) => new ethers.providers.WebSocketProvider(url)); } +/** + * @notice Returns a cached SVMProvider. + */ +export function getSvmProvider(): SVMProvider { + const nodeUrlList = getNodeUrlList(MAINNET_CHAIN_IDs.SOLANA); + const namespace = process.env["NODE_PROVIDER_CACHE_NAMESPACE"] ?? "default_svm_provider"; + const providerFactory = new sdkProviders.CachedSolanaRpcFactory( + namespace, + undefined, + 10, + 0, + undefined, + Object.values(nodeUrlList)[0], + MAINNET_CHAIN_IDs.SOLANA + ); + return providerFactory.createRpcClient(); +} + export function getNodeUrlList( chainId: number, quorum = 1, diff --git a/src/utils/SDKUtils.ts b/src/utils/SDKUtils.ts index 154d0af93d..72bb2d3ef8 100644 --- a/src/utils/SDKUtils.ts +++ b/src/utils/SDKUtils.ts @@ -9,6 +9,7 @@ export const addressAdapters = sdk.addressAggregator.adapters; export class PriceClient extends sdk.priceClient.PriceClient {} export const { acrossApi, coingecko, defiLlama } = sdk.priceClient.adapters; +export const { isEVMSpokePoolClient, isSVMSpokePoolClient } = sdk.clients; export class Address extends sdk.utils.Address {} export class EvmAddress extends sdk.utils.EvmAddress {} diff --git a/yarn.lock b/yarn.lock index fbe128e74e..481e8eacfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -59,10 +59,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.2.1.tgz#a8e578d15e87a19b7565e33d57ee7b54ccec0cdb" - integrity sha512-D25ING133oEgbUJZ9NVxLLAxW6iOqP7K9a3fKx+zZwjxw9wnhcywSv59bhSEXqrS5/+3f+5KZqFU1R+qQNf4JA== +"@across-protocol/sdk@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.2.2.tgz#ff3d2711c7125daaefc3a5fe083ddb42f092f0ff" + integrity sha512-to0sr8h3YP9IDJ0uPG0RgryHYMbAMxkv77gw2YlNoxcozP773HB2KG+AhOz3rjFlRqmNTA2Tcr8K0/dA5RW0lQ== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.66" From d15ad938736f17e2135c31bbfcee1b398ea6fa47 Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 20 May 2025 16:53:16 -0500 Subject: [PATCH 09/20] tests Signed-off-by: bennett --- package.json | 2 +- src/clients/ProfitClient.ts | 2 +- src/monitor/Monitor.ts | 2 +- src/utils/BlockUtils.ts | 5 ++++- test/ProfitClient.ConsiderProfitability.ts | 4 ++-- test/Relayer.BasicFill.ts | 12 +++++++--- test/Relayer.SlowFill.ts | 5 +++-- test/Relayer.TokenShortfall.ts | 13 ++++++++--- test/Relayer.UnfilledDeposits.ts | 13 ++++++++--- test/TokenClient.Approval.ts | 6 ++--- test/TokenClient.BalanceAlowance.ts | 6 ++--- test/TokenClient.TokenShortfall.ts | 6 ++--- test/fixtures/Dataworker.Fixture.ts | 2 +- .../AdapterManager.SendTokensCrossChain.ts | 22 +++++++++++-------- test/generic-adapters/Arbitrum.ts | 6 ++--- test/generic-adapters/Linea.ts | 6 ++--- test/generic-adapters/OpStack.ts | 6 ++--- test/generic-adapters/Polygon.ts | 6 ++--- test/generic-adapters/Scroll.ts | 6 ++--- test/generic-adapters/zkSync.ts | 8 +++---- yarn.lock | 8 +++---- 21 files changed, 87 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index f2ffd77335..3f54ac752f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "@across-protocol/constants": "^3.1.66", "@across-protocol/contracts": "^4.0.9", - "@across-protocol/sdk": "4.2.2", + "@across-protocol/sdk": "4.2.3", "@arbitrum/sdk": "^4.0.2", "@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 9f4ae7d94a..e76e94be51 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -150,7 +150,7 @@ export class ProfitClient { if (isEVMSpokePoolClient(spokePoolClient)) { provider = spokePoolClient.spokePool.provider; } else if (isSVMSpokePoolClient(spokePoolClient)) { - provider = spokePoolClient.svmEventsClient.rpc; + provider = spokePoolClient.svmEventsClient.getRpc(); } this.relayerFeeQueries[chainId] = this.constructRelayerFeeQuery(chainId, provider); } diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index 023fec1e08..4ae76fa95a 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -1207,7 +1207,7 @@ export class Monitor { this.spokePoolsBlocks[chainId].startingBlock = startingBlock; this.spokePoolsBlocks[chainId].endingBlock = endingBlock; } else if (isSVMSpokePoolClient(spokePoolClient)) { - const latestSlot = Number(await spokePoolClient.svmEventsClient.rpc.getSlot().send()); + const latestSlot = Number(await spokePoolClient.svmEventsClient.getRpc().getSlot().send()); const endingBlock = this.monitorConfig.spokePoolsBlocks[chainId]?.endingBlock; this.monitorConfig.spokePoolsBlocks[chainId] ??= { startingBlock: undefined, endingBlock: undefined }; if (this.monitorConfig.pollingDelay === 0) { diff --git a/src/utils/BlockUtils.ts b/src/utils/BlockUtils.ts index fa093a21aa..4251f277e9 100644 --- a/src/utils/BlockUtils.ts +++ b/src/utils/BlockUtils.ts @@ -58,7 +58,10 @@ export async function getTimestampsForBundleEndBlocks( if (isEVMSpokePoolClient(spokePoolClient)) { return [chainId, (await spokePoolClient.spokePool.getCurrentTime({ blockTag: endBlock })).toNumber()]; } else if (isSVMSpokePoolClient(spokePoolClient)) { - return [chainId, Number(await spokePoolClient.svmEventsClient.rpc.getBlockTime(BigInt(endBlock)).send())]; + return [ + chainId, + Number(await spokePoolClient.svmEventsClient.getRpc().getBlockTime(BigInt(endBlock)).send()), + ]; } }) ).filter(isDefined) diff --git a/test/ProfitClient.ConsiderProfitability.ts b/test/ProfitClient.ConsiderProfitability.ts index fa643b7a4d..d01379d5e5 100644 --- a/test/ProfitClient.ConsiderProfitability.ts +++ b/test/ProfitClient.ConsiderProfitability.ts @@ -1,7 +1,7 @@ import { assert } from "chai"; import { random } from "lodash"; import { constants as sdkConstants, utils as sdkUtils } from "@across-protocol/sdk"; -import { ConfigStoreClient, FillProfit, SpokePoolClient } from "../src/clients"; +import { ConfigStoreClient, FillProfit, EVMSpokePoolClient } from "../src/clients"; import { Deposit } from "../src/interfaces"; import { bnZero, @@ -154,7 +154,7 @@ describe("ProfitClient: Consider relay profit", () => { spokeChainId, chainIds[(idx + 1) % 2] // @dev Only works for 2 chainIds. ); - const spokePoolClient = new SpokePoolClient( + const spokePoolClient = new EVMSpokePoolClient( spyLogger, spokePool.connect(owner), null, diff --git a/test/Relayer.BasicFill.ts b/test/Relayer.BasicFill.ts index 9bd40083ed..f486785086 100644 --- a/test/Relayer.BasicFill.ts +++ b/test/Relayer.BasicFill.ts @@ -1,6 +1,12 @@ import { clients, constants, utils as sdkUtils, arch } from "@across-protocol/sdk"; import hre from "hardhat"; -import { AcrossApiClient, ConfigStoreClient, MultiCallerClient, SpokePoolClient } from "../src/clients"; +import { + AcrossApiClient, + ConfigStoreClient, + MultiCallerClient, + SpokePoolClient, + EVMSpokePoolClient, +} from "../src/clients"; import { FillStatus, Deposit, RelayData } from "../src/interfaces"; import { CONFIG_STORE_VERSION } from "../src/common"; import { bnZero, bnOne, bnUint256Max, getNetworkName, getAllUnfilledDeposits, getMessageHash } from "../src/utils"; @@ -126,14 +132,14 @@ describe("Relayer: Check for Unfilled Deposits and Fill", async function () { multiCallerClient = new MockedMultiCallerClient(spyLogger); tryMulticallClient = new MockedMultiCallerClient(spyLogger); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( spyLogger, spokePool_1.connect(relayer), hubPoolClient, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( spyLogger, spokePool_2.connect(relayer), hubPoolClient, diff --git a/test/Relayer.SlowFill.ts b/test/Relayer.SlowFill.ts index b01555a0b1..5732997ef9 100644 --- a/test/Relayer.SlowFill.ts +++ b/test/Relayer.SlowFill.ts @@ -5,6 +5,7 @@ import { MultiCallerClient, SpokePoolClient, TokenClient, + EVMSpokePoolClient, } from "../src/clients"; import { CONFIG_STORE_VERSION } from "../src/common"; import { @@ -113,14 +114,14 @@ describe("Relayer: Initiates slow fill requests", async function () { multiCallerClient = new MockedMultiCallerClient(spyLogger); // leave out the gasEstimator for now. tryMulticallClient = new MockedMultiCallerClient(spyLogger); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( spyLogger, spokePool_1.connect(relayer), hubPoolClient, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( spyLogger, spokePool_2.connect(relayer), hubPoolClient, diff --git a/test/Relayer.TokenShortfall.ts b/test/Relayer.TokenShortfall.ts index b014c8d2f5..f636f8f686 100644 --- a/test/Relayer.TokenShortfall.ts +++ b/test/Relayer.TokenShortfall.ts @@ -1,4 +1,11 @@ -import { AcrossApiClient, ConfigStoreClient, HubPoolClient, MultiCallerClient, SpokePoolClient } from "../src/clients"; +import { + AcrossApiClient, + ConfigStoreClient, + HubPoolClient, + MultiCallerClient, + SpokePoolClient, + EVMSpokePoolClient, +} from "../src/clients"; import { CONFIG_STORE_VERSION } from "../src/common"; import { Relayer } from "../src/relayer/Relayer"; import { RelayerConfig } from "../src/relayer/RelayerConfig"; // Tested @@ -113,14 +120,14 @@ describe("Relayer: Token balance shortfall", async function () { multiCallerClient = new MockedMultiCallerClient(spyLogger); // leave out the gasEstimator for now. tryMulticallClient = new MockedMultiCallerClient(spyLogger); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( spyLogger, spokePool_1.connect(relayer), hubPoolClient, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( spyLogger, spokePool_2.connect(relayer), hubPoolClient, diff --git a/test/Relayer.UnfilledDeposits.ts b/test/Relayer.UnfilledDeposits.ts index 0610fb9afc..24a8c29d3c 100644 --- a/test/Relayer.UnfilledDeposits.ts +++ b/test/Relayer.UnfilledDeposits.ts @@ -1,7 +1,14 @@ import * as contracts from "@across-protocol/contracts/dist/test-utils"; import { ExpandedERC20__factory as ERC20 } from "@across-protocol/contracts"; import { utils as sdkUtils } from "@across-protocol/sdk"; -import { AcrossApiClient, ConfigStoreClient, HubPoolClient, MultiCallerClient, SpokePoolClient } from "../src/clients"; +import { + AcrossApiClient, + ConfigStoreClient, + HubPoolClient, + MultiCallerClient, + SpokePoolClient, + EVMSpokePoolClient, +} from "../src/clients"; import { DepositWithBlock, FillStatus } from "../src/interfaces"; import { CHAIN_ID_TEST_LIST, @@ -110,14 +117,14 @@ describe("Relayer: Unfilled Deposits", async function () { (hubPoolClient as SimpleMockHubPoolClient).mapTokenInfo(erc20_1.address, "L1Token1"); (hubPoolClient as SimpleMockHubPoolClient).mapTokenInfo(erc20_2.address, "L1Token1"); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( spyLogger, spokePool_1, hubPoolClient, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( spyLogger, spokePool_2, hubPoolClient, diff --git a/test/TokenClient.Approval.ts b/test/TokenClient.Approval.ts index 119d6597fe..53cceca41d 100644 --- a/test/TokenClient.Approval.ts +++ b/test/TokenClient.Approval.ts @@ -1,4 +1,4 @@ -import { ConfigStoreClient, SpokePoolClient } from "../src/clients"; +import { ConfigStoreClient, SpokePoolClient, EVMSpokePoolClient } from "../src/clients"; import { originChainId, destinationChainId, ZERO_ADDRESS } from "./constants"; import { Contract, @@ -66,14 +66,14 @@ describe("TokenClient: Origin token approval", async function () { ({ hubPool, l1Token_1, l1Token_2 } = await deployAndConfigureHubPool(owner, [], finder.address, ZERO_ADDRESS)); await collateralWhitelist.addToWhitelist(l1Token_1.address); await hubPool.setBond(l1Token_1.address, toBNWei("5")); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_1, null, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_2, null, diff --git a/test/TokenClient.BalanceAlowance.ts b/test/TokenClient.BalanceAlowance.ts index e47cacfa2e..419fb83a0a 100644 --- a/test/TokenClient.BalanceAlowance.ts +++ b/test/TokenClient.BalanceAlowance.ts @@ -1,4 +1,4 @@ -import { ConfigStoreClient, SpokePoolClient, TokenDataType } from "../src/clients"; // Tested +import { ConfigStoreClient, SpokePoolClient, TokenDataType, EVMSpokePoolClient } from "../src/clients"; // Tested import { originChainId, destinationChainId, ZERO_ADDRESS } from "./constants"; import { MockHubPoolClient, SimpleMockTokenClient } from "./mocks"; import { @@ -84,14 +84,14 @@ describe("TokenClient: Balance and Allowance", async function () { const l1Tokens = hubPoolClient.getL1Tokens(); expect(l1Tokens.length).to.equal(2); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_1, null, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_2, null, diff --git a/test/TokenClient.TokenShortfall.ts b/test/TokenClient.TokenShortfall.ts index 3bf1895feb..dffce51404 100644 --- a/test/TokenClient.TokenShortfall.ts +++ b/test/TokenClient.TokenShortfall.ts @@ -1,4 +1,4 @@ -import { SpokePoolClient } from "../src/clients"; +import { EVMSpokePoolClient, SpokePoolClient } from "../src/clients"; import { MockConfigStoreClient, MockHubPoolClient, SimpleMockTokenClient } from "./mocks"; import { originChainId, destinationChainId, ZERO_ADDRESS } from "./constants"; import { @@ -48,14 +48,14 @@ describe("TokenClient: Token shortfall", async function () { const configStoreClient = new MockConfigStoreClient(createSpyLogger().spyLogger, configStore); - spokePoolClient_1 = new SpokePoolClient( + spokePoolClient_1 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_1, null, originChainId, spokePool1DeploymentBlock ); - spokePoolClient_2 = new SpokePoolClient( + spokePoolClient_2 = new EVMSpokePoolClient( createSpyLogger().spyLogger, spokePool_2, null, diff --git a/test/fixtures/Dataworker.Fixture.ts b/test/fixtures/Dataworker.Fixture.ts index 051c338b86..e89be3e3bf 100644 --- a/test/fixtures/Dataworker.Fixture.ts +++ b/test/fixtures/Dataworker.Fixture.ts @@ -46,7 +46,7 @@ async function _constructSpokePoolClientsWithLookback( await hubPoolClient.update(); const latestBlocks = await Promise.all(spokePools.map((x) => x.provider.getBlockNumber())); return spokePools.map((pool, i) => { - return new clients.SpokePoolClient( + return new clients.EVMSpokePoolClient( spyLogger, pool.connect(signer), hubPoolClient, diff --git a/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts b/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts index db4b416860..4a566e9ab8 100644 --- a/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts +++ b/test/generic-adapters/AdapterManager.SendTokensCrossChain.ts @@ -1,4 +1,4 @@ -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { AdapterManager } from "../../src/clients/bridges"; import { CONTRACT_ADDRESSES } from "../../src/common"; import { @@ -10,6 +10,7 @@ import { bnZero, getCctpDomainForChainId, EvmAddress, + ZERO_ADDRESS, } from "../../src/utils"; import { MockConfigStoreClient, MockHubPoolClient } from "../mocks"; import { @@ -28,8 +29,9 @@ import { let hubPoolClient: MockHubPoolClient; const mockSpokePoolClients: { - [chainId: number]: SpokePoolClient; + [chainId: number]: EVMSpokePoolClient; } = {}; +let logger; let relayer: SignerWithAddress, owner: SignerWithAddress, spyLogger: winston.Logger, amountToSend: BigNumber; let adapterManager: AdapterManager; @@ -69,6 +71,7 @@ describe("AdapterManager: Send tokens cross-chain", async function () { beforeEach(async function () { [relayer, owner] = await ethers.getSigners(); ({ spyLogger } = createSpyLogger()); + logger = spyLogger; const { configStore } = await deployConfigStore(owner, []); const configStoreClient = new MockConfigStoreClient(spyLogger, configStore); @@ -418,15 +421,16 @@ async function seedMocks() { // Construct fake spoke pool clients. All the adapters need is a signer and a provider on each chain. for (const chainId of enabledChainIds) { if (!mockSpokePoolClients[chainId]) { - mockSpokePoolClients[chainId] = {} as unknown as SpokePoolClient; + mockSpokePoolClients[chainId] = {} as unknown as EVMSpokePoolClient; } - mockSpokePoolClients[chainId] = { + mockSpokePoolClients[chainId] = new EVMSpokePoolClient( + logger, + { address: ZERO_ADDRESS, provider: ethers.provider, signer: (await ethers.getSigners())[0] } as Contract, + undefined, chainId, - spokePool: { - provider: ethers.provider, - signer: (await ethers.getSigners())[0], - }, - } as unknown as SpokePoolClient; + 0, + {} + ); } } diff --git a/test/generic-adapters/Arbitrum.ts b/test/generic-adapters/Arbitrum.ts index 8380cde191..0ebcd95404 100644 --- a/test/generic-adapters/Arbitrum.ts +++ b/test/generic-adapters/Arbitrum.ts @@ -1,5 +1,5 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { BaseChainAdapter } from "../../src/adapter"; import { ArbitrumOrbitBridge, UsdcTokenSplitterBridge } from "../../src/adapter/bridges"; import { ethers, getContractFactory, Contract, randomAddress, expect, toBN, createSpyLogger } from "../utils"; @@ -54,10 +54,10 @@ describe("Cross Chain Adapter: Arbitrum", async function () { erc20BridgeContract = await (await getContractFactory("ArbitrumERC20Bridge", deployer)).deploy(); cctpBridgeContract = await (await getContractFactory("CctpTokenMessenger", deployer)).deploy(); - const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, null, CHAIN_IDs.ARBITRUM, 0, { + const l2SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, null, CHAIN_IDs.ARBITRUM, 0, { from: 0, }); - const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, null, CHAIN_IDs.MAINNET, 0, { + const l1SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, null, CHAIN_IDs.MAINNET, 0, { from: 0, }); diff --git a/test/generic-adapters/Linea.ts b/test/generic-adapters/Linea.ts index 1a9d72050b..d7b606ab5a 100644 --- a/test/generic-adapters/Linea.ts +++ b/test/generic-adapters/Linea.ts @@ -1,5 +1,5 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { LineaBridge, LineaWethBridge, UsdcCCTPBridge } from "../../src/adapter/bridges"; import { BaseChainAdapter } from "../../src/adapter"; import { ethers, getContractFactory, Contract, randomAddress, expect, createRandomBytes32 } from "../utils"; @@ -44,10 +44,10 @@ describe("Cross Chain Adapter: Linea", async function () { const spokePool = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); - const l2SpokePoolClient = new SpokePoolClient(null, spokePool, null, l2ChainId, 0, { + const l2SpokePoolClient = new EVMSpokePoolClient(null, spokePool, null, l2ChainId, 0, { from: 0, }); - const l1SpokePoolClient = new SpokePoolClient(null, spokePool, null, hubChainId, 0, { + const l1SpokePoolClient = new EVMSpokePoolClient(null, spokePool, null, hubChainId, 0, { from: 0, }); diff --git a/test/generic-adapters/OpStack.ts b/test/generic-adapters/OpStack.ts index d4c62a0f08..594e5f6bbd 100644 --- a/test/generic-adapters/OpStack.ts +++ b/test/generic-adapters/OpStack.ts @@ -11,7 +11,7 @@ import { UsdcTokenSplitterBridge, } from "../../src/adapter/bridges"; import { BaseChainAdapter } from "../../src/adapter/BaseChainAdapter"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { ZERO_ADDRESS } from "../constants"; import { ethers, getContractFactory, Contract, randomAddress, expect, createSpyLogger, toBN } from "../utils"; @@ -108,10 +108,10 @@ describe("Cross Chain Adapter: OP Stack", async function () { spokePoolContract = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); - const l2SpokePoolClient = new SpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.OPTIMISM, 0, { + const l2SpokePoolClient = new EVMSpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.OPTIMISM, 0, { from: 0, }); - const l1SpokePoolClient = new SpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.MAINNET, 0, { + const l1SpokePoolClient = new EVMSpokePoolClient(logger, spokePoolContract, null, CHAIN_IDs.MAINNET, 0, { from: 0, }); diff --git a/test/generic-adapters/Polygon.ts b/test/generic-adapters/Polygon.ts index 7fd9873bf4..cd8ea1ac4f 100644 --- a/test/generic-adapters/Polygon.ts +++ b/test/generic-adapters/Polygon.ts @@ -1,6 +1,6 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; import { utils } from "@across-protocol/sdk"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { BaseChainAdapter } from "../../src/adapter/BaseChainAdapter"; import { PolygonWethBridge, PolygonERC20Bridge, UsdcTokenSplitterBridge } from "../../src/adapter/bridges"; import { @@ -78,10 +78,10 @@ describe("Cross Chain Adapter: Polygon", async function () { const deploymentBlock = spokePool.deployTransaction.blockNumber!; const hubPoolClient = null; - const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, POLYGON, deploymentBlock, { + const l2SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, hubPoolClient, POLYGON, deploymentBlock, { from: deploymentBlock, }); - const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { + const l1SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { from: deploymentBlock, }); searchConfig = { from: deploymentBlock, to: 1_000_000 }; diff --git a/test/generic-adapters/Scroll.ts b/test/generic-adapters/Scroll.ts index abd6dce28a..fa7acbd5dc 100644 --- a/test/generic-adapters/Scroll.ts +++ b/test/generic-adapters/Scroll.ts @@ -1,5 +1,5 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { ScrollERC20Bridge } from "../../src/adapter/bridges"; import { BaseChainAdapter } from "../../src/adapter"; import { ethers, getContractFactory, Contract, randomAddress, expect } from "../utils"; @@ -41,10 +41,10 @@ describe("Cross Chain Adapter: Scroll", async function () { const spokePool = await (await getContractFactory("MockSpokePool", deployer)).deploy(ZERO_ADDRESS); - const l2SpokePoolClient = new SpokePoolClient(null, spokePool, null, l2ChainId, 0, { + const l2SpokePoolClient = new EVMSpokePoolClient(null, spokePool, null, l2ChainId, 0, { from: 0, }); - const l1SpokePoolClient = new SpokePoolClient(null, spokePool, null, hubChainId, 0, { + const l1SpokePoolClient = new EVMSpokePoolClient(null, spokePool, null, hubChainId, 0, { from: 0, }); spokeAddress = l2SpokePoolClient.spokePool.address; diff --git a/test/generic-adapters/zkSync.ts b/test/generic-adapters/zkSync.ts index 0dbebe0b1c..7c2379b46a 100644 --- a/test/generic-adapters/zkSync.ts +++ b/test/generic-adapters/zkSync.ts @@ -1,6 +1,6 @@ import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; import { utils } from "@across-protocol/sdk"; -import { SpokePoolClient } from "../../src/clients"; +import { EVMSpokePoolClient } from "../../src/clients"; import { BaseChainAdapter } from "../../src/adapter/BaseChainAdapter"; import { ZKStackUSDCBridge, ZKStackWethBridge, ZKStackBridge } from "../../src/adapter/bridges"; import { bnZero, EvmAddress } from "../../src/utils"; @@ -134,10 +134,10 @@ describe("Cross Chain Adapter: zkSync", async function () { const deploymentBlock = spokePool.deployTransaction.blockNumber!; const hubPoolClient = null; - const l2SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, ZK_SYNC, deploymentBlock, { + const l2SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, hubPoolClient, ZK_SYNC, deploymentBlock, { from: deploymentBlock, }); - const l1SpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { + const l1SpokePoolClient = new EVMSpokePoolClient(logger, spokePool, hubPoolClient, MAINNET, deploymentBlock, { from: deploymentBlock, }); searchConfig = { from: deploymentBlock, to: 1_000_000 }; @@ -1033,7 +1033,7 @@ describe("Cross Chain Adapter: zkSync", async function () { const hubPoolClient = null; const deploymentBlock = spokePool.deployTransaction.blockNumber!; - const lensSpokePoolClient = new SpokePoolClient(logger, spokePool, hubPoolClient, LENS, deploymentBlock, { + const lensSpokePoolClient = new EVMSpokePoolClient(logger, spokePool, hubPoolClient, LENS, deploymentBlock, { from: deploymentBlock, }); diff --git a/yarn.lock b/yarn.lock index 481e8eacfe..7c275c3ea2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -59,10 +59,10 @@ yargs "^17.7.2" zksync-web3 "^0.14.3" -"@across-protocol/sdk@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.2.2.tgz#ff3d2711c7125daaefc3a5fe083ddb42f092f0ff" - integrity sha512-to0sr8h3YP9IDJ0uPG0RgryHYMbAMxkv77gw2YlNoxcozP773HB2KG+AhOz3rjFlRqmNTA2Tcr8K0/dA5RW0lQ== +"@across-protocol/sdk@4.2.3": + version "4.2.3" + resolved "https://registry.yarnpkg.com/@across-protocol/sdk/-/sdk-4.2.3.tgz#8a9139c02e32cf835dc4b5184ca80d4f252d332d" + integrity sha512-o5dIS06GOCvg32e0VI7yK09Zsm0KoeUjsumX4IGgDd7wb2tcZuqKeAMra1egxX18Tyzg1ydJoRO+Ux01ijwCFA== dependencies: "@across-protocol/across-token" "^1.0.0" "@across-protocol/constants" "^3.1.66" From 039df8dd7544cb75302b7ba8c83b402bf1d889df Mon Sep 17 00:00:00 2001 From: bennett Date: Wed, 21 May 2025 15:02:09 -0500 Subject: [PATCH 10/20] update Signed-off-by: bennett --- src/clients/ProfitClient.ts | 10 ++++- src/clients/TokenClient.ts | 61 ++++++++++++++++----------- src/clients/bridges/AdapterManager.ts | 23 +++++++--- src/common/ClientHelper.ts | 6 ++- src/common/Constants.ts | 2 +- src/finalizer/index.ts | 17 +++++--- src/finalizer/utils/cctp/l1ToL2.ts | 27 +++++++----- src/relayer/Relayer.ts | 2 +- src/utils/CCTPUtils.ts | 14 +++--- 9 files changed, 107 insertions(+), 55 deletions(-) diff --git a/src/clients/ProfitClient.ts b/src/clients/ProfitClient.ts index e76e94be51..a1da125d75 100644 --- a/src/clients/ProfitClient.ts +++ b/src/clients/ProfitClient.ts @@ -35,6 +35,8 @@ import { SVMProvider, isEVMSpokePoolClient, isSVMSpokePoolClient, + getDeployedAddress, + chainIsEvm, } from "../utils"; import { Deposit, DepositWithBlock, L1Token, SpokePoolClientsByChain } from "../interfaces"; import { getAcrossHost } from "./AcrossAPIClient"; @@ -586,7 +588,7 @@ export class ProfitClient { // Also ensure all gas tokens are included in the lookup. this.enabledChainIds.forEach((chainId) => { const symbol = getNativeTokenSymbol(chainId); - let nativeTokenAddress = TOKEN_SYMBOLS_MAP[symbol].addresses[CHAIN_IDs.MAINNET]; + let nativeTokenAddress = TOKEN_SYMBOLS_MAP[symbol]?.addresses[this._getNativeTokenNetwork(symbol)]; // For testnet only, if the custom gas token has no mainnet address, use ETH. if (this.hubPoolClient.chainId === CHAIN_IDs.SEPOLIA && !isDefined(nativeTokenAddress)) { nativeTokenAddress = TOKEN_SYMBOLS_MAP["ETH"].addresses[CHAIN_IDs.MAINNET]; @@ -720,6 +722,10 @@ export class ProfitClient { return dedupArray([...hubPoolTokens, ...additionalL1Tokens]); } + private _getNativeTokenNetwork(symbol: string): number { + return symbol === "SOL" ? CHAIN_IDs.SOLANA : CHAIN_IDs.MAINNET; + } + private constructRelayerFeeQuery( chainId: number, provider: Provider | SVMProvider @@ -732,7 +738,7 @@ export class ProfitClient { chainId, provider, undefined, // symbolMapping - undefined, // spokePoolAddress + chainIsEvm(chainId) ? undefined : getDeployedAddress("SvmSpoke", chainId), // spokePoolAddress undefined, // simulatedRelayerAddress coingeckoProApiKey, this.logger diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index 61c00f0936..b0d7b835d7 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -23,6 +23,7 @@ import { getTokenInfo, isEVMSpokePoolClient, assert, + isSVMSpokePoolClient, } from "../utils"; export type TokenDataType = { [chainId: number]: { [token: string]: { balance: BigNumber; allowance: BigNumber } } }; @@ -224,35 +225,47 @@ export class TokenClient { ): Promise> { const spokePoolClient = this.spokePoolClients[chainId]; - assert(isEVMSpokePoolClient(spokePoolClient)); - const multicall3 = sdkUtils.getMulticall3(chainId, spokePoolClient.spokePool.provider); - if (!isDefined(multicall3)) { - return this.fetchTokenData(chainId, hubPoolTokens); - } + if (isEVMSpokePoolClient(spokePoolClient)) { + const multicall3 = sdkUtils.getMulticall3(chainId, spokePoolClient.spokePool.provider); + if (!isDefined(multicall3)) { + return this.fetchTokenData(chainId, hubPoolTokens); + } - const { relayerAddress } = this; - const balances: sdkUtils.Call3[] = []; - const allowances: sdkUtils.Call3[] = []; - this.resolveRemoteTokens(chainId, hubPoolTokens).forEach((token) => { - balances.push({ contract: token, method: "balanceOf", args: [relayerAddress] }); - allowances.push({ - contract: token, - method: "allowance", - args: [relayerAddress, spokePoolClient.spokePoolAddress.toEvmAddress()], + const { relayerAddress } = this; + const balances: sdkUtils.Call3[] = []; + const allowances: sdkUtils.Call3[] = []; + this.resolveRemoteTokens(chainId, hubPoolTokens).forEach((token) => { + balances.push({ contract: token, method: "balanceOf", args: [relayerAddress] }); + allowances.push({ + contract: token, + method: "allowance", + args: [relayerAddress, spokePoolClient.spokePoolAddress.toEvmAddress()], + }); }); - }); - const calls = [...balances, ...allowances]; - const results = await sdkUtils.aggregate(multicall3, calls); + const calls = [...balances, ...allowances]; + const results = await sdkUtils.aggregate(multicall3, calls); - const allowanceOffset = balances.length; - const balanceInfo = Object.fromEntries( - balances.map(({ contract: { address } }, idx) => { - return [address, { balance: results[idx][0], allowance: results[allowanceOffset + idx][0] }]; - }) - ); + const allowanceOffset = balances.length; + const balanceInfo = Object.fromEntries( + balances.map(({ contract: { address } }, idx) => { + return [address, { balance: results[idx][0], allowance: results[allowanceOffset + idx][0] }]; + }) + ); - return balanceInfo; + return balanceInfo; + } else if (isSVMSpokePoolClient(spokePoolClient)) { + return Object.fromEntries( + hubPoolTokens + .map((token) => { + const remoteToken = getRemoteTokenForL1Token(token.address, chainId, { + chainId: this.hubPoolClient.chainId, + }); + return isDefined(remoteToken) ? [remoteToken, { balance: toBN(0), allowance: toBN(0) }] : undefined; + }) + .filter(isDefined) + ); + } } async update(): Promise { diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index dde43b314f..48c124fbca 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -24,6 +24,7 @@ import { getRemoteTokenForL1Token, getTokenInfo, isEVMSpokePoolClient, + isSVMSpokePoolClient, } from "../../utils"; import { SpokePoolClient, HubPoolClient } from "../"; import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants"; @@ -70,11 +71,21 @@ export class AdapterManager { return Object.fromEntries( SUPPORTED_TOKENS[chainId]?.map((symbol) => { const spokePoolClient = spokePoolClients[chainId]; - assert(isEVMSpokePoolClient(spokePoolClient)); - const l2Signer = spokePoolClient.spokePool.signer; + let l2SignerOrProvider; + if (isEVMSpokePoolClient(spokePoolClient)) { + l2SignerOrProvider = spokePoolClient.spokePool.signer; + } else if (isSVMSpokePoolClient(spokePoolClient)) { + l2SignerOrProvider = spokePoolClient.svmEventsClient.getRpc(); + } const l1Token = TOKEN_SYMBOLS_MAP[symbol].addresses[hubChainId]; const bridgeConstructor = CUSTOM_BRIDGE[chainId]?.[l1Token] ?? CANONICAL_BRIDGE[chainId]; - const bridge = new bridgeConstructor(chainId, hubChainId, l1Signer, l2Signer, EvmAddress.from(l1Token)); + const bridge = new bridgeConstructor( + chainId, + hubChainId, + l1Signer, + l2SignerOrProvider, + EvmAddress.from(l1Token) + ); return [l1Token, bridge]; }) ?? [] ); @@ -84,8 +95,10 @@ export class AdapterManager { return {}; } const spokePoolClient = spokePoolClients[chainId]; - assert(isEVMSpokePoolClient(spokePoolClient)); - const l2Signer = spokePoolClient.spokePool.signer; + let l2Signer; + if (isEVMSpokePoolClient(spokePoolClient)) { + l2Signer = spokePoolClient.spokePool.signer; + } return Object.fromEntries( SUPPORTED_TOKENS[chainId] ?.map((symbol) => { diff --git a/src/common/ClientHelper.ts b/src/common/ClientHelper.ts index 65c144d495..cd7a5a2b61 100644 --- a/src/common/ClientHelper.ts +++ b/src/common/ClientHelper.ts @@ -217,7 +217,11 @@ export async function constructSpokePoolClientsWithStartBlocks( const spokePoolSigners = await getSpokePoolSigners(baseSigner, enabledChains); const spokePools = await Promise.all( enabledChains.map(async (chainId) => { - const registrationBlock = await resolveSpokePoolActivationBlock(chainId, hubPoolClient, toBlockOverride[1]); + const registrationBlock = Number( + process.env[`REGISTRATION_BLOCK_OVERRIDE_${chainId}`] ?? + (await resolveSpokePoolActivationBlock(chainId, hubPoolClient, toBlockOverride[1])) + ); + // const registrationBlock = await resolveSpokePoolActivationBlock(chainId, hubPoolClient, toBlockOverride[1]); if (chainIsEvm(chainId)) { const spokePoolAddr = hubPoolClient.getSpokePoolForBlock(chainId, toBlockOverride[1]); // TODO: initialize using typechain factory after V3.5 migration. diff --git a/src/common/Constants.ts b/src/common/Constants.ts index dc8bdfdb16..dc4165450f 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -150,7 +150,7 @@ export const CHAIN_MAX_BLOCK_LOOKBACK = { [CHAIN_IDs.REDSTONE]: 10000, [CHAIN_IDs.SCROLL]: 10000, [CHAIN_IDs.SONEIUM]: 10000, - [CHAIN_IDs.SOLANA]: 10000, + [CHAIN_IDs.SOLANA]: 1000, [CHAIN_IDs.WORLD_CHAIN]: 10000, [CHAIN_IDs.ZK_SYNC]: 10000, [CHAIN_IDs.ZORA]: 10000, diff --git a/src/finalizer/index.ts b/src/finalizer/index.ts index f989f48774..55e5f8420e 100644 --- a/src/finalizer/index.ts +++ b/src/finalizer/index.ts @@ -32,6 +32,7 @@ import { Profiler, stringifyThrownValue, isEVMSpokePoolClient, + chainIsEvm, } from "../utils"; import { ChainFinalizer, CrossChainMessage, isAugmentedTransaction } from "./types"; import { @@ -103,6 +104,10 @@ const chainFinalizers: { [chainId: number]: { finalizeOnL2: ChainFinalizer[]; fi finalizeOnL1: [scrollFinalizer], finalizeOnL2: [], }, + [CHAIN_IDs.SOLANA]: { + finalizeOnL1: [cctpL2toL1Finalizer], + finalizeOnL2: [cctpL1toL2Finalizer], + }, [CHAIN_IDs.MODE]: { finalizeOnL1: [opStackFinalizer], finalizeOnL2: [], @@ -297,11 +302,13 @@ export async function finalize( // since any L2 -> L1 transfers will be finalized on the hub chain. hubChainId, ...configuredChainIds, - ]).map(async (chainId) => { - const spokePoolClient = spokePoolClients[chainId]; - assert(isEVMSpokePoolClient(spokePoolClient)); - return [chainId, await getMultisender(chainId, spokePoolClient.spokePool.signer)] as [number, Contract]; - }) + ]) + .filter(chainIsEvm) + .map(async (chainId) => { + const spokePoolClient = spokePoolClients[chainId]; + assert(isEVMSpokePoolClient(spokePoolClient)); + return [chainId, await getMultisender(chainId, spokePoolClient.spokePool.signer)] as [number, Contract]; + }) ) ); // Assert that no multicall2Lookup is undefined diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index ea756c0b78..d9b4120fe5 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -30,7 +30,6 @@ export async function cctpL1toL2Finalizer( l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { - assert(isEVMSpokePoolClient(l1SpokePoolClient) && isEVMSpokePoolClient(l2SpokePoolClient)); const searchConfig: EventSearchConfig = { from: l1SpokePoolClient.eventSearchConfig.from, to: l1SpokePoolClient.latestHeightSearched, @@ -57,16 +56,22 @@ export async function cctpL1toL2Finalizer( }); const { address, abi } = getCctpMessageTransmitter(l2SpokePoolClient.chainId, l2SpokePoolClient.chainId); - const l2MessengerContract = new ethers.Contract(address, abi, l2SpokePoolClient.spokePool.provider); - - return { - crossChainMessages: await generateDepositData( - unprocessedMessages, - hubPoolClient.chainId, - l2SpokePoolClient.chainId - ), - callData: await generateMultiCallData(l2MessengerContract, unprocessedMessages), - }; + if (isEVMSpokePoolClient(l2SpokePoolClient)) { + const l2Messenger = new ethers.Contract(address, abi, l2SpokePoolClient.spokePool.provider); + return { + crossChainMessages: await generateDepositData( + unprocessedMessages, + hubPoolClient.chainId, + l2SpokePoolClient.chainId + ), + callData: await generateMultiCallData(l2Messenger, unprocessedMessages), + }; + } else { + return { + crossChainMessages: [], + callData: [], + }; + } } /** diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index ea15c3faf4..1a24dce3be 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -140,7 +140,7 @@ export class Relayer { await updateSpokePoolClients(spokePoolClients, [ "FundsDeposited", - "RequestedSpeedUpDeposit", + // "RequestedSpeedUpDeposit", "FilledRelay", "RelayedRootBundle", "ExecutedRelayerRefundRoot", diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index 6d35632f0b..e125b116df 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -4,7 +4,7 @@ import axios from "axios"; import { Contract, ethers } from "ethers"; import { CONTRACT_ADDRESSES } from "../common"; import { BigNumber } from "./BNUtils"; -import { bnZero, compareAddressesSimple } from "./SDKUtils"; +import { bnZero, compareAddressesSimple, chainIsEvm } from "./SDKUtils"; import { isDefined } from "./TypeGuards"; import { getCachedProvider } from "./ProviderUtils"; import { EventSearchConfig, paginatedEventQuery } from "./EventUtils"; @@ -298,10 +298,9 @@ export async function getAttestationsForCCTPDepositEvents( // deposits in getCCTPDepositEventsWithStatus(). if (isCctpV2ApiResponse(attestation)) { const attestationForDeposit = attestation.messages[count]; - const processed = await _hasCCTPMessageBeenProcessed( - attestationForDeposit.eventNonce, - destinationMessageTransmitter - ); + const processed = chainIsEvm(l2ChainId) + ? await _hasCCTPMessageBeenProcessed(attestationForDeposit.eventNonce, destinationMessageTransmitter) + : await _hasCCTPMessageBeenProcessedSvm(attestationForDeposit.eventNonce); if (processed) { return { ...deposit, @@ -345,6 +344,11 @@ async function _hasCCTPMessageBeenProcessed(nonceHash: string, contract: ethers. return (resultingCall ?? bnZero).toNumber() === 1; } +async function _hasCCTPMessageBeenProcessedSvm(nonceHash: string): Promise { + const messageProcessedResult = await Promise.resolve(1); + return messageProcessedResult === 1; +} + function _decodeCCTPV1Message(message: { data: string }): CCTPDeposit { // Source: https://developers.circle.com/stablecoins/message-format const messageBytes = ethers.utils.defaultAbiCoder.decode(["bytes"], message.data)[0]; From e3bc755574778ec34d1da6751c2487ca5635a88c Mon Sep 17 00:00:00 2001 From: bennett Date: Wed, 21 May 2025 16:20:55 -0500 Subject: [PATCH 11/20] scaffold Signed-off-by: bennett --- src/utils/CCTPUtils.ts | 37 ++++++++++++++++++++++++++++++------- src/utils/SDKUtils.ts | 1 + 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index e125b116df..9b6e309174 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -4,7 +4,7 @@ import axios from "axios"; import { Contract, ethers } from "ethers"; import { CONTRACT_ADDRESSES } from "../common"; import { BigNumber } from "./BNUtils"; -import { bnZero, compareAddressesSimple, chainIsEvm } from "./SDKUtils"; +import { bnZero, compareAddressesSimple, chainIsSvm } from "./SDKUtils"; import { isDefined } from "./TypeGuards"; import { getCachedProvider } from "./ProviderUtils"; import { EventSearchConfig, paginatedEventQuery } from "./EventUtils"; @@ -126,6 +126,15 @@ async function getCCTPDepositEvents( // Step 1: Get all DepositForBurn events matching the senderAddress and source chain. const srcProvider = getCachedProvider(sourceChainId); const { address, abi } = getCctpTokenMessenger(l2ChainId, sourceChainId); + if (chainIsSvm(sourceChainId) || chainIsSvm(destinationChainId)) { + return _getCCTPDepositEventsSvm( + senderAddresses, + sourceChainId, + destinationChainId, + l2ChainId, + sourceEventSearchConfig + ); + } const srcTokenMessenger = new Contract(address, abi, srcProvider); const eventFilterParams = isCctpV2 ? [TOKEN_SYMBOLS_MAP.USDC.addresses[sourceChainId], undefined, senderAddresses] @@ -217,7 +226,7 @@ async function getCCTPDepositEventsWithStatus( ); const dstProvider = getCachedProvider(destinationChainId); const { address, abi } = getCctpMessageTransmitter(l2ChainId, destinationChainId); - const destinationMessageTransmitter = new ethers.Contract(address, abi, dstProvider); + const messageTransmitterContract = chainIsSvm(l2ChainId) ? undefined : new Contract(address, abi, dstProvider); return await Promise.all( deposits.map(async (deposit) => { // @dev Currently we have no way to recreate the V2 nonce hash until after we've received the attestation, @@ -229,7 +238,9 @@ async function getCCTPDepositEventsWithStatus( status: "pending", }; } - const processed = await _hasCCTPMessageBeenProcessed(deposit.nonceHash, destinationMessageTransmitter); + const processed = chainIsSvm(l2ChainId) + ? await _hasCCTPMessageBeenProcessedSvm(deposit.nonceHash) + : await _hasCCTPMessageBeenProcessed(deposit.nonceHash, messageTransmitterContract); if (!processed) { return { ...deposit, @@ -276,7 +287,9 @@ export async function getAttestationsForCCTPDepositEvents( const txnReceiptHashCount: { [hash: string]: number } = {}; const dstProvider = getCachedProvider(destinationChainId); const { address, abi } = getCctpMessageTransmitter(l2ChainId, destinationChainId); - const destinationMessageTransmitter = new ethers.Contract(address, abi, dstProvider); + const destinationMessageTransmitter = chainIsSvm(destinationChainId) + ? undefined + : new ethers.Contract(address, abi, dstProvider); const attestedDeposits = await Promise.all( depositsWithStatus.map(async (deposit) => { @@ -298,9 +311,9 @@ export async function getAttestationsForCCTPDepositEvents( // deposits in getCCTPDepositEventsWithStatus(). if (isCctpV2ApiResponse(attestation)) { const attestationForDeposit = attestation.messages[count]; - const processed = chainIsEvm(l2ChainId) - ? await _hasCCTPMessageBeenProcessed(attestationForDeposit.eventNonce, destinationMessageTransmitter) - : await _hasCCTPMessageBeenProcessedSvm(attestationForDeposit.eventNonce); + const processed = chainIsSvm(destinationChainId) + ? await _hasCCTPMessageBeenProcessedSvm(attestationForDeposit.eventNonce) + : await _hasCCTPMessageBeenProcessed(attestationForDeposit.eventNonce, destinationMessageTransmitter); if (processed) { return { ...deposit, @@ -349,6 +362,16 @@ async function _hasCCTPMessageBeenProcessedSvm(nonceHash: string): Promise { + return []; +} + function _decodeCCTPV1Message(message: { data: string }): CCTPDeposit { // Source: https://developers.circle.com/stablecoins/message-format const messageBytes = ethers.utils.defaultAbiCoder.decode(["bytes"], message.data)[0]; diff --git a/src/utils/SDKUtils.ts b/src/utils/SDKUtils.ts index 1bbc9ae88e..eef18bc375 100644 --- a/src/utils/SDKUtils.ts +++ b/src/utils/SDKUtils.ts @@ -74,6 +74,7 @@ export const { validateFillForDeposit, toAddressType, chainIsEvm, + chainIsSvm, ConvertDecimals, getTokenInfo, } = sdk.utils; From 8654ffac48d23c7d2a931b6a992dd745e0c61560 Mon Sep 17 00:00:00 2001 From: bennett Date: Thu, 22 May 2025 14:28:50 -0500 Subject: [PATCH 12/20] l2 -> l1 finalizer and rebalancer Signed-off-by: bennett --- src/adapter/bridges/SolanaUsdcCCTPBridge.ts | 25 ++++- src/finalizer/utils/cctp/l1ToL2.ts | 1 + src/utils/CCTPUtils.ts | 118 +++++++++++++++----- src/utils/SDKUtils.ts | 2 + 4 files changed, 118 insertions(+), 28 deletions(-) diff --git a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts index adfb4d719f..67e292fec7 100644 --- a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts +++ b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts @@ -14,6 +14,7 @@ import { paginatedEventQuery, ZERO_BYTES, SVMProvider, + isDefined, } from "../../utils"; import { processEvent } from "../utils"; import { getCctpTokenMessenger, isCctpV2L2ChainId } from "../../utils/CCTPUtils"; @@ -21,6 +22,11 @@ import { CCTP_NO_DOMAIN } from "@across-protocol/constants"; import { arch } from "@across-protocol/sdk"; import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; +type MintAndWithdrawData = { + mintRecipient: string; + amount: bigint; +}; + export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { private CCTP_MAX_SEND_AMOUNT = toBN(1_000_000_000_000); // 1MM USDC. private IS_CCTP_V2 = false; @@ -116,13 +122,28 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { this.solanaEventsClient ??= await this.solanaEventsClientPromise; assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); const l2FinalizationEvents = await this.solanaEventsClient.queryDerivedAddressEvents( - "mintAndWithdraw", + "MintAndWithdraw", this.solanaMessageTransmitter.toV2Address(), BigInt(eventConfig.from), BigInt(eventConfig.to) ); return { - [this.resolveL2TokenAddress(l1Token)]: [], + [this.resolveL2TokenAddress(l1Token)]: l2FinalizationEvents + .map((event) => { + const data = event.data as MintAndWithdrawData; + if (String(data.mintRecipient) !== toAddress.toBase58()) { + return undefined; + } + return { + amount: toBN(data.amount), + blockNumber: Number(event.slot), + txnRef: event.signature, + // There is no log/transaction index on Solana. + txnIndex: 0, + logIndex: 0, + }; + }) + .filter(isDefined), }; } } diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index d9b4120fe5..163a78f5b5 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -67,6 +67,7 @@ export async function cctpL1toL2Finalizer( callData: await generateMultiCallData(l2Messenger, unprocessedMessages), }; } else { + // If the l2SpokePoolClient is not an EVM client, then we must have send the finalization here, since we cannot return SVM calldata. return { crossChainMessages: [], callData: [], diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index 9b6e309174..351f676ac7 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -1,12 +1,21 @@ import { utils } from "@across-protocol/sdk"; +import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; import { PUBLIC_NETWORKS, CHAIN_IDs, TOKEN_SYMBOLS_MAP, CCTP_NO_DOMAIN } from "@across-protocol/constants"; import axios from "axios"; import { Contract, ethers } from "ethers"; import { CONTRACT_ADDRESSES } from "../common"; import { BigNumber } from "./BNUtils"; -import { bnZero, compareAddressesSimple, chainIsSvm } from "./SDKUtils"; +import { + bnZero, + compareAddressesSimple, + chainIsSvm, + SvmCpiEventsClient, + SvmAddress, + mapAsync, + chainIsProd, +} from "./SDKUtils"; import { isDefined } from "./TypeGuards"; -import { getCachedProvider } from "./ProviderUtils"; +import { getCachedProvider, getSvmProvider } from "./ProviderUtils"; import { EventSearchConfig, paginatedEventQuery } from "./EventUtils"; import { findLast } from "lodash"; import { Log } from "../interfaces"; @@ -21,6 +30,7 @@ type CCTPDeposit = { messageHash: string; messageBytes: string; }; + type CCTPDepositEvent = CCTPDeposit & { log: Log }; type CCTPAPIGetAttestationResponse = { status: string; attestation: string }; type CCTPV2APIAttestation = { @@ -30,6 +40,13 @@ type CCTPV2APIAttestation = { eventNonce: string; cctpVersion: number; }; +type CCTPSvmAPIAttestation = { + attestation: string; + message: string; + eventNonce: string; +}; + +type CCTPSvmAPIGetAttestationResponse = { messages: CCTPSvmAPIAttestation[] }; type CCTPV2APIGetAttestationResponse = { messages: CCTPV2APIAttestation[] }; function isCctpV2ApiResponse( obj: CCTPAPIGetAttestationResponse | CCTPV2APIGetAttestationResponse @@ -126,15 +143,6 @@ async function getCCTPDepositEvents( // Step 1: Get all DepositForBurn events matching the senderAddress and source chain. const srcProvider = getCachedProvider(sourceChainId); const { address, abi } = getCctpTokenMessenger(l2ChainId, sourceChainId); - if (chainIsSvm(sourceChainId) || chainIsSvm(destinationChainId)) { - return _getCCTPDepositEventsSvm( - senderAddresses, - sourceChainId, - destinationChainId, - l2ChainId, - sourceEventSearchConfig - ); - } const srcTokenMessenger = new Contract(address, abi, srcProvider); const eventFilterParams = isCctpV2 ? [TOKEN_SYMBOLS_MAP.USDC.addresses[sourceChainId], undefined, senderAddresses] @@ -226,7 +234,9 @@ async function getCCTPDepositEventsWithStatus( ); const dstProvider = getCachedProvider(destinationChainId); const { address, abi } = getCctpMessageTransmitter(l2ChainId, destinationChainId); - const messageTransmitterContract = chainIsSvm(l2ChainId) ? undefined : new Contract(address, abi, dstProvider); + const messageTransmitterContract = chainIsSvm(destinationChainId) + ? undefined + : new Contract(address, abi, dstProvider); return await Promise.all( deposits.map(async (deposit) => { // @dev Currently we have no way to recreate the V2 nonce hash until after we've received the attestation, @@ -238,7 +248,7 @@ async function getCCTPDepositEventsWithStatus( status: "pending", }; } - const processed = chainIsSvm(l2ChainId) + const processed = chainIsSvm(destinationChainId) ? await _hasCCTPMessageBeenProcessedSvm(deposit.nonceHash) : await _hasCCTPMessageBeenProcessed(deposit.nonceHash, messageTransmitterContract); if (!processed) { @@ -275,6 +285,16 @@ export async function getAttestationsForCCTPDepositEvents( ): Promise { const isCctpV2 = isCctpV2L2ChainId(l2ChainId); const isMainnet = utils.chainIsProd(destinationChainId); + // Reading Solana deposits/attestations follows a different flow from EVM networks, so divert to this flow if the source chain is Solana. + if (chainIsSvm(sourceChainId)) { + return _getCCTPDepositEventsSvm( + senderAddresses, + sourceChainId, + destinationChainId, + l2ChainId, + sourceEventSearchConfig + ); + } const depositsWithStatus = await getCCTPDepositEventsWithStatus( senderAddresses, sourceChainId, @@ -287,10 +307,6 @@ export async function getAttestationsForCCTPDepositEvents( const txnReceiptHashCount: { [hash: string]: number } = {}; const dstProvider = getCachedProvider(destinationChainId); const { address, abi } = getCctpMessageTransmitter(l2ChainId, destinationChainId); - const destinationMessageTransmitter = chainIsSvm(destinationChainId) - ? undefined - : new ethers.Contract(address, abi, dstProvider); - const attestedDeposits = await Promise.all( depositsWithStatus.map(async (deposit) => { // If deposit is already finalized, we won't change its attestation status: @@ -311,9 +327,11 @@ export async function getAttestationsForCCTPDepositEvents( // deposits in getCCTPDepositEventsWithStatus(). if (isCctpV2ApiResponse(attestation)) { const attestationForDeposit = attestation.messages[count]; - const processed = chainIsSvm(destinationChainId) - ? await _hasCCTPMessageBeenProcessedSvm(attestationForDeposit.eventNonce) - : await _hasCCTPMessageBeenProcessed(attestationForDeposit.eventNonce, destinationMessageTransmitter); + const destinationMessageTransmitter = new ethers.Contract(address, abi, dstProvider); + const processed = await _hasCCTPMessageBeenProcessed( + attestationForDeposit.eventNonce, + destinationMessageTransmitter + ); if (processed) { return { ...deposit, @@ -357,7 +375,11 @@ async function _hasCCTPMessageBeenProcessed(nonceHash: string, contract: ethers. return (resultingCall ?? bnZero).toNumber() === 1; } +// A CCTP message is processed if `isNonceUsed` is true on the message transmitter. async function _hasCCTPMessageBeenProcessedSvm(nonceHash: string): Promise { + // const provider = getSvmProvider(); + // const { address } = getCctpTokenMessenger(l2ChainId, sourceChainId); + // @todo const messageProcessedResult = await Promise.resolve(1); return messageProcessedResult === 1; } @@ -368,22 +390,58 @@ async function _getCCTPDepositEventsSvm( destinationChainId: number, l2ChainId: number, sourceEventSearchConfig: EventSearchConfig -): Promise { - return []; +): Promise { + // Get the `DepositForBurn` events on Solana. + const provider = getSvmProvider(); + const { address } = getCctpTokenMessenger(l2ChainId, sourceChainId); + const eventClient = await SvmCpiEventsClient.createFor(provider, address, TokenMessengerMinterIdl); + const depositForBurnEvents = await eventClient.queryDerivedAddressEvents( + "DepositForBurn", + SvmAddress.from(address).toV2Address(), + BigInt(sourceEventSearchConfig.from), + BigInt(sourceEventSearchConfig.to) + ); + + // Query the CCTP API to get the encoded message bytes/attestation. + // Return undefined if we need to filter out the deposit event. + const _depositsWithAttestations = await mapAsync(depositForBurnEvents, async (event) => { + const attestation = await _generateCCTPSvmAttestationProof(event.signature); + return await mapAsync(attestation.messages, async (data) => { + const decodedMessage = _decodeCCTPV1Message({ data: data.message }, true); + if ( + !senderAddresses.includes(cctpBytes32ToAddress(decodedMessage.sender)) || + getCctpDomainForChainId(destinationChainId) !== decodedMessage.destinationDomain + ) { + return undefined; + } + // The destination network cannot be Solana since the origin network is Solana. + const attestationStatusObject = await _generateCCTPAttestationProof( + decodedMessage.messageHash, + chainIsProd(l2ChainId) + ); + return { + ...decodedMessage, + attestation: data.attestation, + status: _getPendingAttestationStatus(attestationStatusObject.status), + log: undefined, + }; + }); + }); + return _depositsWithAttestations.flat().filter(isDefined); } -function _decodeCCTPV1Message(message: { data: string }): CCTPDeposit { +function _decodeCCTPV1Message(message: { data: string }, isSvm = false): CCTPDeposit { // Source: https://developers.circle.com/stablecoins/message-format - const messageBytes = ethers.utils.defaultAbiCoder.decode(["bytes"], message.data)[0]; + const messageBytes = isSvm ? message.data : ethers.utils.defaultAbiCoder.decode(["bytes"], message.data)[0]; const messageBytesArray = ethers.utils.arrayify(messageBytes); const sourceDomain = Number(ethers.utils.hexlify(messageBytesArray.slice(4, 8))); // sourceDomain 4 bytes starting index 4 const destinationDomain = Number(ethers.utils.hexlify(messageBytesArray.slice(8, 12))); // destinationDomain 4 bytes starting index 8 const nonce = BigNumber.from(ethers.utils.hexlify(messageBytesArray.slice(12, 20))).toNumber(); // nonce 8 bytes starting index 12 // V1 nonce hash is a simple hash of the nonce emitted in Deposit event with the source domain ID. const nonceHash = ethers.utils.keccak256(ethers.utils.solidityPack(["uint32", "uint64"], [sourceDomain, nonce])); // - const recipient = cctpBytes32ToAddress(ethers.utils.hexlify(messageBytesArray.slice(152, 184))); // recipient 32 bytes starting index 152 (idx 36 of body after idx 116 which ends the header) + const recipient = ethers.utils.hexlify(messageBytesArray.slice(152, 184)); // recipient 32 bytes starting index 152 (idx 36 of body after idx 116 which ends the header) const amount = ethers.utils.hexlify(messageBytesArray.slice(184, 216)); // amount 32 bytes starting index 184 (idx 68 of body after idx 116 which ends the header) - const sender = cctpBytes32ToAddress(ethers.utils.hexlify(messageBytesArray.slice(216, 248))); // sender 32 bytes starting index 216 (idx 100 of body after idx 116 which ends the header) + const sender = ethers.utils.hexlify(messageBytesArray.slice(216, 248)); // sender 32 bytes starting index 216 (idx 100 of body after idx 116 which ends the header) return { nonceHash, @@ -440,6 +498,14 @@ async function _generateCCTPAttestationProof( return attestationResponse; } +async function _generateCCTPSvmAttestationProof(transactionHash: string): Promise { + const httpResponse = await axios.get( + `https://iris-api.circle.com/messages/5/${transactionHash}` + ); + const attestationResponse = httpResponse.data; + return attestationResponse; +} + // @todo: We can pass in a nonceHash here once we know how to recreate the nonceHash. async function _generateCCTPV2AttestationProof( sourceDomainId: number, diff --git a/src/utils/SDKUtils.ts b/src/utils/SDKUtils.ts index eef18bc375..5d40510ec4 100644 --- a/src/utils/SDKUtils.ts +++ b/src/utils/SDKUtils.ts @@ -8,6 +8,8 @@ export type BlockFinderHints = sdk.utils.BlockFinderHints; export class AddressAggregator extends sdk.addressAggregator.AddressAggregator {} export const addressAdapters = sdk.addressAggregator.adapters; +export class SvmCpiEventsClient extends sdk.arch.svm.SvmCpiEventsClient {} + export class PriceClient extends sdk.priceClient.PriceClient {} export const { acrossApi, coingecko, defiLlama } = sdk.priceClient.adapters; export const { isEVMSpokePoolClient, isSVMSpokePoolClient } = sdk.clients; From 39fb7ae3924a2a63cf322152f509506897d82329 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 23 May 2025 10:13:47 +0000 Subject: [PATCH 13/20] squash --- src/monitor/Monitor.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index 4ae76fa95a..5750a27efd 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -1271,12 +1271,8 @@ export class Monitor { await new Contract(token, ERC20.abi, spokePoolClient.spokePool.provider).balanceOf(account, { blockTag: spokePoolClient.latestHeightSearched, }); - if (!this.balanceCache[chainId]) { - this.balanceCache[chainId] = {}; - } - if (!this.balanceCache[chainId][token]) { - this.balanceCache[chainId][token] = {}; - } + this.balanceCache[chainId] ??= {}; + this.balanceCache[chainId][token] ??= {}; this.balanceCache[chainId][token][account] = balance; return balance; } From 02382906e69363d54b7e60543904ec6cdabe2efa Mon Sep 17 00:00:00 2001 From: bennett Date: Fri, 23 May 2025 08:25:45 -0500 Subject: [PATCH 14/20] comment Signed-off-by: bennett --- src/utils/CCTPUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index aee188ba40..691ee073b6 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -233,7 +233,7 @@ async function getCCTPDepositEventsWithStatus( if (!processed) { return { ...deposit, - status: "pending", // We'll flip to ready once we get the attestation + status: "pending", // We'll flip to ready once we get the attestation. }; } else { return { From d19c0f2c474a722543f5ff29780a41368eea08b2 Mon Sep 17 00:00:00 2001 From: bennett Date: Fri, 23 May 2025 15:08:26 -0500 Subject: [PATCH 15/20] wip Signed-off-by: bennett --- package.json | 1 + src/utils/AnchorUtils.ts | 14 ++++++++++++++ src/utils/CCTPUtils.ts | 33 +++++++++++++++++++++++++-------- src/utils/index.ts | 1 + 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 src/utils/AnchorUtils.ts diff --git a/package.json b/package.json index ba362a2b6f..77da919f18 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@across-protocol/sdk": "4.2.5", "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", + "@coral-xyz/anchor": "^0.31.1", "@defi-wonderland/smock": "^2.3.5", "@eth-optimism/sdk": "^3.3.2", "@ethersproject/abi": "^5.7.0", diff --git a/src/utils/AnchorUtils.ts b/src/utils/AnchorUtils.ts new file mode 100644 index 0000000000..96b6d1ef49 --- /dev/null +++ b/src/utils/AnchorUtils.ts @@ -0,0 +1,14 @@ +import { getNodeUrlList, CHAIN_IDs } from "./"; +import { AnchorProvider, Program, Idl, web3, Wallet } from "@coral-xyz/anchor"; + +export function getAnchorProgram(idl: Idl): Program { + const provider = getAnchorProvider(); + return new Program(idl, provider); +} + +export function getAnchorProvider(): AnchorProvider { + const nodeUrlList = getNodeUrlList(CHAIN_IDs.SOLANA); + return new AnchorProvider(new web3.Connection(Object.values(nodeUrlList)[0]), { + publicKey: new web3.PublicKey("CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd"), + } as unknown as Wallet); +} diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index a4d621d8df..f9f46fb76a 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -1,5 +1,5 @@ import { utils } from "@across-protocol/sdk"; -import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; +import { TokenMessengerMinterIdl, MessageTransmitterIdl } from "@across-protocol/contracts"; import { PUBLIC_NETWORKS, CHAIN_IDs, TOKEN_SYMBOLS_MAP, CCTP_NO_DOMAIN } from "@across-protocol/constants"; import axios from "axios"; import { Contract, ethers } from "ethers"; @@ -17,9 +17,12 @@ import { import { isDefined } from "./TypeGuards"; import { getCachedProvider, getSvmProvider } from "./ProviderUtils"; import { EventSearchConfig, paginatedEventQuery } from "./EventUtils"; +import { getAnchorProgram } from "./AnchorUtils"; import { findLast } from "lodash"; import { Log } from "../interfaces"; +import { BN } from "@coral-xyz/anchor"; + type CCTPDeposit = { nonceHash: string; amount: string; @@ -249,7 +252,7 @@ async function getCCTPDepositEventsWithStatus( }; } const processed = chainIsSvm(destinationChainId) - ? await _hasCCTPMessageBeenProcessedSvm(deposit.nonceHash) + ? await _hasCCTPMessageBeenProcessedSvm(0, 0) : await _hasCCTPMessageBeenProcessed(deposit.nonceHash, messageTransmitterContract); if (!processed) { return { @@ -376,12 +379,25 @@ async function _hasCCTPMessageBeenProcessed(nonceHash: string, contract: ethers. } // A CCTP message is processed if `isNonceUsed` is true on the message transmitter. -async function _hasCCTPMessageBeenProcessedSvm(nonceHash: string): Promise { - // const provider = getSvmProvider(); - // const { address } = getCctpTokenMessenger(l2ChainId, sourceChainId); - // @todo - const messageProcessedResult = await Promise.resolve(1); - return messageProcessedResult === 1; +async function _hasCCTPMessageBeenProcessedSvm(nonce: number, sourceDomain: number): Promise { + // Specifically retrieve the Solana MessageTransmitter contract. + const program = getAnchorProgram(MessageTransmitterIdl); + const noncePda = await program.methods + .getNoncePda({ + nonce: new BN(nonce), + sourceDomain, + }) + .accounts({ + messageTransmitter: MessageTransmitterIdl.address, + }) + .view(); + const nonceUsed = await program.methods + .isNonceUsed(1) + .accounts({ + usedNonces: noncePda, + }) + .view(); + return nonceUsed; } async function _getCCTPDepositEventsSvm( @@ -394,6 +410,7 @@ async function _getCCTPDepositEventsSvm( // Get the `DepositForBurn` events on Solana. const provider = getSvmProvider(); const { address } = getCctpTokenMessenger(l2ChainId, sourceChainId); + const eventClient = await SvmCpiEventsClient.createFor(provider, address, TokenMessengerMinterIdl); const depositForBurnEvents = await eventClient.queryDerivedAddressEvents( "DepositForBurn", diff --git a/src/utils/index.ts b/src/utils/index.ts index 09e3ee32ea..724353a404 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -49,6 +49,7 @@ export { } from "@across-protocol/contracts"; // Utils specifically for this bot. +export * from "./AnchorUtils"; export * from "./SDKUtils"; export * from "./chains"; export * from "./fsUtils"; From a0bc7e029b85555b8aeb3ee2cee8b13dc775c513 Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 27 May 2025 14:00:02 -0500 Subject: [PATCH 16/20] wip Signed-off-by: bennett --- package.json | 4 +- src/adapter/bridges/SolanaUsdcCCTPBridge.ts | 17 +- src/clients/TokenClient.ts | 5 - src/clients/bridges/AdapterManager.ts | 8 - src/finalizer/utils/cctp/l1ToL2.ts | 57 ++- src/utils/AnchorUtils.ts | 42 ++- src/utils/CCTPUtils.ts | 47 +-- src/utils/SvmSignerUtils.ts | 27 +- src/utils/index.ts | 10 +- yarn.lock | 374 +------------------- 10 files changed, 145 insertions(+), 446 deletions(-) diff --git a/package.json b/package.json index 58a1c65e26..3c694ed712 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "@arbitrum/sdk": "^4.0.2", "@consensys/linea-sdk": "^0.2.1", "@coral-xyz/anchor": "^0.31.1", - "@coral-xyz/borsh": "^0.30.1", "@defi-wonderland/smock": "^2.3.5", "@eth-optimism/sdk": "^3.3.2", "@ethersproject/abi": "^5.7.0", @@ -28,11 +27,11 @@ "@maticnetwork/maticjs": "^3.6.0", "@maticnetwork/maticjs-ethers": "^1.0.3", "@openzeppelin/hardhat-upgrades": "^1.28.0", - "@solana/web3.js": "2.0.0", "@uma/common": "2.33.0", "@uma/logger": "^1.3.0", "axios": "^1.7.4", "binance-api-node": "0.12.7", + "bs58": "^6.0.0", "dotenv": "^16.3.1", "ethers": "^5.7.2", "hardhat": "^2.14.0", @@ -43,6 +42,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", + "tweetnacl": "1.0.0", "viem": "^2.26.1", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" diff --git a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts index 67e292fec7..de55b4cd30 100644 --- a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts +++ b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts @@ -15,12 +15,16 @@ import { ZERO_BYTES, SVMProvider, isDefined, + getSvmSignerFromEvmSigner, + Wallet, + ethers, } from "../../utils"; import { processEvent } from "../utils"; import { getCctpTokenMessenger, isCctpV2L2ChainId } from "../../utils/CCTPUtils"; import { CCTP_NO_DOMAIN } from "@across-protocol/constants"; import { arch } from "@across-protocol/sdk"; import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; +import bs58 from "bs58"; type MintAndWithdrawData = { mintRecipient: string; @@ -36,6 +40,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { // this bridge holds onto the client promise and lazily evaluates it for when it needs to use it (in `queryL2BridgeFinalizationEvents`). private readonly solanaEventsClientPromise: Promise; private solanaEventsClient: arch.svm.SvmCpiEventsClient; + private svmAddress: string; constructor(l2chainId: number, hubChainId: number, l1Signer: Signer, l2Provider: SVMProvider) { super(l2chainId, hubChainId, l1Signer, [EvmAddress.from(getCctpTokenMessenger(l2chainId, hubChainId).address)]); @@ -55,6 +60,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { l2Address, TokenMessengerMinterIdl ); + this.svmAddress = this._getEquivalentSvmAddress(); this.l1UsdcTokenAddress = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId]); } @@ -75,6 +81,8 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { amount: BigNumber ): Promise { assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); + const signer = await this.l1Signer.getAddress(); + assert(compareAddressesSimple(signer, toAddress.toEvmAddress()), "Cannot rebalance to a non-signer address"); amount = amount.gt(this.CCTP_MAX_SEND_AMOUNT) ? this.CCTP_MAX_SEND_AMOUNT : amount; return Promise.resolve({ contract: this.getL1Bridge(), @@ -83,13 +91,13 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { ? [ amount, this.l2DestinationDomain, - toAddress.toBytes32(), + this._getEquivalentSvmAddress(), this.l1UsdcTokenAddress.toAddress(), ZERO_BYTES, // Anyone can finalize the message on domain when this is set to bytes32(0) 0, // maxFee set to 0 so this will be a "standard" speed transfer 2000, // Hardcoded minFinalityThreshold value for standard transfer ] - : [amount, this.l2DestinationDomain, toAddress.toBytes32(), this.l1UsdcTokenAddress.toAddress()], + : [amount, this.l2DestinationDomain, this._getEquivalentSvmAddress(), this.l1UsdcTokenAddress.toAddress()], }); } @@ -146,4 +154,9 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { .filter(isDefined), }; } + + _getEquivalentSvmAddress(): string { + const svmSigner = getSvmSignerFromEvmSigner(this.l1Signer as Wallet); + return ethers.utils.hexlify(bs58.decode(svmSigner.publicKey.toBase58())); + } } diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index ea377a9e79..b0d7b835d7 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -4,7 +4,6 @@ import { CachingMechanismInterface, L1Token, Deposit } from "../interfaces"; import { BigNumber, bnZero, - chainIsEvm, Contract, dedupArray, ERC20, @@ -224,10 +223,6 @@ export class TokenClient { chainId: number, hubPoolTokens: L1Token[] ): Promise> { - if (!chainIsEvm(chainId)) { - return {}; // @todo - } - const spokePoolClient = this.spokePoolClients[chainId]; if (isEVMSpokePoolClient(spokePoolClient)) { diff --git a/src/clients/bridges/AdapterManager.ts b/src/clients/bridges/AdapterManager.ts index 218891d69e..4c8f0501ef 100644 --- a/src/clients/bridges/AdapterManager.ts +++ b/src/clients/bridges/AdapterManager.ts @@ -72,10 +72,6 @@ export class AdapterManager { return {}; } // Special case for the EthereumAdapter - if (!chainIsEvm(chainId)) { - return; // @todo - } - return Object.fromEntries( SUPPORTED_TOKENS[chainId]?.map((symbol) => { const spokePoolClient = spokePoolClients[chainId]; @@ -122,10 +118,6 @@ export class AdapterManager { ); }; Object.values(this.spokePoolClients).map(({ chainId }) => { - if (!chainIsEvm(chainId)) { - return; // @todo - } - // Instantiate a generic adapter and supply all network-specific configurations. this.adapters[chainId] = new BaseChainAdapter( spokePoolClients, diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index 8b58953d3c..65e402ff69 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -1,4 +1,5 @@ import { TransactionRequest } from "@ethersproject/abstract-provider"; +import { MessageTransmitterIdl, TokenMessengerMinterIdl } from "@across-protocol/contracts"; import { ethers } from "ethers"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; import { @@ -13,6 +14,11 @@ import { winston, convertFromWei, isEVMSpokePoolClient, + getAnchorProgram, + Wallet, + mapAsync, + getSvmSignerFromEvmSigner, + toPublicKey, } from "../../../utils"; import { AttestedCCTPDepositEvent, @@ -21,6 +27,7 @@ import { getCctpMessageTransmitter, } from "../../../utils/CCTPUtils"; import { FinalizerPromise, CrossChainMessage } from "../../types"; +import { web3, utils, BN } from "@coral-xyz/anchor"; export async function cctpL1toL2Finalizer( logger: winston.Logger, @@ -30,7 +37,7 @@ export async function cctpL1toL2Finalizer( l1SpokePoolClient: SpokePoolClient, senderAddresses: string[] ): Promise { - assert(isEVMSpokePoolClient(l1SpokePoolClient) && isEVMSpokePoolClient(l2SpokePoolClient)); + assert(isEVMSpokePoolClient(l1SpokePoolClient)); const searchConfig: EventSearchConfig = { from: l1SpokePoolClient.eventSearchConfig.from, to: l1SpokePoolClient.latestHeightSearched, @@ -69,6 +76,13 @@ export async function cctpL1toL2Finalizer( }; } else { // If the l2SpokePoolClient is not an EVM client, then we must have send the finalization here, since we cannot return SVM calldata. + const signatures = await finalizeSvmWithdrawals(unprocessedMessages, hubPoolClient.hubPool.signer); + const amountFinalized = unprocessedMessages.reduce((acc, event) => acc + Number(event.amount), 0); + logger.info({ + at: `Finalizer#CCTPL1ToL2Finalizer:${l2SpokePoolClient.chainId}`, + message: `Finalized ${unprocessedMessages.length} deposits on Solana for ${amountFinalized} USDC.`, + signatures, + }); return { crossChainMessages: [], callData: [], @@ -121,3 +135,44 @@ async function generateDepositData( destinationChainId, })); } + +/** + * Finalizes CCTP deposits on Solana. + * @param deposits The CCTP deposits to withdraw on Solana. + * @param signer A base signer to be converted into a Solana signer. + * @returns A list of executed transaction signatures. + */ +async function finalizeSvmWithdrawals(deposits: AttestedCCTPDepositEvent[], signer: Signer): Promise { + const [svmSigner, messageTransmitterProgram] = await Promise.all([ + getSvmSignerFromEvmSigner(signer as Wallet), + getAnchorProgram(MessageTransmitterIdl, signer as Wallet), + ]); + const messageTransmitter = toPublicKey(MessageTransmitterIdl.address); + const tokenMessengerMinter = toPublicKey(TokenMessengerMinterIdl.address); + const authorityPda = web3.PublicKey.findProgramAddressSync( + [Buffer.from(utils.bytes.utf8.encode("sender_authority"))], + tokenMessengerMinter + ); + return mapAsync(deposits, async (deposit) => { + const noncePda = await messageTransmitterProgram.methods + .getNoncePda({ nonce: new BN(deposit.log.args.nonce.toNumber()), sourceDomain: deposit.sourceDomain }) + .accounts({ messageTransmitter }) + .view(); + + return messageTransmitterProgram.methods + .receiveMessage({ + message: Buffer.from(deposit.messageBytes.slice(2), "hex"), + attestation: Buffer.from(deposit.attestation.slice(2), "hex"), + }) + .accounts({ + payer: svmSigner.publicKey, + caller: svmSigner.publicKey, + authorityPda, + messageTransmitter, + usedNonces: noncePda, + receiver: tokenMessengerMinter, + systemProgram: web3.SystemProgram.programId, + }) + .rpc(); + }); +} diff --git a/src/utils/AnchorUtils.ts b/src/utils/AnchorUtils.ts index 96b6d1ef49..ae001b80e8 100644 --- a/src/utils/AnchorUtils.ts +++ b/src/utils/AnchorUtils.ts @@ -1,14 +1,40 @@ -import { getNodeUrlList, CHAIN_IDs } from "./"; -import { AnchorProvider, Program, Idl, web3, Wallet } from "@coral-xyz/anchor"; +import { + getNodeUrlList, + CHAIN_IDs, + Wallet, + DEFAULT_SIMULATED_RELAYER_ADDRESS_SVM, + isDefined, + getSvmSignerFromEvmSigner, +} from "./"; +import { AnchorProvider, Program, Idl, web3, Wallet as SolanaWallet } from "@coral-xyz/anchor"; -export function getAnchorProgram(idl: Idl): Program { - const provider = getAnchorProvider(); +export async function getAnchorProgram(idl: Idl, signer?: Wallet): Promise { + const wallet = isDefined(signer) + ? new SolanaWallet(await getSvmSignerFromEvmSigner(signer)) + : (AnchorVoidSigner(new web3.PublicKey(DEFAULT_SIMULATED_RELAYER_ADDRESS_SVM)) as SolanaWallet); + const provider = getAnchorProvider(wallet); return new Program(idl, provider); } -export function getAnchorProvider(): AnchorProvider { +export function getAnchorProvider(wallet: SolanaWallet): AnchorProvider { const nodeUrlList = getNodeUrlList(CHAIN_IDs.SOLANA); - return new AnchorProvider(new web3.Connection(Object.values(nodeUrlList)[0]), { - publicKey: new web3.PublicKey("CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd"), - } as unknown as Wallet); + return new AnchorProvider(new web3.Connection(Object.values(nodeUrlList)[0]), wallet); } + +export function toPublicKey(pubkey: string): web3.PublicKey { + return new web3.PublicKey(pubkey); +} + +const AnchorVoidSigner = (publicKey: web3.PublicKey) => { + return { + publicKey, + signTransaction: async (_tx: web3.Transaction): Promise => { + _tx; + throw new Error("Cannot sign transaction with a void signer"); + }, + signAllTransactions: async (_txs: web3.Transaction[]): Promise => { + _txs; + throw new Error("Cannot sign transactions with a void signer"); + }, + }; +}; diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index f9f46fb76a..b536eed84d 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -20,8 +20,7 @@ import { EventSearchConfig, paginatedEventQuery } from "./EventUtils"; import { getAnchorProgram } from "./AnchorUtils"; import { findLast } from "lodash"; import { Log } from "../interfaces"; - -import { BN } from "@coral-xyz/anchor"; +import { BN, web3 } from "@coral-xyz/anchor"; type CCTPDeposit = { nonceHash: string; @@ -192,8 +191,8 @@ async function getCCTPDepositEvents( // Step 4. [Optional] Verify that decoded message matches the DepositForBurn event. We can skip this step // if we find this reduces performance. if ( - !compareAddressesSimple(decodedSender, _depositEvent.args.depositor) || - !compareAddressesSimple(decodedRecipient, cctpBytes32ToAddress(_depositEvent.args.mintRecipient)) || + !compareAddressesSimple(decodedSender, cctpAddressToBytes32(_depositEvent.args.depositor)) || + !compareAddressesSimple(decodedRecipient, cctpAddressToBytes32(_depositEvent.args.mintRecipient)) || !BigNumber.from(decodedAmount).eq(_depositEvent.args.amount) || decodedSourceDomain !== getCctpDomainForChainId(sourceChainId) || decodedDestinationDomain !== getCctpDomainForChainId(destinationChainId) @@ -252,7 +251,7 @@ async function getCCTPDepositEventsWithStatus( }; } const processed = chainIsSvm(destinationChainId) - ? await _hasCCTPMessageBeenProcessedSvm(0, 0) + ? await _hasCCTPMessageBeenProcessedSvm(deposit.log.args.nonce.toNumber(), deposit.sourceDomain) : await _hasCCTPMessageBeenProcessed(deposit.nonceHash, messageTransmitterContract); if (!processed) { return { @@ -381,23 +380,27 @@ async function _hasCCTPMessageBeenProcessed(nonceHash: string, contract: ethers. // A CCTP message is processed if `isNonceUsed` is true on the message transmitter. async function _hasCCTPMessageBeenProcessedSvm(nonce: number, sourceDomain: number): Promise { // Specifically retrieve the Solana MessageTransmitter contract. - const program = getAnchorProgram(MessageTransmitterIdl); - const noncePda = await program.methods - .getNoncePda({ - nonce: new BN(nonce), - sourceDomain, - }) - .accounts({ - messageTransmitter: MessageTransmitterIdl.address, - }) - .view(); - const nonceUsed = await program.methods - .isNonceUsed(1) - .accounts({ - usedNonces: noncePda, - }) - .view(); - return nonceUsed; + try { + const program = await getAnchorProgram(MessageTransmitterIdl); + const noncePda = await program.methods + .getNoncePda({ + nonce: new BN(nonce), + sourceDomain, + }) + .accounts({ + messageTransmitter: new web3.PublicKey(MessageTransmitterIdl.address), + }) + .view(); + const nonceUsed = await program.methods + .isNonceUsed(noncePda) + .accounts({ + usedNonces: noncePda, + }) + .view(); + return nonceUsed; + } catch { + return false; + } } async function _getCCTPDepositEventsSvm( diff --git a/src/utils/SvmSignerUtils.ts b/src/utils/SvmSignerUtils.ts index 815a2fc7e2..0835d173fa 100644 --- a/src/utils/SvmSignerUtils.ts +++ b/src/utils/SvmSignerUtils.ts @@ -1,28 +1,13 @@ -import { - createKeyPairSignerFromPrivateKeyBytes, - KeyPairSigner, - createKeyPairFromBytes, - createSignerFromKeyPair, - Wallet, -} from "./"; -import fs from "fs"; +import { web3 } from "@coral-xyz/anchor"; +import { Wallet } from "./"; -export async function getSvmSignerFromEvmSigner(evmSigner: Wallet): Promise { +export function getSvmSignerFromEvmSigner(evmSigner: Wallet): web3.Keypair { // Extract the private key from the evm signer and use it to create a svm signer. const evmPrivateKey = evmSigner._signingKey().privateKey; - return await getSvmSignerFromPrivateKey(evmPrivateKey); + return getSvmSignerFromPrivateKey(evmPrivateKey); } -export async function getSvmSignerFromFile(filePath: string): Promise { - const keypairFile = fs.readFileSync(filePath); - const keypairBytes = new Uint8Array(JSON.parse(keypairFile.toString())); - - // Create a KeyPairSigner from the bytes. - const keys = await createKeyPairFromBytes(keypairBytes); - return await createSignerFromKeyPair(keys); -} - -export async function getSvmSignerFromPrivateKey(privateKey: string): Promise { +export function getSvmSignerFromPrivateKey(privateKey: string): web3.Keypair { const privateKeyAsBytes = Uint8Array.from(Buffer.from(privateKey.slice(2), "hex")); - return await createKeyPairSignerFromPrivateKeyBytes(privateKeyAsBytes); + return web3.Keypair.fromSeed(privateKeyAsBytes); } diff --git a/src/utils/index.ts b/src/utils/index.ts index b1a6ae285e..dc7f9bb5b1 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,7 +6,7 @@ import winston from "winston"; import assert from "assert"; export { winston, assert }; -export const { MAX_SAFE_ALLOWANCE, ZERO_BYTES } = sdkConstants; +export const { MAX_SAFE_ALLOWANCE, ZERO_BYTES, DEFAULT_SIMULATED_RELAYER_ADDRESS_SVM } = sdkConstants; export const { AddressZero: ZERO_ADDRESS, MaxUint256: MAX_UINT_VAL } = ethersConstants; export { @@ -35,13 +35,6 @@ export { TOKEN_EQUIVALENCE_REMAPPING, } from "@across-protocol/constants"; -export { - createKeyPairSignerFromPrivateKeyBytes, - KeyPairSigner, - createSignerFromKeyPair, - createKeyPairFromBytes, -} from "@solana/web3.js"; - // TypeChain exports used in the bot. export { getContractInfoFromAddress, @@ -62,6 +55,7 @@ export * from "./chains"; export * from "./fsUtils"; export * from "./ProviderUtils"; export * from "./SignerUtils"; +export * from "./SvmSignerUtils"; export * from "./BlockUtils"; export * from "./EventUtils"; export * from "./FillUtils"; diff --git a/yarn.lock b/yarn.lock index 80c07397ee..70cb631406 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2751,18 +2751,6 @@ resolved "https://registry.yarnpkg.com/@solana-program/token/-/token-0.5.1.tgz#10e327df23f05a7f892fd33a9b6418f17dd62296" integrity sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag== -"@solana/accounts@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/accounts/-/accounts-2.0.0.tgz#4d5806079a69d1a15bc85eb4072913cf848ae8f7" - integrity sha512-1CE4P3QSDH5x+ZtSthMY2mn/ekROBnlT3/4f3CHDJicDvLQsgAq2yCvGHsYkK3ZA0mxhFLuhJVjuKASPnmG1rQ== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/rpc-spec" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/accounts@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/accounts/-/accounts-2.1.0.tgz#23fa5ba20dd7af9bfb84abc638096378b62c7276" @@ -2775,16 +2763,6 @@ "@solana/rpc-spec" "2.1.0" "@solana/rpc-types" "2.1.0" -"@solana/addresses@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/addresses/-/addresses-2.0.0.tgz#d1b01a38e0b48d7e4fea223821655a0c2b903c28" - integrity sha512-8n3c/mUlH1/z+pM8e7OJ6uDSXw26Be0dgYiokiqblO66DGQ0d+7pqFUFZ5pEGjJ9PU2lDTSfY8rHf4cemOqwzQ== - dependencies: - "@solana/assertions" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/addresses@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/addresses/-/addresses-2.1.0.tgz#28b07b46c41c101a1c1d894b905f27b847c095a8" @@ -2795,13 +2773,6 @@ "@solana/codecs-strings" "2.1.0" "@solana/errors" "2.1.0" -"@solana/assertions@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/assertions/-/assertions-2.0.0.tgz#b02fc874a890f252c4595a0e35deeb1719d5f02b" - integrity sha512-NyPPqZRNGXs/GAjfgsw7YS6vCTXWt4ibXveS+ciy5sdmp/0v3pA6DlzYjleF9Sljrew0IiON15rjaXamhDxYfQ== - dependencies: - "@solana/errors" "2.0.0" - "@solana/assertions@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/assertions/-/assertions-2.1.0.tgz#ba32442e5a70ca8ddaec2d857dde1a57c5569689" @@ -2826,13 +2797,6 @@ dependencies: buffer "~6.0.3" -"@solana/codecs-core@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0.tgz#31d4a6acce9ac49f786939c4e564adf9a68c56ef" - integrity sha512-qCG+3hDU5Pm8V6joJjR4j4Zv9md1z0RaecniNDIkEglnxmOUODnmPLWbtOjnDylfItyuZeDihK8hkewdj8cUtw== - dependencies: - "@solana/errors" "2.0.0" - "@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" @@ -2847,15 +2811,6 @@ dependencies: "@solana/errors" "2.1.0" -"@solana/codecs-data-structures@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0.tgz#0a06b8646634dcf44a7b1d968fe8d9218c3cb745" - integrity sha512-N98Y4jsrC/XeOgqrfsGqcOFIaOoMsKdAxOmy5oqVaEN67YoGSLNC9ROnqamOAOrsZdicTWx9/YLKFmQi9DPh1A== - dependencies: - "@solana/codecs-core" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/errors" "2.0.0" - "@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" @@ -2874,14 +2829,6 @@ "@solana/codecs-numbers" "2.1.0" "@solana/errors" "2.1.0" -"@solana/codecs-numbers@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0.tgz#c08250968fa1cbfab076367b650269271061c646" - integrity sha512-r66i7VzJO1MZkQWZIAI6jjJOFVpnq0+FIabo2Z2ZDtrArFus/SbSEv543yCLeD2tdR/G/p+1+P5On10qF50Y1Q== - dependencies: - "@solana/codecs-core" "2.0.0" - "@solana/errors" "2.0.0" - "@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" @@ -2898,15 +2845,6 @@ "@solana/codecs-core" "2.1.0" "@solana/errors" "2.1.0" -"@solana/codecs-strings@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0.tgz#46e728adee9a4737c3ee811af452948aab31cbd4" - integrity sha512-dNqeCypsvaHcjW86H0gYgAZGGkKVBeKVeh7WXlOZ9kno7PeQ2wNkpccyzDfuzaIsKv+HZUD3v/eo86GCvnKazQ== - dependencies: - "@solana/codecs-core" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/errors" "2.0.0" - "@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" @@ -2925,17 +2863,6 @@ "@solana/codecs-numbers" "2.1.0" "@solana/errors" "2.1.0" -"@solana/codecs@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0.tgz#2a3f272932eebad5b8592e6263b068c7d0761e7f" - integrity sha512-xneIG5ppE6WIGaZCK7JTys0uLhzlnEJUdBO8nRVIyerwH6aqCfb0fGe7q5WNNYAVDRSxC0Pc1TDe1hpdx3KWmQ== - dependencies: - "@solana/codecs-core" "2.0.0" - "@solana/codecs-data-structures" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/options" "2.0.0" - "@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" @@ -2958,14 +2885,6 @@ "@solana/codecs-strings" "2.1.0" "@solana/options" "2.1.0" -"@solana/errors@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0.tgz#31c87baaf4b19aaa2a1d8bbc4dfa6efd449d7bbe" - integrity sha512-IHlaPFSy4lvYco1oHJ3X8DbchWwAwJaL/4wZKnF1ugwZ0g0re8wbABrqNOe/jyZ84VU9Z14PYM8W9oDAebdJbw== - dependencies: - chalk "^5.3.0" - commander "^12.1.0" - "@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" @@ -2982,33 +2901,16 @@ chalk "^5.3.0" commander "^13.1.0" -"@solana/fast-stable-stringify@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/fast-stable-stringify/-/fast-stable-stringify-2.0.0.tgz#ac06b304ee3e050c171bcbe885e91772e22e06fb" - integrity sha512-EsIx9z+eoxOmC+FpzhEb+H67CCYTbs/omAqXD4EdEYnCHWrI1li1oYBV+NoKzfx8fKlX+nzNB7S/9kc4u7Etpw== - "@solana/fast-stable-stringify@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/fast-stable-stringify/-/fast-stable-stringify-2.1.0.tgz#df24390539968c2e7157c0757961b097fc7ee1fa" integrity sha512-a8vR92qbe/VsvQ1BpN3PIEwnoHD2fTHEwCJh9GG58z3R15RIjk73gc0khjcdg4U1tZwTJqWkvk8SbDIgGdOgMA== -"@solana/functional@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/functional/-/functional-2.0.0.tgz#6e2468cc2ec334ee3c39609130520b3a5c8f9bc0" - integrity sha512-Sj+sLiUTimnMEyGnSLGt0lbih2xPDUhxhonnrIkPwA+hjQ3ULGHAxeevHU06nqiVEgENQYUJ5rCtHs4xhUFAkQ== - "@solana/functional@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/functional/-/functional-2.1.0.tgz#01c80d870479a8a6d14dd511fd0ae0262fc2e09d" integrity sha512-RVij8Av4F2uUOFcEC8n9lgD72e9gQMritmGHhMh+G91Xops4I6Few+oQ++XgSTiL2t3g3Cs0QZ13onZ0FL45FQ== -"@solana/instructions@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/instructions/-/instructions-2.0.0.tgz#4062a2211b376dc2a9cc5a25ad50f1de0ea44e5b" - integrity sha512-MiTEiNF7Pzp+Y+x4yadl2VUcNHboaW5WP52psBuhHns3GpbbruRv5efMpM9OEQNe1OsN+Eg39vjEidX55+P+DQ== - dependencies: - "@solana/errors" "2.0.0" - "@solana/instructions@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/instructions/-/instructions-2.1.0.tgz#67ac468ff9293cf881392c8180950c4023b8dff0" @@ -3017,16 +2919,6 @@ "@solana/codecs-core" "2.1.0" "@solana/errors" "2.1.0" -"@solana/keys@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/keys/-/keys-2.0.0.tgz#b4b31815265a003b8840028979e83e1b723ee02c" - integrity sha512-SSLSX8BXRvfLKBqsmBghmlhMKpwHeWd5CHi5zXgTS1BRrtiU6lcrTVC9ie6B+WaNNq7oe3e6K5bdbhu3fFZ+0g== - dependencies: - "@solana/assertions" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/keys@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/keys/-/keys-2.1.0.tgz#c0f8940bfa4e2fa052aa1b955cbdb1e742e31fe8" @@ -3061,17 +2953,6 @@ "@solana/transaction-messages" "2.1.0" "@solana/transactions" "2.1.0" -"@solana/options@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0.tgz#0dbbecd8511c1e600cad8615a836c6e06c3191d5" - integrity sha512-OVc4KnYosB8oAukQ/htgrxXSxlUP6gUu5Aau6d/BgEkPQzWd/Pr+w91VWw3i3zZuu2SGpedbyh05RoJBe/hSXA== - dependencies: - "@solana/codecs-core" "2.0.0" - "@solana/codecs-data-structures" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.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" @@ -3094,14 +2975,6 @@ "@solana/codecs-strings" "2.1.0" "@solana/errors" "2.1.0" -"@solana/programs@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/programs/-/programs-2.0.0.tgz#1c0fa1c98a8cf6fab3ac722fe768e110057eeaf9" - integrity sha512-JPIKB61pWfODnsvEAaPALc6vR5rn7kmHLpFaviWhBtfUlEVgB8yVTR0MURe4+z+fJCPRV5wWss+svA4EeGDYzQ== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/programs@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/programs/-/programs-2.1.0.tgz#3ae57734a0ab6985cc016d90d1b8bde55edeee46" @@ -3110,33 +2983,11 @@ "@solana/addresses" "2.1.0" "@solana/errors" "2.1.0" -"@solana/promises@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/promises/-/promises-2.0.0.tgz#81c8ee7c706ea4c46892022666da51bb9da921ef" - integrity sha512-4teQ52HDjK16ORrZe1zl+Q9WcZdQ+YEl0M1gk59XG7D0P9WqaVEQzeXGnKSCs+Y9bnB1u5xCJccwpUhHYWq6gg== - "@solana/promises@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/promises/-/promises-2.1.0.tgz#d740e440d85533cdf796f694e90fcb557dabdde9" integrity sha512-eQJaQXA2kD4dVyifzhslV3wOvq27fwOJ4az89BQ4Cz83zPbR94xOeDShwcXrKBYqaUf6XqH5MzdEo14t4tKAFQ== -"@solana/rpc-api@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-api/-/rpc-api-2.0.0.tgz#84ab27beb3ec7416bc1aa263281a582060953450" - integrity sha512-1FwitYxwADMF/6zKP2kNXg8ESxB6GhNBNW1c4f5dEmuXuBbeD/enLV3WMrpg8zJkIaaYarEFNbt7R7HyFzmURQ== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/rpc-parsed-types" "2.0.0" - "@solana/rpc-spec" "2.0.0" - "@solana/rpc-transformers" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solana/rpc-api@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-api/-/rpc-api-2.1.0.tgz#d4efc40d6e0dc42cf99c8e21344b71378a8bbb87" @@ -3154,34 +3005,16 @@ "@solana/transaction-messages" "2.1.0" "@solana/transactions" "2.1.0" -"@solana/rpc-parsed-types@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-parsed-types/-/rpc-parsed-types-2.0.0.tgz#b83840981ce816142681d4f091a314300d4b10ab" - integrity sha512-VCeY/oKVEtBnp8EDOc5LSSiOeIOLFIgLndcxqU0ij/cZaQ01DOoHbhluvhZtU80Z3dUeicec8TiMgkFzed+WhQ== - "@solana/rpc-parsed-types@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-parsed-types/-/rpc-parsed-types-2.1.0.tgz#7e197942a1c2c96286d4e4df9db41796d4033d1a" integrity sha512-mRzHemxlWDS9p1fPQNKwL+1vEOUMG8peSUJb0X/NbM12yjowDNdzM++fkOgIyCKDPddfkcoNmNrQmr2jwjdN1Q== -"@solana/rpc-spec-types@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-spec-types/-/rpc-spec-types-2.0.0.tgz#49e46188f77aeeda0cf6f0e40117e2ba4a35cc14" - integrity sha512-G2lmhFhgtxMQd/D6B04BHGE7bm5dMZdIPQNOqVGhzNAVjrmyapD3JN2hKAbmaYPe97wLfZERw0Ux1u4Y6q7TqA== - "@solana/rpc-spec-types@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-spec-types/-/rpc-spec-types-2.1.0.tgz#3285766dffeffb6612bb837b5f9bac9d1386d595" integrity sha512-NxcZ8piXMyCdbNUL6d36QJfL2UAQEN33StlGku0ltTVe1nrokZ5WRNjSPohU1fODlNaZzTvUFzvUkM1yGCkyzw== -"@solana/rpc-spec@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-spec/-/rpc-spec-2.0.0.tgz#d0cbacd1c1dcb1a98d240488afd1e63878e7b17b" - integrity sha512-1uIDzj7vocCUqfOifjv1zAuxQ53ugiup/42edVFoQLOnJresoEZLL6WjnsJq4oCTccEAvGhUBI1WWKeZTGNxFQ== - dependencies: - "@solana/errors" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-spec@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-spec/-/rpc-spec-2.1.0.tgz#ae28b04930a80c1f79c0fb6bb18ff9ba90f7ec1e" @@ -3190,19 +3023,6 @@ "@solana/errors" "2.1.0" "@solana/rpc-spec-types" "2.1.0" -"@solana/rpc-subscriptions-api@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-2.0.0.tgz#bd2e8ce566e9bf530d678ea4733472e1da5890af" - integrity sha512-NAJQvSFXYIIf8zxsMFBCkSbZNZgT32pzPZ1V6ZAd+U2iDEjx3L+yFwoJgfOcHp8kAV+alsF2lIsGBlG4u+ehvw== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/rpc-subscriptions-spec" "2.0.0" - "@solana/rpc-transformers" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solana/rpc-subscriptions-api@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-2.1.0.tgz#7b588452aec5e9c661889737ea9f2d2d03a28a81" @@ -3216,16 +3036,6 @@ "@solana/transaction-messages" "2.1.0" "@solana/transactions" "2.1.0" -"@solana/rpc-subscriptions-channel-websocket@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-2.0.0.tgz#7bff107b03cafe7ead1cf3801d9ed8078a01217c" - integrity sha512-hSQDZBmcp2t+gLZsSBqs/SqVw4RuNSC7njiP46azyzW7oGg8X2YPV36AHGsHD12KPsc0UpT1OAZ4+AN9meVKww== - dependencies: - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/rpc-subscriptions-spec" "2.0.0" - "@solana/subscribable" "2.0.0" - "@solana/rpc-subscriptions-channel-websocket@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-2.1.0.tgz#17ca9df1a57e5f6cf7c6de5b777e561b3896e69f" @@ -3236,16 +3046,6 @@ "@solana/rpc-subscriptions-spec" "2.1.0" "@solana/subscribable" "2.1.0" -"@solana/rpc-subscriptions-spec@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-2.0.0.tgz#b476b449d917134476001c22c54fbeb69bfae4cb" - integrity sha512-VXMiI3fYtU1PkVVTXL87pcY48ZY8aCi1N6FqtxSP2xg/GASL01j1qbwyIL1OvoCqGyRgIxdd/YfaByW9wmWBhA== - dependencies: - "@solana/errors" "2.0.0" - "@solana/promises" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/subscribable" "2.0.0" - "@solana/rpc-subscriptions-spec@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-2.1.0.tgz#5675d65b60c9c96a633a01951edb3eac347d71b1" @@ -3256,23 +3056,6 @@ "@solana/rpc-spec-types" "2.1.0" "@solana/subscribable" "2.1.0" -"@solana/rpc-subscriptions@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions/-/rpc-subscriptions-2.0.0.tgz#c512b261a428f550510fe855bb751c0638547d4f" - integrity sha512-AdwMJHMrhlj7q1MPjZmVcKq3iLqMW3N0MT8kzIAP2vP+8o/d6Fn4aqGxoz2Hlfn3OYIZoYStN2VBtwzbcfEgMA== - dependencies: - "@solana/errors" "2.0.0" - "@solana/fast-stable-stringify" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/promises" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-subscriptions-api" "2.0.0" - "@solana/rpc-subscriptions-channel-websocket" "2.0.0" - "@solana/rpc-subscriptions-spec" "2.0.0" - "@solana/rpc-transformers" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/subscribable" "2.0.0" - "@solana/rpc-subscriptions@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-subscriptions/-/rpc-subscriptions-2.1.0.tgz#5b86072009682ea31d1f19b21426c7f04ed92411" @@ -3290,16 +3073,6 @@ "@solana/rpc-types" "2.1.0" "@solana/subscribable" "2.1.0" -"@solana/rpc-transformers@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-transformers/-/rpc-transformers-2.0.0.tgz#592f7a2cc18378bf29248d059d1142897edf497f" - integrity sha512-H6tN0qcqzUangowsLLQtYXKJsf1Roe3/qJ1Cy0gv9ojY9uEvNbJqpeEj+7blv0MUZfEe+rECAwBhxxRKPMhYGw== - dependencies: - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/rpc-transformers@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-transformers/-/rpc-transformers-2.1.0.tgz#216be58f9369794f74a77a17ea51e6f531d4ef83" @@ -3310,16 +3083,6 @@ "@solana/rpc-spec-types" "2.1.0" "@solana/rpc-types" "2.1.0" -"@solana/rpc-transport-http@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-transport-http/-/rpc-transport-http-2.0.0.tgz#87aecad790dfefe723262778b3c3be73d9a35426" - integrity sha512-UJLhKhhxDd1OPi8hb2AenHsDm1mofCBbhWn4bDCnH2Q3ulwYadUhcNqNbxjJPQ774VNhAf53SSI5A6PQo8IZSQ== - dependencies: - "@solana/errors" "2.0.0" - "@solana/rpc-spec" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - undici-types "^6.20.0" - "@solana/rpc-transport-http@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-transport-http/-/rpc-transport-http-2.1.0.tgz#397e0a0747d01153524e030cb6cb41a2f2cc2c04" @@ -3330,17 +3093,6 @@ "@solana/rpc-spec-types" "2.1.0" undici-types "^7.3.0" -"@solana/rpc-types@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc-types/-/rpc-types-2.0.0.tgz#332989671606914f9ab0d196cb94e83f626bef34" - integrity sha512-o1ApB9PYR0A3XjVSOh//SOVWgjDcqMlR3UNmtqciuREIBmWqnvPirdOa5EJxD3iPhfA4gnNnhGzT+tMDeDW/Kw== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/rpc-types@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc-types/-/rpc-types-2.1.0.tgz#3b4b515a1da36ffff7386f8ba831fddcebf3dde3" @@ -3352,21 +3104,6 @@ "@solana/codecs-strings" "2.1.0" "@solana/errors" "2.1.0" -"@solana/rpc@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/rpc/-/rpc-2.0.0.tgz#afc43a9be80f9c9b254da30bb31c2b3f34025c66" - integrity sha512-TumQ9DFRpib/RyaIqLVfr7UjqSo7ldfzpae0tgjM93YjbItB4Z0VcUXc3uAFvkeYw2/HIMb46Zg43mkUwozjDg== - dependencies: - "@solana/errors" "2.0.0" - "@solana/fast-stable-stringify" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/rpc-api" "2.0.0" - "@solana/rpc-spec" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-transformers" "2.0.0" - "@solana/rpc-transport-http" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/rpc@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/rpc/-/rpc-2.1.0.tgz#9320f562537da880e76843cc5c7d815fa030e7c3" @@ -3382,19 +3119,6 @@ "@solana/rpc-transport-http" "2.1.0" "@solana/rpc-types" "2.1.0" -"@solana/signers@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/signers/-/signers-2.0.0.tgz#896f5e0fc17ea8e47042cfcb1c24b225cb8def3d" - integrity sha512-JEYJS3x/iKkqPV/3b1nLpX9lHib21wQKV3fOuu1aDLQqmX9OYKrnIIITYdnFDhmvGhpEpkkbPnqu7yVaFIBYsQ== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/instructions" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solana/signers@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/signers/-/signers-2.1.0.tgz#3bdf5ec358c5243cdf49702e5d6dc260e531da34" @@ -3433,13 +3157,6 @@ "@solana/spl-token-metadata" "^0.1.6" buffer "^6.0.3" -"@solana/subscribable@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/subscribable/-/subscribable-2.0.0.tgz#6476bf253395c077f9fdbd4a9b83011734a86b06" - integrity sha512-Ex7d2GnTSNVMZDU3z6nKN4agRDDgCgBDiLnmn1hmt0iFo3alr3gRAqiqa7qGouAtYh9/29pyc8tVJCijHWJPQQ== - dependencies: - "@solana/errors" "2.0.0" - "@solana/subscribable@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/subscribable/-/subscribable-2.1.0.tgz#690880897ff4953dc79230d7fb8ed7f3fa8fd527" @@ -3447,16 +3164,6 @@ dependencies: "@solana/errors" "2.1.0" -"@solana/sysvars@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/sysvars/-/sysvars-2.0.0.tgz#60f1e3b918bfdd34420f1ca2d6458cc2538d16b7" - integrity sha512-8D4ajKcCYQsTG1p4k30lre2vjxLR6S5MftUGJnIaQObDCzGmaeA9GRti4Kk4gSPWVYFTBoj1ASx8EcEXaB3eIQ== - dependencies: - "@solana/accounts" "2.0.0" - "@solana/codecs" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/sysvars@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/sysvars/-/sysvars-2.1.0.tgz#4e2c6df16e84e3ce43d9d33b68583c9eded1ee00" @@ -3467,22 +3174,6 @@ "@solana/errors" "2.1.0" "@solana/rpc-types" "2.1.0" -"@solana/transaction-confirmation@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/transaction-confirmation/-/transaction-confirmation-2.0.0.tgz#53385e31f94ab6b1f35c25576cb478f383476c81" - integrity sha512-JkTw5gXLiqQjf6xK0fpVcoJ/aMp2kagtFSD/BAOazdJ3UYzOzbzqvECt6uWa3ConcMswQ2vXalVtI7ZjmYuIeg== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/promises" "2.0.0" - "@solana/rpc" "2.0.0" - "@solana/rpc-subscriptions" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solana/transaction-confirmation@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/transaction-confirmation/-/transaction-confirmation-2.1.0.tgz#a0e8f911c84cc2503ae20307bdc479b1adfa23a9" @@ -3499,20 +3190,6 @@ "@solana/transaction-messages" "2.1.0" "@solana/transactions" "2.1.0" -"@solana/transaction-messages@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/transaction-messages/-/transaction-messages-2.0.0.tgz#ad362eb7f4a14efab31e5dfaa65f24959030d8f8" - integrity sha512-Uc6Fw1EJLBrmgS1lH2ZfLAAKFvprWPQQzOVwZS78Pv8Whsk7tweYTK6S0Upv0nHr50rGpnORJfmdBrXE6OfNGg== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-data-structures" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/instructions" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/transaction-messages@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/transaction-messages/-/transaction-messages-2.1.0.tgz#2a38e44cc036875409da99b2ecef4a1e8eedb010" @@ -3527,23 +3204,6 @@ "@solana/instructions" "2.1.0" "@solana/rpc-types" "2.1.0" -"@solana/transactions@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/transactions/-/transactions-2.0.0.tgz#c27cb998e2c701fc49bda2cc5ca896e6067840dc" - integrity sha512-VfdTE+59WKvuBG//6iE9RPjAB+ZT2kLgY2CDHabaz6RkH6OjOkMez9fWPVa3Xtcus+YQWN1SnQoryjF/xSx04w== - dependencies: - "@solana/addresses" "2.0.0" - "@solana/codecs-core" "2.0.0" - "@solana/codecs-data-structures" "2.0.0" - "@solana/codecs-numbers" "2.0.0" - "@solana/codecs-strings" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/instructions" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@solana/transactions/-/transactions-2.1.0.tgz#d51d8ede0cc218cc03ba822929f567b4475a3508" @@ -3582,30 +3242,6 @@ rpc-websockets "^9.0.2" superstruct "^2.0.2" -"@solana/web3.js@2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-2.0.0.tgz#192918343982e1964269b3adb2567532c1e12c89" - integrity sha512-x+ZRB2/r5tVK/xw8QRbAfgPcX51G9f2ifEyAQ/J5npOO+6+MPeeCjtr5UxHNDAYs9Ypo0PN+YJATCO4vhzQJGg== - dependencies: - "@solana/accounts" "2.0.0" - "@solana/addresses" "2.0.0" - "@solana/codecs" "2.0.0" - "@solana/errors" "2.0.0" - "@solana/functional" "2.0.0" - "@solana/instructions" "2.0.0" - "@solana/keys" "2.0.0" - "@solana/programs" "2.0.0" - "@solana/rpc" "2.0.0" - "@solana/rpc-parsed-types" "2.0.0" - "@solana/rpc-spec-types" "2.0.0" - "@solana/rpc-subscriptions" "2.0.0" - "@solana/rpc-types" "2.0.0" - "@solana/signers" "2.0.0" - "@solana/sysvars" "2.0.0" - "@solana/transaction-confirmation" "2.0.0" - "@solana/transaction-messages" "2.0.0" - "@solana/transactions" "2.0.0" - "@solana/web3.js@^1.31.0", "@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0": version "1.95.4" resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.95.4.tgz#771603f60d75cf7556ad867e1fd2efae32f9ad09" @@ -16056,6 +15692,11 @@ tweetnacl-util@^0.15.0, tweetnacl-util@^0.15.1: resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== +tweetnacl@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.0.tgz#713d8b818da42068740bf68386d0479e66fc8a7b" + integrity sha512-Nvl4Z9G3mH2/OBrODQlNhZ2T+ZaNe3AQoBHn0IxH2Klv9J45QYUY5trS/Dsk+Psmn3kCxUNPjsLtgwrofpFASA== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -16253,11 +15894,6 @@ underscore@~1.13.2: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== -undici-types@^6.20.0: - version "6.21.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" - integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== - undici-types@^7.3.0: version "7.5.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.5.0.tgz#7bb9a3dec9646c8e87d299d9d16865d102cdfecd" From bbfddabae20713973d75b082f2691dae320c65af Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 27 May 2025 20:29:30 -0500 Subject: [PATCH 17/20] mostly complete finalization/rebalancing Signed-off-by: bennett --- package.json | 2 +- src/adapter/bridges/SolanaUsdcCCTPBridge.ts | 39 ++++-- src/finalizer/utils/cctp/l1ToL2.ts | 146 ++++++++++++++++++-- src/utils/CCTPUtils.ts | 21 ++- src/utils/SDKUtils.ts | 1 + yarn.lock | 2 +- 6 files changed, 179 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 3c694ed712..d93c55a34b 100644 --- a/package.json +++ b/package.json @@ -27,11 +27,11 @@ "@maticnetwork/maticjs": "^3.6.0", "@maticnetwork/maticjs-ethers": "^1.0.3", "@openzeppelin/hardhat-upgrades": "^1.28.0", + "@solana-program/token": "^0.5.0", "@uma/common": "2.33.0", "@uma/logger": "^1.3.0", "axios": "^1.7.4", "binance-api-node": "0.12.7", - "bs58": "^6.0.0", "dotenv": "^16.3.1", "ethers": "^5.7.2", "hardhat": "^2.14.0", diff --git a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts index de55b4cd30..36a0ea811a 100644 --- a/src/adapter/bridges/SolanaUsdcCCTPBridge.ts +++ b/src/adapter/bridges/SolanaUsdcCCTPBridge.ts @@ -17,14 +17,13 @@ import { isDefined, getSvmSignerFromEvmSigner, Wallet, - ethers, + getAssociatedTokenAddress, } from "../../utils"; import { processEvent } from "../utils"; import { getCctpTokenMessenger, isCctpV2L2ChainId } from "../../utils/CCTPUtils"; import { CCTP_NO_DOMAIN } from "@across-protocol/constants"; import { arch } from "@across-protocol/sdk"; import { TokenMessengerMinterIdl } from "@across-protocol/contracts"; -import bs58 from "bs58"; type MintAndWithdrawData = { mintRecipient: string; @@ -35,6 +34,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { private CCTP_MAX_SEND_AMOUNT = toBN(1_000_000_000_000); // 1MM USDC. private IS_CCTP_V2 = false; private readonly l1UsdcTokenAddress: EvmAddress; + private readonly l2UsdcTokenAddress: SvmAddress; private readonly solanaMessageTransmitter: SvmAddress; // We need the constructor to operate in a synchronous context, but the call to construct an event client is asynchronous, so // this bridge holds onto the client promise and lazily evaluates it for when it needs to use it (in `queryL2BridgeFinalizationEvents`). @@ -60,9 +60,8 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { l2Address, TokenMessengerMinterIdl ); - this.svmAddress = this._getEquivalentSvmAddress(); - this.l1UsdcTokenAddress = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId]); + this.l2UsdcTokenAddress = SvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[this.l2chainId]); } private get l2DestinationDomain(): number { @@ -83,6 +82,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); const signer = await this.l1Signer.getAddress(); assert(compareAddressesSimple(signer, toAddress.toEvmAddress()), "Cannot rebalance to a non-signer address"); + const associatedTokenAddress = await this._getAssociatedTokenAddress(); amount = amount.gt(this.CCTP_MAX_SEND_AMOUNT) ? this.CCTP_MAX_SEND_AMOUNT : amount; return Promise.resolve({ contract: this.getL1Bridge(), @@ -91,13 +91,13 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { ? [ amount, this.l2DestinationDomain, - this._getEquivalentSvmAddress(), + associatedTokenAddress.toBytes32(), this.l1UsdcTokenAddress.toAddress(), ZERO_BYTES, // Anyone can finalize the message on domain when this is set to bytes32(0) 0, // maxFee set to 0 so this will be a "standard" speed transfer 2000, // Hardcoded minFinalityThreshold value for standard transfer ] - : [amount, this.l2DestinationDomain, this._getEquivalentSvmAddress(), this.l1UsdcTokenAddress.toAddress()], + : [amount, this.l2DestinationDomain, associatedTokenAddress.toBytes32(), this.l1UsdcTokenAddress.toAddress()], }); } @@ -107,13 +107,20 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { toAddress: Address, eventConfig: EventSearchConfig ): Promise { + const signer = await this.l1Signer.getAddress(); + // @todo. We can only track EOA transfers of the signer of the bot since we cannot translate an EVM address to an SVM token account + // unless we have knowledge of the private key. + if (fromAddress.toAddress() !== signer) { + return {}; + } + const associatedTokenAddress = await this._getAssociatedTokenAddress(); assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); const eventFilterArgs = this.IS_CCTP_V2 ? [this.l1UsdcTokenAddress.toAddress(), undefined, fromAddress.toAddress()] : [undefined, this.l1UsdcTokenAddress.toAddress(), undefined, fromAddress.toAddress()]; const eventFilter = this.getL1Bridge().filters.DepositForBurn(...eventFilterArgs); const events = (await paginatedEventQuery(this.getL1Bridge(), eventFilter, eventConfig)).filter((event) => - compareAddressesSimple(event.args.mintRecipient, toAddress.toBytes32()) + compareAddressesSimple(event.args.mintRecipient, associatedTokenAddress.toBytes32()) ); return { [this.resolveL2TokenAddress(l1Token)]: events.map((event) => processEvent(event, "amount")), @@ -126,6 +133,14 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { toAddress: Address, eventConfig: EventSearchConfig ): Promise { + const signer = await this.l1Signer.getAddress(); + // @todo. We can only track EOA transfers of the signer of the bot since we cannot translate an EVM address to an SVM token account + // unless we have knowledge of the private key. + if (fromAddress.toAddress() !== signer) { + return {}; + } + const associatedTokenAddress = await this._getAssociatedTokenAddress(); + // Lazily evaluate the events client. this.solanaEventsClient ??= await this.solanaEventsClientPromise; assert(compareAddressesSimple(l1Token.toAddress(), TOKEN_SYMBOLS_MAP.USDC.addresses[this.hubChainId])); @@ -139,7 +154,7 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { [this.resolveL2TokenAddress(l1Token)]: l2FinalizationEvents .map((event) => { const data = event.data as MintAndWithdrawData; - if (String(data.mintRecipient) !== toAddress.toBase58()) { + if (String(data.mintRecipient) !== associatedTokenAddress.toBase58()) { return undefined; } return { @@ -155,8 +170,12 @@ export class SolanaUsdcCCTPBridge extends BaseBridgeAdapter { }; } - _getEquivalentSvmAddress(): string { + async _getAssociatedTokenAddress(): Promise
{ const svmSigner = getSvmSignerFromEvmSigner(this.l1Signer as Wallet); - return ethers.utils.hexlify(bs58.decode(svmSigner.publicKey.toBase58())); + const associatedTokenAddress = await getAssociatedTokenAddress( + SvmAddress.from(svmSigner.publicKey.toBase58()), + this.l2UsdcTokenAddress + ); + return SvmAddress.from(associatedTokenAddress as string); } } diff --git a/src/finalizer/utils/cctp/l1ToL2.ts b/src/finalizer/utils/cctp/l1ToL2.ts index 65e402ff69..70639fde38 100644 --- a/src/finalizer/utils/cctp/l1ToL2.ts +++ b/src/finalizer/utils/cctp/l1ToL2.ts @@ -1,6 +1,7 @@ import { TransactionRequest } from "@ethersproject/abstract-provider"; import { MessageTransmitterIdl, TokenMessengerMinterIdl } from "@across-protocol/contracts"; -import { ethers } from "ethers"; +import { web3, BN } from "@coral-xyz/anchor"; +import { TOKEN_PROGRAM_ADDRESS } from "@solana-program/token"; import { HubPoolClient, SpokePoolClient } from "../../../clients"; import { Contract, @@ -19,6 +20,12 @@ import { mapAsync, getSvmSignerFromEvmSigner, toPublicKey, + CHAIN_IDs, + getAssociatedTokenAddress, + SvmAddress, + EvmAddress, + ethers, + chainIsProd, } from "../../../utils"; import { AttestedCCTPDepositEvent, @@ -27,7 +34,6 @@ import { getCctpMessageTransmitter, } from "../../../utils/CCTPUtils"; import { FinalizerPromise, CrossChainMessage } from "../../types"; -import { web3, utils, BN } from "@coral-xyz/anchor"; export async function cctpL1toL2Finalizer( logger: winston.Logger, @@ -75,12 +81,21 @@ export async function cctpL1toL2Finalizer( callData: await generateMultiCallData(l2Messenger, unprocessedMessages), }; } else { + const simulate = process.env["SEND_TRANSACTIONS"] !== "true"; // If the l2SpokePoolClient is not an EVM client, then we must have send the finalization here, since we cannot return SVM calldata. - const signatures = await finalizeSvmWithdrawals(unprocessedMessages, hubPoolClient.hubPool.signer); + const signatures = await finalizeSvmWithdrawals( + unprocessedMessages, + hubPoolClient.hubPool.signer, + simulate, + hubPoolClient.chainId + ); const amountFinalized = unprocessedMessages.reduce((acc, event) => acc + Number(event.amount), 0); - logger.info({ + logger[simulate ? "debug" : "info"]({ at: `Finalizer#CCTPL1ToL2Finalizer:${l2SpokePoolClient.chainId}`, - message: `Finalized ${unprocessedMessages.length} deposits on Solana for ${amountFinalized} USDC.`, + message: `Finalized ${unprocessedMessages.length} deposits on Solana for ${convertFromWei( + String(amountFinalized), + TOKEN_SYMBOLS_MAP.USDC.decimals + )} USDC.`, signatures, }); return { @@ -142,24 +157,124 @@ async function generateDepositData( * @param signer A base signer to be converted into a Solana signer. * @returns A list of executed transaction signatures. */ -async function finalizeSvmWithdrawals(deposits: AttestedCCTPDepositEvent[], signer: Signer): Promise { +async function finalizeSvmWithdrawals( + deposits: AttestedCCTPDepositEvent[], + signer: Signer, + simulate = false, + hubChainId = 1 +): Promise { + const l1Usdc = EvmAddress.from(TOKEN_SYMBOLS_MAP.USDC.addresses[hubChainId]); + const l2Usdc = SvmAddress.from( + TOKEN_SYMBOLS_MAP.USDC.addresses[chainIsProd(hubChainId) ? CHAIN_IDs.SOLANA : CHAIN_IDs.SOLANA_DEVNET] + ); const [svmSigner, messageTransmitterProgram] = await Promise.all([ getSvmSignerFromEvmSigner(signer as Wallet), getAnchorProgram(MessageTransmitterIdl, signer as Wallet), ]); const messageTransmitter = toPublicKey(MessageTransmitterIdl.address); const tokenMessengerMinter = toPublicKey(TokenMessengerMinterIdl.address); - const authorityPda = web3.PublicKey.findProgramAddressSync( - [Buffer.from(utils.bytes.utf8.encode("sender_authority"))], + + // Define global accounts to access. + const [messageTransmitterPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("message_transmitter")], + messageTransmitter + ); + const [tokenMessengerPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("token_messenger")], tokenMessengerMinter ); + const [tokenMinterPda] = web3.PublicKey.findProgramAddressSync([Buffer.from("token_minter")], tokenMessengerMinter); + const [localTokenPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("local_token"), l2Usdc.toBuffer()], + tokenMessengerMinter + ); + const [tokenMessengerEventAuthorityPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("__event_authority")], + tokenMessengerMinter + ); + const [custodyTokenAccountPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("custody"), l2Usdc.toBuffer()], + tokenMessengerMinter + ); + const [authorityPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("message_transmitter_authority"), tokenMessengerMinter.toBuffer()], + messageTransmitter + ); + const tokenAccount = await getAssociatedTokenAddress(SvmAddress.from(svmSigner.publicKey.toBase58()), l2Usdc); return mapAsync(deposits, async (deposit) => { + // Define accounts dependent on deposit information. + const [tokenPairPda] = web3.PublicKey.findProgramAddressSync( + [ + Buffer.from("token_pair"), + Buffer.from(String(deposit.sourceDomain)), + Buffer.from(l1Usdc.toBytes32().slice(2), "hex"), + ], + tokenMessengerMinter + ); + const [remoteTokenMessengerPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("remote_token_messenger"), Buffer.from(String(deposit.sourceDomain))], + tokenMessengerMinter + ); const noncePda = await messageTransmitterProgram.methods - .getNoncePda({ nonce: new BN(deposit.log.args.nonce.toNumber()), sourceDomain: deposit.sourceDomain }) - .accounts({ messageTransmitter }) + .getNoncePda({ nonce: new BN(deposit.log.args.nonce.toNumber()), sourceDomain: deposit.log.args.remoteDomain }) + .accounts({ messageTransmitter: messageTransmitterPda }) .view(); - return messageTransmitterProgram.methods + // Append extra accounts. + const accountMetas = [ + { + isSigner: false, + isWritable: false, + pubkey: tokenMessengerPda, + }, + { + isSigner: false, + isWritable: false, + pubkey: remoteTokenMessengerPda, + }, + { + isSigner: false, + isWritable: true, + pubkey: tokenMinterPda, + }, + { + isSigner: false, + isWritable: true, + pubkey: localTokenPda, + }, + { + isSigner: false, + isWritable: false, + pubkey: tokenPairPda, + }, + { + isSigner: false, + isWritable: true, + pubkey: toPublicKey(tokenAccount), + }, + { + isSigner: false, + isWritable: true, + pubkey: custodyTokenAccountPda, + }, + { + isSigner: false, + isWritable: false, + pubkey: toPublicKey(TOKEN_PROGRAM_ADDRESS), + }, + { + isSigner: false, + isWritable: false, + pubkey: tokenMessengerEventAuthorityPda, + }, + { + isSigner: false, + isWritable: false, + pubkey: tokenMessengerMinter, + }, + ]; + + const pendingTx = messageTransmitterProgram.methods .receiveMessage({ message: Buffer.from(deposit.messageBytes.slice(2), "hex"), attestation: Buffer.from(deposit.attestation.slice(2), "hex"), @@ -168,11 +283,16 @@ async function finalizeSvmWithdrawals(deposits: AttestedCCTPDepositEvent[], sign payer: svmSigner.publicKey, caller: svmSigner.publicKey, authorityPda, - messageTransmitter, + messageTransmitter: messageTransmitterPda, usedNonces: noncePda, receiver: tokenMessengerMinter, systemProgram: web3.SystemProgram.programId, }) - .rpc(); + .remainingAccounts(accountMetas); + if (simulate) { + await pendingTx.simulate(); + return ""; + } + return pendingTx.rpc(); }); } diff --git a/src/utils/CCTPUtils.ts b/src/utils/CCTPUtils.ts index b536eed84d..80211f3081 100644 --- a/src/utils/CCTPUtils.ts +++ b/src/utils/CCTPUtils.ts @@ -251,7 +251,7 @@ async function getCCTPDepositEventsWithStatus( }; } const processed = chainIsSvm(destinationChainId) - ? await _hasCCTPMessageBeenProcessedSvm(deposit.log.args.nonce.toNumber(), deposit.sourceDomain) + ? await _hasCCTPMessageBeenProcessedSvm(deposit.log.args.nonce.toNumber(), deposit.log.args.remoteDomain) : await _hasCCTPMessageBeenProcessed(deposit.nonceHash, messageTransmitterContract); if (!processed) { return { @@ -381,18 +381,25 @@ async function _hasCCTPMessageBeenProcessed(nonceHash: string, contract: ethers. async function _hasCCTPMessageBeenProcessedSvm(nonce: number, sourceDomain: number): Promise { // Specifically retrieve the Solana MessageTransmitter contract. try { - const program = await getAnchorProgram(MessageTransmitterIdl); - const noncePda = await program.methods + const bnNonce = new BN(nonce); + const messageTransmitterProgram = await getAnchorProgram(MessageTransmitterIdl); + const [messageTransmitterPda] = web3.PublicKey.findProgramAddressSync( + [Buffer.from("message_transmitter")], + messageTransmitterProgram.programId + ); + const noncePda = await messageTransmitterProgram.methods .getNoncePda({ - nonce: new BN(nonce), + nonce: bnNonce, sourceDomain, }) .accounts({ - messageTransmitter: new web3.PublicKey(MessageTransmitterIdl.address), + messageTransmitter: messageTransmitterPda, }) .view(); - const nonceUsed = await program.methods - .isNonceUsed(noncePda) + const nonceUsed = await messageTransmitterProgram.methods + .isNonceUsed({ + nonce: bnNonce, + }) .accounts({ usedNonces: noncePda, }) diff --git a/src/utils/SDKUtils.ts b/src/utils/SDKUtils.ts index 5d40510ec4..b34698d8c4 100644 --- a/src/utils/SDKUtils.ts +++ b/src/utils/SDKUtils.ts @@ -21,6 +21,7 @@ export class SvmAddress extends sdk.utils.SvmAddress {} export type EvmGasPriceEstimate = sdk.gasPriceOracle.EvmGasPriceEstimate; export const { fillStatusArray, populateV3Relay, relayFillStatus, getTimestampForBlock } = sdk.arch.evm; +export const { getAssociatedTokenAddress } = sdk.arch.svm; export type SVMProvider = sdk.arch.svm.SVMProvider; export const { diff --git a/yarn.lock b/yarn.lock index 70cb631406..0355ead2d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2746,7 +2746,7 @@ resolved "https://registry.yarnpkg.com/@solana-program/token-2022/-/token-2022-0.4.1.tgz#4060407f68db7dda7cd919d7c867cfd72b172145" integrity sha512-zIjdwUwirmvvF1nb95I/88+76d9HPCmAIcjjM4NpznDFjZpPItpfKIbTyp/uxeVOzi7d5LmvuvXRr373gpdGCg== -"@solana-program/token@^0.5.1": +"@solana-program/token@^0.5.0", "@solana-program/token@^0.5.1": version "0.5.1" resolved "https://registry.yarnpkg.com/@solana-program/token/-/token-0.5.1.tgz#10e327df23f05a7f892fd33a9b6418f17dd62296" integrity sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag== From 7315bb6d7f713f2a07266a4c875257bebce5305c Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 27 May 2025 20:32:42 -0500 Subject: [PATCH 18/20] remove extraneous Signed-off-by: bennett --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index d93c55a34b..ec6d46cb28 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", - "tweetnacl": "1.0.0", "viem": "^2.26.1", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" From 426f93b75d90ed44037c16259196796f8e4f771b Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 27 May 2025 20:33:24 -0500 Subject: [PATCH 19/20] resync Signed-off-by: bennett --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0355ead2d2..2a3311559b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15692,11 +15692,6 @@ tweetnacl-util@^0.15.0, tweetnacl-util@^0.15.1: resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz#b80fcdb5c97bcc508be18c44a4be50f022eea00b" integrity sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw== -tweetnacl@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.0.tgz#713d8b818da42068740bf68386d0479e66fc8a7b" - integrity sha512-Nvl4Z9G3mH2/OBrODQlNhZ2T+ZaNe3AQoBHn0IxH2Klv9J45QYUY5trS/Dsk+Psmn3kCxUNPjsLtgwrofpFASA== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" From bb3168190a53b28506147a593488fe89024a79a4 Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 27 May 2025 20:37:00 -0500 Subject: [PATCH 20/20] comment Signed-off-by: bennett --- src/relayer/Relayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relayer/Relayer.ts b/src/relayer/Relayer.ts index 1a24dce3be..ea15c3faf4 100644 --- a/src/relayer/Relayer.ts +++ b/src/relayer/Relayer.ts @@ -140,7 +140,7 @@ export class Relayer { await updateSpokePoolClients(spokePoolClients, [ "FundsDeposited", - // "RequestedSpeedUpDeposit", + "RequestedSpeedUpDeposit", "FilledRelay", "RelayedRootBundle", "ExecutedRelayerRefundRoot",