Skip to content

Commit

Permalink
feat: chain type (#42)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes ZKS-79

## Description

- Add `chainType` logic to `L1MetricsService`
- extract diamond proxy fetching logic into a function called
`fetchDiamondProxyAddress`
  • Loading branch information
0xkenj1 authored Aug 8, 2024
1 parent b154e12 commit d94bdd0
Show file tree
Hide file tree
Showing 5 changed files with 170 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 "./l1MetricsService.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 @@ -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,
Expand Down Expand Up @@ -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<BatchesInfo> {
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<BatchesInfo> {
const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId);
const [commited, verified, executed] = await this.evmProviderService.multicall({
contracts: [
{
Expand Down Expand Up @@ -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<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.
*/
private async fetchDiamondProxyAddress(chainId: ChainId): Promise<Address> {
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;
}

/**
Expand Down
111 changes: 106 additions & 5 deletions libs/metrics/test/unit/l1/l1MetricsService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
);
});
});

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 d94bdd0

Please sign in to comment.