Skip to content

feat: Handle cases where a deposit's inputToken and originChain cannot be mapped to a PoolRebalanceRoute #2177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/clients/InventoryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export class InventoryClient {
refundsToConsider = lodash.cloneDeep(await this.bundleRefundsPromise);
const totalRefundsPerChain = this.getEnabledChains().reduce(
(refunds: { [chainId: string]: BigNumber }, chainId) => {
if (!this.hubPoolClient.l2TokenEnabledForL1Token(l1Token, chainId)) {
if (!this.hubPoolClient.l2TokenHasPoolRebalanceRoute(l1Token, chainId)) {
refunds[chainId] = bnZero;
} else {
const destinationToken = this.getRepaymentTokenForL1Token(l1Token, chainId);
Expand Down Expand Up @@ -423,6 +423,12 @@ export class InventoryClient {
const { originChainId, destinationChainId, inputToken, outputToken, inputAmount } = deposit;
const hubChainId = this.hubPoolClient.chainId;

// If the token cannot be mapped to any PoolRebalanceRoute, then the decision for now is to return zero repayment
// chains and force the relayer to ignore this deposit.
if (!this.hubPoolClient.l2TokenHasPoolRebalanceRoute(deposit.inputToken, deposit.originChainId)) {
return [];
}

if (!this.isInventoryManagementEnabled()) {
return [deposit.fromLiteChain ? originChainId : destinationChainId];
}
Expand Down Expand Up @@ -1423,7 +1429,7 @@ export class InventoryClient {
return SLOW_WITHDRAWAL_CHAINS.filter(
(chainId) =>
this._l1TokenEnabledForChain(l1Token, Number(chainId)) &&
this.hubPoolClient.l2TokenEnabledForL1Token(l1Token, Number(chainId))
this.hubPoolClient.l2TokenHasPoolRebalanceRoute(l1Token, Number(chainId))
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ export class AdapterManager {
}

l2TokenExistForL1Token(l1Token: string, l2ChainId: number): boolean {
return this.hubPoolClient.l2TokenEnabledForL1Token(l1Token, l2ChainId);
return this.hubPoolClient.l2TokenHasPoolRebalanceRoute(l1Token, l2ChainId);
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down
43 changes: 26 additions & 17 deletions src/dataworker/DataworkerUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,26 +237,35 @@ export function _buildRelayerRefundRoot(
Object.entries(combinedRefunds).forEach(([_repaymentChainId, refundsForChain]) => {
const repaymentChainId = Number(_repaymentChainId);
Object.entries(refundsForChain).forEach(([l2TokenAddress, refunds]) => {
const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
l2TokenAddress,
repaymentChainId,
endBlockForMainnet
);
// If the token cannot be mapped to any PoolRebalanceRoute, then the amount to return must be 0 since there
// is no way to send the token back to the HubPool.
if (!clients.hubPoolClient.l2TokenHasPoolRebalanceRoute(l2TokenAddress, repaymentChainId)) {
relayerRefundLeaves.push(
..._getRefundLeaves(refunds, bnZero, repaymentChainId, l2TokenAddress, maxRefundCount)
);
} else {
const l1TokenCounterpart = clients.hubPoolClient.getL1TokenForL2TokenAtBlock(
l2TokenAddress,
repaymentChainId,
endBlockForMainnet
);

const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
l1TokenCounterpart,
repaymentChainId,
endBlockForMainnet
);
const spokePoolTargetBalance = clients.configStoreClient.getSpokeTargetBalancesForBlock(
l1TokenCounterpart,
repaymentChainId,
endBlockForMainnet
);

// The `amountToReturn` for a { repaymentChainId, L2TokenAddress} should be set to max(-netSendAmount, 0).
const amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[repaymentChainId][l1TokenCounterpart]
);
// The `amountToReturn` for a { repaymentChainId, L2TokenAddress} should be set to max(-netSendAmount, 0).
const amountToReturn = getAmountToReturnForRelayerRefundLeaf(
spokePoolTargetBalance,
runningBalances[repaymentChainId][l1TokenCounterpart]
);

const _refundLeaves = _getRefundLeaves(refunds, amountToReturn, repaymentChainId, l2TokenAddress, maxRefundCount);
relayerRefundLeaves.push(..._refundLeaves);
relayerRefundLeaves.push(
..._getRefundLeaves(refunds, amountToReturn, repaymentChainId, l2TokenAddress, maxRefundCount)
);
}
});
});

Expand Down
4 changes: 2 additions & 2 deletions src/monitor/Monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class Monitor {
const tokensPerChain = Object.fromEntries(
this.monitorChains.map((chainId) => {
const l2Tokens = l1Tokens
.filter((l1Token) => hubPoolClient.l2TokenEnabledForL1Token(l1Token, chainId))
.filter((l1Token) => hubPoolClient.l2TokenHasPoolRebalanceRoute(l1Token, chainId))
.map((l1Token) => {
const l2Token = hubPoolClient.getL2TokenForL1TokenAtBlock(l1Token, chainId);
return l2Token;
Expand Down Expand Up @@ -336,7 +336,7 @@ export class Monitor {
for (const relayer of this.monitorConfig.monitoredRelayers) {
for (const chainId of this.monitorChains) {
const l1Tokens = _l1Tokens.filter(({ address: l1Token }) =>
hubPoolClient.l2TokenEnabledForL1Token(l1Token, chainId)
hubPoolClient.l2TokenHasPoolRebalanceRoute(l1Token, chainId)
);
const l2ToL1Tokens = this.getL2ToL1TokenMap(l1Tokens, chainId);
const l2TokenAddresses = Object.keys(l2ToL1Tokens);
Expand Down
30 changes: 29 additions & 1 deletion src/relayer/Relayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,15 @@ export class Relayer {
return ignoreDeposit();
}

if (!hubPoolClient.l2TokenHasPoolRebalanceRoute(inputToken, originChainId)) {
this.logger.debug({
at: "Relayer::filterDeposit",
message: `Skipping ${srcChain} deposit for input token ${inputToken} due to missing pool rebalance route.`,
transactionHash: deposit.transactionHash,
});
return ignoreDeposit();
}

// Skip any L1 tokens that are not specified in the config.
// If relayerTokens is an empty list, we'll assume that all tokens are supported.
const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId);
Expand Down Expand Up @@ -715,6 +724,16 @@ export class Relayer {
}
}

if (!hubPoolClient.l2TokenHasPoolRebalanceRoute(inputToken, originChainId)) {
this.logger.debug({
at: "Relayer::evaluateFill",
message: `Skipping ${originChain} deposit ${depositId.toString()} for input token ${
deposit.inputToken
} due to missing pool rebalance route.`,
transactionHash,
});
return;
}
const l1Token = hubPoolClient.getL1TokenInfoForL2Token(inputToken, originChainId);
if (tokenClient.hasBalanceForFill(deposit)) {
const { repaymentChainId, repaymentChainProfitability } = await this.resolveRepaymentChain(
Expand Down Expand Up @@ -976,7 +995,16 @@ export class Relayer {

requestSlowFill(deposit: Deposit): void {
// don't request slow fill if origin/destination chain is a lite chain
if (deposit.fromLiteChain || deposit.toLiteChain) {
if (
deposit.fromLiteChain ||
deposit.toLiteChain ||
!this.clients.hubPoolClient.areTokensEquivalent(
deposit.inputToken,
deposit.originChainId,
deposit.outputToken,
deposit.destinationChainId
)
) {
this.logger.debug({
at: "Relayer::requestSlowFill",
message: "Prevent requesting slow fill request to/from lite chain.",
Expand Down
4 changes: 2 additions & 2 deletions test/InventoryClient.RefundChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ describe("InventoryClient: Refund chain selection", async function () {
bundleDataClient.setReturnedNextBundleRefunds({
[OPTIMISM]: createRefunds(owner.address, toWei(5), l2TokensForWeth[OPTIMISM]),
});
// We need HubPoolClient.l2TokenEnabledForL1Token() to return true for a given
// We need HubPoolClient.l2TokenHasPoolRebalanceRoute() to return true for a given
// L1 token and destination chain ID, otherwise it won't be counted in upcoming
// refunds.
hubPoolClient.setEnableAllL2Tokens(true);
Expand Down Expand Up @@ -440,7 +440,7 @@ describe("InventoryClient: Refund chain selection", async function () {
bundleDataClient.setReturnedPendingBundleRefunds({
[POLYGON]: createRefunds(owner.address, toWei(5), l2TokensForWeth[POLYGON]),
});
// We need HubPoolClient.l2TokenEnabledForL1Token() to return true for a given
// We need HubPoolClient.l2TokenHasPoolRebalanceRoute() to return true for a given
// L1 token and destination chain ID, otherwise it won't be counted in upcoming
// refunds.
hubPoolClient.setEnableAllL2Tokens(true);
Expand Down
4 changes: 2 additions & 2 deletions test/mocks/MockHubPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ export class MockHubPoolClient extends clients.mocks.MockHubPoolClient {
this.enableAllL2Tokens = enableAllL2Tokens;
}

l2TokenEnabledForL1Token(l1Token: string, destinationChainId: number): boolean {
l2TokenHasPoolRebalanceRoute(l1Token: string, destinationChainId: number): boolean {
if (this.enableAllL2Tokens === undefined) {
return super.l2TokenEnabledForL1Token(l1Token, destinationChainId);
return super.l2TokenHasPoolRebalanceRoute(l1Token, destinationChainId);
}
return this.enableAllL2Tokens;
}
Expand Down