Skip to content

Commit

Permalink
feat: read fee params from diamond proxy storage
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnigir1 committed Aug 8, 2024
1 parent d94bdd0 commit 6b3139a
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 25 deletions.
66 changes: 52 additions & 14 deletions libs/metrics/src/l1/l1MetricsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
encodeFunctionData,
erc20Abi,
formatUnits,
Hex,
parseEther,
parseUnits,
zeroAddress,
Expand All @@ -18,7 +19,7 @@ import {
L1MetricsServiceException,
} from "@zkchainhub/metrics/exceptions";
import { bridgeHubAbi, diamondProxyAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis";
import { AssetTvl, GasInfo } from "@zkchainhub/metrics/types";
import { AssetTvl, FeeParams, feeParamsFieldLengths, GasInfo } from "@zkchainhub/metrics/types";
import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing";
import { EvmProviderService } from "@zkchainhub/providers";
import {
Expand All @@ -39,6 +40,7 @@ import {
} from "@zkchainhub/shared/tokens/tokens";

const ONE_ETHER = parseEther("1");
const FEE_PARAMS_SLOT: Hex = `0x26`;

/**
* Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain.
Expand Down Expand Up @@ -324,20 +326,56 @@ export class L1MetricsService {
}
}

//TODO: Implement feeParams.
async feeParams(_chainId: ChainId): Promise<{
batchOverheadL1Gas: number;
maxPubdataPerBatch: number;
maxL2GasPerBatch: number;
priorityTxMaxPubdata: number;
minimalL2GasPrice: number;
}> {
/**
* Retrieves the fee parameters for a specific chain.
*
* @param chainId - The ID of the chain.
* @returns A Promise that resolves to a FeeParams object containing the fee parameters.
* @throws {L1MetricsServiceException} If the fee parameters cannot be retrieved from L1.
*/
async feeParams(chainId: ChainId): Promise<FeeParams> {
const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId);

// Read the storage at the target slot;
const feeParamsData = await this.evmProviderService.getStorageAt(
diamondProxyAddress,
FEE_PARAMS_SLOT,
);
if (!feeParamsData) {
throw new L1MetricsServiceException("Failed to get fee params from L1.");
}

const strippedParamsData = feeParamsData.slice(2); // Remove the 0x prefix
let cursor = strippedParamsData.length;
const values: string[] = [];

for (const value of Object.values(feeParamsFieldLengths)) {
values.push(strippedParamsData.slice(cursor - value, cursor));
cursor -= value;
}

assert(
values.length === Object.keys(feeParamsFieldLengths).length,
"Error parsing fee params",
);

const [
pubdataPricingMode,
batchOverheadL1Gas,
maxPubdataPerBatch,
maxL2GasPerBatch,
priorityTxMaxPubdata,
minimalL2GasPrice,
] = values as [string, string, string, string, string, string];

// Convert hex to decimal
return {
batchOverheadL1Gas: 50000,
maxPubdataPerBatch: 120000,
maxL2GasPerBatch: 10000000,
priorityTxMaxPubdata: 15000,
minimalL2GasPrice: 10000000,
pubdataPricingMode: parseInt(pubdataPricingMode, 16),
batchOverheadL1Gas: parseInt(batchOverheadL1Gas, 16),
maxPubdataPerBatch: parseInt(maxPubdataPerBatch, 16),
maxL2GasPerBatch: parseInt(maxL2GasPerBatch, 16),
priorityTxMaxPubdata: parseInt(priorityTxMaxPubdata, 16),
minimalL2GasPrice: BigInt(`0x${minimalL2GasPrice}`),
};
}
}
19 changes: 19 additions & 0 deletions libs/metrics/src/types/feeParams.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//See: https://github.com/matter-labs/era-contracts/blob/8a70bbbc48125f5bde6189b4e3c6a3ee79631678/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol#L52
export type FeeParams = {
pubdataPricingMode: number;
batchOverheadL1Gas: number;
maxPubdataPerBatch: number;
maxL2GasPerBatch: number;
priorityTxMaxPubdata: number;
minimalL2GasPrice?: bigint;
};

// Define the lengths for each field (in hex digits, each byte is 2 hex digits)
export const feeParamsFieldLengths = {
pubdataPricingMode: 2, // uint8 -> 1 byte -> 2 hex digits
batchOverheadL1Gas: 8, // uint32 -> 4 bytes -> 8 hex digits
maxPubdataPerBatch: 8, // uint32 -> 4 bytes -> 8 hex digits
maxL2GasPerBatch: 8, // uint32 -> 4 bytes -> 8 hex digits
priorityTxMaxPubdata: 8, // uint32 -> 4 bytes -> 8 hex digits
minimalL2GasPrice: 16, // uint64 -> 8 bytes -> 16 hex digits
} as const;
1 change: 1 addition & 0 deletions libs/metrics/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./gasInfo.type";
export * from "./tvl.type";
export * from "./feeParams.type";
49 changes: 41 additions & 8 deletions libs/metrics/test/unit/l1/l1MetricsService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,15 +717,48 @@ describe("L1MetricsService", () => {
});

describe("feeParams", () => {
it("return feeParams", async () => {
const result = await l1MetricsService.feeParams(1n);
expect(result).toEqual({
batchOverheadL1Gas: 50000,
it("should retrieve the fee parameters for a specific chain", async () => {
// Mock the dependencies
const chainId = 324n; // this is ZKsyncEra chain id
const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890";
const mockFeeParamsRawData =
"0x00000000000000000000000ee6b280000182b804c4b4000001d4c0000f424000";

const mockFeeParams = {
pubdataPricingMode: 0,
batchOverheadL1Gas: 1000000,
maxPubdataPerBatch: 120000,
maxL2GasPerBatch: 10000000,
priorityTxMaxPubdata: 15000,
minimalL2GasPrice: 10000000,
});
maxL2GasPerBatch: 80000000,
priorityTxMaxPubdata: 99000,
minimalL2GasPrice: 250000000n,
};

l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress);
jest.spyOn(mockEvmProviderService, "getStorageAt").mockResolvedValue(
mockFeeParamsRawData,
);

const result = await l1MetricsService.feeParams(chainId);

expect(mockEvmProviderService.getStorageAt).toHaveBeenCalledWith(
mockedDiamondProxyAddress,
`0x26`,
);

expect(result).toEqual(mockFeeParams);
});

it("should throw an exception if the fee parameters cannot be retrieved from L1", async () => {
// Mock the dependencies
const chainId = 324n; // this is ZKsyncEra chain id
const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890";
l1MetricsService["diamondContracts"].set(chainId, mockedDiamondProxyAddress);

jest.spyOn(mockEvmProviderService, "getStorageAt").mockResolvedValue(undefined);

await expect(l1MetricsService.feeParams(chainId)).rejects.toThrow(
L1MetricsServiceException,
);
});
});
});
6 changes: 3 additions & 3 deletions libs/providers/src/providers/evmProvider.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ export class EvmProviderService {
* @returns {Promise<Hex>} A Promise that resolves to the value of the storage slot.
* @throws {InvalidArgumentException} If the slot is not a positive integer.
*/
async getStorageAt(address: Address, slot: number): Promise<Hex | undefined> {
if (slot <= 0 || !Number.isInteger(slot)) {
async getStorageAt(address: Address, slot: number | Hex): Promise<Hex | undefined> {
if (typeof slot === "number" && (slot <= 0 || !Number.isInteger(slot))) {
throw new InvalidArgumentException(
`Slot must be a positive integer number. Received: ${slot}`,
);
}

return this.client.getStorageAt({
address,
slot: toHex(slot),
slot: typeof slot === "string" ? slot : toHex(slot),
});
}

Expand Down
12 changes: 12 additions & 0 deletions libs/providers/test/unit/providers/evmProvider.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ describe("EvmProviderService", () => {
expect(mockClient.getStorageAt).toHaveBeenCalledWith({ address, slot: "0x1" });
});

it("should return the value of the storage slot at the given address and slot value", async () => {
const address = "0x123456789";
const slot = "0x12";
const expectedValue = "0xabcdef";
jest.spyOn(mockClient, "getStorageAt").mockResolvedValue(expectedValue);

const value = await viemProvider.getStorageAt(address, slot);

expect(value).toBe(expectedValue);
expect(mockClient.getStorageAt).toHaveBeenCalledWith({ address, slot: "0x12" });
});

it("should throw an error if the slot is not a positive integer", async () => {
const address = "0x123456789";
const slot = -1;
Expand Down

0 comments on commit 6b3139a

Please sign in to comment.