From d94bdd077c427c0402b7db9b1417764d599b8a5f Mon Sep 17 00:00:00 2001 From: 0xkenj1 <165053496+0xkenj1@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:27:32 -0300 Subject: [PATCH] feat: chain type (#42) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 🤖 Linear Closes ZKS-79 ## Description - Add `chainType` logic to `L1MetricsService` - extract diamond proxy fetching logic into a function called `fetchDiamondProxyAddress` --- libs/metrics/src/exceptions/index.ts | 1 + .../exceptions/invalidChainType.exception.ts | 11 ++ libs/metrics/src/l1/l1MetricsService.ts | 72 ++++++++---- .../test/unit/l1/l1MetricsService.spec.ts | 111 +++++++++++++++++- libs/shared/src/types/rollup.type.ts | 1 + 5 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 libs/metrics/src/exceptions/invalidChainType.exception.ts diff --git a/libs/metrics/src/exceptions/index.ts b/libs/metrics/src/exceptions/index.ts index 43d33e4..9fd908d 100644 --- a/libs/metrics/src/exceptions/index.ts +++ b/libs/metrics/src/exceptions/index.ts @@ -1,2 +1,3 @@ export * from "./invalidChainId.exception"; export * from "./l1MetricsService.exception"; +export * from "./invalidChainType.exception"; diff --git a/libs/metrics/src/exceptions/invalidChainType.exception.ts b/libs/metrics/src/exceptions/invalidChainType.exception.ts new file mode 100644 index 0000000..ae9c0bd --- /dev/null +++ b/libs/metrics/src/exceptions/invalidChainType.exception.ts @@ -0,0 +1,11 @@ +import { Chains } from "@zkchainhub/shared"; + +export class InvalidChainType extends Error { + constructor(index: number) { + super(`Current supported types are [${Chains.join( + ", ", + )}], but received index ${index}. Verify if supported chains are consistent with https://github.com/matter-labs/era-contracts/blob/8a70bbbc48125f5bde6189b4e3c6a3ee79631678/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol#L39 +`); + this.name = "InvalidChainType"; + } +} diff --git a/libs/metrics/src/l1/l1MetricsService.ts b/libs/metrics/src/l1/l1MetricsService.ts index 67f6100..67d34ca 100644 --- a/libs/metrics/src/l1/l1MetricsService.ts +++ b/libs/metrics/src/l1/l1MetricsService.ts @@ -12,12 +12,23 @@ import { zeroAddress, } from "viem"; -import { InvalidChainId, L1MetricsServiceException } from "@zkchainhub/metrics/exceptions"; +import { + InvalidChainId, + InvalidChainType, + L1MetricsServiceException, +} from "@zkchainhub/metrics/exceptions"; import { bridgeHubAbi, diamondProxyAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; import { AssetTvl, GasInfo } from "@zkchainhub/metrics/types"; import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; import { EvmProviderService } from "@zkchainhub/providers"; -import { BatchesInfo, ChainId, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared"; +import { + BatchesInfo, + ChainId, + Chains, + ChainType, + L1_CONTRACTS, + vitalikAddress, +} from "@zkchainhub/shared"; import { ETH_TOKEN_ADDRESS } from "@zkchainhub/shared/constants"; import { erc20Tokens, @@ -148,22 +159,8 @@ export class L1MetricsService { * @param chainId - The chain id for which to get the batches info * @returns commits, verified and executed batches */ - async getBatchesInfo(chainId: ChainId): Promise { - let diamondProxyAddress: Address | undefined = this.diamondContracts.get(chainId); - - if (!diamondProxyAddress) { - diamondProxyAddress = await this.evmProviderService.readContract( - this.bridgeHub.address, - this.bridgeHub.abi, - "getHyperchain", - [chainId], - ); - if (diamondProxyAddress == zeroAddress) { - throw new InvalidChainId(`Chain ID ${chainId} doesn't exist on the ecosystem`); - } - this.diamondContracts.set(chainId, diamondProxyAddress); - } - + async getBatchesInfo(chainId: bigint): Promise { + const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId); const [commited, verified, executed] = await this.evmProviderService.multicall({ contracts: [ { @@ -237,9 +234,42 @@ export class L1MetricsService { return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) }; } - //TODO: Implement chainType. - async chainType(_chainId: ChainId): Promise<"validium" | "rollup"> { - return "rollup"; + async chainType(chainId: ChainId): Promise { + const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId); + const chainTypeIndex = await this.evmProviderService.readContract( + diamondProxyAddress, + diamondProxyAbi, + "getPubdataPricingMode", + [], + ); + const chainType = Chains[chainTypeIndex]; + if (!chainType) { + throw new InvalidChainType(chainTypeIndex); + } + return chainType; + } + + /** + * Fetches the diamond proxy address for the given chain id and caches it for future use. + * @param chainId - The chain id for which to fetch the diamond proxy address. + * @returns Diamond proxy address. + */ + private async fetchDiamondProxyAddress(chainId: ChainId): Promise
{ + let diamondProxyAddress: Address | undefined = this.diamondContracts.get(chainId); + + if (!diamondProxyAddress) { + diamondProxyAddress = await this.evmProviderService.readContract( + this.bridgeHub.address, + this.bridgeHub.abi, + "getHyperchain", + [chainId], + ); + if (diamondProxyAddress == zeroAddress) { + throw new InvalidChainId(`Chain ID ${chainId} doesn't exist on the ecosystem`); + } + this.diamondContracts.set(chainId, diamondProxyAddress); + } + return diamondProxyAddress; } /** diff --git a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts index aa58646..17089a1 100644 --- a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts +++ b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts @@ -4,12 +4,22 @@ import { Test, TestingModule } from "@nestjs/testing"; import { WINSTON_MODULE_PROVIDER } from "nest-winston"; import { encodeFunctionData, erc20Abi, parseEther, zeroAddress } from "viem"; -import { InvalidChainId, L1MetricsServiceException } from "@zkchainhub/metrics/exceptions"; +import { + InvalidChainId, + InvalidChainType, + L1MetricsServiceException, +} from "@zkchainhub/metrics/exceptions"; import { L1MetricsService } from "@zkchainhub/metrics/l1/"; import { bridgeHubAbi, diamondProxyAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; import { EvmProviderService } from "@zkchainhub/providers"; -import { BatchesInfo, ETH_TOKEN_ADDRESS, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared"; +import { + BatchesInfo, + ChainType, + ETH_TOKEN_ADDRESS, + L1_CONTRACTS, + vitalikAddress, +} from "@zkchainhub/shared"; import { nativeToken, WETH } from "@zkchainhub/shared/tokens/tokens"; // Mock implementations of the dependencies @@ -361,6 +371,47 @@ describe("L1MetricsService", () => { }); }); + describe("fetchDiamondProxyAddress", () => { + it("returns address if already exists in the map", async () => { + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + l1MetricsService["diamondContracts"].clear(); + l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + + const readContractSpy = jest.spyOn(mockEvmProviderService, "readContract"); + const result = await l1MetricsService["fetchDiamondProxyAddress"](chainId); + + expect(result).toEqual(mockedDiamondProxyAddress); + expect(l1MetricsService["diamondContracts"].get(chainId)).toEqual( + mockedDiamondProxyAddress, + ); + expect(readContractSpy).toHaveBeenCalledTimes(0); + }); + it("fetches and sets diamond proxy if chainId doesn't exists on map", async () => { + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1MetricsService["diamondContracts"].clear(); + + jest.spyOn(mockEvmProviderService, "readContract").mockResolvedValue( + mockedDiamondProxyAddress, + ); + const result = await l1MetricsService["fetchDiamondProxyAddress"](chainId); + + expect(result).toEqual(mockedDiamondProxyAddress); + + expect(l1MetricsService["diamondContracts"].get(chainId)).toEqual( + mockedDiamondProxyAddress, + ); + expect(mockEvmProviderService.readContract).toHaveBeenCalledWith( + l1MetricsService["bridgeHub"].address, + l1MetricsService["bridgeHub"].abi, + "getHyperchain", + [BigInt(chainId)], + ); + }); + }); + describe("tvl", () => { it("return the TVL for chain id", async () => { const mockBalances = [60_841_657_140641n, 135_63005559n, 123_803_824374847279970609n]; // Mocked balances @@ -460,9 +511,59 @@ describe("L1MetricsService", () => { }); describe("chainType", () => { - it("return chainType", async () => { - const result = await l1MetricsService.chainType(1n); - expect(result).toBe("rollup"); + it("returns chainType", async () => { + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + const mockChainType: ChainType = "Rollup"; + + const readContractSpy = jest + .spyOn(mockEvmProviderService, "readContract") + .mockResolvedValue(0); + + const result = await l1MetricsService.chainType(chainId); + + expect(result).toEqual(mockChainType); + expect(readContractSpy).toHaveBeenCalledWith( + mockedDiamondProxyAddress, + diamondProxyAbi, + "getPubdataPricingMode", + [], + ); + }); + it("returns chainType", async () => { + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + const mockChainType: ChainType = "Validium"; + + const readContractSpy = jest + .spyOn(mockEvmProviderService, "readContract") + .mockResolvedValue(1); + + const result = await l1MetricsService.chainType(chainId); + + expect(result).toEqual(mockChainType); + expect(readContractSpy).toHaveBeenCalledWith( + mockedDiamondProxyAddress, + diamondProxyAbi, + "getPubdataPricingMode", + [], + ); + }); + it("throws if blockchain returns an out of bounds index", async () => { + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + + jest.spyOn(mockEvmProviderService, "readContract").mockResolvedValue(100); + + await expect(l1MetricsService.chainType(chainId)).rejects.toThrowError( + InvalidChainType, + ); }); }); diff --git a/libs/shared/src/types/rollup.type.ts b/libs/shared/src/types/rollup.type.ts index 37e7549..664390f 100644 --- a/libs/shared/src/types/rollup.type.ts +++ b/libs/shared/src/types/rollup.type.ts @@ -1,3 +1,4 @@ +// Chains should not change since it is a direct mapping of https://github.com/matter-labs/era-contracts/blob/8a70bbbc48125f5bde6189b4e3c6a3ee79631678/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol#L39 /** * An array of supported chain types. * @readonly