Skip to content

Commit

Permalink
fix: use multicall contract from chain
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Aug 9, 2024
1 parent 102d870 commit a593bed
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 23 deletions.
58 changes: 38 additions & 20 deletions libs/metrics/src/l1/l1MetricsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};
}

/**
Expand Down
91 changes: 89 additions & 2 deletions libs/metrics/test/unit/l1/l1MetricsService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
ChainType,
ETH_TOKEN_ADDRESS,
L1_CONTRACTS,
multicall3EthereumAddress,
vitalikAddress,
} from "@zkchainhub/shared";
import { nativeToken, WETH } from "@zkchainhub/shared/tokens/tokens";
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -234,7 +237,7 @@ describe("L1MetricsService", () => {
args: [L1_CONTRACTS.SHARED_BRIDGE],
},
{
address: multicall3EthereumAddress,
address: multicallAddress,
abi: multicall3Abi,
functionName: "getEthBalance",
args: [L1_CONTRACTS.SHARED_BRIDGE],
Expand All @@ -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");
Expand All @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions libs/providers/src/providers/evmProvider.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion libs/shared/src/constants/addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

0 comments on commit a593bed

Please sign in to comment.