Skip to content

Commit

Permalink
feat: l1 chainType metric
Browse files Browse the repository at this point in the history
  • Loading branch information
0xkenj1 committed Aug 8, 2024
1 parent 2767677 commit e714d45
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 26 deletions.
1 change: 1 addition & 0 deletions libs/metrics/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./invalidChainId.exception";
export * from "./metricsService.exception";
export * from "./invalidChainType.exception";
11 changes: 11 additions & 0 deletions libs/metrics/src/exceptions/invalidChainType.exception.ts
Original file line number Diff line number Diff line change
@@ -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";
}
}
72 changes: 51 additions & 21 deletions libs/metrics/src/l1/l1MetricsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import {
zeroAddress,
} from "viem";

import { InvalidChainId, L1MetricsServiceException } from "@zkchainhub/metrics/exceptions";
import {
InvalidChainId,
InvalidChainType,
L1MetricsServiceException,
} from "@zkchainhub/metrics/exceptions";
import {
bridgeHubAbi,
diamondProxyAbi,
Expand All @@ -25,7 +29,14 @@ import { tokenBalancesBytecode } from "@zkchainhub/metrics/l1/bytecode";
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,
Expand Down Expand Up @@ -156,22 +167,7 @@ export class L1MetricsService {
* @returns commits, verified and executed batches
*/
async getBatchesInfo(chainId: number): Promise<BatchesInfo> {
if (!Number.isInteger(chainId)) {
throw new InvalidChainId("chain id must be an integer");
}
const chainIdBn = BigInt(chainId);
let diamondProxyAddress: Address | undefined = this.diamondContracts.get(chainId);

if (!diamondProxyAddress) {
diamondProxyAddress = await this.evmProviderService.readContract(
this.bridgeHub.address,
this.bridgeHub.abi,
"getHyperchain",
[chainIdBn],
);
this.diamondContracts.set(chainId, diamondProxyAddress);
}

const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId);
const [commited, verified, executed] = await this.evmProviderService.multicall({
contracts: [
{
Expand Down Expand Up @@ -246,9 +242,43 @@ export class L1MetricsService {
return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) };
}

//TODO: Implement chainType.
async chainType(_chainId: number): Promise<"validium" | "rollup"> {
return "rollup";
async chainType(chainId: number): Promise<ChainType> {
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.
*/
async fetchDiamondProxyAddress(chainId: number): Promise<Address> {
if (!Number.isInteger(chainId)) {
throw new InvalidChainId("chain id must be an integer");
}
const chainIdBn = BigInt(chainId);
let diamondProxyAddress: Address | undefined = this.diamondContracts.get(chainId);

if (!diamondProxyAddress) {
diamondProxyAddress = await this.evmProviderService.readContract(
this.bridgeHub.address,
this.bridgeHub.abi,
"getHyperchain",
[chainIdBn],
);
this.diamondContracts.set(chainId, diamondProxyAddress);
}
return diamondProxyAddress;
}

/**
Expand Down
101 changes: 96 additions & 5 deletions libs/metrics/test/unit/l1/l1MetricsService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ 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,
Expand All @@ -15,7 +19,13 @@ import {
import { tokenBalancesBytecode } from "@zkchainhub/metrics/l1/bytecode";
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
Expand Down Expand Up @@ -354,6 +364,54 @@ describe("L1MetricsService", () => {
});
});

describe("fetchDiamondProxyAddress", () => {
it("returns address if already exists in the map", async () => {
const chainId = 324; // 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("throws if invalid chainId ", async () => {
const chainId = 324.123123; // this is ZKsyncEra chain id

await expect(
l1MetricsService["fetchDiamondProxyAddress"](chainId),
).rejects.toThrowError(InvalidChainId);
});
it("fetches and sets diamond proxy if chainId doesn't exists on map", async () => {
const chainId = 324; // 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
Expand Down Expand Up @@ -453,9 +511,42 @@ describe("L1MetricsService", () => {
});

describe("chainType", () => {
it("return chainType", async () => {
const result = await l1MetricsService.chainType(1);
expect(result).toBe("rollup");
it("returns chainType", async () => {
const chainId = 324; // 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("throws if blockchain returns an out of bounds index", async () => {
const chainId = 324; // 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,
);
});
it("throws if invalid chainId is passed", async () => {
const chainId = 324.123123;
await expect(l1MetricsService.chainType(chainId)).rejects.toThrowError(InvalidChainId);
});
});

Expand Down
1 change: 1 addition & 0 deletions libs/shared/src/types/rollup.type.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit e714d45

Please sign in to comment.