From a593bed9195b9504b47b3d8d82213ae453eab67f Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Fri, 9 Aug 2024 17:18:13 -0300 Subject: [PATCH] fix: use multicall contract from chain --- libs/metrics/src/l1/l1MetricsService.ts | 58 ++++++++---- .../test/unit/l1/l1MetricsService.spec.ts | 91 ++++++++++++++++++- .../src/providers/evmProvider.service.ts | 8 ++ libs/shared/src/constants/addresses.ts | 1 - 4 files changed, 135 insertions(+), 23 deletions(-) diff --git a/libs/metrics/src/l1/l1MetricsService.ts b/libs/metrics/src/l1/l1MetricsService.ts index 73b7123..232a53b 100644 --- a/libs/metrics/src/l1/l1MetricsService.ts +++ b/libs/metrics/src/l1/l1MetricsService.ts @@ -34,7 +34,7 @@ import { L1_CONTRACTS, vitalikAddress, } from "@zkchainhub/shared"; -import { ETH_TOKEN_ADDRESS, multicall3EthereumAddress } from "@zkchainhub/shared/constants"; +import { ETH_TOKEN_ADDRESS } from "@zkchainhub/shared/constants"; import { erc20Tokens, isNativeToken, @@ -139,29 +139,47 @@ export class L1MetricsService { private async fetchTokenBalances( addresses: Address[], ): Promise<{ ethBalance: bigint; addressesBalance: bigint[] }> { - const balances = await this.evmProviderService.multicall({ - contracts: [ - ...addresses.map((tokenAddress) => { - return { - address: tokenAddress, - abi: erc20Abi, - functionName: "balanceOf", - args: [this.sharedBridge.address], - } as const; - }), - { - address: multicall3EthereumAddress, - abi: multicall3Abi, - functionName: "getEthBalance", - args: [this.sharedBridge.address], - } as const, - ], - allowFailure: false, + const multicall3Address = this.evmProviderService.getMulticall3Address(); + const contracts = addresses.map((tokenAddress) => { + return { + address: tokenAddress, + abi: erc20Abi, + functionName: "balanceOf", + args: [this.sharedBridge.address], + } as const; }); + let balances: bigint[] = []; + + if (multicall3Address) { + balances = await this.evmProviderService.multicall({ + contracts: [ + ...contracts, + { + address: multicall3Address, + abi: multicall3Abi, + functionName: "getEthBalance", + args: [this.sharedBridge.address], + } as const, + ], + allowFailure: false, + } as const); + } else { + const [erc20Balances, ethBalance] = await Promise.all([ + this.evmProviderService.multicall({ + contracts: contracts, + allowFailure: false, + } as const), + this.evmProviderService.getBalance(this.sharedBridge.address), + ]); + balances = [...erc20Balances, ethBalance]; + } assert(balances.length === addresses.length + 1, "Invalid balances length"); - return { ethBalance: balances[addresses.length]!, addressesBalance: balances }; + return { + ethBalance: balances[addresses.length]!, + addressesBalance: balances, + }; } /** diff --git a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts index 4f1c442..64df37f 100644 --- a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts +++ b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts @@ -23,7 +23,6 @@ import { ChainType, ETH_TOKEN_ADDRESS, L1_CONTRACTS, - multicall3EthereumAddress, vitalikAddress, } from "@zkchainhub/shared"; import { nativeToken, WETH } from "@zkchainhub/shared/tokens/tokens"; @@ -172,7 +171,11 @@ describe("L1MetricsService", () => { 123_803_824374847279970609n, ]; // Mocked balances const mockPrices = { "wrapped-bitcoin": 66_129, "usd-coin": 0.999, ethereum: 3_181.09 }; // Mocked prices + const multicallAddress = "0x123452"; + jest.spyOn(mockEvmProviderService, "getMulticall3Address").mockReturnValue( + multicallAddress, + ); jest.spyOn(mockEvmProviderService, "multicall").mockResolvedValue( mockMulticallBalances, ); @@ -234,7 +237,7 @@ describe("L1MetricsService", () => { args: [L1_CONTRACTS.SHARED_BRIDGE], }, { - address: multicall3EthereumAddress, + address: multicallAddress, abi: multicall3Abi, functionName: "getEthBalance", args: [L1_CONTRACTS.SHARED_BRIDGE], @@ -247,9 +250,92 @@ describe("L1MetricsService", () => { "usd-coin", "wrapped-bitcoin", ]); + expect(mockEvmProviderService.getBalance).not.toHaveBeenCalled(); + }); + + it("fetch ethBalance using getBalance", async () => { + const mockMulticallBalances = [60_841_657_140641n, 135_63005559n]; // Mocked balances + const mockPrices = { "wrapped-bitcoin": 66_129, "usd-coin": 0.999, ethereum: 3_181.09 }; // Mocked prices + + jest.spyOn(mockEvmProviderService, "getMulticall3Address").mockReturnValue(undefined); + jest.spyOn(mockEvmProviderService, "multicall").mockResolvedValue( + mockMulticallBalances, + ); + jest.spyOn(mockEvmProviderService, "getBalance").mockResolvedValue( + 123_803_824374847279970609n, + ); + jest.spyOn(mockPricingService, "getTokenPrices").mockResolvedValue(mockPrices); + + const result = await l1MetricsService.l1Tvl(); + + expect(result).toHaveLength(3); + expect(result).toEqual([ + { + amount: "123803.824374847279970609", + amountUsd: expect.stringContaining("393831107.68"), + price: "3181.09", + name: "Ethereum", + symbol: "ETH", + contractAddress: null, + type: "native", + imageUrl: + "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628", + decimals: 18, + }, + { + amount: "60841657.140641", + amountUsd: expect.stringContaining("60780815.48"), + price: "0.999", + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + { + amount: "135.63005559", + amountUsd: expect.stringContaining("8969079.94"), + price: "66129", + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + imageUrl: + "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", + type: "erc20", + decimals: 8, + }, + ]); + expect(mockEvmProviderService.multicall).toHaveBeenCalledWith({ + contracts: [ + { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + abi: erc20Abi, + functionName: "balanceOf", + args: [L1_CONTRACTS.SHARED_BRIDGE], + }, + { + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + abi: erc20Abi, + functionName: "balanceOf", + args: [L1_CONTRACTS.SHARED_BRIDGE], + }, + ], + allowFailure: false, + }); + expect(mockPricingService.getTokenPrices).toHaveBeenCalledWith([ + "ethereum", + "usd-coin", + "wrapped-bitcoin", + ]); + expect(mockEvmProviderService.getBalance).toHaveBeenCalledWith( + L1_CONTRACTS.SHARED_BRIDGE, + ); }); it("throws an error if the balances length is invalid", async () => { + jest.spyOn(mockEvmProviderService, "getMulticall3Address").mockReturnValue("0x123452"); jest.spyOn(mockEvmProviderService, "multicall").mockResolvedValue([]); await expect(l1MetricsService.l1Tvl()).rejects.toThrowError("Invalid balances length"); @@ -261,6 +347,7 @@ describe("L1MetricsService", () => { 135_63005559n, 123_803_824374847279970609n, ]); + jest.spyOn(mockEvmProviderService, "getMulticall3Address").mockReturnValue("0x123452"); jest.spyOn(mockPricingService, "getTokenPrices").mockResolvedValue({ ethereum: 3_181.09, "usd-coin": 0.999, diff --git a/libs/providers/src/providers/evmProvider.service.ts b/libs/providers/src/providers/evmProvider.service.ts index 470b674..5609317 100644 --- a/libs/providers/src/providers/evmProvider.service.ts +++ b/libs/providers/src/providers/evmProvider.service.ts @@ -49,6 +49,14 @@ export class EvmProviderService { }); } + /** + * Retrieves the address of the Multicall3 contract. + * @returns {Address | undefined} The address of the Multicall3 contract, or undefined if not found. + */ + getMulticall3Address(): Address | undefined { + return this.chain.contracts?.multicall3?.address; + } + /** * Retrieves the balance of the specified address. * @param {Address} address The address for which to retrieve the balance. diff --git a/libs/shared/src/constants/addresses.ts b/libs/shared/src/constants/addresses.ts index 414815f..f8f3817 100644 --- a/libs/shared/src/constants/addresses.ts +++ b/libs/shared/src/constants/addresses.ts @@ -4,4 +4,3 @@ import { Address } from "abitype"; // See: https://github.com/matter-labs/era-contracts/blob/8a70bbbc48125f5bde6189b4e3c6a3ee79631678/l1-contracts/contracts/common/Config.sol#L105 export const ETH_TOKEN_ADDRESS: Address = "0x0000000000000000000000000000000000000001"; export const vitalikAddress: Address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; -export const multicall3EthereumAddress: Address = "0xcA11bde05977b3631167028862bE2a173976CA11";