From 070bb0c2e57b97db3dcc1d3010bf25e3000a8363 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 10 May 2024 15:44:33 +0200 Subject: [PATCH 1/3] improve(HubPoolClient): Don't throw on token resolution failure This is too violent for a lot of use cases and requires callers to wrap their calls in try/catch statements, which has its own set of problems. Instead, just return undefined if the requested token can't be resolved and allow the caller to decide on how to respond to that. --- src/clients/HubPoolClient.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index 28ec91e22..1e1489b69 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -181,24 +181,15 @@ export class HubPoolClient extends BaseAbstractClient { l1Token: string, destinationChainId: number, latestHubBlock = Number.MAX_SAFE_INTEGER - ): string { + ): string | undefined { if (!this.l1TokensToDestinationTokensWithBlock?.[l1Token]?.[destinationChainId]) { - const chain = getNetworkName(destinationChainId); - const { symbol } = this.l1Tokens.find(({ address }) => address === l1Token) ?? { symbol: l1Token }; - throw new Error(`Could not find SpokePool mapping for ${symbol} on ${chain} and L1 token ${l1Token}`); + return undefined; } // Find the last mapping published before the target block. const l2Token: DestinationTokenWithBlock | undefined = sortEventsDescending( this.l1TokensToDestinationTokensWithBlock[l1Token][destinationChainId] ).find((mapping: DestinationTokenWithBlock) => mapping.blockNumber <= latestHubBlock); - if (!l2Token) { - const chain = getNetworkName(destinationChainId); - const { symbol } = this.l1Tokens.find(({ address }) => address === l1Token) ?? { symbol: l1Token }; - throw new Error( - `Could not find SpokePool mapping for ${symbol} on ${chain} at or before HubPool block ${latestHubBlock}!` - ); - } - return l2Token.l2Token; + return l2Token?.l2Token; } // Returns the latest L1 token to use for an L2 token as of the input hub block. @@ -206,7 +197,7 @@ export class HubPoolClient extends BaseAbstractClient { l2Token: string, destinationChainId: number, latestHubBlock = Number.MAX_SAFE_INTEGER - ): string { + ): string | undefined { const l2Tokens = Object.keys(this.l1TokensToDestinationTokensWithBlock) .filter((l1Token) => this.l2TokenEnabledForL1Token(l1Token, destinationChainId)) .map((l1Token) => { @@ -216,14 +207,9 @@ export class HubPoolClient extends BaseAbstractClient { ); }) .flat(); - if (l2Tokens.length === 0) { - const chain = getNetworkName(destinationChainId); - throw new Error( - `Could not find HubPool mapping for ${l2Token} on ${chain} at or before HubPool block ${latestHubBlock}!` - ); - } + // Find the last mapping published before the target block. - return sortEventsDescending(l2Tokens)[0].l1Token; + return sortEventsDescending(l2Tokens)[0]?.l1Token; } /** From 0a212461cbf110b9f97189aa595af2e23c0138f4 Mon Sep 17 00:00:00 2001 From: Paul <108695806+pxrl@users.noreply.github.com> Date: Fri, 10 May 2024 15:54:34 +0200 Subject: [PATCH 2/3] lint --- src/clients/HubPoolClient.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index 1e1489b69..b3a064bbc 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -31,7 +31,6 @@ import { fetchTokenInfo, getCachedBlockForTimestamp, getCurrentTime, - getNetworkName, isDefined, mapAsync, paginatedEventQuery, From 384853bae2f9f70cbe49f4496f5f7439a60ca195 Mon Sep 17 00:00:00 2001 From: bennett Date: Tue, 3 Sep 2024 12:29:07 -0500 Subject: [PATCH 3/3] basic accomodation for undefined Signed-off-by: bennett --- .../BundleDataClient/utils/DataworkerUtils.ts | 31 ++++++---- .../BundleDataClient/utils/FillUtils.ts | 8 ++- .../utils/PoolRebalanceUtils.ts | 6 +- src/clients/HubPoolClient.ts | 58 ++++++++++++++----- src/clients/SpokePoolClient.ts | 3 +- src/clients/mocks/MockHubPoolClient.ts | 2 +- ...HubPoolClient.DepositToDestinationToken.ts | 53 ++++++----------- 7 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/clients/BundleDataClient/utils/DataworkerUtils.ts b/src/clients/BundleDataClient/utils/DataworkerUtils.ts index 7a9405531..e11732530 100644 --- a/src/clients/BundleDataClient/utils/DataworkerUtils.ts +++ b/src/clients/BundleDataClient/utils/DataworkerUtils.ts @@ -18,6 +18,7 @@ import { fixedPointAdjustment, count2DDictionaryValues, count3DDictionaryValues, + isDefined, } from "../../../utils"; import { addLastRunningBalance, @@ -160,8 +161,10 @@ export function _buildPoolRebalanceRoot( mainnetBundleEndBlock ); - updateRunningBalance(runningBalances, repaymentChainId, l1TokenCounterpart, totalRefundAmount); - updateRunningBalance(realizedLpFees, repaymentChainId, l1TokenCounterpart, totalRealizedLpFee); + if (isDefined(l1TokenCounterpart)) { + updateRunningBalance(runningBalances, repaymentChainId, l1TokenCounterpart, totalRefundAmount); + updateRunningBalance(realizedLpFees, repaymentChainId, l1TokenCounterpart, totalRealizedLpFee); + } } ); }); @@ -182,10 +185,12 @@ export function _buildPoolRebalanceRoot( destinationChainId, mainnetBundleEndBlock ); - const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment); - updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, deposit.inputAmount.sub(lpFee)); - // Slow fill LP fees are accounted for when the slow fill executes and a V3FilledRelay is emitted. i.e. when - // the slow fill execution is included in bundleFillsV3. + if (isDefined(l1TokenCounterpart)) { + const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment); + updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, deposit.inputAmount.sub(lpFee)); + // Slow fill LP fees are accounted for when the slow fill executes and a V3FilledRelay is emitted. i.e. when + // the slow fill execution is included in bundleFillsV3. + } }); }); }); @@ -206,10 +211,12 @@ export function _buildPoolRebalanceRoot( destinationChainId, mainnetBundleEndBlock ); - const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment); - updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, lpFee.sub(deposit.inputAmount)); - // Slow fills don't add to lpFees, only when the slow fill is executed and a V3FilledRelay is emitted, so - // we don't need to subtract it here. Moreover, the HubPoole expects bundleLpFees to be > 0. + if (isDefined(l1TokenCounterpart)) { + const lpFee = deposit.lpFeePct.mul(deposit.inputAmount).div(fixedPointAdjustment); + updateRunningBalance(runningBalances, destinationChainId, l1TokenCounterpart, lpFee.sub(deposit.inputAmount)); + // Slow fills don't add to lpFees, only when the slow fill is executed and a V3FilledRelay is emitted, so + // we don't need to subtract it here. Moreover, the HubPoole expects bundleLpFees to be > 0. + } }); }); }); @@ -242,7 +249,9 @@ export function _buildPoolRebalanceRoot( originChainId, mainnetBundleEndBlock ); - updateRunningBalance(runningBalances, originChainId, l1TokenCounterpart, deposit.inputAmount); + if (isDefined(l1TokenCounterpart)) { + updateRunningBalance(runningBalances, originChainId, l1TokenCounterpart, deposit.inputAmount); + } }); }); }); diff --git a/src/clients/BundleDataClient/utils/FillUtils.ts b/src/clients/BundleDataClient/utils/FillUtils.ts index 7fb0c9df1..50f921760 100644 --- a/src/clients/BundleDataClient/utils/FillUtils.ts +++ b/src/clients/BundleDataClient/utils/FillUtils.ts @@ -1,5 +1,5 @@ import { Fill } from "../../../interfaces"; -import { getBlockRangeForChain, isSlowFill } from "../../../utils"; +import { getBlockRangeForChain, isSlowFill, assert, isDefined } from "../../../utils"; import { HubPoolClient } from "../../HubPoolClient"; export function getRefundInformationFromFill( @@ -32,13 +32,15 @@ export function getRefundInformationFromFill( fill.inputToken, fill.originChainId, endBlockForMainnet - ); + )!; + assert(isDefined(l1TokenCounterpart), "There must be an l1 token counterpart for a filled deposit"); const repaymentToken = hubPoolClient.getL2TokenForL1TokenAtBlock( l1TokenCounterpart, chainToSendRefundTo, endBlockForMainnet - ); + )!; + assert(isDefined(repaymentToken), "There must be defined repayment token for a filled deposit"); return { chainToSendRefundTo, repaymentToken, diff --git a/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts b/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts index 01c6cbb42..1cc3996b5 100644 --- a/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts +++ b/src/clients/BundleDataClient/utils/PoolRebalanceUtils.ts @@ -2,7 +2,7 @@ import { MerkleTree } from "@across-protocol/contracts/dist/utils/MerkleTree"; import { RunningBalances, PoolRebalanceLeaf, Clients, SpokePoolTargetBalance } from "../../../interfaces"; import { SpokePoolClient } from "../../SpokePoolClient"; import { BigNumber } from "ethers"; -import { bnZero, compareAddresses } from "../../../utils"; +import { bnZero, compareAddresses, isDefined } from "../../../utils"; import { HubPoolClient } from "../../HubPoolClient"; import { V3DepositWithBlock } from "./shims"; import { AcrossConfigStoreClient } from "../../AcrossConfigStoreClient"; @@ -171,7 +171,9 @@ export function updateRunningBalanceForDeposit( deposit.originChainId, deposit.quoteBlockNumber ); - updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount); + if (isDefined(l1TokenCounterpart)) { + updateRunningBalance(runningBalances, deposit.originChainId, l1TokenCounterpart, updateAmount); + } } export function constructPoolRebalanceLeaves( diff --git a/src/clients/HubPoolClient.ts b/src/clients/HubPoolClient.ts index 17c1a2719..605cdac1c 100644 --- a/src/clients/HubPoolClient.ts +++ b/src/clients/HubPoolClient.ts @@ -221,7 +221,9 @@ export class HubPoolClient extends BaseAbstractClient { * @param deposit Deposit event * @param returns string L1 token counterpart for Deposit */ - getL1TokenForDeposit(deposit: Pick): string { + getL1TokenForDeposit( + deposit: Pick + ): string | undefined { // L1-->L2 token mappings are set via PoolRebalanceRoutes which occur on mainnet, // so we use the latest token mapping. This way if a very old deposit is filled, the relayer can use the // latest L2 token mapping to find the L1 token counterpart. @@ -232,16 +234,18 @@ export class HubPoolClient extends BaseAbstractClient { * Returns the L2 token that should be used as a counterpart to a deposit event. For example, the caller * might want to know what the refund token will be on l2ChainId for the deposit event. * @param l2ChainId Chain where caller wants to get L2 token counterpart for - * @param event Deposit event + * @param event Deposit eventn * @returns string L2 token counterpart on l2ChainId */ getL2TokenForDeposit( deposit: Pick, l2ChainId = deposit.destinationChainId - ): string { + ): string | undefined { const l1Token = this.getL1TokenForDeposit(deposit); // Use the latest hub block number to find the L2 token counterpart. - return this.getL2TokenForL1TokenAtBlock(l1Token, l2ChainId, deposit.quoteBlockNumber); + return isDefined(l1Token) + ? this.getL2TokenForL1TokenAtBlock(l1Token, l2ChainId, deposit.quoteBlockNumber) + : undefined; } l2TokenEnabledForL1Token(l1Token: string, destinationChainId: number): boolean { @@ -363,11 +367,23 @@ export class HubPoolClient extends BaseAbstractClient { // Map SpokePool token addresses to HubPool token addresses. // Note: Should only be accessed via `getHubPoolToken()` or `getHubPoolTokens()`. const hubPoolTokens: { [k: string]: string } = {}; - const getHubPoolToken = (deposit: LpFeeRequest, quoteBlockNumber: number): string => { + const getHubPoolToken = (deposit: LpFeeRequest, quoteBlockNumber: number): string | undefined => { const tokenKey = `${deposit.originChainId}-${deposit.inputToken}`; - return (hubPoolTokens[tokenKey] ??= this.getL1TokenForDeposit({ ...deposit, quoteBlockNumber })); + if (isDefined(hubPoolTokens[tokenKey])) { + return hubPoolTokens[tokenKey]; + } + const l1Token = this.getL1TokenForDeposit({ ...deposit, quoteBlockNumber }); + if (!isDefined(l1Token)) { + this.logger.warn({ + at: "HubPoolClient", + message: `Unable to determine an appropriate L1 Token for deposit ${deposit}`, + }); + } + hubPoolTokens[tokenKey] = l1Token; + return l1Token; }; - const getHubPoolTokens = (): string[] => dedupArray(Object.values(hubPoolTokens)); + const getHubPoolTokens = (): string[] => + dedupArray(Object.values(hubPoolTokens)).filter((token) => isDefined(token)); // Helper to resolve the unqiue hubPoolToken & quoteTimestamp mappings. const resolveUniqueQuoteTimestamps = (deposit: LpFeeRequest): void => { @@ -376,6 +392,9 @@ export class HubPoolClient extends BaseAbstractClient { // Resolve the HubPool token address for this origin chainId/token pair, if it isn't already known. const quoteBlockNumber = quoteBlocks[quoteTimestamp]; const hubPoolToken = getHubPoolToken(deposit, quoteBlockNumber); + if (!isDefined(hubPoolToken)) { + return; + } // Append the quoteTimestamp for this HubPool token, if it isn't already enqueued. utilizationTimestamps[hubPoolToken] ??= []; @@ -416,11 +435,11 @@ export class HubPoolClient extends BaseAbstractClient { const { originChainId, paymentChainId, inputAmount, quoteTimestamp } = deposit; const quoteBlock = quoteBlocks[quoteTimestamp]; - if (paymentChainId === undefined) { + const hubPoolToken = getHubPoolToken(deposit, quoteBlock); + if (!isDefined(paymentChainId) || !isDefined(hubPoolToken)) { return { quoteBlock, realizedLpFeePct: bnZero }; } - const hubPoolToken = getHubPoolToken(deposit, quoteBlock); const rateModel = this.configStoreClient.getRateModelForBlockNumber( hubPoolToken, originChainId, @@ -480,13 +499,16 @@ export class HubPoolClient extends BaseAbstractClient { getL1TokenInfoForL2Token(l2Token: string, chainId: number): L1Token | undefined { const l1TokenCounterpart = this.getL1TokenForL2TokenAtBlock(l2Token, chainId, this.latestBlockSearched); - return this.getTokenInfoForL1Token(l1TokenCounterpart); + return isDefined(l1TokenCounterpart) ? this.getTokenInfoForL1Token(l1TokenCounterpart) : undefined; } getTokenInfoForDeposit(deposit: Deposit): L1Token | undefined { - return this.getTokenInfoForL1Token( - this.getL1TokenForL2TokenAtBlock(deposit.inputToken, deposit.originChainId, this.latestBlockSearched) + const l1Token = this.getL1TokenForL2TokenAtBlock( + deposit.inputToken, + deposit.originChainId, + this.latestBlockSearched ); + return isDefined(l1Token) ? this.getTokenInfoForL1Token(l1Token) : undefined; } getTokenInfo(chainId: number | string, tokenAddress: string): L1Token | undefined { @@ -509,10 +531,14 @@ export class HubPoolClient extends BaseAbstractClient { return false; } - // Resolve both HubPool tokens back to a current SpokePool token and verify that they match. - const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock); - const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock); - return tokenA === _tokenA && tokenB === _tokenB; + if (isDefined(l1TokenA) && isDefined(l1TokenB)) { + // Resolve both HubPool tokens back to a current SpokePool token and verify that they match. + const _tokenA = this.getL2TokenForL1TokenAtBlock(l1TokenA, chainIdA, hubPoolBlock); + const _tokenB = this.getL2TokenForL1TokenAtBlock(l1TokenB, chainIdB, hubPoolBlock); + return tokenA === _tokenA && tokenB === _tokenB; + } else { + return false; + } } catch { return false; // One or both input tokens were not recognised. } diff --git a/src/clients/SpokePoolClient.ts b/src/clients/SpokePoolClient.ts index 75c082862..ebb70eae1 100644 --- a/src/clients/SpokePoolClient.ts +++ b/src/clients/SpokePoolClient.ts @@ -769,7 +769,8 @@ export class SpokePoolClient extends BaseAbstractClient { return ZERO_ADDRESS; } - return this.hubPoolClient.getL2TokenForDeposit(deposit); + // If there is no l2 token for the deposit also return the zero address. + return this.hubPoolClient.getL2TokenForDeposit(deposit) ?? ZERO_ADDRESS; } /** diff --git a/src/clients/mocks/MockHubPoolClient.ts b/src/clients/mocks/MockHubPoolClient.ts index ca35d307d..6ab376315 100644 --- a/src/clients/mocks/MockHubPoolClient.ts +++ b/src/clients/mocks/MockHubPoolClient.ts @@ -100,7 +100,7 @@ export class MockHubPoolClient extends HubPoolClient { this.spokePoolTokens[l1Token][chainId] = l2Token; } - getL1TokenForL2TokenAtBlock(l2Token: string, chainId: number, blockNumber: number): string { + getL1TokenForL2TokenAtBlock(l2Token: string, chainId: number, blockNumber: number): string | undefined { const l1Token = Object.keys(this.spokePoolTokens).find( (l1Token) => this.spokePoolTokens[l1Token]?.[chainId] === l2Token ); diff --git a/test/HubPoolClient.DepositToDestinationToken.ts b/test/HubPoolClient.DepositToDestinationToken.ts index ad900cee5..2b52d9615 100644 --- a/test/HubPoolClient.DepositToDestinationToken.ts +++ b/test/HubPoolClient.DepositToDestinationToken.ts @@ -47,16 +47,12 @@ describe("HubPoolClient: Deposit to Destination Token", function () { }); it("Gets L2 token counterpart", async function () { - expect(() => hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.throw( - /Could not find SpokePool mapping/ - ); + expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.be.undefined; const e1 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomDestinationToken); await hubPoolClient.update(); // If input hub pool block is before all events, should throw. - expect(() => hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.throw( - /Could not find SpokePool mapping/ - ); + expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, 0)).to.be.undefined; expect(hubPoolClient.getL2TokenForL1TokenAtBlock(randomL1Token, destinationChainId, e1.blockNumber)).to.equal( randomDestinationToken ); @@ -73,16 +69,12 @@ describe("HubPoolClient: Deposit to Destination Token", function () { ); }); it("Gets L1 token counterpart", async function () { - expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.throw( - /Could not find HubPool mapping/ - ); + expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.be.undefined; const e1 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomDestinationToken); await hubPoolClient.update(); - // If input hub pool block is before all events, should throw. - expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.throw( - /Could not find HubPool mapping/ - ); + // If input hub pool block is before all events, should resolve no l1 token. + expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, 0)).to.be.undefined; expect( hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, e1.blockNumber) ).to.equal(randomL1Token); @@ -98,13 +90,11 @@ describe("HubPoolClient: Deposit to Destination Token", function () { hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, destinationChainId, e1.blockNumber) ).to.equal(randomL1Token); - // If L2 token mapping doesn't exist, throw. - expect(() => hubPoolClient.getL1TokenForL2TokenAtBlock(randomL1Token, destinationChainId, e2.blockNumber)).to.throw( - /Could not find HubPool mapping/ - ); - expect(() => - hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, originChainId, e2.blockNumber) - ).to.throw(/Could not find HubPool mapping/); + // If L2 token mapping doesn't exist, return undefined. + expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomL1Token, destinationChainId, e2.blockNumber)).to.be + .undefined; + expect(hubPoolClient.getL1TokenForL2TokenAtBlock(randomDestinationToken, originChainId, e2.blockNumber)).to.be + .undefined; }); it("Gets L1 token for deposit", async function () { const depositData = { @@ -119,18 +109,15 @@ describe("HubPoolClient: Deposit to Destination Token", function () { ); // quote block too early - expect(() => hubPoolClient.getL1TokenForDeposit({ ...depositData, quoteBlockNumber: 0 })).to.throw( - /Could not find HubPool mapping/ - ); - + expect(hubPoolClient.getL1TokenForDeposit({ ...depositData, quoteBlockNumber: 0 })).to.be.undefined; // no deposit with matching origin token - expect(() => + expect( hubPoolClient.getL1TokenForDeposit({ ...depositData, inputToken: randomL1Token, quoteBlockNumber: e0.blockNumber, }) - ).to.throw(/Could not find HubPool mapping/); + ).to.be.undefined; const e1 = hubPoolClient.setPoolRebalanceRoute(originChainId, randomOriginToken, randomOriginToken); await hubPoolClient.update(); @@ -152,24 +139,22 @@ describe("HubPoolClient: Deposit to Destination Token", function () { ).to.equal(randomDestinationToken); // origin chain token is set but none for destination chain yet, as of e0. - expect(() => - hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: e0.blockNumber }) - ).to.throw(/Could not find SpokePool mapping/); + expect(hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: e0.blockNumber })) + .to.be.undefined; // quote block too early - expect(() => - hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: 0 }) - ).to.throw(/Could not find HubPool mapping/); + expect(hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, quoteBlockNumber: 0 })).to.be + .undefined; // No deposit with matching token. - expect(() => + expect( hubPoolClient.getL2TokenForDeposit({ ...depositData, destinationChainId, inputToken: randomL1Token, quoteBlockNumber: e0.blockNumber, }) - ).to.throw(/Could not find HubPool mapping/); + ).to.be.undefined; const e2 = hubPoolClient.setPoolRebalanceRoute(destinationChainId, randomL1Token, randomL1Token); await hubPoolClient.update();