From d15f9aae7e162dfa92a887ac18ebc422a460060f Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Mon, 20 May 2024 14:42:14 -0400 Subject: [PATCH] fix(reporter): Accumulate L2 token balances mapped to same L1 token address (#1533) * fix(reporter): Accumulate L2 token balances mapped to same L1 token address Currently we are only querying L2 tokens mapped to l1 tokens via PoolRebalanceRoutes, which means we are not reporting Bridged USDC balances * fix test --- src/monitor/Monitor.ts | 40 +++++++++++++++++++++++++++++++--------- test/Monitor.ts | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/monitor/Monitor.ts b/src/monitor/Monitor.ts index 17e121887..4f2cd5514 100644 --- a/src/monitor/Monitor.ts +++ b/src/monitor/Monitor.ts @@ -27,6 +27,7 @@ import { toBN, toBNWei, winston, + TOKEN_SYMBOLS_MAP, } from "../utils"; import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper"; @@ -265,18 +266,13 @@ export class Monitor { // Update current balances of all tokens on each supported chain for each relayer. async updateCurrentRelayerBalances(relayerBalanceReport: RelayerBalanceReport): Promise { const { hubPoolClient } = this.clients; - const l1Tokens = hubPoolClient.getL1Tokens(); + const _l1Tokens = hubPoolClient.getL1Tokens(); for (const relayer of this.monitorConfig.monitoredRelayers) { for (const chainId of this.monitorChains) { - const l2ToL1Tokens = Object.fromEntries( - l1Tokens - .filter(({ address: l1Token }) => hubPoolClient.l2TokenEnabledForL1Token(l1Token, chainId)) - .map((l1Token) => { - const l2Token = hubPoolClient.getL2TokenForL1TokenAtBlock(l1Token.address, chainId); - return [l2Token, l1Token]; - }) + const l1Tokens = _l1Tokens.filter(({ address: l1Token }) => + hubPoolClient.l2TokenEnabledForL1Token(l1Token, chainId) ); - + const l2ToL1Tokens = this.getL2ToL1TokenMap(l1Tokens, chainId); const l2TokenAddresses = Object.keys(l2ToL1Tokens); const tokenBalances = await this._getBalances( l2TokenAddresses.map((address) => ({ @@ -300,6 +296,32 @@ export class Monitor { } } + // Returns a dictionary of L2 token addresses on this chain to their mapped L1 token info. For example, this + // will return a dictionary for Optimism including WETH, WBTC, USDC, USDC.e, USDT entries where the key is + // the token's Optimism address and the value is the equivalent L1 token info. + protected getL2ToL1TokenMap(l1Tokens: L1Token[], chainId: number): { [l2TokenAddress: string]: L1Token } { + return Object.fromEntries( + l1Tokens + .map((l1Token) => { + // @dev l2TokenSymbols is a list of all keys in TOKEN_SYMBOLS_MAP where the hub chain address is equal to the + // l1 token address. + const l2TokenSymbols = Object.entries(TOKEN_SYMBOLS_MAP) + .filter( + ([, { addresses }]) => + addresses[this.clients.hubPoolClient.chainId]?.toLowerCase() === l1Token.address.toLowerCase() + ) + .map(([symbol]) => symbol); + + // Create an entry for all L2 tokens that share a symbol with the L1 token. This includes tokens + // like USDC which has multiple L2 tokens mapped to the same L1 token for a given chain ID. + return l2TokenSymbols + .filter((symbol) => TOKEN_SYMBOLS_MAP[symbol].addresses[chainId] !== undefined) + .map((symbol) => [TOKEN_SYMBOLS_MAP[symbol].addresses[chainId], l1Token]); + }) + .flat() + ); + } + async checkBalances(): Promise { const { monitoredBalances } = this.monitorConfig; const balances = await this._getBalances(monitoredBalances); diff --git a/test/Monitor.ts b/test/Monitor.ts index 6dd54a5da..0cd353100 100644 --- a/test/Monitor.ts +++ b/test/Monitor.ts @@ -10,7 +10,7 @@ import { import { CrossChainTransferClient } from "../src/clients/bridges"; import { spokePoolClientsToProviders } from "../src/common"; import { Dataworker } from "../src/dataworker/Dataworker"; -import { BalanceType, V3DepositWithBlock } from "../src/interfaces"; +import { BalanceType, L1Token, V3DepositWithBlock } from "../src/interfaces"; import { ALL_CHAINS_NAME, Monitor, REBALANCE_FINALIZE_GRACE_PERIOD } from "../src/monitor/Monitor"; import { MonitorConfig } from "../src/monitor/MonitorConfig"; import { MAX_UINT_VAL, getNetworkName, toBN } from "../src/utils"; @@ -31,6 +31,20 @@ import { toBNWei, } from "./utils"; +type TokenMap = { [l2TokenAddress: string]: L1Token }; + +class TestMonitor extends Monitor { + private overriddenTokenMap: { [chainId: number]: TokenMap } = {}; + + setL2ToL1TokenMap(chainId: number, map: TokenMap): void { + this.overriddenTokenMap[chainId] = map; + } + // Override internal function that calls into externally defined and hard-coded TOKEN_SYMBOLS_MAP. + protected getL2ToL1TokenMap(l1Tokens: L1Token[], chainId): TokenMap { + return this.overriddenTokenMap[chainId] ?? super.getL2ToL1TokenMap(l1Tokens, chainId); + } +} + describe("Monitor", async function () { const TEST_NETWORK_NAMES = ["Hardhat1", "Hardhat2", "unknown", ALL_CHAINS_NAME]; let l1Token: Contract, l2Token: Contract, erc20_2: Contract; @@ -154,7 +168,7 @@ describe("Monitor", async function () { adapterManager = new MockAdapterManager(null, null, null, null); adapterManager.setSupportedChains(chainIds); crossChainTransferClient = new CrossChainTransferClient(spyLogger, chainIds, adapterManager); - monitorInstance = new Monitor(spyLogger, monitorConfig, { + monitorInstance = new TestMonitor(spyLogger, monitorConfig, { bundleDataClient, configStoreClient, multiCallerClient, @@ -163,7 +177,27 @@ describe("Monitor", async function () { tokenTransferClient, crossChainTransferClient, }); - + (monitorInstance as TestMonitor).setL2ToL1TokenMap(originChainId, { + [l2Token.address]: { + symbol: "L1Token1", + address: l1Token.address, + decimals: 18, + }, + }); + (monitorInstance as TestMonitor).setL2ToL1TokenMap(destinationChainId, { + [erc20_2.address]: { + symbol: "L1Token1", + address: l1Token.address, + decimals: 18, + }, + }); + (monitorInstance as TestMonitor).setL2ToL1TokenMap(hubPoolClient.chainId, { + [l1Token.address]: { + symbol: "L1Token1", + address: l1Token.address, + decimals: 18, + }, + }); await updateAllClients(); });