diff --git a/packages/metrics/package.json b/packages/metrics/package.json index 69342a8..77e81cb 100644 --- a/packages/metrics/package.json +++ b/packages/metrics/package.json @@ -11,8 +11,14 @@ "lint": "eslint \"{src,test}/**/*.{js,ts,json}\"", "lint:fix": "pnpm lint --fix", "format": "prettier --check \"{src,test}/**/*.{js,ts,json}\"", - "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"" + "format:fix": "prettier --write \"{src,test}/**/*.{js,ts,json}\"", + "test": "vitest run --config vitest.config.ts --passWithNoTests", + "test:cov": "vitest run --config vitest.config.ts --coverage" }, - "dependencies": {}, - "devDependencies": {} + "dependencies": { + "@zkchainhub/shared": "workspace:*", + "@zkchainhub/pricing": "workspace:*", + "@zkchainhub/providers": "workspace:*", + "viem": "2.19.6" + } } diff --git a/packages/metrics/src/exceptions/index.ts b/packages/metrics/src/exceptions/index.ts new file mode 100644 index 0000000..68ad4c3 --- /dev/null +++ b/packages/metrics/src/exceptions/index.ts @@ -0,0 +1,3 @@ +export * from "./invalidChainId.exception.js"; +export * from "./l1MetricsService.exception.js"; +export * from "./invalidChainType.exception.js"; diff --git a/packages/metrics/src/exceptions/invalidChainId.exception.ts b/packages/metrics/src/exceptions/invalidChainId.exception.ts new file mode 100644 index 0000000..6eb4827 --- /dev/null +++ b/packages/metrics/src/exceptions/invalidChainId.exception.ts @@ -0,0 +1,6 @@ +export class InvalidChainId extends Error { + constructor(message: string) { + super(message); + this.name = "InvalidChainId"; + } +} diff --git a/packages/metrics/src/exceptions/invalidChainType.exception.ts b/packages/metrics/src/exceptions/invalidChainType.exception.ts new file mode 100644 index 0000000..ae9c0bd --- /dev/null +++ b/packages/metrics/src/exceptions/invalidChainType.exception.ts @@ -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"; + } +} diff --git a/packages/metrics/src/exceptions/l1MetricsService.exception.ts b/packages/metrics/src/exceptions/l1MetricsService.exception.ts new file mode 100644 index 0000000..40f1fb8 --- /dev/null +++ b/packages/metrics/src/exceptions/l1MetricsService.exception.ts @@ -0,0 +1,6 @@ +export class L1MetricsServiceException extends Error { + constructor(message: string) { + super(message); + this.name = "L1MetricsServiceException"; + } +} diff --git a/packages/metrics/src/external.ts b/packages/metrics/src/external.ts new file mode 100644 index 0000000..e250302 --- /dev/null +++ b/packages/metrics/src/external.ts @@ -0,0 +1,5 @@ +export type { FeeParams, GasInfo, AssetTvl } from "./internal.js"; + +export { InvalidChainId, InvalidChainType, L1MetricsServiceException } from "./internal.js"; + +export { L1MetricsService } from "./internal.js"; diff --git a/packages/metrics/src/index.ts b/packages/metrics/src/index.ts index e69de29..a5a2748 100644 --- a/packages/metrics/src/index.ts +++ b/packages/metrics/src/index.ts @@ -0,0 +1 @@ +export * from "./external.js"; diff --git a/packages/metrics/src/internal.ts b/packages/metrics/src/internal.ts new file mode 100644 index 0000000..a802462 --- /dev/null +++ b/packages/metrics/src/internal.ts @@ -0,0 +1,4 @@ +export * from "./types/index.js"; +export * from "./exceptions/index.js"; +export * from "./l1/abis/index.js"; +export * from "./l1/index.js"; diff --git a/packages/metrics/src/l1/abis/bridgeHub.abi.ts b/packages/metrics/src/l1/abis/bridgeHub.abi.ts new file mode 100644 index 0000000..d26112e --- /dev/null +++ b/packages/metrics/src/l1/abis/bridgeHub.abi.ts @@ -0,0 +1,819 @@ +export const bridgeHubAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldAdmin", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "NewAdmin", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: false, + internalType: "address", + name: "stateTransitionManager", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "chainGovernance", + type: "address", + }, + ], + name: "NewChain", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldPendingAdmin", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newPendingAdmin", + type: "address", + }, + ], + name: "NewPendingAdmin", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferStarted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + inputs: [], + name: "acceptAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "acceptOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_stateTransitionManager", + type: "address", + }, + ], + name: "addStateTransitionManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + ], + name: "addToken", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "admin", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "baseToken", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_stateTransitionManager", + type: "address", + }, + { + internalType: "address", + name: "_baseToken", + type: "address", + }, + { + internalType: "uint256", + name: "_salt", + type: "uint256", + }, + { + internalType: "address", + name: "_admin", + type: "address", + }, + { + internalType: "bytes", + name: "_initData", + type: "bytes", + }, + ], + name: "createNewChain", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "getHyperchain", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_owner", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_gasPrice", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2GasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2GasPerPubdataByteLimit", + type: "uint256", + }, + ], + name: "l2TransactionBaseCost", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "bytes32", + name: "_l2TxHash", + type: "bytes32", + }, + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + { + internalType: "uint16", + name: "_l2TxNumberInBatch", + type: "uint16", + }, + { + internalType: "bytes32[]", + name: "_merkleProof", + type: "bytes32[]", + }, + { + internalType: "enum TxStatus", + name: "_status", + type: "uint8", + }, + ], + name: "proveL1ToL2TransactionStatus", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_batchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_index", + type: "uint256", + }, + { + components: [ + { + internalType: "uint8", + name: "l2ShardId", + type: "uint8", + }, + { + internalType: "bool", + name: "isService", + type: "bool", + }, + { + internalType: "uint16", + name: "txNumberInBatch", + type: "uint16", + }, + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "bytes32", + name: "key", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "value", + type: "bytes32", + }, + ], + internalType: "struct L2Log", + name: "_log", + type: "tuple", + }, + { + internalType: "bytes32[]", + name: "_proof", + type: "bytes32[]", + }, + ], + name: "proveL2LogInclusion", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_batchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_index", + type: "uint256", + }, + { + components: [ + { + internalType: "uint16", + name: "txNumberInBatch", + type: "uint16", + }, + { + internalType: "address", + name: "sender", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + internalType: "struct L2Message", + name: "_message", + type: "tuple", + }, + { + internalType: "bytes32[]", + name: "_proof", + type: "bytes32[]", + }, + ], + name: "proveL2MessageInclusion", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_stateTransitionManager", + type: "address", + }, + ], + name: "removeStateTransitionManager", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "mintValue", + type: "uint256", + }, + { + internalType: "address", + name: "l2Contract", + type: "address", + }, + { + internalType: "uint256", + name: "l2Value", + type: "uint256", + }, + { + internalType: "bytes", + name: "l2Calldata", + type: "bytes", + }, + { + internalType: "uint256", + name: "l2GasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2GasPerPubdataByteLimit", + type: "uint256", + }, + { + internalType: "bytes[]", + name: "factoryDeps", + type: "bytes[]", + }, + { + internalType: "address", + name: "refundRecipient", + type: "address", + }, + ], + internalType: "struct L2TransactionRequestDirect", + name: "_request", + type: "tuple", + }, + ], + name: "requestL2TransactionDirect", + outputs: [ + { + internalType: "bytes32", + name: "canonicalTxHash", + type: "bytes32", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "mintValue", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2Value", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2GasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2GasPerPubdataByteLimit", + type: "uint256", + }, + { + internalType: "address", + name: "refundRecipient", + type: "address", + }, + { + internalType: "address", + name: "secondBridgeAddress", + type: "address", + }, + { + internalType: "uint256", + name: "secondBridgeValue", + type: "uint256", + }, + { + internalType: "bytes", + name: "secondBridgeCalldata", + type: "bytes", + }, + ], + internalType: "struct L2TransactionRequestTwoBridgesOuter", + name: "_request", + type: "tuple", + }, + ], + name: "requestL2TransactionTwoBridges", + outputs: [ + { + internalType: "bytes32", + name: "canonicalTxHash", + type: "bytes32", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_newPendingAdmin", + type: "address", + }, + ], + name: "setPendingAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_sharedBridge", + type: "address", + }, + ], + name: "setSharedBridge", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "sharedBridge", + outputs: [ + { + internalType: "contract IL1SharedBridge", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "stateTransitionManager", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_stateTransitionManager", + type: "address", + }, + ], + name: "stateTransitionManagerIsRegistered", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + ], + name: "tokenIsRegistered", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/packages/metrics/src/l1/abis/diamondProxy.abi.ts b/packages/metrics/src/l1/abis/diamondProxy.abi.ts new file mode 100644 index 0000000..2a5a8e5 --- /dev/null +++ b/packages/metrics/src/l1/abis/diamondProxy.abi.ts @@ -0,0 +1,663 @@ +export const diamondProxyAbi = [ + { + inputs: [], + name: "baseTokenGasPriceMultiplierDenominator", + outputs: [ + { + internalType: "uint128", + name: "", + type: "uint128", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "baseTokenGasPriceMultiplierNominator", + outputs: [ + { + internalType: "uint128", + name: "", + type: "uint128", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "_selector", + type: "bytes4", + }, + ], + name: "facetAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "facetAddresses", + outputs: [ + { + internalType: "address[]", + name: "", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_facet", + type: "address", + }, + ], + name: "facetFunctionSelectors", + outputs: [ + { + internalType: "bytes4[]", + name: "", + type: "bytes4[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "facets", + outputs: [ + { + components: [ + { + internalType: "address", + name: "addr", + type: "address", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct IGetters.Facet[]", + name: "result", + type: "tuple[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAdmin", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBaseToken", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBaseTokenBridge", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBridgehub", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getFirstUnprocessedPriorityTx", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getL2BootloaderBytecodeHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getL2DefaultAccountBytecodeHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getL2SystemContractsUpgradeBatchNumber", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getL2SystemContractsUpgradeBlockNumber", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getL2SystemContractsUpgradeTxHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getName", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPendingAdmin", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPriorityQueueSize", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPriorityTxMaxGasLimit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getProtocolVersion", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPubdataPricingMode", + outputs: [ + { + internalType: "enum PubdataPricingMode", + name: "", + type: "uint8", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getSemverProtocolVersion", + outputs: [ + { + internalType: "uint32", + name: "", + type: "uint32", + }, + { + internalType: "uint32", + name: "", + type: "uint32", + }, + { + internalType: "uint32", + name: "", + type: "uint32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getStateTransitionManager", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBatchesCommitted", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBatchesExecuted", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBatchesVerified", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBlocksCommitted", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBlocksExecuted", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalBlocksVerified", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getTotalPriorityTxs", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getVerifier", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getVerifierParams", + outputs: [ + { + components: [ + { + internalType: "bytes32", + name: "recursionNodeLevelVkHash", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "recursionLeafLevelVkHash", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "recursionCircuitsSetVksHash", + type: "bytes32", + }, + ], + internalType: "struct VerifierParams", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "isDiamondStorageFrozen", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + ], + name: "isEthWithdrawalFinalized", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_facet", + type: "address", + }, + ], + name: "isFacetFreezable", + outputs: [ + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bytes4", + name: "_selector", + type: "bytes4", + }, + ], + name: "isFunctionFreezable", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_address", + type: "address", + }, + ], + name: "isValidator", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_batchNumber", + type: "uint256", + }, + ], + name: "l2LogsRootHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "priorityQueueFrontOperation", + outputs: [ + { + components: [ + { + internalType: "bytes32", + name: "canonicalTxHash", + type: "bytes32", + }, + { + internalType: "uint64", + name: "expirationTimestamp", + type: "uint64", + }, + { + internalType: "uint192", + name: "layer2Tip", + type: "uint192", + }, + ], + internalType: "struct PriorityOperation", + name: "", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_batchNumber", + type: "uint256", + }, + ], + name: "storedBatchHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_batchNumber", + type: "uint256", + }, + ], + name: "storedBlockHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/packages/metrics/src/l1/abis/index.ts b/packages/metrics/src/l1/abis/index.ts new file mode 100644 index 0000000..a95b8eb --- /dev/null +++ b/packages/metrics/src/l1/abis/index.ts @@ -0,0 +1,5 @@ +export * from "./bridgeHub.abi.js"; +export * from "./diamondProxy.abi.js"; +export * from "./sharedBridge.abi.js"; +export * from "./multicall3.abi.js"; +export * from "./stateTransitionManager.abi.js"; diff --git a/packages/metrics/src/l1/abis/multicall3.abi.ts b/packages/metrics/src/l1/abis/multicall3.abi.ts new file mode 100644 index 0000000..e664a83 --- /dev/null +++ b/packages/metrics/src/l1/abis/multicall3.abi.ts @@ -0,0 +1,440 @@ +export const multicall3Abi = [ + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate", + outputs: [ + { + internalType: "uint256", + name: "blockNumber", + type: "uint256", + }, + { + internalType: "bytes[]", + name: "returnData", + type: "bytes[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bool", + name: "allowFailure", + type: "bool", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call3[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate3", + outputs: [ + { + components: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bool", + name: "allowFailure", + type: "bool", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call3Value[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "aggregate3Value", + outputs: [ + { + components: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "blockAndAggregate", + outputs: [ + { + internalType: "uint256", + name: "blockNumber", + type: "uint256", + }, + { + internalType: "bytes32", + name: "blockHash", + type: "bytes32", + }, + { + components: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getBasefee", + outputs: [ + { + internalType: "uint256", + name: "basefee", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "blockNumber", + type: "uint256", + }, + ], + name: "getBlockHash", + outputs: [ + { + internalType: "bytes32", + name: "blockHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getBlockNumber", + outputs: [ + { + internalType: "uint256", + name: "blockNumber", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getChainId", + outputs: [ + { + internalType: "uint256", + name: "chainid", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockCoinbase", + outputs: [ + { + internalType: "address", + name: "coinbase", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockDifficulty", + outputs: [ + { + internalType: "uint256", + name: "difficulty", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockGasLimit", + outputs: [ + { + internalType: "uint256", + name: "gaslimit", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getCurrentBlockTimestamp", + outputs: [ + { + internalType: "uint256", + name: "timestamp", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "addr", + type: "address", + }, + ], + name: "getEthBalance", + outputs: [ + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getLastBlockHash", + outputs: [ + { + internalType: "bytes32", + name: "blockHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "bool", + name: "requireSuccess", + type: "bool", + }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "tryAggregate", + outputs: [ + { + components: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "bool", + name: "requireSuccess", + type: "bool", + }, + { + components: [ + { + internalType: "address", + name: "target", + type: "address", + }, + { + internalType: "bytes", + name: "callData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Call[]", + name: "calls", + type: "tuple[]", + }, + ], + name: "tryBlockAndAggregate", + outputs: [ + { + internalType: "uint256", + name: "blockNumber", + type: "uint256", + }, + { + internalType: "bytes32", + name: "blockHash", + type: "bytes32", + }, + { + components: [ + { + internalType: "bool", + name: "success", + type: "bool", + }, + { + internalType: "bytes", + name: "returnData", + type: "bytes", + }, + ], + internalType: "struct Multicall3.Result[]", + name: "returnData", + type: "tuple[]", + }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/packages/metrics/src/l1/abis/sharedBridge.abi.ts b/packages/metrics/src/l1/abis/sharedBridge.abi.ts new file mode 100644 index 0000000..30114f5 --- /dev/null +++ b/packages/metrics/src/l1/abis/sharedBridge.abi.ts @@ -0,0 +1,1051 @@ +export const sharedBridgeAbi = [ + { + inputs: [ + { + internalType: "address", + name: "_l1WethAddress", + type: "address", + }, + { + internalType: "contract IBridgehub", + name: "_bridgehub", + type: "address", + }, + { + internalType: "uint256", + name: "_eraChainId", + type: "uint256", + }, + { + internalType: "address", + name: "_eraDiamondProxy", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "l1Token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "BridgehubDepositBaseTokenInitiated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "bytes32", + name: "txDataHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "bytes32", + name: "l2DepositTxHash", + type: "bytes32", + }, + ], + name: "BridgehubDepositFinalized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "bytes32", + name: "txDataHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "l1Token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "BridgehubDepositInitiated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "l1Token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "ClaimedFailedDepositSharedBridge", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "bytes32", + name: "l2DepositTxHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "from", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "l1Token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "LegacyDepositInitiated", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferStarted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Paused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "account", + type: "address", + }, + ], + name: "Unpaused", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "to", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "l1Token", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "WithdrawalFinalizedSharedBridge", + type: "event", + }, + { + inputs: [], + name: "BRIDGE_HUB", + outputs: [ + { + internalType: "contract IBridgehub", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "ERA_CHAIN_ID", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "ERA_DIAMOND_PROXY", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "L1_WETH_TOKEN", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "acceptOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "bytes32", + name: "_txDataHash", + type: "bytes32", + }, + { + internalType: "bytes32", + name: "_txHash", + type: "bytes32", + }, + ], + name: "bridgehubConfirmL2Transaction", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_prevMsgSender", + type: "address", + }, + { + internalType: "uint256", + name: "_l2Value", + type: "uint256", + }, + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + ], + name: "bridgehubDeposit", + outputs: [ + { + components: [ + { + internalType: "bytes32", + name: "magicValue", + type: "bytes32", + }, + { + internalType: "address", + name: "l2Contract", + type: "address", + }, + { + internalType: "bytes", + name: "l2Calldata", + type: "bytes", + }, + { + internalType: "bytes[]", + name: "factoryDeps", + type: "bytes[]", + }, + { + internalType: "bytes32", + name: "txDataHash", + type: "bytes32", + }, + ], + internalType: "struct L2TransactionRequestTwoBridgesInner", + name: "request", + type: "tuple", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_prevMsgSender", + type: "address", + }, + { + internalType: "address", + name: "_l1Token", + type: "address", + }, + { + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + ], + name: "bridgehubDepositBaseToken", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "address", + name: "l1Token", + type: "address", + }, + ], + name: "chainBalance", + outputs: [ + { + internalType: "uint256", + name: "balance", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_depositSender", + type: "address", + }, + { + internalType: "address", + name: "_l1Token", + type: "address", + }, + { + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + { + internalType: "bytes32", + name: "_l2TxHash", + type: "bytes32", + }, + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + { + internalType: "uint16", + name: "_l2TxNumberInBatch", + type: "uint16", + }, + { + internalType: "bytes32[]", + name: "_merkleProof", + type: "bytes32[]", + }, + ], + name: "claimFailedDeposit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_depositSender", + type: "address", + }, + { + internalType: "address", + name: "_l1Token", + type: "address", + }, + { + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + { + internalType: "bytes32", + name: "_l2TxHash", + type: "bytes32", + }, + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + { + internalType: "uint16", + name: "_l2TxNumberInBatch", + type: "uint16", + }, + { + internalType: "bytes32[]", + name: "_merkleProof", + type: "bytes32[]", + }, + ], + name: "claimFailedDepositLegacyErc20Bridge", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "bytes32", + name: "l2DepositTxHash", + type: "bytes32", + }, + ], + name: "depositHappened", + outputs: [ + { + internalType: "bytes32", + name: "depositDataHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_prevMsgSender", + type: "address", + }, + { + internalType: "address", + name: "_l2Receiver", + type: "address", + }, + { + internalType: "address", + name: "_l1Token", + type: "address", + }, + { + internalType: "uint256", + name: "_amount", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2TxGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2TxGasPerPubdataByte", + type: "uint256", + }, + { + internalType: "address", + name: "_refundRecipient", + type: "address", + }, + ], + name: "depositLegacyErc20Bridge", + outputs: [ + { + internalType: "bytes32", + name: "l2TxHash", + type: "bytes32", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + { + internalType: "uint16", + name: "_l2TxNumberInBatch", + type: "uint16", + }, + { + internalType: "bytes", + name: "_message", + type: "bytes", + }, + { + internalType: "bytes32[]", + name: "_merkleProof", + type: "bytes32[]", + }, + ], + name: "finalizeWithdrawal", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "_l2MessageIndex", + type: "uint256", + }, + { + internalType: "uint16", + name: "_l2TxNumberInBatch", + type: "uint16", + }, + { + internalType: "bytes", + name: "_message", + type: "bytes", + }, + { + internalType: "bytes32[]", + name: "_merkleProof", + type: "bytes32[]", + }, + ], + name: "finalizeWithdrawalLegacyErc20Bridge", + outputs: [ + { + internalType: "address", + name: "l1Receiver", + type: "address", + }, + { + internalType: "address", + name: "l1Token", + type: "address", + }, + { + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_owner", + type: "address", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_l2BridgeAddress", + type: "address", + }, + ], + name: "initializeChainGovernance", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2BatchNumber", + type: "uint256", + }, + { + internalType: "uint256", + name: "l2ToL1MessageNumber", + type: "uint256", + }, + ], + name: "isWithdrawalFinalized", + outputs: [ + { + internalType: "bool", + name: "isFinalized", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "chainId", + type: "uint256", + }, + ], + name: "l2BridgeAddress", + outputs: [ + { + internalType: "address", + name: "l2Bridge", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "legacyBridge", + outputs: [ + { + internalType: "contract IL1ERC20Bridge", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "paused", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "receiveEth", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + { + internalType: "address", + name: "_target", + type: "address", + }, + { + internalType: "uint256", + name: "_targetChainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_gasPerToken", + type: "uint256", + }, + ], + name: "safeTransferFundsFromLegacy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_eraLegacyBridgeLastDepositBatch", + type: "uint256", + }, + { + internalType: "uint256", + name: "_eraLegacyBridgeLastDepositTxNumber", + type: "uint256", + }, + ], + name: "setEraLegacyBridgeLastDepositTime", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_eraPostDiamondUpgradeFirstBatch", + type: "uint256", + }, + ], + name: "setEraPostDiamondUpgradeFirstBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_eraPostLegacyBridgeUpgradeFirstBatch", + type: "uint256", + }, + ], + name: "setEraPostLegacyBridgeUpgradeFirstBatch", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_legacyBridge", + type: "address", + }, + ], + name: "setL1Erc20Bridge", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_token", + type: "address", + }, + { + internalType: "address", + name: "_target", + type: "address", + }, + { + internalType: "uint256", + name: "_targetChainId", + type: "uint256", + }, + ], + name: "transferFundsFromLegacy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "unpause", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/packages/metrics/src/l1/abis/stateTransitionManager.abi.ts b/packages/metrics/src/l1/abis/stateTransitionManager.abi.ts new file mode 100644 index 0000000..b01bfcb --- /dev/null +++ b/packages/metrics/src/l1/abis/stateTransitionManager.abi.ts @@ -0,0 +1,1329 @@ +export const stateTransitionManagerAbi = [ + { + inputs: [ + { + internalType: "address", + name: "_bridgehub", + type: "address", + }, + { + internalType: "uint256", + name: "_maxNumberOfHyperchains", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "uint8", + name: "version", + type: "uint8", + }, + ], + name: "Initialized", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldAdmin", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "NewAdmin", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "genesisUpgrade", + type: "address", + }, + { + indexed: false, + internalType: "bytes32", + name: "genesisBatchHash", + type: "bytes32", + }, + { + indexed: false, + internalType: "uint64", + name: "genesisIndexRepeatedStorageChanges", + type: "uint64", + }, + { + indexed: false, + internalType: "bytes32", + name: "genesisBatchCommitment", + type: "bytes32", + }, + { + indexed: false, + internalType: "bytes32", + name: "newInitialCutHash", + type: "bytes32", + }, + ], + name: "NewChainCreationParams", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + indexed: true, + internalType: "address", + name: "_hyperchainContract", + type: "address", + }, + ], + name: "NewHyperchain", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldPendingAdmin", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newPendingAdmin", + type: "address", + }, + ], + name: "NewPendingAdmin", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "oldProtocolVersion", + type: "uint256", + }, + { + indexed: true, + internalType: "uint256", + name: "newProtocolVersion", + type: "uint256", + }, + ], + name: "NewProtocolVersion", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "uint256", + name: "protocolVersion", + type: "uint256", + }, + { + indexed: true, + internalType: "bytes32", + name: "upgradeCutHash", + type: "bytes32", + }, + ], + name: "NewUpgradeCutHash", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "oldValidatorTimelock", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newValidatorTimelock", + type: "address", + }, + ], + name: "NewValidatorTimelock", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferStarted", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "previousOwner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "_hyperchain", + type: "address", + }, + { + components: [ + { + internalType: "uint256", + name: "txType", + type: "uint256", + }, + { + internalType: "uint256", + name: "from", + type: "uint256", + }, + { + internalType: "uint256", + name: "to", + type: "uint256", + }, + { + internalType: "uint256", + name: "gasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "gasPerPubdataByteLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "maxFeePerGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "maxPriorityFeePerGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "paymaster", + type: "uint256", + }, + { + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + internalType: "uint256[4]", + name: "reserved", + type: "uint256[4]", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + { + internalType: "bytes", + name: "signature", + type: "bytes", + }, + { + internalType: "uint256[]", + name: "factoryDeps", + type: "uint256[]", + }, + { + internalType: "bytes", + name: "paymasterInput", + type: "bytes", + }, + { + internalType: "bytes", + name: "reservedDynamic", + type: "bytes", + }, + ], + indexed: false, + internalType: "struct L2CanonicalTransaction", + name: "_l2Transaction", + type: "tuple", + }, + { + indexed: true, + internalType: "uint256", + name: "_protocolVersion", + type: "uint256", + }, + ], + name: "SetChainIdUpgrade", + type: "event", + }, + { + inputs: [], + name: "BRIDGE_HUB", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "MAX_NUMBER_OF_HYPERCHAINS", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "acceptAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "acceptOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "admin", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + components: [ + { + internalType: "enum PubdataPricingMode", + name: "pubdataPricingMode", + type: "uint8", + }, + { + internalType: "uint32", + name: "batchOverheadL1Gas", + type: "uint32", + }, + { + internalType: "uint32", + name: "maxPubdataPerBatch", + type: "uint32", + }, + { + internalType: "uint32", + name: "maxL2GasPerBatch", + type: "uint32", + }, + { + internalType: "uint32", + name: "priorityTxMaxPubdata", + type: "uint32", + }, + { + internalType: "uint64", + name: "minimalL2GasPrice", + type: "uint64", + }, + ], + internalType: "struct FeeParams", + name: "_newFeeParams", + type: "tuple", + }, + ], + name: "changeFeeParams", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_baseToken", + type: "address", + }, + { + internalType: "address", + name: "_sharedBridge", + type: "address", + }, + { + internalType: "address", + name: "_admin", + type: "address", + }, + { + internalType: "bytes", + name: "_diamondCut", + type: "bytes", + }, + ], + name: "createNewChain", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "_diamondCut", + type: "tuple", + }, + ], + name: "executeUpgrade", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "freezeChain", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "genesisUpgrade", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAllHyperchainChainIDs", + outputs: [ + { + internalType: "uint256[]", + name: "", + type: "uint256[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getAllHyperchains", + outputs: [ + { + internalType: "address[]", + name: "chainAddresses", + type: "address[]", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "getChainAdmin", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "getHyperchain", + outputs: [ + { + internalType: "address", + name: "chainAddress", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getSemverProtocolVersion", + outputs: [ + { + internalType: "uint32", + name: "", + type: "uint32", + }, + { + internalType: "uint32", + name: "", + type: "uint32", + }, + { + internalType: "uint32", + name: "", + type: "uint32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "initialCutHash", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "owner", + type: "address", + }, + { + internalType: "address", + name: "validatorTimelock", + type: "address", + }, + { + components: [ + { + internalType: "address", + name: "genesisUpgrade", + type: "address", + }, + { + internalType: "bytes32", + name: "genesisBatchHash", + type: "bytes32", + }, + { + internalType: "uint64", + name: "genesisIndexRepeatedStorageChanges", + type: "uint64", + }, + { + internalType: "bytes32", + name: "genesisBatchCommitment", + type: "bytes32", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "diamondCut", + type: "tuple", + }, + ], + internalType: "struct ChainCreationParams", + name: "chainCreationParams", + type: "tuple", + }, + { + internalType: "uint256", + name: "protocolVersion", + type: "uint256", + }, + ], + internalType: "struct StateTransitionManagerInitializeData", + name: "_initializeData", + type: "tuple", + }, + ], + name: "initialize", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pendingOwner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "protocolVersion", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_protocolVersion", + type: "uint256", + }, + ], + name: "protocolVersionDeadline", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_protocolVersion", + type: "uint256", + }, + ], + name: "protocolVersionIsActive", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_hyperchain", + type: "address", + }, + ], + name: "registerAlreadyDeployedHyperchain", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "renounceOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_newLastBatch", + type: "uint256", + }, + ], + name: "revertBatches", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + internalType: "address", + name: "genesisUpgrade", + type: "address", + }, + { + internalType: "bytes32", + name: "genesisBatchHash", + type: "bytes32", + }, + { + internalType: "uint64", + name: "genesisIndexRepeatedStorageChanges", + type: "uint64", + }, + { + internalType: "bytes32", + name: "genesisBatchCommitment", + type: "bytes32", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "diamondCut", + type: "tuple", + }, + ], + internalType: "struct ChainCreationParams", + name: "_chainCreationParams", + type: "tuple", + }, + ], + name: "setChainCreationParams", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "_cutData", + type: "tuple", + }, + { + internalType: "uint256", + name: "_oldProtocolVersion", + type: "uint256", + }, + { + internalType: "uint256", + name: "_oldProtocolVersionDeadline", + type: "uint256", + }, + { + internalType: "uint256", + name: "_newProtocolVersion", + type: "uint256", + }, + ], + name: "setNewVersionUpgrade", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_newPendingAdmin", + type: "address", + }, + ], + name: "setPendingAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "bool", + name: "_zkPorterIsAvailable", + type: "bool", + }, + ], + name: "setPorterAvailability", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_maxGasLimit", + type: "uint256", + }, + ], + name: "setPriorityTxMaxGasLimit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_protocolVersion", + type: "uint256", + }, + { + internalType: "uint256", + name: "_timestamp", + type: "uint256", + }, + ], + name: "setProtocolVersionDeadline", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint128", + name: "_nominator", + type: "uint128", + }, + { + internalType: "uint128", + name: "_denominator", + type: "uint128", + }, + ], + name: "setTokenMultiplier", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "_cutData", + type: "tuple", + }, + { + internalType: "uint256", + name: "_oldProtocolVersion", + type: "uint256", + }, + ], + name: "setUpgradeDiamondCut", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "address", + name: "_validator", + type: "address", + }, + { + internalType: "bool", + name: "_active", + type: "bool", + }, + ], + name: "setValidator", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "_validatorTimelock", + type: "address", + }, + ], + name: "setValidatorTimelock", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "storedBatchZero", + outputs: [ + { + internalType: "bytes32", + name: "", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newOwner", + type: "address", + }, + ], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + ], + name: "unfreezeChain", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_chainId", + type: "uint256", + }, + { + internalType: "uint256", + name: "_oldProtocolVersion", + type: "uint256", + }, + { + components: [ + { + components: [ + { + internalType: "address", + name: "facet", + type: "address", + }, + { + internalType: "enum Diamond.Action", + name: "action", + type: "uint8", + }, + { + internalType: "bool", + name: "isFreezable", + type: "bool", + }, + { + internalType: "bytes4[]", + name: "selectors", + type: "bytes4[]", + }, + ], + internalType: "struct Diamond.FacetCut[]", + name: "facetCuts", + type: "tuple[]", + }, + { + internalType: "address", + name: "initAddress", + type: "address", + }, + { + internalType: "bytes", + name: "initCalldata", + type: "bytes", + }, + ], + internalType: "struct Diamond.DiamondCutData", + name: "_diamondCut", + type: "tuple", + }, + ], + name: "upgradeChainFromVersion", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "protocolVersion", + type: "uint256", + }, + ], + name: "upgradeCutHash", + outputs: [ + { + internalType: "bytes32", + name: "cutHash", + type: "bytes32", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "validatorTimelock", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/packages/metrics/src/l1/index.ts b/packages/metrics/src/l1/index.ts new file mode 100644 index 0000000..bd60d70 --- /dev/null +++ b/packages/metrics/src/l1/index.ts @@ -0,0 +1 @@ +export * from "./l1MetricsService.js"; diff --git a/packages/metrics/src/l1/l1MetricsService.ts b/packages/metrics/src/l1/l1MetricsService.ts new file mode 100644 index 0000000..fec2bcf --- /dev/null +++ b/packages/metrics/src/l1/l1MetricsService.ts @@ -0,0 +1,451 @@ +import assert from "assert"; +import { isNativeError } from "util/types"; +import { + Address, + encodeFunctionData, + erc20Abi, + formatUnits, + Hex, + parseEther, + parseUnits, + zeroAddress, +} from "viem"; + +import { IPricingService } from "@zkchainhub/pricing"; +import { EvmProviderService } from "@zkchainhub/providers"; +import { + BatchesInfo, + ChainId, + Chains, + ChainType, + erc20Tokens, + ETH_TOKEN_ADDRESS, + ILogger, + isNativeToken, + nativeToken, + Token, + tokens, + WETH, +} from "@zkchainhub/shared"; + +import { + AssetTvl, + bridgeHubAbi, + diamondProxyAbi, + FeeParams, + feeParamsFieldHexDigits, + GasInfo, + InvalidChainId, + InvalidChainType, + L1MetricsServiceException, + multicall3Abi, + sharedBridgeAbi, + stateTransitionManagerAbi, +} from "../internal.js"; + +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. + */ +export class L1MetricsService { + private readonly diamondContracts: Map = new Map(); + private chainIds?: ChainId[]; + constructor( + private readonly bridgeHubAddress: Address, + private readonly sharedBridgeAddress: Address, + private readonly stateTransitionManagerAddresses: Address[], + private readonly evmProviderService: EvmProviderService, + private readonly pricingService: IPricingService, + private readonly logger: ILogger, + ) {} + + /** + * Retrieves the Total Value Locked by token on L1 Shared Bridge contract + * @returns A Promise that resolves to an array of AssetTvl objects representing the TVL for each asset. + */ + async l1Tvl(): Promise { + const erc20Addresses = Object.values(erc20Tokens).map((token) => token.contractAddress); + + const balances = await this.fetchTokenBalances(erc20Addresses); + const pricesRecord = await this.pricingService.getTokenPrices( + tokens.map((token) => token.coingeckoId), + ); + + assert(Object.keys(pricesRecord).length === tokens.length, "Invalid prices length"); + + return this.calculateTvl(balances, erc20Addresses, pricesRecord); + } + + /** + * Calculates the Total Value Locked (TVL) for each token based on the provided balances, addresses, and prices. + * @param balances - The balances object containing the ETH balance and an array of erc20 token addresses balance. + * @param addresses - The array of erc20 addresses. + * @param prices - The object containing the prices of tokens. + * @returns An array of AssetTvl objects representing the TVL for each token in descending order. + */ + private calculateTvl( + balances: { ethBalance: bigint; addressesBalance: bigint[] }, + addresses: Address[], + prices: Record, + ): AssetTvl[] { + const tvl: AssetTvl[] = []; + + for (const token of tokens) { + const { coingeckoId, ...tokenInfo } = token; + + const balance = isNativeToken(token) + ? balances.ethBalance + : balances.addressesBalance[ + addresses.indexOf(tokenInfo.contractAddress as Address) + ]; + + assert(balance !== undefined, `Balance for ${tokenInfo.symbol} not found`); + + const price = prices[coingeckoId] as number; + // math is done with bigints for better precision + const tvlValue = formatUnits( + balance * parseUnits(price.toString(), tokenInfo.decimals), + tokenInfo.decimals * 2, + ); + + const assetTvl: AssetTvl = { + amount: formatUnits(balance, tokenInfo.decimals), + amountUsd: tvlValue, + price: price.toString(), + ...tokenInfo, + }; + + tvl.push(assetTvl); + } + + // we assume the rounding error is negligible for sorting purposes + tvl.sort((a, b) => Number(b.amountUsd) - Number(a.amountUsd)); + + return tvl; + } + + /** + * Fetches the token balances for the given addresses and ETH balance. + * @param addresses - An array of addresses for which to fetch the token balances. + * @returns A promise that resolves to an object containing the ETH balance and an array of address balances. + */ + private async fetchTokenBalances( + addresses: Address[], + ): Promise<{ ethBalance: bigint; addressesBalance: bigint[] }> { + const multicall3Address = this.evmProviderService.getMulticall3Address(); + let balances: bigint[] = []; + + if (multicall3Address) { + balances = await this.evmProviderService.multicall({ + contracts: [ + ...addresses.map((tokenAddress) => { + return { + address: tokenAddress, + abi: erc20Abi, + functionName: "balanceOf", + args: [this.sharedBridgeAddress], + } as const; + }), + { + address: multicall3Address, + abi: multicall3Abi, + functionName: "getEthBalance", + args: [this.sharedBridgeAddress], + } as const, + ], + allowFailure: false, + } as const); + } else { + balances = await Promise.all([ + ...addresses.map((tokenAddress) => + this.evmProviderService.readContract(tokenAddress, erc20Abi, "balanceOf", [ + this.sharedBridgeAddress, + ]), + ), + this.evmProviderService.getBalance(this.sharedBridgeAddress), + ]); + } + + assert(balances.length === addresses.length + 1, "Invalid balances length"); + + return { + ethBalance: balances[addresses.length]!, + addressesBalance: balances, + }; + } + + /** + * 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: bigint): Promise { + const diamondProxyAddress = await this.fetchDiamondProxyAddress(chainId); + 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 }; + } + + /** + * Retrieves the Total Value Locked for {chainId} by L1 token + * @returns A Promise that resolves to an array of AssetTvl objects representing the TVL for each asset. + */ + async tvl(chainId: ChainId): Promise { + const erc20Addresses = Object.values(erc20Tokens).map((token) => token.contractAddress); + + const balances = await this.fetchTokenBalancesByChain(chainId, erc20Addresses); + const pricesRecord = await this.pricingService.getTokenPrices( + tokens.map((token) => token.coingeckoId), + ); + + assert(Object.keys(pricesRecord).length === tokens.length, "Invalid prices length"); + + return this.calculateTvl(balances, erc20Addresses, pricesRecord); + } + + /** + * Fetches the token balances for the given addresses and ETH balance on {chainId} + * Note: The last balance in the returned array is the ETH balance, so the fetch length should be addresses.length + 1. + * @param addresses - An array of addresses for which to fetch the token balances. + * @returns A promise that resolves to an object containing the ETH balance and an array of address balances. + */ + private async fetchTokenBalancesByChain(chainId: ChainId, addresses: Address[]) { + const balances = await this.evmProviderService.multicall({ + contracts: [ + ...addresses.map((tokenAddress) => { + return { + address: this.sharedBridgeAddress, + abi: sharedBridgeAbi, + functionName: "chainBalance", + args: [chainId, tokenAddress], + } as const; + }), + { + address: this.sharedBridgeAddress, + abi: sharedBridgeAbi, + functionName: "chainBalance", + args: [chainId, ETH_TOKEN_ADDRESS], + } as const, + ], + allowFailure: false, + }); + + return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) }; + } + + async chainType(chainId: ChainId): Promise { + 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
{ + let diamondProxyAddress: Address | undefined = this.diamondContracts.get(chainId); + + if (!diamondProxyAddress) { + diamondProxyAddress = await this.evmProviderService.readContract( + this.bridgeHubAddress, + bridgeHubAbi, + "getHyperchain", + [chainId], + ); + if (diamondProxyAddress == zeroAddress) { + throw new InvalidChainId(`Chain ID ${chainId} doesn't exist on the ecosystem`); + } + this.diamondContracts.set(chainId, diamondProxyAddress); + } + return diamondProxyAddress; + } + + /** + * 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: zeroAddress, + to: zeroAddress, + value: ONE_ETHER, + }), + // Estimate gas for an ERC20 transfer. + this.evmProviderService.estimateGas({ + account: zeroAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [this.sharedBridgeAddress, 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([ + nativeToken.coingeckoId, + ]); + ethPriceInUsd = priceResult[nativeToken.coingeckoId]; + } catch (e) { + this.logger.error("Failed to get the price of ether."); + } + + return { + gasPrice, + ethPrice: ethPriceInUsd, + ethTransfer: ethTransferGasCost, + erc20Transfer: erc20TransferGasCost, + }; + } catch (e: unknown) { + if (isNativeError(e)) { + this.logger.error(`Failed to get gas information: ${e.message}`); + } + throw new L1MetricsServiceException(`Failed to get gas information from L1. ${e}`); + } + } + + /** + * Get the chainIds for the ecosystem + * @returns A list of chainIds + */ + async getChainIds(): Promise { + //FIXME: this should have a ttl. Will be fixed once we add caching here. + if (!this.chainIds) { + const chainIds = await this.evmProviderService.multicall({ + contracts: this.stateTransitionManagerAddresses.map((address) => { + return { + address, + abi: stateTransitionManagerAbi, + functionName: "getAllHyperchainChainIDs", + args: [], + } as const; + }), + allowFailure: false, + }); + this.chainIds = chainIds.flat(); + } + return this.chainIds; + } + /** + * Get the base token for each chain + * @returns A map of chainId to base token address + */ + async getBaseTokens(chainIds: ChainId[]): Promise[]> { + if (chainIds.length === 0) return []; + const baseTokens = await this.evmProviderService.multicall({ + contracts: chainIds.map((chainId) => { + return { + address: this.bridgeHubAddress, + abi: bridgeHubAbi, + functionName: "baseToken", + args: [chainId], + } as const; + }), + allowFailure: false, + }); + return baseTokens.map((baseToken) => { + return baseToken === ETH_TOKEN_ADDRESS + ? nativeToken + : erc20Tokens[baseToken] || { + contractAddress: baseToken, + decimals: 18, + name: "unknown", + type: "erc20", + symbol: "unknown", + coingeckoId: "unknown", + }; + }); + } + + /** + * 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 { + 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.replace(/^0x/, ""); + let cursor = strippedParamsData.length; + const values: string[] = []; + + //read fields from Right to Left + for (const digits of feeParamsFieldHexDigits) { + const hexValue = strippedParamsData.slice(cursor - digits, cursor); + assert(hexValue, "Error parsing fee params"); + values.push(hexValue); + cursor -= digits; + } + + const [ + pubdataPricingMode, + batchOverheadL1Gas, + maxPubdataPerBatch, + maxL2GasPerBatch, + priorityTxMaxPubdata, + minimalL2GasPrice, + ] = values as [string, string, string, string, string, string]; + + // Convert hex to decimal + return { + pubdataPricingMode: parseInt(pubdataPricingMode, 16), + batchOverheadL1Gas: parseInt(batchOverheadL1Gas, 16), + maxPubdataPerBatch: parseInt(maxPubdataPerBatch, 16), + maxL2GasPerBatch: parseInt(maxL2GasPerBatch, 16), + priorityTxMaxPubdata: parseInt(priorityTxMaxPubdata, 16), + minimalL2GasPrice: BigInt(`0x${minimalL2GasPrice}`), + }; + } +} diff --git a/packages/metrics/src/l2/index.ts b/packages/metrics/src/l2/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/metrics/src/l2/l2MetricsService.ts b/packages/metrics/src/l2/l2MetricsService.ts new file mode 100644 index 0000000..e69de29 diff --git a/packages/metrics/src/types/feeParams.type.ts b/packages/metrics/src/types/feeParams.type.ts new file mode 100644 index 0000000..4634bf3 --- /dev/null +++ b/packages/metrics/src/types/feeParams.type.ts @@ -0,0 +1,22 @@ +//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) +/* +{ + pubdataPricingMode: uint8 -> 1 byte -> 2 hex digits + batchOverheadL1Gas: uint32 -> 4 bytes -> 8 hex digits + maxPubdataPerBatch: uint32 -> 4 bytes -> 8 hex digits + maxL2GasPerBatch: uint32 -> 4 bytes -> 8 hex digits + priorityTxMaxPubdata: uint32 -> 4 bytes -> 8 hex digits + minimalL2GasPrice: uint64 -> 8 bytes -> 16 hex digits + } +*/ +export const feeParamsFieldHexDigits = [2, 8, 8, 8, 8, 16] as const; diff --git a/packages/metrics/src/types/gasInfo.type.ts b/packages/metrics/src/types/gasInfo.type.ts new file mode 100644 index 0000000..366d8db --- /dev/null +++ b/packages/metrics/src/types/gasInfo.type.ts @@ -0,0 +1,6 @@ +export type GasInfo = { + gasPrice: bigint; // wei + ethPrice?: number; // USD + ethTransfer: bigint; // units of gas + erc20Transfer: bigint; // units of gas +}; diff --git a/packages/metrics/src/types/index.ts b/packages/metrics/src/types/index.ts new file mode 100644 index 0000000..5147408 --- /dev/null +++ b/packages/metrics/src/types/index.ts @@ -0,0 +1,3 @@ +export * from "./gasInfo.type.js"; +export * from "./tvl.type.js"; +export * from "./feeParams.type.js"; diff --git a/packages/metrics/src/types/tvl.type.ts b/packages/metrics/src/types/tvl.type.ts new file mode 100644 index 0000000..c0fc73b --- /dev/null +++ b/packages/metrics/src/types/tvl.type.ts @@ -0,0 +1,7 @@ +import { Token, TokenType } from "@zkchainhub/shared"; + +export type AssetTvl = Omit, "coingeckoId"> & { + amount: string; + amountUsd: string; + price: string; +}; diff --git a/packages/metrics/test/unit/l1/l1MetricsService.spec.ts b/packages/metrics/test/unit/l1/l1MetricsService.spec.ts new file mode 100644 index 0000000..90615f5 --- /dev/null +++ b/packages/metrics/test/unit/l1/l1MetricsService.spec.ts @@ -0,0 +1,1090 @@ +import { Address, encodeFunctionData, erc20Abi, parseEther, zeroAddress } from "viem"; +import { afterEach, describe, expect, it, Mocked, vi } from "vitest"; + +import { IPricingService } from "@zkchainhub/pricing"; +import { EvmProviderService, MulticallNotFound } from "@zkchainhub/providers"; +import { + BatchesInfo, + ChainId, + ChainType, + erc20Tokens, + ETH_TOKEN_ADDRESS, + ILogger, + nativeToken, + Token, + TokenType, + WETH, +} from "@zkchainhub/shared"; + +import { + bridgeHubAbi, + diamondProxyAbi, + FeeParams, + InvalidChainId, + InvalidChainType, + L1MetricsService, + L1MetricsServiceException, + multicall3Abi, + sharedBridgeAbi, +} from "../../../src/internal.js"; + +const ONE_ETHER = parseEther("1"); +vi.mock("@zkchainhub/shared/", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + erc20Tokens: { + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + coingeckoId: "usd-coin", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": { + 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, + }, + }, + get tokens() { + return [ + { + 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, + }, + { + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + coingeckoId: "usd-coin", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + { + 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, + }, + ]; + }, + }; +}); + +const mockMetricsModule = ( + mockedBridgeHubAddress: Address, + mockedSharedBridgeAddress: Address, + mockedSTMAddresses: Address[], +) => { + const evmProviderService = { + getBlockNumber: vi.fn(), + estimateGas: vi.fn(), + getGasPrice: vi.fn(), + readContract: vi.fn(), + getMulticall3Address: vi.fn(), + multicall: vi.fn(), + getBalance: vi.fn(), + getStorageAt: vi.fn(), + getBlockByNumber: vi.fn(), + batchRequest: vi.fn(), + } as unknown as EvmProviderService; + const pricingService: Mocked = { + getTokenPrices: vi.fn(), + }; + + const mockLogger: ILogger = { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }; + const l1Metrics = new L1MetricsService( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + evmProviderService as EvmProviderService, + pricingService, + mockLogger, + ); + + return { l1Metrics, pricingService, evmProviderService }; +}; + +describe("l1Metrics", () => { + const mockedBridgeHubAddress = "0x1234567890123456789012345678901234567890"; + const mockedSharedBridgeAddress = "0x1234567890123456789012345678901234567891"; + const mockedSTMAddresses: Address[] = [ + "0x1234567890123456789012345678901234567892", + "0x1234567890123456789012345678901234567893", + ]; + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("constructor", () => { + it("initialize bridgeHub and sharedBridge", () => { + const { l1Metrics } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + expect(l1Metrics["bridgeHubAddress"]).toEqual(mockedBridgeHubAddress); + expect(l1Metrics["sharedBridgeAddress"]).toEqual(mockedSharedBridgeAddress); + expect(l1Metrics["stateTransitionManagerAddresses"]).toEqual(mockedSTMAddresses); + }); + + it("initialize diamondContracts map as empty", () => { + const { l1Metrics } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + expect(l1Metrics["diamondContracts"].size).toBe(0); + }); + }); + + describe("l1Tvl", () => { + it("return the TVL on L1 Shared Bridge", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockMulticallBalances = [ + 60_841_657_140641n, + 135_63005559n, + 123_803_824374847279970609n, + ]; // Mocked balances + const mockPrices = { "wrapped-bitcoin": 66_129, "usd-coin": 0.999, ethereum: 3_181.09 }; // Mocked prices + const multicallAddress = "0x123452"; + + vi.spyOn(evmProviderService, "getMulticall3Address").mockReturnValue(multicallAddress); + vi.spyOn(evmProviderService, "multicall").mockResolvedValue(mockMulticallBalances); + vi.spyOn(pricingService, "getTokenPrices").mockResolvedValue(mockPrices); + + const result = await l1Metrics.l1Tvl(); + + expect(result).toHaveLength(3); + expect(result).toEqual([ + { + amount: "123803.824374847279970609", + amountUsd: expect.stringContaining("393831107.68"), + price: "3181.09", + name: "Ethereum", + symbol: "ETH", + contractAddress: null, + type: "native", + imageUrl: + "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628", + decimals: 18, + }, + { + amount: "60841657.140641", + amountUsd: expect.stringContaining("60780815.48"), + price: "0.999", + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + { + amount: "135.63005559", + amountUsd: expect.stringContaining("8969079.94"), + price: "66129", + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + imageUrl: + "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", + type: "erc20", + decimals: 8, + }, + ]); + expect(evmProviderService.multicall).toHaveBeenCalledWith({ + contracts: [ + { + address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + abi: erc20Abi, + functionName: "balanceOf", + args: [mockedSharedBridgeAddress], + } as const, + { + address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + abi: erc20Abi, + functionName: "balanceOf", + args: [mockedSharedBridgeAddress], + } as const, + { + address: multicallAddress, + abi: multicall3Abi, + functionName: "getEthBalance", + args: [mockedSharedBridgeAddress], + } as const, + ], + allowFailure: false, + } as const); + expect(pricingService.getTokenPrices).toHaveBeenCalledWith([ + "ethereum", + "usd-coin", + "wrapped-bitcoin", + ]); + expect(evmProviderService.getBalance).not.toHaveBeenCalled(); + }); + + it("return the TVL on L1 Shared Bridge without multicall", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockPrices = { "wrapped-bitcoin": 66_129, "usd-coin": 0.999, ethereum: 3_181.09 }; // Mocked prices + + vi.spyOn(evmProviderService, "getMulticall3Address").mockReturnValue(undefined); + vi.spyOn(evmProviderService, "multicall").mockRejectedValue(MulticallNotFound); + vi.spyOn(evmProviderService, "readContract") + .mockResolvedValueOnce(60_841_657_140641n) + .mockResolvedValueOnce(135_63005559n); + vi.spyOn(evmProviderService, "getBalance").mockResolvedValue( + 123_803_824374847279970609n, + ); + vi.spyOn(pricingService, "getTokenPrices").mockResolvedValue(mockPrices); + + const result = await l1Metrics.l1Tvl(); + + expect(result).toHaveLength(3); + expect(result).toEqual([ + { + amount: "123803.824374847279970609", + amountUsd: expect.stringContaining("393831107.68"), + price: "3181.09", + name: "Ethereum", + symbol: "ETH", + contractAddress: null, + type: "native", + imageUrl: + "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628", + decimals: 18, + }, + { + amount: "60841657.140641", + amountUsd: expect.stringContaining("60780815.48"), + price: "0.999", + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + { + amount: "135.63005559", + amountUsd: expect.stringContaining("8969079.94"), + price: "66129", + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + imageUrl: + "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", + type: "erc20", + decimals: 8, + }, + ]); + expect(evmProviderService.multicall).not.toHaveBeenCalled(); + expect(evmProviderService.readContract).toHaveBeenNthCalledWith( + 1, + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + erc20Abi, + "balanceOf", + [l1Metrics["sharedBridgeAddress"]], + ); + expect(evmProviderService.readContract).toHaveBeenNthCalledWith( + 2, + "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + erc20Abi, + "balanceOf", + [l1Metrics["sharedBridgeAddress"]], + ); + expect(evmProviderService.getBalance).toHaveBeenCalledWith( + l1Metrics["sharedBridgeAddress"], + ); + expect(pricingService.getTokenPrices).toHaveBeenCalledWith([ + "ethereum", + "usd-coin", + "wrapped-bitcoin", + ]); + }); + + it("throws an error if the balances length is invalid", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + vi.spyOn(evmProviderService, "getMulticall3Address").mockReturnValue("0x123452"); + vi.spyOn(evmProviderService, "multicall").mockResolvedValue([]); + + await expect(l1Metrics.l1Tvl()).rejects.toThrowError("Invalid balances length"); + }); + + it("throws an error if the prices length is invalid", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + + vi.spyOn(evmProviderService, "multicall").mockResolvedValue([ + 60_841_657_140641n, + 135_63005559n, + 123_803_824374847279970609n, + ]); + vi.spyOn(evmProviderService, "getMulticall3Address").mockReturnValue("0x123452"); + vi.spyOn(pricingService, "getTokenPrices").mockResolvedValue({ + ethereum: 3_181.09, + "usd-coin": 0.999, + }); + + await expect(l1Metrics.l1Tvl()).rejects.toThrowError("Invalid prices length"); + }); + }); + + describe("getBatchesInfo", () => { + it("returns batches info for chain id", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + const mockBatchesInfo: BatchesInfo = { commited: 300n, verified: 200n, executed: 100n }; + const batchesInfoMulticallResponse = [ + mockBatchesInfo.commited, + mockBatchesInfo.verified, + mockBatchesInfo.executed, + ]; + + vi.spyOn(evmProviderService, "multicall").mockResolvedValue( + batchesInfoMulticallResponse, + ); + + const result = await l1Metrics.getBatchesInfo(chainId); + + expect(result).toEqual(mockBatchesInfo); + expect(evmProviderService.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 chainId doesn't exist on the ecosystem", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + l1Metrics["diamondContracts"].clear(); + vi.spyOn(evmProviderService, "readContract").mockResolvedValue(zeroAddress); + await expect(l1Metrics.getBatchesInfo(chainId)).rejects.toThrow(InvalidChainId); + }); + + it("fetches and sets diamond proxy if chainId doesn't exists on map", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].clear(); + + const mockBatchesInfo: BatchesInfo = { commited: 300n, verified: 200n, executed: 100n }; + const batchesInfoMulticallResponse = [ + mockBatchesInfo.commited, + mockBatchesInfo.verified, + mockBatchesInfo.executed, + ]; + + vi.spyOn(evmProviderService, "readContract").mockResolvedValue( + mockedDiamondProxyAddress, + ); + vi.spyOn(evmProviderService, "multicall").mockResolvedValue( + batchesInfoMulticallResponse, + ); + const result = await l1Metrics.getBatchesInfo(chainId); + + expect(result).toEqual(mockBatchesInfo); + + expect(l1Metrics["diamondContracts"].get(chainId)).toEqual(mockedDiamondProxyAddress); + expect(evmProviderService.readContract).toHaveBeenCalledWith( + l1Metrics["bridgeHubAddress"], + bridgeHubAbi, + "getHyperchain", + [chainId], + ); + expect(evmProviderService.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, + }); + }); + }); + + describe("fetchDiamondProxyAddress", () => { + it("returns address if already exists in the map", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + l1Metrics["diamondContracts"].clear(); + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + + const readContractSpy = vi.spyOn(evmProviderService, "readContract"); + const result = await l1Metrics["fetchDiamondProxyAddress"](chainId); + + expect(result).toEqual(mockedDiamondProxyAddress); + expect(l1Metrics["diamondContracts"].get(chainId)).toEqual(mockedDiamondProxyAddress); + expect(readContractSpy).toHaveBeenCalledTimes(0); + }); + it("fetches and sets diamond proxy if chainId doesn't exists on map", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].clear(); + + vi.spyOn(evmProviderService, "readContract").mockResolvedValue( + mockedDiamondProxyAddress, + ); + const result = await l1Metrics["fetchDiamondProxyAddress"](chainId); + + expect(result).toEqual(mockedDiamondProxyAddress); + + expect(l1Metrics["diamondContracts"].get(chainId)).toEqual(mockedDiamondProxyAddress); + expect(evmProviderService.readContract).toHaveBeenCalledWith( + l1Metrics["bridgeHubAddress"], + bridgeHubAbi, + "getHyperchain", + [chainId], + ); + }); + }); + + describe("tvl", () => { + it("return the TVL for chain id", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockBalances = [60_841_657_140641n, 135_63005559n, 123_803_824374847279970609n]; // Mocked balances + const mockPrices = { "wrapped-bitcoin": 66_129, "usd-coin": 0.999, ethereum: 3_181.09 }; // Mocked prices + const chainId = 324n; // this is ZKsyncEra chain id + + vi.spyOn(evmProviderService, "multicall").mockResolvedValue(mockBalances); + vi.spyOn(pricingService, "getTokenPrices").mockResolvedValue(mockPrices); + + const result = await l1Metrics.tvl(chainId); + + expect(result).toHaveLength(3); + expect(result).toEqual([ + { + amount: "123803.824374847279970609", + amountUsd: expect.stringContaining("393831107.68"), + price: "3181.09", + name: "Ethereum", + symbol: "ETH", + contractAddress: null, + type: "native", + imageUrl: + "https://coin-images.coingecko.com/coins/images/279/large/ethereum.png?1696501628", + decimals: 18, + }, + { + amount: "60841657.140641", + amountUsd: expect.stringContaining("60780815.48"), + price: "0.999", + name: "USDC", + symbol: "USDC", + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + imageUrl: + "https://coin-images.coingecko.com/coins/images/6319/large/usdc.png?1696506694", + type: "erc20", + decimals: 6, + }, + { + amount: "135.63005559", + amountUsd: expect.stringContaining("8969079.94"), + price: "66129", + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", + imageUrl: + "https://coin-images.coingecko.com/coins/images/7598/large/wrapped_bitcoin_wbtc.png?1696507857", + type: "erc20", + decimals: 8, + }, + ]); + expect(evmProviderService.multicall).toHaveBeenCalledWith({ + contracts: [ + { + address: l1Metrics["sharedBridgeAddress"], + abi: sharedBridgeAbi, + functionName: "chainBalance", + args: [chainId, "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"], + }, + { + address: l1Metrics["sharedBridgeAddress"], + abi: sharedBridgeAbi, + functionName: "chainBalance", + args: [chainId, "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"], + }, + { + address: l1Metrics["sharedBridgeAddress"], + abi: sharedBridgeAbi, + functionName: "chainBalance", + args: [chainId, ETH_TOKEN_ADDRESS], + }, + ], + allowFailure: false, + }); + expect(pricingService.getTokenPrices).toHaveBeenCalledWith([ + "ethereum", + "usd-coin", + "wrapped-bitcoin", + ]); + }); + + it("throws an error if the prices length is invalid", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; + vi.spyOn(evmProviderService, "multicall").mockResolvedValue([ + 60_841_657_140641n, + 135_63005559n, + 123_803_824374847279970609n, + ]); + vi.spyOn(pricingService, "getTokenPrices").mockResolvedValue({ + ethereum: 3_181.09, + "usd-coin": 0.999, + }); + + await expect(l1Metrics.tvl(chainId)).rejects.toThrowError("Invalid prices length"); + }); + }); + + describe("chainType", () => { + it("returns chainType", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + const mockChainType: ChainType = "Rollup"; + + const readContractSpy = vi + .spyOn(evmProviderService, "readContract") + .mockResolvedValue(0); + + const result = await l1Metrics.chainType(chainId); + + expect(result).toEqual(mockChainType); + expect(readContractSpy).toHaveBeenCalledWith( + mockedDiamondProxyAddress, + diamondProxyAbi, + "getPubdataPricingMode", + [], + ); + }); + it("returns chainType", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + const mockChainType: ChainType = "Validium"; + + const readContractSpy = vi + .spyOn(evmProviderService, "readContract") + .mockResolvedValue(1); + + const result = await l1Metrics.chainType(chainId); + + expect(result).toEqual(mockChainType); + expect(readContractSpy).toHaveBeenCalledWith( + mockedDiamondProxyAddress, + diamondProxyAbi, + "getPubdataPricingMode", + [], + ); + }); + it("throws if blockchain returns an out of bounds index", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + + vi.spyOn(evmProviderService, "readContract").mockResolvedValue(100); + + await expect(l1Metrics.chainType(chainId)).rejects.toThrowError(InvalidChainType); + }); + }); + + describe("ethGasInfo", () => { + it("returns gas information from L1", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + // Mock the necessary dependencies + const mockEstimateGas = vi.spyOn(evmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost' + const mockGetGasPrice = vi.spyOn(evmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = vi.spyOn(pricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [nativeToken.coingeckoId]: 2000 }); // ethPriceInUsd + // Call the method + const result = await l1Metrics.ethGasInfo(); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: zeroAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: zeroAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [l1Metrics["sharedBridgeAddress"], ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).toHaveBeenCalledTimes(1); + expect(mockGetTokenPrices).toHaveBeenCalledWith([nativeToken.coingeckoId]); + + expect(result).toEqual({ + gasPrice: 50000000000n, + ethPrice: 2000, + ethTransfer: 21000n, + erc20Transfer: 65000n, + }); + }); + + it("returns gas information from L1 without ether price", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockEstimateGas = vi.spyOn(evmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost + + const mockGetGasPrice = vi.spyOn(evmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = vi.spyOn(pricingService, "getTokenPrices"); + mockGetTokenPrices.mockRejectedValueOnce(new Error("Failed to get token prices")); + + const result = await l1Metrics.ethGasInfo(); + + // Assertions + expect(result).toEqual({ + gasPrice: 50000000000n, + ethPrice: undefined, + ethTransfer: 21000n, + erc20Transfer: 65000n, + }); + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: zeroAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: zeroAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [l1Metrics["sharedBridgeAddress"], ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).toHaveBeenCalledTimes(1); + expect(mockGetTokenPrices).toHaveBeenCalledWith([nativeToken.coingeckoId]); + }); + + it("throws l1MetricsException when estimateGas fails", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + // Mock the necessary dependencies + const mockEstimateGas = vi.spyOn(evmProviderService, "estimateGas"); + mockEstimateGas.mockRejectedValueOnce(new Error("Failed to estimate gas")); + + const mockGetGasPrice = vi.spyOn(evmProviderService, "getGasPrice"); + mockGetGasPrice.mockResolvedValueOnce(BigInt(50000000000)); // gasPrice + + const mockGetTokenPrices = vi.spyOn(pricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [nativeToken.coingeckoId]: 2000 }); // ethPriceInUsd + + // Call the method and expect it to throw L1MetricsServiceException + await expect(l1Metrics.ethGasInfo()).rejects.toThrow(L1MetricsServiceException); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledWith({ + account: zeroAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + + expect(mockGetTokenPrices).not.toHaveBeenCalled(); + }); + + it("throws l1MetricsException when getGasPrice fails", async () => { + const { l1Metrics, evmProviderService, pricingService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + // Mock the necessary dependencies + const mockEstimateGas = vi.spyOn(evmProviderService, "estimateGas"); + mockEstimateGas.mockResolvedValueOnce(BigInt(21000)); // ethTransferGasCost + mockEstimateGas.mockResolvedValueOnce(BigInt(65000)); // erc20TransferGasCost + + const mockGetGasPrice = vi.spyOn(evmProviderService, "getGasPrice"); + mockGetGasPrice.mockRejectedValueOnce(new Error("Failed to get gas price")); + + const mockGetTokenPrices = vi.spyOn(pricingService, "getTokenPrices"); + mockGetTokenPrices.mockResolvedValueOnce({ [nativeToken.coingeckoId]: 2000 }); // ethPriceInUsd + + // Call the method and expect it to throw l1MetricsException + await expect(l1Metrics.ethGasInfo()).rejects.toThrow(L1MetricsServiceException); + + // Assertions + expect(mockEstimateGas).toHaveBeenCalledTimes(2); + expect(mockEstimateGas).toHaveBeenNthCalledWith(1, { + account: zeroAddress, + to: zeroAddress, + value: ONE_ETHER, + }); + expect(mockEstimateGas).toHaveBeenNthCalledWith(2, { + account: zeroAddress, + to: WETH.contractAddress, + data: encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [l1Metrics["sharedBridgeAddress"], ONE_ETHER], + }), + }); + + expect(mockGetGasPrice).toHaveBeenCalledTimes(1); + + expect(mockGetTokenPrices).not.toHaveBeenCalled(); + }); + }); + + describe("getChainIds", () => { + it("returns chainIds", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedMulticallReturnValue = [ + [1n, 2n, 3n], + [4n, 5n, 6n], + ]; + vi.spyOn(evmProviderService, "multicall").mockResolvedValue(mockedMulticallReturnValue); + + const result = await l1Metrics.getChainIds(); + + expect(result).toEqual(mockedMulticallReturnValue.flat()); + expect(l1Metrics["chainIds"]).toEqual(mockedMulticallReturnValue.flat()); + }); + it("returns chainIds previously setted up", async () => { + const mockedChainIds = [1n, 2n, 3n, 4n, 5n]; + const { l1Metrics } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + l1Metrics["chainIds"] = mockedChainIds; + + const result = await l1Metrics.getChainIds(); + + expect(result).toEqual(mockedChainIds); + }); + it("returns empty array if chainIds are empty", async () => { + const mockedChainIds: bigint[] = []; + const { l1Metrics } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + l1Metrics["chainIds"] = mockedChainIds; + + const result = await l1Metrics.getChainIds(); + + expect(result).toEqual(mockedChainIds); + }); + it("throws if multicall throws", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + vi.spyOn(evmProviderService, "multicall").mockRejectedValue(new Error()); + await expect(l1Metrics.getChainIds()).rejects.toThrow(Error); + }); + }); + + describe("getBaseTokens", () => { + it("returns known tokens", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedChainIds = [1n, 2n]; + const knownTokenAddress1 = Object.keys(erc20Tokens)[0]; + const knownTokenAddress2 = Object.keys(erc20Tokens)[0]; + + if (!knownTokenAddress1 || !knownTokenAddress2) { + throw new Error("ERC20 tokens are not defined"); + } + const mockedMulticallReturnValue = [knownTokenAddress1, knownTokenAddress2]; + vi.spyOn(evmProviderService, "multicall").mockResolvedValue(mockedMulticallReturnValue); + const mockedReturnData: Token[] = [ + erc20Tokens[knownTokenAddress1 as Address] as Token<"erc20">, + erc20Tokens[knownTokenAddress2 as Address] as Token<"erc20">, + ]; + + const result = await l1Metrics.getBaseTokens(mockedChainIds); + + expect(result).toEqual(mockedReturnData); + }); + + it("returns unknown tokens", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedChainIds = [1n, 2n]; + const mockedMulticallReturnValue = [ + "0x1234567890123456789012345678901234567123", + "0x1234567890123456789012345678901234567345", + ]; + vi.spyOn(evmProviderService, "multicall").mockResolvedValue(mockedMulticallReturnValue); + const mockedReturnData: Token[] = [ + { + contractAddress: "0x1234567890123456789012345678901234567123", + symbol: "unknown", + name: "unknown", + decimals: 18, + type: "erc20", + coingeckoId: "unknown", + }, + { + contractAddress: "0x1234567890123456789012345678901234567345", + symbol: "unknown", + name: "unknown", + decimals: 18, + type: "erc20", + coingeckoId: "unknown", + }, + ]; + + const result = await l1Metrics.getBaseTokens(mockedChainIds); + + expect(result).toEqual(mockedReturnData); + }); + it("returns empty array if chainIds is empty", async () => { + const { l1Metrics } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedChainIds: ChainId[] = []; + const result = await l1Metrics.getBaseTokens(mockedChainIds); + expect(result).toEqual([]); + }); + it("throws if multicall fails", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedChainIds: ChainId[] = [1n, 2n]; + vi.spyOn(evmProviderService, "multicall").mockRejectedValue(new Error()); + await expect(l1Metrics.getBaseTokens(mockedChainIds)).rejects.toThrow(Error); + }); + it("returns eth token", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + const mockedChainIds: ChainId[] = [1n, 2n]; + vi.spyOn(evmProviderService, "multicall").mockRejectedValue(new Error()); + await expect(l1Metrics.getBaseTokens(mockedChainIds)).rejects.toThrow(Error); + }); + }); + + describe("feeParams", () => { + it("should retrieve the fee parameters for a specific chain", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + // Mock the dependencies + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + const mockFeeParamsRawData = + "0x00000000000000000000000ee6b280000182b804c4b4000001d4c0000f424000"; + + const mockFeeParams: FeeParams = { + pubdataPricingMode: 0, + batchOverheadL1Gas: 1000000, + maxPubdataPerBatch: 120000, + maxL2GasPerBatch: 80000000, + priorityTxMaxPubdata: 99000, + minimalL2GasPrice: 250000000n, + }; + + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + vi.spyOn(evmProviderService, "getStorageAt").mockResolvedValue(mockFeeParamsRawData); + + const result = await l1Metrics.feeParams(chainId); + + expect(evmProviderService.getStorageAt).toHaveBeenCalledWith( + mockedDiamondProxyAddress, + `0x26`, + ); + + expect(result).toEqual(mockFeeParams); + }); + + it("should throw an exception if the fee parameters cannot be retrieved from L1", async () => { + const { l1Metrics, evmProviderService } = mockMetricsModule( + mockedBridgeHubAddress, + mockedSharedBridgeAddress, + mockedSTMAddresses, + ); + // Mock the dependencies + const chainId = 324n; // this is ZKsyncEra chain id + const mockedDiamondProxyAddress = "0x1234567890123456789012345678901234567890"; + l1Metrics["diamondContracts"].set(chainId, mockedDiamondProxyAddress); + + vi.spyOn(evmProviderService, "getStorageAt").mockResolvedValue(undefined); + + await expect(l1Metrics.feeParams(chainId)).rejects.toThrow(L1MetricsServiceException); + }); + }); +}); diff --git a/packages/metrics/vitest.config.ts b/packages/metrics/vitest.config.ts new file mode 100644 index 0000000..3aa3262 --- /dev/null +++ b/packages/metrics/vitest.config.ts @@ -0,0 +1,22 @@ +import path from "path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + globals: true, // Use Vitest's global API without importing it in each file + environment: "node", // Use the Node.js environment + include: ["test/**/*.spec.ts"], // Include test files + exclude: ["node_modules", "dist"], // Exclude certain directories + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], // Coverage reporters + exclude: ["node_modules", "dist"], // Files to exclude from coverage + }, + }, + resolve: { + alias: { + // Setup path alias based on tsconfig paths + "@": path.resolve(__dirname, "src"), + }, + }, +}); diff --git a/packages/pricing/src/external.ts b/packages/pricing/src/external.ts index 266fc90..9fe48eb 100644 --- a/packages/pricing/src/external.ts +++ b/packages/pricing/src/external.ts @@ -1,3 +1,5 @@ -export type { IPricingService, RateLimitExceeded, ApiNotAvailable } from "./internal.js"; +export type { IPricingService } from "./internal.js"; + +export { RateLimitExceeded, ApiNotAvailable } from "./internal.js"; export { CoingeckoService } from "./internal.js"; diff --git a/packages/providers/src/external.ts b/packages/providers/src/external.ts index 24e5f4b..3f3ddee 100644 --- a/packages/providers/src/external.ts +++ b/packages/providers/src/external.ts @@ -1,4 +1,4 @@ -export type { +export { DataDecodeException, InvalidArgumentException, MulticallNotFound, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccc8e88..abd17ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,7 +93,20 @@ importers: specifier: 4.17.0 version: 4.17.0 - packages/metrics: {} + packages/metrics: + dependencies: + "@zkchainhub/pricing": + specifier: workspace:* + version: link:../pricing + "@zkchainhub/providers": + specifier: workspace:* + version: link:../providers + "@zkchainhub/shared": + specifier: workspace:* + version: link:../shared + viem: + specifier: 2.19.6 + version: 2.19.6(typescript@5.1.3) packages/pricing: dependencies: