From 9542d1eb3a29ebe23fee925c747de5dbf23c7065 Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:42:21 -0300 Subject: [PATCH 1/3] feat: get gas info --- .../src/exceptions/provider.exception.ts | 13 +++ libs/metrics/src/l1/l1MetricsService.ts | 82 ++++++++++++++++++- libs/metrics/src/types/gasInfo.type.ts | 6 ++ libs/metrics/src/types/index.ts | 1 + .../src/providers/evmProvider.service.ts | 5 ++ libs/shared/src/constants/addresses.ts | 3 + libs/shared/src/constants/index.ts | 1 + libs/shared/src/tokens/tokens.ts | 25 +++++- 8 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 libs/metrics/src/exceptions/provider.exception.ts create mode 100644 libs/metrics/src/types/gasInfo.type.ts create mode 100644 libs/shared/src/constants/addresses.ts diff --git a/libs/metrics/src/exceptions/provider.exception.ts b/libs/metrics/src/exceptions/provider.exception.ts new file mode 100644 index 0000000..d596bac --- /dev/null +++ b/libs/metrics/src/exceptions/provider.exception.ts @@ -0,0 +1,13 @@ +export class ProviderException extends Error { + constructor(message: string) { + super(message); + this.name = "ProviderException"; + } +} + +export class L1ProviderException extends ProviderException { + constructor(message: string) { + super(message); + this.name = "L1ProviderException"; + } +} diff --git a/libs/metrics/src/l1/l1MetricsService.ts b/libs/metrics/src/l1/l1MetricsService.ts index 346ebf5..35ebcec 100644 --- a/libs/metrics/src/l1/l1MetricsService.ts +++ b/libs/metrics/src/l1/l1MetricsService.ts @@ -1,10 +1,25 @@ +import { isNativeError } from "util/types"; import { Inject, Injectable, LoggerService } from "@nestjs/common"; import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston"; +import { + encodeFunctionData, + erc20Abi, + formatGwei, + formatUnits, + parseEther, + zeroAddress, +} from "viem"; +import { L1ProviderException } from "@zkchainhub/metrics/exceptions/provider.exception"; import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; +import { GasInfo } from "@zkchainhub/metrics/types"; import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; import { EvmProviderService } from "@zkchainhub/providers"; -import { AbiWithAddress, ChainId, L1_CONTRACTS } from "@zkchainhub/shared"; +import { AbiWithAddress, ChainId, L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared"; +import { ETH, WETH } from "@zkchainhub/shared/tokens/tokens"; + +const ONE_ETHER = parseEther("1"); +const ETHER_DECIMALS = 18; /** * Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain. @@ -47,10 +62,69 @@ export class L1MetricsService { async chainType(_chainId: number): Promise<"validium" | "rollup"> { return "rollup"; } - //TODO: Implement ethGasInfo. - async ethGasInfo(): Promise<{ gasPrice: number; ethTransfer: number; erc20Transfer: number }> { - return { gasPrice: 50, ethTransfer: 21000, erc20Transfer: 65000 }; + + /** + * Retrieves gas information for Ethereum transfers and ERC20 token transfers. + * @returns {GasInfo} A promise that resolves to an object containing gas-related information. + */ + async ethGasInfo(): Promise { + try { + const [ethTransferGasCost, erc20TransferGasCost, gasPrice] = await Promise.all([ + // Estimate gas for an ETH transfer. + this.evmProviderService.estimateGas({ + account: vitalikAddress, + to: zeroAddress, + value: ONE_ETHER, + }), + // Estimate gas for an ERC20 transfer. + this.evmProviderService.estimateGas({ + account: vitalikAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [L1_CONTRACTS.SHARED_BRIDGE, ONE_ETHER], + }), + }), + // Get the current gas price. + this.evmProviderService.getGasPrice(), + ]); + + // Get the current price of ether. + let ethPriceInUsd: number | undefined = undefined; + try { + const priceResult = await this.pricingService.getTokenPrices([ETH.coingeckoId]); + ethPriceInUsd = priceResult[ETH.coingeckoId]; + } catch (e) { + this.logger.error("Failed to get the price of ether."); + } + + return { + gasPriceInGwei: Number(formatGwei(gasPrice)), + ethPrice: ethPriceInUsd, + ethTransferGas: Number(ethTransferGasCost), + erc20TransferGas: Number(erc20TransferGasCost), + }; + } catch (e: unknown) { + if (isNativeError(e)) { + this.logger.error(`Failed to get gas information: ${e.message}`); + } + throw new L1ProviderException("Failed to get gas information from L1."); + } } + + /** + * Calculates the transaction value in USD based on the gas used, gas price, and ether price. + * Formula: (txGas * gasPriceInWei/1e18) * etherPrice + * @param txGas - The amount of gas used for the transaction. + * @param gasPrice - The price of gas in wei. + * @param etherPrice - The current price of ether in USD. + * @returns The transaction value in USD. + */ + private transactionInUsd(txGas: bigint, gasPriceInWei: bigint, etherPrice: number): number { + return Number(formatUnits(txGas * gasPriceInWei, ETHER_DECIMALS)) * etherPrice; + } + //TODO: Implement feeParams. async feeParams(_chainId: number): Promise<{ batchOverheadL1Gas: number; diff --git a/libs/metrics/src/types/gasInfo.type.ts b/libs/metrics/src/types/gasInfo.type.ts new file mode 100644 index 0000000..25e8e0b --- /dev/null +++ b/libs/metrics/src/types/gasInfo.type.ts @@ -0,0 +1,6 @@ +export type GasInfo = { + gasPriceInGwei: number; + ethPrice?: number; // USD + ethTransferGas: number; // units of gas + erc20TransferGas: number; // units of gas +}; diff --git a/libs/metrics/src/types/index.ts b/libs/metrics/src/types/index.ts index e69de29..5db10ba 100644 --- a/libs/metrics/src/types/index.ts +++ b/libs/metrics/src/types/index.ts @@ -0,0 +1 @@ +export * from "./gasInfo.type"; diff --git a/libs/providers/src/providers/evmProvider.service.ts b/libs/providers/src/providers/evmProvider.service.ts index e698c63..9cd018b 100644 --- a/libs/providers/src/providers/evmProvider.service.ts +++ b/libs/providers/src/providers/evmProvider.service.ts @@ -13,6 +13,7 @@ import { decodeAbiParameters, DecodeAbiParametersReturnType, encodeDeployData, + EstimateGasParameters, GetBlockReturnType, Hex, http, @@ -74,6 +75,10 @@ export class EvmProviderService { return this.client.getGasPrice(); } + async estimateGas(args: EstimateGasParameters): Promise { + return this.client.estimateGas(args); + } + /** * Retrieves the value from a storage slot at a given address. * @param {Address} address The address of the contract. diff --git a/libs/shared/src/constants/addresses.ts b/libs/shared/src/constants/addresses.ts new file mode 100644 index 0000000..c8c6fd0 --- /dev/null +++ b/libs/shared/src/constants/addresses.ts @@ -0,0 +1,3 @@ +import { Address } from "abitype"; + +export const vitalikAddress: Address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; diff --git a/libs/shared/src/constants/index.ts b/libs/shared/src/constants/index.ts index 9962fbe..4016c2e 100644 --- a/libs/shared/src/constants/index.ts +++ b/libs/shared/src/constants/index.ts @@ -1,3 +1,4 @@ export * from "./l1"; +export * from "./addresses"; export const TOKEN_CACHE_TTL_IN_SEC = 60; export const BASE_CURRENCY = "usd"; diff --git a/libs/shared/src/tokens/tokens.ts b/libs/shared/src/tokens/tokens.ts index ba1ded9..f63e8c3 100644 --- a/libs/shared/src/tokens/tokens.ts +++ b/libs/shared/src/tokens/tokens.ts @@ -1,13 +1,36 @@ +import { Address } from "abitype"; + export type TokenType = { name: string; symbol: string; coingeckoId: string; type: "erc20" | "native"; - contractAddress: string | null; + contractAddress: Address | null; decimals: number; imageUrl?: string; }; +export const WETH: TokenType = { + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + coingeckoId: "wrapped-bitcoin", + imageUrl: + "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", + type: "erc20", + decimals: 8, +}; + +export const ETH: TokenType = { + name: "Ethereum", + symbol: "ETH", + contractAddress: null, + coingeckoId: "ethereum", + type: "native", + imageUrl: "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628", + decimals: 18, +}; + export const tokens: TokenType[] = [ { name: "Ethereum", From 27a2ea0ee83af107ecf889bca97aad0359236f47 Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:47:37 -0300 Subject: [PATCH 2/3] test: add unit tests --- .../test/unit/l1/l1MetricsService.spec.ts | 176 ++++++++++++++++-- .../providers/evmProvider.service.spec.ts | 18 ++ 2 files changed, 182 insertions(+), 12 deletions(-) diff --git a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts index 1c6bb25..d9527b7 100644 --- a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts +++ b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts @@ -1,21 +1,23 @@ +import { createMock } from "@golevelup/ts-jest"; import { Logger } from "@nestjs/common"; 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 { L1MetricsService } from "@zkchainhub/metrics/l1/"; import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; import { EvmProviderService } from "@zkchainhub/providers"; -import { L1_CONTRACTS } from "@zkchainhub/shared"; +import { L1_CONTRACTS, vitalikAddress } from "@zkchainhub/shared"; +import { ETH, WETH } from "@zkchainhub/shared/tokens/tokens"; // Mock implementations of the dependencies -const mockEvmProviderService = { - // Mock methods and properties as needed -}; +const mockEvmProviderService = createMock(); -const mockPricingService = { - // Mock methods and properties as needed -}; +const mockPricingService = createMock(); + +const ONE_ETHER = parseEther("1"); export const mockLogger: Partial = { log: jest.fn(), @@ -34,11 +36,15 @@ describe("L1MetricsService", () => { { provide: L1MetricsService, useFactory: ( - evmProviderService: EvmProviderService, - pricingService: IPricingService, + mockEvmProviderService: EvmProviderService, + mockPricingService: IPricingService, logger: Logger, ) => { - return new L1MetricsService(evmProviderService, pricingService, logger); + return new L1MetricsService( + mockEvmProviderService, + mockPricingService, + logger, + ); }, inject: [EvmProviderService, PRICING_PROVIDER, WINSTON_MODULE_PROVIDER], }, @@ -60,6 +66,10 @@ describe("L1MetricsService", () => { l1MetricsService = module.get(L1MetricsService); }); + afterEach(() => { + jest.clearAllMocks(); + }); + describe("constructor", () => { it("initialize bridgeHub and sharedBridge", () => { expect(l1MetricsService["bridgeHub"]).toEqual({ @@ -106,9 +116,151 @@ describe("L1MetricsService", () => { }); describe("ethGasInfo", () => { - it("return ethGasInfo", async () => { + it("returns gas information from L1", async () => { + // Mock the necessary dependencies + const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost' + const mockGetGasPrice = jest.spyOn(mockEvmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [ETH.coingeckoId]: 2000 }); // ethPriceInUsd + + // Call the method const result = await l1MetricsService.ethGasInfo(); - expect(result).toEqual({ gasPrice: 50, ethTransfer: 21000, erc20Transfer: 65000 }); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: vitalikAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: vitalikAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [L1_CONTRACTS.SHARED_BRIDGE, ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).toHaveBeenCalledTimes(1); + expect(mockGetTokenPrices).toHaveBeenCalledWith([ETH.coingeckoId]); + + expect(result).toEqual({ + gasPriceInGwei: 50, + ethPrice: 2000, + ethTransferGas: 21000, + erc20TransferGas: 65000, + }); + }); + + it("returns gas information from L1 without ether price", async () => { + const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost + + const mockGetGasPrice = jest.spyOn(mockEvmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices"); + mockGetTokenPrices.mockRejectedValueOnce(new Error("Failed to get token prices")); + + const result = await l1MetricsService.ethGasInfo(); + + // Assertions + expect(result).toEqual({ + gasPriceInGwei: 50, + ethPrice: undefined, + ethTransferGas: 21000, + erc20TransferGas: 65000, + }); + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: vitalikAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: vitalikAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [L1_CONTRACTS.SHARED_BRIDGE, ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).toHaveBeenCalledTimes(1); + expect(mockGetTokenPrices).toHaveBeenCalledWith([ETH.coingeckoId]); + }); + + it("throws L1ProviderException when estimateGas fails", async () => { + // Mock the necessary dependencies + const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas"); + mockEstimateGas.mockRejectedValueOnce(new Error("Failed to estimate gas")); + + const mockGetGasPrice = jest.spyOn(mockEvmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [ETH.coingeckoId]: 2000 }); // ethPriceInUsd + + // Call the method and expect it to throw L1ProviderException + await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1ProviderException); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledWith({ + account: vitalikAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + + expect(mockGetTokenPrices).not.toHaveBeenCalled(); + }); + + it("throws L1ProviderException when getGasPrice fails", async () => { + // Mock the necessary dependencies + const mockEstimateGas = jest.spyOn(mockEvmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost + + const mockGetGasPrice = jest.spyOn(mockEvmProviderService, "getGasPrice"); + mockGetGasPrice.mockRejectedValueOnce(new Error("Failed to get gas price")); + + const mockGetTokenPrices = jest.spyOn(mockPricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [ETH.coingeckoId]: 2000 }); // ethPriceInUsd + + // Call the method and expect it to throw L1ProviderException + await expect(l1MetricsService.ethGasInfo()).rejects.toThrow(L1ProviderException); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: vitalikAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: vitalikAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [L1_CONTRACTS.SHARED_BRIDGE, ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).not.toHaveBeenCalled(); }); }); diff --git a/libs/providers/test/unit/providers/evmProvider.service.spec.ts b/libs/providers/test/unit/providers/evmProvider.service.spec.ts index c6c97a7..73cc90f 100644 --- a/libs/providers/test/unit/providers/evmProvider.service.spec.ts +++ b/libs/providers/test/unit/providers/evmProvider.service.spec.ts @@ -99,6 +99,24 @@ describe("EvmProviderService", () => { }); }); + describe("estimateGas", () => { + it("return the estimated gas for the given transaction", async () => { + const args = createMock>({ + account: "0xffff", + to: viem.zeroAddress, + value: 100n, + }); + + const expectedGas = 50000n; + jest.spyOn(mockClient, "estimateGas").mockResolvedValue(expectedGas); + + const gas = await viemProvider.estimateGas(args); + + expect(gas).toBe(expectedGas); + expect(mockClient.estimateGas).toHaveBeenCalledWith(args); + }); + }); + describe("getStorageAt", () => { it("should return the value of the storage slot at the given address and slot number", async () => { const address = "0x123456789"; From 865eca36fc0389666c9824e2ad3ff0d42b75b40d Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 5 Aug 2024 21:24:13 -0300 Subject: [PATCH 3/3] fix: delete unused function and update return types --- libs/metrics/src/l1/l1MetricsService.ts | 30 ++++--------------- libs/metrics/src/types/gasInfo.type.ts | 8 ++--- .../test/unit/l1/l1MetricsService.spec.ts | 14 ++++----- 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/libs/metrics/src/l1/l1MetricsService.ts b/libs/metrics/src/l1/l1MetricsService.ts index 35ebcec..e7d2ad1 100644 --- a/libs/metrics/src/l1/l1MetricsService.ts +++ b/libs/metrics/src/l1/l1MetricsService.ts @@ -1,14 +1,7 @@ import { isNativeError } from "util/types"; import { Inject, Injectable, LoggerService } from "@nestjs/common"; import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston"; -import { - encodeFunctionData, - erc20Abi, - formatGwei, - formatUnits, - parseEther, - zeroAddress, -} from "viem"; +import { encodeFunctionData, erc20Abi, formatGwei, parseEther, zeroAddress } from "viem"; import { L1ProviderException } from "@zkchainhub/metrics/exceptions/provider.exception"; import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; @@ -19,7 +12,6 @@ import { AbiWithAddress, ChainId, L1_CONTRACTS, vitalikAddress } from "@zkchainh import { ETH, WETH } from "@zkchainhub/shared/tokens/tokens"; const ONE_ETHER = parseEther("1"); -const ETHER_DECIMALS = 18; /** * Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain. @@ -100,10 +92,10 @@ export class L1MetricsService { } return { - gasPriceInGwei: Number(formatGwei(gasPrice)), - ethPrice: ethPriceInUsd, - ethTransferGas: Number(ethTransferGasCost), - erc20TransferGas: Number(erc20TransferGasCost), + gasPriceInGwei: formatGwei(gasPrice), + ethPrice: ethPriceInUsd?.toString(), + ethTransferGas: ethTransferGasCost.toString(), + erc20TransferGas: erc20TransferGasCost.toString(), }; } catch (e: unknown) { if (isNativeError(e)) { @@ -113,18 +105,6 @@ export class L1MetricsService { } } - /** - * Calculates the transaction value in USD based on the gas used, gas price, and ether price. - * Formula: (txGas * gasPriceInWei/1e18) * etherPrice - * @param txGas - The amount of gas used for the transaction. - * @param gasPrice - The price of gas in wei. - * @param etherPrice - The current price of ether in USD. - * @returns The transaction value in USD. - */ - private transactionInUsd(txGas: bigint, gasPriceInWei: bigint, etherPrice: number): number { - return Number(formatUnits(txGas * gasPriceInWei, ETHER_DECIMALS)) * etherPrice; - } - //TODO: Implement feeParams. async feeParams(_chainId: number): Promise<{ batchOverheadL1Gas: number; diff --git a/libs/metrics/src/types/gasInfo.type.ts b/libs/metrics/src/types/gasInfo.type.ts index 25e8e0b..d270e51 100644 --- a/libs/metrics/src/types/gasInfo.type.ts +++ b/libs/metrics/src/types/gasInfo.type.ts @@ -1,6 +1,6 @@ export type GasInfo = { - gasPriceInGwei: number; - ethPrice?: number; // USD - ethTransferGas: number; // units of gas - erc20TransferGas: number; // units of gas + gasPriceInGwei: string; + ethPrice?: string; // USD + ethTransferGas: string; // units of gas + erc20TransferGas: string; // units of gas }; diff --git a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts index d9527b7..6ce1bfb 100644 --- a/libs/metrics/test/unit/l1/l1MetricsService.spec.ts +++ b/libs/metrics/test/unit/l1/l1MetricsService.spec.ts @@ -153,10 +153,10 @@ describe("L1MetricsService", () => { expect(mockGetTokenPrices).toHaveBeenCalledWith([ETH.coingeckoId]); expect(result).toEqual({ - gasPriceInGwei: 50, - ethPrice: 2000, - ethTransferGas: 21000, - erc20TransferGas: 65000, + gasPriceInGwei: "50", + ethPrice: "2000", + ethTransferGas: "21000", + erc20TransferGas: "65000", }); }); @@ -175,10 +175,10 @@ describe("L1MetricsService", () => { // Assertions expect(result).toEqual({ - gasPriceInGwei: 50, + gasPriceInGwei: "50", ethPrice: undefined, - ethTransferGas: 21000, - erc20TransferGas: 65000, + ethTransferGas: "21000", + erc20TransferGas: "65000", }); expect(mockEstimateGas).toHaveBeenCalledTimes(2); expect(mockEstimateGas).toHaveBeenNthCalledWith(1, {