Skip to content

Commit

Permalink
feat: batches info l1 metric
Browse files Browse the repository at this point in the history
  • Loading branch information
0xkenj1 committed Aug 8, 2024
1 parent 9238bcc commit 2767677
Show file tree
Hide file tree
Showing 10 changed files with 223 additions and 44 deletions.
2 changes: 2 additions & 0 deletions libs/metrics/src/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./invalidChainId.exception";
export * from "./metricsService.exception";
6 changes: 6 additions & 0 deletions libs/metrics/src/exceptions/invalidChainId.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class InvalidChainId extends Error {
constructor(message: string) {
super(message);
this.name = "InvalidChainId";
}
}
13 changes: 13 additions & 0 deletions libs/metrics/src/exceptions/metricsService.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class MetricsServiceException extends Error {
constructor(message: string) {
super(message);
this.name = "MetricsServiceException";
}
}

export class L1MetricsServiceException extends MetricsServiceException {
constructor(message: string) {
super(message);
this.name = "L1MetricsServiceException";
}
}
13 changes: 0 additions & 13 deletions libs/metrics/src/exceptions/provider.exception.ts

This file was deleted.

1 change: 1 addition & 0 deletions libs/metrics/src/l1/abis/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./bridgeHub.abi";
export * from "./diamondProxy.abi";
export * from "./sharedBridge.abi";
export * from "./tokenBalances.abi";
77 changes: 61 additions & 16 deletions libs/metrics/src/l1/l1MetricsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ import {
zeroAddress,
} from "viem";

import { L1ProviderException } from "@zkchainhub/metrics/exceptions/provider.exception";
import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis";
import { tokenBalancesAbi } from "@zkchainhub/metrics/l1/abis/tokenBalances.abi";
import { InvalidChainId, L1MetricsServiceException } from "@zkchainhub/metrics/exceptions";
import {
bridgeHubAbi,
diamondProxyAbi,
sharedBridgeAbi,
tokenBalancesAbi,
} from "@zkchainhub/metrics/l1/abis";
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 { AbiWithAddress, ChainId, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared";
import { ETH_TOKEN_ADDRESS } from "@zkchainhub/shared/constants/addresses";
import { BatchesInfo, ChainId, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared";
import { ETH_TOKEN_ADDRESS } from "@zkchainhub/shared/constants";
import {
erc20Tokens,
isNativeToken,
Expand All @@ -38,15 +42,15 @@ const ONE_ETHER = parseEther("1");
*/
@Injectable()
export class L1MetricsService {
private readonly bridgeHub: Readonly<AbiWithAddress> = {
private readonly bridgeHub = {
abi: bridgeHubAbi,
address: L1_CONTRACTS.BRIDGE_HUB,
};
private readonly sharedBridge: Readonly<AbiWithAddress> = {
private readonly sharedBridge = {
abi: sharedBridgeAbi,
address: L1_CONTRACTS.SHARED_BRIDGE,
};
private readonly diamondContracts: Map<ChainId, AbiWithAddress> = new Map();
private readonly diamondContracts: Map<ChainId, Address> = new Map();

constructor(
private readonly evmProviderService: EvmProviderService,
Expand Down Expand Up @@ -146,11 +150,52 @@ export class L1MetricsService {
return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) };
}

//TODO: Implement getBatchesInfo.
async getBatchesInfo(
_chainId: number,
): Promise<{ commited: number; verified: number; proved: number }> {
return { commited: 100, verified: 100, proved: 100 };
/**
* Retrieves the information about the batches from L2 chain
* @param chainId - The chain id for which to get the batches info
* @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 [commited, verified, executed] = await this.evmProviderService.multicall({
contracts: [
{
address: diamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesCommitted",
args: [],
} as const,
{
address: diamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesVerified",
args: [],
} as const,
{
address: diamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesExecuted",
args: [],
} as const,
],
allowFailure: false,
});
return { commited, verified, executed };
}

/**
Expand Down Expand Up @@ -183,14 +228,14 @@ export class L1MetricsService {
...addresses.map((tokenAddress) => {
return {
address: this.sharedBridge.address,
abi: sharedBridgeAbi,
abi: this.sharedBridge.abi,
functionName: "chainBalance",
args: [chainIdBn, tokenAddress],
} as const;
}),
{
address: this.sharedBridge.address,
abi: sharedBridgeAbi,
abi: this.sharedBridge.abi,
functionName: "chainBalance",
args: [chainIdBn, ETH_TOKEN_ADDRESS],
} as const,
Expand Down Expand Up @@ -254,7 +299,7 @@ export class L1MetricsService {
if (isNativeError(e)) {
this.logger.error(`Failed to get gas information: ${e.message}`);
}
throw new L1ProviderException("Failed to get gas information from L1.");
throw new L1MetricsServiceException("Failed to get gas information from L1.");
}
}

Expand Down
132 changes: 119 additions & 13 deletions libs/metrics/test/unit/l1/l1MetricsService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import { Test, TestingModule } from "@nestjs/testing";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { encodeFunctionData, erc20Abi, parseEther, zeroAddress } from "viem";

import { L1ProviderException } from "@zkchainhub/metrics/exceptions/provider.exception";
import { InvalidChainId, L1MetricsServiceException } from "@zkchainhub/metrics/exceptions";
import { L1MetricsService } from "@zkchainhub/metrics/l1/";
import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis";
import { tokenBalancesAbi } from "@zkchainhub/metrics/l1/abis/tokenBalances.abi";
import {
bridgeHubAbi,
diamondProxyAbi,
sharedBridgeAbi,
tokenBalancesAbi,
} from "@zkchainhub/metrics/l1/abis";
import { tokenBalancesBytecode } from "@zkchainhub/metrics/l1/bytecode";
import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing";
import { EvmProviderService } from "@zkchainhub/providers";
import { ETH_TOKEN_ADDRESS, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared";
import { BatchesInfo, 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 @@ -242,9 +246,111 @@ describe("L1MetricsService", () => {
});

describe("getBatchesInfo", () => {
it("return getBatchesInfo", async () => {
const result = await l1MetricsService.getBatchesInfo(1);
expect(result).toEqual({ commited: 100, verified: 100, proved: 100 });
it("returns batches info for chain id", async () => {
const chainId = 324; // this is ZKsyncEra chain id
const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890";

l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress);
const mockBatchesInfo: BatchesInfo = { commited: 300n, verified: 200n, executed: 100n };
const batchesInfoMulticallResponse = [
mockBatchesInfo.commited,
mockBatchesInfo.verified,
mockBatchesInfo.executed,
];

jest.spyOn(mockEvmProviderService, "multicall").mockResolvedValue(
batchesInfoMulticallResponse,
);

const result = await l1MetricsService.getBatchesInfo(chainId);

expect(result).toEqual(mockBatchesInfo);
expect(mockEvmProviderService.multicall).toHaveBeenCalledWith({
contracts: [
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesCommitted",
args: [],
},
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesVerified",
args: [],
},
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesExecuted",
args: [],
},
],
allowFailure: false,
});
});
it("throws if invalid chainId ", async () => {
const chainId = 324.123123; // this is ZKsyncEra chain id

await expect(l1MetricsService.getBatchesInfo(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();

const mockBatchesInfo: BatchesInfo = { commited: 300n, verified: 200n, executed: 100n };
const batchesInfoMulticallResponse = [
mockBatchesInfo.commited,
mockBatchesInfo.verified,
mockBatchesInfo.executed,
];

jest.spyOn(mockEvmProviderService, "readContract").mockResolvedValue(
mockedDiamondProxyAddress,
);
jest.spyOn(mockEvmProviderService, "multicall").mockResolvedValue(
batchesInfoMulticallResponse,
);
const result = await l1MetricsService.getBatchesInfo(chainId);

expect(result).toEqual(mockBatchesInfo);

expect(l1MetricsService["diamondContracts"].get(chainId)).toEqual(
mockedDiamondProxyAddress,
);
expect(mockEvmProviderService.readContract).toHaveBeenCalledWith(
l1MetricsService["bridgeHub"].address,
l1MetricsService["bridgeHub"].abi,
"getHyperchain",
[BigInt(chainId)],
);
expect(mockEvmProviderService.multicall).toHaveBeenCalledWith({
contracts: [
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesCommitted",
args: [],
},
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesVerified",
args: [],
},
{
address: mockedDiamondProxyAddress,
abi: diamondProxyAbi,
functionName: "getTotalBatchesExecuted",
args: [],
},
],
allowFailure: false,
});
});
});

Expand Down Expand Up @@ -440,7 +546,7 @@ describe("L1MetricsService", () => {
expect(mockGetTokenPrices).toHaveBeenCalledWith([nativeToken.coingeckoId]);
});

it("throws L1ProviderException when estimateGas fails", async () => {
it("throws L1MetricsServiceException when estimateGas fails", async () => {
// Mock the necessary dependencies
const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas");
mockEstimateGas.mockRejectedValueOnce(new Error("Failed to estimate gas"));
Expand All @@ -451,8 +557,8 @@ describe("L1MetricsService", () => {
const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices");
mockGetTokenPrices.mockResolvedValueOnce({ [nativeToken.coingeckoId]: 2000 }); // ethPriceInUsd

// Call the method and expect it to throw L1ProviderException
await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1ProviderException);
// Call the method and expect it to throw L1MetricsServiceException
await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1MetricsServiceException);

// Assertions
expect(mockEstimateGas).toHaveBeenCalledWith({
Expand All @@ -464,7 +570,7 @@ describe("L1MetricsService", () => {
expect(mockGetTokenPrices).not.toHaveBeenCalled();
});

it("throws L1ProviderException when getGasPrice fails", async () => {
it("throws L1MetricsServiceException when getGasPrice fails", async () => {
// Mock the necessary dependencies
const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas");
mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost
Expand All @@ -476,8 +582,8 @@ describe("L1MetricsService", () => {
const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices");
mockGetTokenPrices.mockResolvedValueOnce({ [nativeToken.coingeckoId]: 2000 }); // ethPriceInUsd

// Call the method and expect it to throw L1ProviderException
await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1ProviderException);
// Call the method and expect it to throw L1MetricsServiceException
await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1MetricsServiceException);

// Assertions
expect(mockEstimateGas).toHaveBeenCalledTimes(2);
Expand Down
1 change: 1 addition & 0 deletions libs/shared/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./rollup.type";
export * from "./utils.type";
export * from "./l1.type";
17 changes: 17 additions & 0 deletions libs/shared/src/types/l1.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Represents the information about the batches from L2 chain
*/
export interface BatchesInfo {
/**
* The total number of batches that were committed
*/
commited: bigint;
/**
* The total number of batches that were committed & verified
*/
verified: bigint;
/**
* The total number of batches that were committed & verified & executed
*/
executed: bigint;
}
5 changes: 3 additions & 2 deletions libs/shared/src/types/utils.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Abi, Address } from "abitype";
import { Address } from "abitype";
import { AbiItem } from "viem";

export type AbiWithAddress = { abi: Abi; address: Address };
export type AbiWithAddress<T extends AbiItem[]> = { abi: T; address: Address };

export type ChainId = number;

0 comments on commit 2767677

Please sign in to comment.