Skip to content

Commit

Permalink
fix(reporter): Accumulate L2 token balances mapped to same L1 token a…
Browse files Browse the repository at this point in the history
…ddress (#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
  • Loading branch information
nicholaspai authored May 20, 2024
1 parent 066733b commit d15f9aa
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 12 deletions.
40 changes: 31 additions & 9 deletions src/monitor/Monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
toBN,
toBNWei,
winston,
TOKEN_SYMBOLS_MAP,
} from "../utils";

import { MonitorClients, updateMonitorClients } from "./MonitorClientHelper";
Expand Down Expand Up @@ -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<void> {
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) => ({
Expand All @@ -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<void> {
const { monitoredBalances } = this.monitorConfig;
const balances = await this._getBalances(monitoredBalances);
Expand Down
40 changes: 37 additions & 3 deletions test/Monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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();
});

Expand Down

0 comments on commit d15f9aa

Please sign in to comment.