From 317ef4f06ebf29d673e4fe7bca10db9bb5554bab Mon Sep 17 00:00:00 2001 From: Victorien Gauch <85494462+VGau@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:12:01 +0100 Subject: [PATCH] fix: improve postman logging and error parsing (#622) * fix: improve postman logging and error parsing * fix: remove unnecessary casting --- .../L2ClaimMessageTransactionSizeProcessor.ts | 34 ++- .../processors/MessageAnchoringProcessor.ts | 8 +- .../processors/MessageClaimingPersister.ts | 10 +- .../processors/MessageClaimingProcessor.ts | 5 +- ...aimMessageTransactionSizeProcessor.test.ts | 53 +++- .../MessageAnchoringProcessor.test.ts | 5 +- .../MessageClaimingPersister.test.ts | 6 +- .../MessageClaimingProcessor.test.ts | 1 + postman/src/utils/ErrorParser.ts | 190 ++++++++---- .../src/utils/__tests__/ErrorParser.test.ts | 289 +++++++++++------- sdk/src/LineaSDK.ts | 18 +- sdk/src/clients/ethereum/L1ClaimingService.ts | 4 +- sdk/src/clients/ethereum/LineaRollupClient.ts | 30 +- sdk/src/clients/ethereum/MerkleTreeService.ts | 18 +- .../__tests__/LineaRollupClient.test.ts | 5 +- sdk/src/clients/gas/DefaultGasProvider.ts | 4 +- sdk/src/clients/gas/GasProvider.ts | 4 +- .../gas/__tests__/DefaultGasProvider.test.ts | 6 +- .../clients/linea/L2MessageServiceClient.ts | 19 +- .../__tests__/L2MessageServiceClient.test.ts | 5 +- sdk/src/clients/providers/lineaProvider.ts | 4 +- sdk/src/clients/providers/provider.ts | 4 +- sdk/src/core/errors/BaseError.ts | 38 ++- sdk/src/core/errors/GasFeeErrors.ts | 16 - .../core/errors/__tests__/BaseError.test.ts | 10 +- .../errors/__tests__/GasFeeErrors.test.ts | 50 --- sdk/src/core/errors/index.ts | 2 +- sdk/src/core/errors/utils.ts | 35 +++ sdk/src/index.ts | 2 +- sdk/src/utils/merkleTree/SparseMerkleTree.ts | 10 +- 30 files changed, 547 insertions(+), 338 deletions(-) delete mode 100644 sdk/src/core/errors/GasFeeErrors.ts delete mode 100644 sdk/src/core/errors/__tests__/GasFeeErrors.test.ts create mode 100644 sdk/src/core/errors/utils.ts diff --git a/postman/src/services/processors/L2ClaimMessageTransactionSizeProcessor.ts b/postman/src/services/processors/L2ClaimMessageTransactionSizeProcessor.ts index 6a7cd1718..9dcb265d0 100644 --- a/postman/src/services/processors/L2ClaimMessageTransactionSizeProcessor.ts +++ b/postman/src/services/processors/L2ClaimMessageTransactionSizeProcessor.ts @@ -15,6 +15,8 @@ import { L2ClaimMessageTransactionSizeProcessorConfig, } from "../../core/services/processors/IL2ClaimMessageTransactionSizeProcessor"; import { IL2ClaimTransactionSizeCalculator } from "../../core/services/processors/IL2ClaimTransactionSizeCalculator"; +import { ErrorParser } from "../../utils/ErrorParser"; +import { Message } from "../../core/entities/Message"; export class L2ClaimMessageTransactionSizeProcessor implements IL2ClaimMessageTransactionSizeProcessor { /** @@ -48,6 +50,8 @@ export class L2ClaimMessageTransactionSizeProcessor implements IL2ClaimMessageTr * @returns {Promise} A promise that resolves when the processing is complete. */ public async process(): Promise { + let message: Message | null = null; + try { const messages = await this.databaseService.getNFirstMessagesByStatus( MessageStatus.ANCHORED, @@ -57,10 +61,11 @@ export class L2ClaimMessageTransactionSizeProcessor implements IL2ClaimMessageTr ); if (messages.length === 0) { + this.logger.info("No anchored messages found to compute transaction size."); return; } - const message = messages[0]; + message = messages[0]; const { gasLimit, maxPriorityFeePerGas, maxFeePerGas } = await this.l2MessageServiceClient.estimateClaimGasFees(message); @@ -86,7 +91,32 @@ export class L2ClaimMessageTransactionSizeProcessor implements IL2ClaimMessageTr gasLimit, ); } catch (e) { - this.logger.error(e); + await this.handleProcessingError(e, message); } } + + /** + * Handles error that occur during the processing. + * + * @param {unknown} e - The error that occurred. + * @param {Message | null} message - The message object being processed when the error occurred. + * @returns {Promise} A promise that resolves when the error has been handled. + */ + private async handleProcessingError(e: unknown, message: Message | null): Promise { + const parsedError = ErrorParser.parseErrorWithMitigation(e); + + if (parsedError?.mitigation && !parsedError.mitigation.shouldRetry && message) { + message.edit({ status: MessageStatus.NON_EXECUTABLE }); + await this.databaseService.updateMessage(message); + this.logger.warnOrError("Error occurred while processing message transaction size.", { + ...parsedError, + messageHash: message.messageHash, + }); + return; + } + + this.logger.warnOrError("Error occurred while processing message transaction size.", { + parsedError, + }); + } } diff --git a/postman/src/services/processors/MessageAnchoringProcessor.ts b/postman/src/services/processors/MessageAnchoringProcessor.ts index 709a3675d..336761172 100644 --- a/postman/src/services/processors/MessageAnchoringProcessor.ts +++ b/postman/src/services/processors/MessageAnchoringProcessor.ts @@ -18,6 +18,7 @@ import { MessageStatus } from "../../core/enums"; import { ILogger } from "../../core/utils/logging/ILogger"; import { IMessageServiceContract } from "../../core/services/contracts/IMessageServiceContract"; import { IMessageDBService } from "../../core/persistence/IMessageDBService"; +import { ErrorParser } from "../../utils/ErrorParser"; export class MessageAnchoringProcessor implements IMessageAnchoringProcessor { private readonly maxFetchMessagesFromDb: number; @@ -93,7 +94,12 @@ export class MessageAnchoringProcessor implements IMessageAnchoringProcessor { await this.databaseService.saveMessages(messages); } catch (e) { - this.logger.error(e); + const error = ErrorParser.parseErrorWithMitigation(e); + this.logger.error("An error occurred while processing messages.", { + errorCode: error?.errorCode, + errorMessage: error?.errorMessage, + ...(error?.data ? { data: error.data } : {}), + }); } } } diff --git a/postman/src/services/processors/MessageClaimingPersister.ts b/postman/src/services/processors/MessageClaimingPersister.ts index 9f8f16959..7ef63db1f 100644 --- a/postman/src/services/processors/MessageClaimingPersister.ts +++ b/postman/src/services/processors/MessageClaimingPersister.ts @@ -20,6 +20,7 @@ import { MessageClaimingPersisterConfig, } from "../../core/services/processors/IMessageClaimingPersister"; import { IMessageDBService } from "../../core/persistence/IMessageDBService"; +import { ErrorParser } from "../../utils/ErrorParser"; export class MessageClaimingPersister implements IMessageClaimingPersister { private messageBeingRetry: { message: Message | null; retries: number }; @@ -79,6 +80,7 @@ export class MessageClaimingPersister implements IMessageClaimingPersister { try { firstPendingMessage = await this.databaseService.getFirstPendingMessage(this.config.direction); if (!firstPendingMessage?.claimTxHash) { + this.logger.info("No pending message status to update."); return; } @@ -111,7 +113,13 @@ export class MessageClaimingPersister implements IMessageClaimingPersister { await this.updateReceiptStatus(firstPendingMessage, receipt); } catch (e) { - this.logger.error(e); + const error = ErrorParser.parseErrorWithMitigation(e); + this.logger.error("Error processing message.", { + ...(firstPendingMessage ? { messageHash: firstPendingMessage.messageHash } : {}), + ...(error?.errorCode ? { errorCode: error.errorCode } : {}), + ...(error?.errorMessage ? { errorMessage: error.errorMessage } : {}), + ...(error?.data ? { data: error.data } : {}), + }); } } diff --git a/postman/src/services/processors/MessageClaimingProcessor.ts b/postman/src/services/processors/MessageClaimingProcessor.ts index 515bde0c6..f210ebfb4 100644 --- a/postman/src/services/processors/MessageClaimingProcessor.ts +++ b/postman/src/services/processors/MessageClaimingProcessor.ts @@ -3,7 +3,6 @@ import { Overrides, TransactionResponse, ContractTransactionResponse, - EthersError, TransactionReceipt, Signer, ErrorDescription, @@ -74,6 +73,7 @@ export class MessageClaimingProcessor implements IMessageClaimingProcessor { ); if (!nextMessageToClaim) { + this.logger.info("No message to claim found"); return; } @@ -265,7 +265,7 @@ export class MessageClaimingProcessor implements IMessageClaimingProcessor { * @returns {Promise} A promise that resolves when the error has been handled. */ private async handleProcessingError(e: unknown, message: Message | null): Promise { - const parsedError = ErrorParser.parseErrorWithMitigation(e as EthersError); + const parsedError = ErrorParser.parseErrorWithMitigation(e); if (parsedError?.mitigation && !parsedError.mitigation.shouldRetry && message) { message.edit({ status: MessageStatus.NON_EXECUTABLE }); @@ -274,6 +274,7 @@ export class MessageClaimingProcessor implements IMessageClaimingProcessor { this.logger.warnOrError(e, { parsedError, + ...(message ? { messageHash: message.messageHash } : {}), }); } } diff --git a/postman/src/services/processors/__tests__/L2ClaimMessageTransactionSizeProcessor.test.ts b/postman/src/services/processors/__tests__/L2ClaimMessageTransactionSizeProcessor.test.ts index 080fd536f..53b78ad5c 100644 --- a/postman/src/services/processors/__tests__/L2ClaimMessageTransactionSizeProcessor.test.ts +++ b/postman/src/services/processors/__tests__/L2ClaimMessageTransactionSizeProcessor.test.ts @@ -3,12 +3,13 @@ import { mock } from "jest-mock-extended"; import { ContractTransactionResponse, ErrorDescription, + makeError, Overrides, Signer, TransactionReceipt, TransactionResponse, } from "ethers"; -import { Direction } from "@consensys/linea-sdk"; +import { Direction, makeBaseError } from "@consensys/linea-sdk"; import { TestLogger } from "../../../utils/testing/helpers"; import { MessageStatus } from "../../../core/enums"; import { testL1NetworkConfig, testMessage, DEFAULT_MAX_FEE_PER_GAS } from "../../../utils/testing/constants"; @@ -67,7 +68,7 @@ describe("L2ClaimMessageTransactionSizeProcessor", () => { it("Should log as error when calculateTransactionSize failed", async () => { const testGasLimit = 50_000n; - const loggerErrorSpy = jest.spyOn(logger, "error"); + const loggerErrorSpy = jest.spyOn(logger, "warnOrError"); jest.spyOn(databaseService, "getNFirstMessagesByStatus").mockResolvedValue([testMessage]); jest.spyOn(l2ContractClientMock, "estimateClaimGasFees").mockResolvedValue({ gasLimit: testGasLimit, @@ -81,7 +82,53 @@ describe("L2ClaimMessageTransactionSizeProcessor", () => { await transactionSizeProcessor.process(); expect(loggerErrorSpy).toHaveBeenCalledTimes(1); - expect(loggerErrorSpy).toHaveBeenCalledWith(new Error("calculation failed.")); + expect(loggerErrorSpy).toHaveBeenCalledWith("Error occurred while processing message transaction size.", { + errorCode: "UNKNOWN_ERROR", + errorMessage: "calculation failed.", + messageHash: testMessage.messageHash, + mitigation: { shouldRetry: false }, + }); + }); + + it("Should log as error when estimateClaimGasFees failed", async () => { + const loggerErrorSpy = jest.spyOn(logger, "warnOrError"); + jest.spyOn(databaseService, "getNFirstMessagesByStatus").mockResolvedValue([testMessage]); + jest.spyOn(l2ContractClientMock, "estimateClaimGasFees").mockRejectedValue( + makeBaseError( + makeError("could not coalesce error", "UNKNOWN_ERROR", { + error: { + code: -32000, + data: "0x5461344300000000000000000000000034be5b8c30ee4fde069dc878989686abe9884470", + message: "Execution reverted", + }, + payload: { + id: 1, + jsonrpc: "2.0", + method: "linea_estimateGas", + params: [ + { + data: "0x491e09360000000000000000000000004420ce157f2c39edaae6cc107a42c8e527d6e02800000000000000000000000034be5b8c30ee4fde069dc878989686abe988447000000000000000000000000000000000000000000000000000006182ba2f0b400000000000000000000000000000000000000000000000000001c6bf52634000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000052b130000000000000000000000000000000000000000000000000000000000000000", + from: "0x46eA7a855DA88FBC09cc59de93468E6bFbf0d81b", + to: "0x508Ca82Df566dCD1B0DE8296e70a96332cD644ec", + value: "0", + }, + ], + }, + }), + testMessage, + ), + ); + + await transactionSizeProcessor.process(); + + expect(loggerErrorSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith("Error occurred while processing message transaction size.", { + data: "0x5461344300000000000000000000000034be5b8c30ee4fde069dc878989686abe9884470", + errorCode: "UNKNOWN_ERROR", + errorMessage: "Execution reverted", + messageHash: testMessage.messageHash, + mitigation: { shouldRetry: false }, + }); }); it("Should log as info and call updateMessage if the transaction size calculation succeed", async () => { diff --git a/postman/src/services/processors/__tests__/MessageAnchoringProcessor.test.ts b/postman/src/services/processors/__tests__/MessageAnchoringProcessor.test.ts index 4897ee932..3ab5a46cf 100644 --- a/postman/src/services/processors/__tests__/MessageAnchoringProcessor.test.ts +++ b/postman/src/services/processors/__tests__/MessageAnchoringProcessor.test.ts @@ -129,7 +129,10 @@ describe("TestMessageAnchoringProcessor", () => { await anchoringProcessor.process(); expect(loggerErrorSpy).toHaveBeenCalledTimes(1); - expect(loggerErrorSpy).toHaveBeenCalledWith(error); + expect(loggerErrorSpy).toHaveBeenCalledWith("An error occurred while processing messages.", { + errorCode: "UNKNOWN_ERROR", + errorMessage: error.message, + }); expect(messageRepositoryMockSaveSpy).toHaveBeenCalledTimes(0); }); }); diff --git a/postman/src/services/processors/__tests__/MessageClaimingPersister.test.ts b/postman/src/services/processors/__tests__/MessageClaimingPersister.test.ts index be19c62e9..523937433 100644 --- a/postman/src/services/processors/__tests__/MessageClaimingPersister.test.ts +++ b/postman/src/services/processors/__tests__/MessageClaimingPersister.test.ts @@ -82,7 +82,11 @@ describe("TestMessageClaimingPersister ", () => { await messageClaimingPersister.process(); expect(loggerErrorSpy).toHaveBeenCalledTimes(1); - expect(loggerErrorSpy).toHaveBeenCalledWith(getTxReceiptError); + expect(loggerErrorSpy).toHaveBeenCalledWith("Error processing message.", { + messageHash: testPendingMessage.messageHash, + errorCode: "UNKNOWN_ERROR", + errorMessage: getTxReceiptError.message, + }); }); it("Should log as info and update message as claimed success if successful", async () => { diff --git a/postman/src/services/processors/__tests__/MessageClaimingProcessor.test.ts b/postman/src/services/processors/__tests__/MessageClaimingProcessor.test.ts index 0af999d9c..5897ab9b8 100644 --- a/postman/src/services/processors/__tests__/MessageClaimingProcessor.test.ts +++ b/postman/src/services/processors/__tests__/MessageClaimingProcessor.test.ts @@ -370,6 +370,7 @@ describe("TestMessageClaimingProcessor", () => { expect(loggerWarnOrErrorSpy).toHaveBeenCalledTimes(1); expect(loggerWarnOrErrorSpy).toHaveBeenCalledWith(actionRejectedError, { parsedError: ErrorParser.parseErrorWithMitigation(actionRejectedError as EthersError), + messageHash: expectedLoggingMessage.messageHash, }); }); }); diff --git a/postman/src/utils/ErrorParser.ts b/postman/src/utils/ErrorParser.ts index d24bfbf8b..1ac102e4c 100644 --- a/postman/src/utils/ErrorParser.ts +++ b/postman/src/utils/ErrorParser.ts @@ -1,5 +1,5 @@ -import { EthersError, ErrorCode } from "ethers"; -import { GasEstimationError } from "@consensys/linea-sdk"; +import { EthersError, ErrorCode, isError } from "ethers"; +import { isBaseError } from "@consensys/linea-sdk"; import { DatabaseAccessError } from "../core/errors/DatabaseErrors"; import { MessageProps } from "../core/entities/Message"; @@ -12,8 +12,8 @@ export type Mitigation = { export type ParsedErrorResult = { errorCode: ErrorCode; - context?: string; - reason?: string; + errorMessage?: string; + data?: string; mitigation: Mitigation; }; @@ -26,82 +26,140 @@ export class ErrorParser { * @param {EthersError} error - The error encountered during Ethereum operations. * @returns {ParsedErrorResult | null} An object containing the parsed error result and mitigation strategies, or `null` if the error is `undefined`. */ - public static parseErrorWithMitigation(error: EthersError): ParsedErrorResult | null { + public static parseErrorWithMitigation(error: unknown): ParsedErrorResult | null { if (!error) { return null; } - const parsedErrResult: ParsedErrorResult = { - errorCode: "UNKNOWN_ERROR", - mitigation: { shouldRetry: false }, - }; + if (!isBaseError(error)) { + if (error instanceof DatabaseAccessError) { + return { + errorCode: "UNKNOWN_ERROR", + errorMessage: (error as DatabaseAccessError).message, + mitigation: { shouldRetry: true }, + }; + } + + return { + errorCode: "UNKNOWN_ERROR", + errorMessage: error instanceof Error ? error.message : String(error), + mitigation: { shouldRetry: false }, + }; + } + + if (!this.isEthersError(error.error)) { + return { + errorCode: "UNKNOWN_ERROR", + errorMessage: error instanceof Error ? error.message : String(error), + mitigation: { shouldRetry: false }, + }; + } - switch (error.code) { - case "NETWORK_ERROR": - case "SERVER_ERROR": - case "TIMEOUT": - case "INSUFFICIENT_FUNDS": - case "REPLACEMENT_UNDERPRICED": - case "NONCE_EXPIRED": - parsedErrResult.context = error.shortMessage; - parsedErrResult.mitigation = { + return this.parseEthersError(error.error); + } + + private static isEthersError(error: unknown): error is EthersError { + return (error as EthersError).shortMessage !== undefined || (error as EthersError).code !== undefined; + } + + public static parseEthersError(error: EthersError): ParsedErrorResult { + if ( + isError(error, "NETWORK_ERROR") || + isError(error, "SERVER_ERROR") || + isError(error, "TIMEOUT") || + isError(error, "INSUFFICIENT_FUNDS") || + isError(error, "REPLACEMENT_UNDERPRICED") || + isError(error, "NONCE_EXPIRED") + ) { + return { + errorCode: error.code, + errorMessage: error.message, + mitigation: { shouldRetry: true, + }, + }; + } + + if (isError(error, "CALL_EXCEPTION")) { + if ( + error.shortMessage.includes("execution reverted") || + error.info?.error?.code === 4001 || //The user rejected the request (EIP-1193) + error.info?.error?.code === -32603 //Internal JSON-RPC error (EIP-1474) + ) { + return { + errorCode: error.code, + errorMessage: error.info?.error?.message ?? error.shortMessage, + mitigation: { + shouldRetry: false, + }, }; - break; - case "CALL_EXCEPTION": - if ( - error.shortMessage.includes("execution reverted") || - error.info?.error?.code === 4001 || //The user rejected the request (EIP-1193) - error.info?.error?.code === -32603 //Internal JSON-RPC error (EIP-1474) - ) { - parsedErrResult.context = error.info?.error?.message ?? error.shortMessage; - break; - } - - if ( - error.info?.error?.code === -32000 && //Missing or invalid parameters (EIP-1474) - (error.info?.error?.message.includes("gas required exceeds allowance (0)") || - error.info?.error?.message.includes("max priority fee per gas higher than max fee per gas") || - error.info?.error?.message.includes("max fee per gas less than block base fee")) - ) { - parsedErrResult.context = error.info?.error?.message; - parsedErrResult.mitigation = { - shouldRetry: true, - }; - break; - } + } - parsedErrResult.context = error.shortMessage; - parsedErrResult.mitigation = { - shouldRetry: true, + if ( + error.info?.error?.code === -32000 && //Missing or invalid parameters (EIP-1474) + (error.info?.error?.message.includes("gas required exceeds allowance (0)") || + error.info?.error?.message.includes("max priority fee per gas higher than max fee per gas") || + error.info?.error?.message.includes("max fee per gas less than block base fee")) + ) { + return { + errorCode: error.code, + errorMessage: error.info?.error?.message ?? error.shortMessage, + mitigation: { + shouldRetry: true, + }, }; - break; - case "ACTION_REJECTED": - case "UNKNOWN_ERROR": - parsedErrResult.context = error.shortMessage; - break; - default: - if (error instanceof GasEstimationError) { - parsedErrResult.context = (error as GasEstimationError).message; - break; - } - - if (error instanceof DatabaseAccessError) { - parsedErrResult.context = (error as DatabaseAccessError).message; - } else { - parsedErrResult.context = error.message; - } - - parsedErrResult.context = error.shortMessage ?? error.message; - parsedErrResult.mitigation = { + } + + return { + errorCode: error.code, + errorMessage: error.shortMessage, + mitigation: { shouldRetry: true, + }, + }; + } + + if (isError(error, "ACTION_REJECTED")) { + return { + errorCode: error.code, + errorMessage: error.info?.error?.message ?? error.shortMessage, + mitigation: { + shouldRetry: false, + }, + }; + } + + if (isError(error, "UNKNOWN_ERROR")) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (error.error?.code === -32000 && error.error?.message?.toLowerCase().includes("execution reverted")) { + return { + errorCode: error.code, + errorMessage: error.error?.message ?? error.shortMessage, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + data: error.error?.data, + mitigation: { + shouldRetry: false, + }, }; - break; + } + + return { + errorCode: error.code, + errorMessage: error.shortMessage, + mitigation: { + shouldRetry: false, + }, + }; } return { - ...parsedErrResult, - errorCode: error.code || parsedErrResult.errorCode, + errorCode: error.code, + errorMessage: error.shortMessage ?? error.message, + mitigation: { + shouldRetry: true, + }, }; } } diff --git a/postman/src/utils/__tests__/ErrorParser.test.ts b/postman/src/utils/__tests__/ErrorParser.test.ts index 263c92af6..b95c019ac 100644 --- a/postman/src/utils/__tests__/ErrorParser.test.ts +++ b/postman/src/utils/__tests__/ErrorParser.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "@jest/globals"; -import { ErrorCode, EthersError } from "ethers"; -import { GasEstimationError } from "@consensys/linea-sdk"; +import { ErrorCode, makeError } from "ethers"; +import { makeBaseError } from "@consensys/linea-sdk"; import { ErrorParser } from "../ErrorParser"; import { DatabaseAccessError } from "../../core/errors"; import { DatabaseErrorType, DatabaseRepoName } from "../../core/enums"; @@ -9,17 +9,36 @@ import { generateMessage } from "../testing/helpers"; describe("ErrorParser", () => { describe("parseErrorWithMitigation", () => { it("should return null when error is null", () => { - expect(ErrorParser.parseErrorWithMitigation(null as unknown as EthersError)).toStrictEqual(null); + expect(ErrorParser.parseErrorWithMitigation(null)).toStrictEqual(null); }); + it("should return UNKNOWN_ERROR and shouldRetry = false when error is not instance of BaseError", () => { + expect(ErrorParser.parseErrorWithMitigation(new Error("any reason"))).toStrictEqual({ + errorMessage: "any reason", + errorCode: "UNKNOWN_ERROR", + mitigation: { + shouldRetry: false, + }, + }); + }); + + it("should return UNKNOWN_ERROR and shouldRetry = false when error is instance of BaseError but the underlying error is not an EthersError", () => { + expect(ErrorParser.parseErrorWithMitigation(makeBaseError(new Error("any reason")))).toStrictEqual({ + errorMessage: "any reason", + errorCode: "UNKNOWN_ERROR", + mitigation: { + shouldRetry: false, + }, + }); + }); + }); + + describe("parseEthersError", () => { it("should return NETWORK_ERROR and shouldRetry = true when error code = NETWORK_ERROR", () => { - const errorMessage = { - code: "NETWORK_ERROR", - shortMessage: "any reason", - }; + const error = makeError("any reason", "NETWORK_ERROR"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=NETWORK_ERROR, version=6.13.4)", errorCode: "NETWORK_ERROR", mitigation: { shouldRetry: true, @@ -28,13 +47,10 @@ describe("ErrorParser", () => { }); it("should return SERVER_ERROR and shouldRetry = true when error code = SERVER_ERROR", () => { - const errorMessage = { - code: "SERVER_ERROR", - shortMessage: "any reason", - }; + const error = makeError("any reason", "SERVER_ERROR"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=SERVER_ERROR, version=6.13.4)", errorCode: "SERVER_ERROR", mitigation: { shouldRetry: true, @@ -43,13 +59,10 @@ describe("ErrorParser", () => { }); it("should return TIMEOUT and shouldRetry = true when error code = TIMEOUT", () => { - const errorMessage = { - code: "TIMEOUT", - shortMessage: "any reason", - }; + const error = makeError("any reason", "TIMEOUT"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=TIMEOUT, version=6.13.4)", errorCode: "TIMEOUT", mitigation: { shouldRetry: true, @@ -58,13 +71,10 @@ describe("ErrorParser", () => { }); it("should return INSUFFICIENT_FUNDS and shouldRetry = true when error code = INSUFFICIENT_FUNDS", () => { - const errorMessage = { - code: "INSUFFICIENT_FUNDS", - shortMessage: "any reason", - }; + const error = makeError("any reason", "INSUFFICIENT_FUNDS"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=INSUFFICIENT_FUNDS, version=6.13.4)", errorCode: "INSUFFICIENT_FUNDS", mitigation: { shouldRetry: true, @@ -72,14 +82,11 @@ describe("ErrorParser", () => { }); }); - it("should return REPLACEMENT_UNDERPRICED and shouldRetry = true when error code = NETWOREPLACEMENT_UNDERPRICEDK_ERROR", () => { - const errorMessage = { - code: "REPLACEMENT_UNDERPRICED", - shortMessage: "any reason", - }; + it("should return REPLACEMENT_UNDERPRICED and shouldRetry = true when error code = REPLACEMENT_UNDERPRICED", () => { + const error = makeError("any reason", "REPLACEMENT_UNDERPRICED"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=REPLACEMENT_UNDERPRICED, version=6.13.4)", errorCode: "REPLACEMENT_UNDERPRICED", mitigation: { shouldRetry: true, @@ -88,13 +95,10 @@ describe("ErrorParser", () => { }); it("should return NONCE_EXPIRED and shouldRetry = true when error code = NONCE_EXPIRED", () => { - const errorMessage = { - code: "NONCE_EXPIRED", - shortMessage: "any reason", - }; + const error = makeError("any reason", "NONCE_EXPIRED"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason (code=NONCE_EXPIRED, version=6.13.4)", errorCode: "NONCE_EXPIRED", mitigation: { shouldRetry: true, @@ -103,13 +107,10 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = true when error code = CALL_EXCEPTION", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", - }; + const error = makeError("any reason", "CALL_EXCEPTION"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: true, @@ -118,18 +119,26 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = false when error code = CALL_EXCEPTION with short message as execution reverted", () => { - const errorMessage = { - shortMessage: "execution reverted", - code: "CALL_EXCEPTION", + const error = makeError("execution reverted", "CALL_EXCEPTION", { info: { error: { message: "execution reverted for some reason", }, }, - }; + reason: "execution reverted", + action: "call", + data: "0x0123456789abcdef", + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "execution reverted for some reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "execution reverted for some reason", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: false, @@ -138,19 +147,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = false when error code = CALL_EXCEPTION with inner error code as 4001", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "execution reverted for some reason", code: 4001, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "execution reverted for some reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "execution reverted for some reason", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: false, @@ -159,19 +176,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = false when error code = CALL_EXCEPTION with inner error code as -32603", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "execution reverted for some reason", code: -32603, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "execution reverted for some reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "execution reverted for some reason", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: false, @@ -180,19 +205,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = true when error code = CALL_EXCEPTION and inner error code as -32000 and message as gas required exceeds allowance (0)", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "gas required exceeds allowance (0)", code: -32000, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "gas required exceeds allowance (0)", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "gas required exceeds allowance (0)", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: true, @@ -201,19 +234,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = true when error code = CALL_EXCEPTION and inner error code as -32000 and message as max priority fee per gas higher", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "max priority fee per gas higher than max fee per gas", code: -32000, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "max priority fee per gas higher than max fee per gas", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "max priority fee per gas higher than max fee per gas", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: true, @@ -222,19 +263,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = true when error code = CALL_EXCEPTION and inner error code as -32000 and message as max fee per gas less than block base fee", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "max fee per gas less than block base fee", code: -32000, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "max fee per gas less than block base fee", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "max fee per gas less than block base fee", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: true, @@ -243,19 +292,27 @@ describe("ErrorParser", () => { }); it("should return CALL_EXCEPTION and shouldRetry = true when error code = CALL_EXCEPTION with other inner error", () => { - const errorMessage = { - shortMessage: "any reason", - code: "CALL_EXCEPTION", + const error = makeError("any reason", "CALL_EXCEPTION", { info: { error: { message: "invalid method parameters", code: -32602, }, }, - }; + action: "call", + data: null, + reason: null, + transaction: { + to: null, + from: undefined, + data: "", + }, + invocation: null, + revert: null, + }); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason", errorCode: "CALL_EXCEPTION", mitigation: { shouldRetry: true, @@ -264,13 +321,10 @@ describe("ErrorParser", () => { }); it("should return ACTION_REJECTED and shouldRetry = false when error code = ACTION_REJECTED", () => { - const errorMessage = { - shortMessage: "any reason", - code: "ACTION_REJECTED", - }; + const error = makeError("any reason", "ACTION_REJECTED"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason", errorCode: "ACTION_REJECTED", mitigation: { shouldRetry: false, @@ -279,13 +333,10 @@ describe("ErrorParser", () => { }); it("should return UNKNOWN_ERROR and shouldRetry = false when error code = UNKNOWN_ERROR", () => { - const errorMessage = { - shortMessage: "any reason", - code: "UNKNOWN_ERROR", - }; + const error = makeError("any reason", "UNKNOWN_ERROR"); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason", errorCode: "UNKNOWN_ERROR", mitigation: { shouldRetry: false, @@ -294,11 +345,30 @@ describe("ErrorParser", () => { }); it("should return UNKNOWN_ERROR and shouldRetry = false when error = GasEstimationError", () => { - const gasEstimationError = new GasEstimationError("Gas estimation failed", generateMessage()); + const gasEstimationError = makeBaseError(makeError("Gas estimation failed", "UNKNOWN_ERROR"), generateMessage()); + + expect(ErrorParser.parseErrorWithMitigation(gasEstimationError)).toStrictEqual({ + errorMessage: "Gas estimation failed", + errorCode: "UNKNOWN_ERROR", + mitigation: { + shouldRetry: false, + }, + }); + }); + + it("should return UNKNOWN_ERROR and shouldRetry = false when error is execution reverted", () => { + const error = makeError("Gas estimation failed", "UNKNOWN_ERROR", { + error: { + code: -32000, + message: "execution reverted", + data: "0x0123456789abcdef", + }, + }); - expect(ErrorParser.parseErrorWithMitigation(gasEstimationError as unknown as EthersError)).toStrictEqual({ - context: "Gas estimation failed", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "execution reverted", errorCode: "UNKNOWN_ERROR", + data: "0x0123456789abcdef", mitigation: { shouldRetry: false, }, @@ -313,8 +383,8 @@ describe("ErrorParser", () => { generateMessage(), ); - expect(ErrorParser.parseErrorWithMitigation(databaseAccessError as unknown as EthersError)).toStrictEqual({ - context: "MessageRepository: insert - Database access failed", + expect(ErrorParser.parseErrorWithMitigation(databaseAccessError)).toStrictEqual({ + errorMessage: "MessageRepository: insert - Database access failed", errorCode: "UNKNOWN_ERROR", mitigation: { shouldRetry: true, @@ -339,13 +409,10 @@ describe("ErrorParser", () => { "OFFCHAIN_FAULT", ]; otherErrorCodes.forEach((errorCode: ErrorCode) => { - const errorMessage = { - shortMessage: "any reason", - code: errorCode, - }; + const error = makeError("any reason", errorCode); - expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({ - context: "any reason", + expect(ErrorParser.parseEthersError(error)).toStrictEqual({ + errorMessage: "any reason", errorCode: errorCode, mitigation: { shouldRetry: true, diff --git a/sdk/src/LineaSDK.ts b/sdk/src/LineaSDK.ts index 9c425ff8f..f8ca267c1 100644 --- a/sdk/src/LineaSDK.ts +++ b/sdk/src/LineaSDK.ts @@ -20,11 +20,11 @@ import { DEFAULT_L2_MESSAGE_TREE_DEPTH, DEFAULT_MAX_FEE_PER_GAS_CAP, } from "./core/constants"; -import { BaseError } from "./core/errors"; import { L1FeeEstimatorOptions, L2FeeEstimatorOptions, LineaSDKOptions, Network, SDKMode } from "./core/types"; import { NETWORKS } from "./core/constants"; import { isString } from "./core/utils"; import { Direction } from "./core/enums"; +import { makeBaseError } from "./core/errors/utils"; export class LineaSDK { private network: Network; @@ -64,7 +64,7 @@ export class LineaSDK { const { l1SignerPrivateKeyOrWallet, l2SignerPrivateKeyOrWallet } = options; if (!l1SignerPrivateKeyOrWallet || !l2SignerPrivateKeyOrWallet) { - throw new BaseError("You need to provide both L1 and L2 signer private keys or wallets."); + throw makeBaseError("You need to provide both L1 and L2 signer private keys or wallets."); } this.l1Signer = this.getWallet(l1SignerPrivateKeyOrWallet).connect(this.l1Provider); @@ -108,7 +108,7 @@ export class LineaSDK { return new BrowserProvider(l1RpcUrlOrProvider); } - throw new BaseError("Invalid argument: l1RpcUrlOrProvider must be a string or Eip1193Provider"); + throw makeBaseError("Invalid argument: l1RpcUrlOrProvider must be a string or Eip1193Provider"); } /** @@ -118,7 +118,7 @@ export class LineaSDK { */ public getL1Signer(): Signer { if (!this.l1Signer) { - throw new BaseError("L1 signer is not available in read-only mode."); + throw makeBaseError("L1 signer is not available in read-only mode."); } return this.l1Signer; } @@ -130,7 +130,7 @@ export class LineaSDK { */ public getL2Signer(): Signer { if (!this.l2Signer) { - throw new BaseError("L2 signer is not available in read-only mode."); + throw makeBaseError("L2 signer is not available in read-only mode."); } return this.l2Signer; } @@ -176,7 +176,7 @@ export class LineaSDK { return new LineaBrowserProvider(l2RpcUrlOrProvider); } - throw new Error("Invalid argument: l2RpcUrlOrProvider must be a string or Eip1193Provider"); + throw makeBaseError("Invalid argument: l2RpcUrlOrProvider must be a string or Eip1193Provider"); } /** @@ -284,13 +284,13 @@ export class LineaSDK { private getContractAddress(contractType: "l1" | "l2", localContractAddress?: string): string { if (this.network === "custom") { if (!localContractAddress) { - throw new BaseError(`You need to provide a ${contractType.toUpperCase()} contract address.`); + throw makeBaseError(`You need to provide a ${contractType.toUpperCase()} contract address.`); } return localContractAddress; } else { const contractAddress = NETWORKS[this.network][`${contractType}ContractAddress`]; if (!contractAddress) { - throw new BaseError(`Contract address for ${contractType.toUpperCase()} not found in network ${this.network}.`); + throw makeBaseError(`Contract address for ${contractType.toUpperCase()} not found in network ${this.network}.`); } return contractAddress; } @@ -308,7 +308,7 @@ export class LineaSDK { return privateKeyOrWallet instanceof Wallet ? privateKeyOrWallet : new Wallet(privateKeyOrWallet); } catch (e) { if (e instanceof Error && e.message.includes("invalid private key")) { - throw new BaseError("Something went wrong when trying to generate Wallet. Please check your private key."); + throw makeBaseError("Something went wrong when trying to generate Wallet. Please check your private key."); } throw e; } diff --git a/sdk/src/clients/ethereum/L1ClaimingService.ts b/sdk/src/clients/ethereum/L1ClaimingService.ts index aacfacedc..124d50a43 100644 --- a/sdk/src/clients/ethereum/L1ClaimingService.ts +++ b/sdk/src/clients/ethereum/L1ClaimingService.ts @@ -11,8 +11,8 @@ import { Cache } from "../../utils/Cache"; import { ILineaRollupClient } from "../../core/clients/ethereum"; import { IL2MessageServiceClient, IL2MessageServiceLogClient } from "../../core/clients/linea"; import { MessageSent, Network } from "../../core/types"; -import { BaseError } from "../../core/errors"; import { FinalizationMessagingInfo, Proof } from "../../core/clients/ethereum/IMerkleTreeService"; +import { makeBaseError } from "../../core/errors/utils"; export class L1ClaimingService { private cache: Cache; @@ -93,7 +93,7 @@ export class L1ClaimingService { }); if (!messageSentEvent) { - throw new BaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); + throw makeBaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); } if (migrationBlock > messageSentEvent.blockNumber) { diff --git a/sdk/src/clients/ethereum/LineaRollupClient.ts b/sdk/src/clients/ethereum/LineaRollupClient.ts index 81e157cec..8651f91fa 100644 --- a/sdk/src/clients/ethereum/LineaRollupClient.ts +++ b/sdk/src/clients/ethereum/LineaRollupClient.ts @@ -9,7 +9,6 @@ import { ErrorDescription, } from "ethers"; import { LineaRollup, LineaRollup__factory } from "../../contracts/typechain"; -import { BaseError, GasEstimationError } from "../../core/errors"; import { Message, SDKMode, MessageSent } from "../../core/types"; import { OnChainMessageStatus } from "../../core/enums"; import { @@ -31,6 +30,7 @@ import { GasFees, IEthereumGasProvider } from "../../core/clients/IGasProvider"; import { IMessageRetriever } from "../../core/clients/IMessageRetriever"; import { IProvider } from "../../core/clients/IProvider"; import { BrowserProvider, Provider } from "../providers"; +import { makeBaseError } from "../../core/errors/utils"; export class LineaRollupClient implements @@ -163,7 +163,7 @@ export class LineaRollupClient } if (!signer) { - throw new BaseError("Please provide a signer."); + throw makeBaseError("Please provide a signer."); } return LineaRollup__factory.connect(contractAddress, signer); @@ -216,7 +216,7 @@ export class LineaRollupClient const [messageEvent] = await this.l2MessageServiceLogClient.getMessageSentEventsByMessageHash({ messageHash }); if (!messageEvent) { - throw new BaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); + throw makeBaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); } const [[l2MessagingBlockAnchoredEvent], isMessageClaimed] = await Promise.all([ @@ -248,7 +248,7 @@ export class LineaRollupClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'EstimateClaimGas' function not callable using readOnly mode."); + throw makeBaseError("'EstimateClaimGas' function not callable using readOnly mode."); } const { messageSender, destination, fee, value, calldata, messageNonce, feeRecipient } = message; @@ -267,9 +267,8 @@ export class LineaRollupClient ...overrides, }, ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw new GasEstimationError(e, message); + } catch (e) { + throw makeBaseError(e, message); } } @@ -284,7 +283,7 @@ export class LineaRollupClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'claim' function not callable using readOnly mode."); + throw makeBaseError("'claim' function not callable using readOnly mode."); } const { messageSender, destination, fee, value, calldata, messageNonce, feeRecipient } = message; @@ -316,7 +315,7 @@ export class LineaRollupClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'EstimateClaimGasFees' function not callable using readOnly mode."); + throw makeBaseError("'EstimateClaimGasFees' function not callable using readOnly mode."); } const { messageSender, destination, fee, value, calldata, messageNonce, feeRecipient } = message; @@ -344,9 +343,8 @@ export class LineaRollupClient ...overrides, }, ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw new GasEstimationError(e, message); + } catch (e) { + throw makeBaseError(e, message); } } @@ -361,7 +359,7 @@ export class LineaRollupClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'claim' function not callable using readOnly mode."); + throw makeBaseError("'claim' function not callable using readOnly mode."); } const { messageSender, destination, fee, value, calldata, messageNonce, feeRecipient } = message; @@ -401,17 +399,17 @@ export class LineaRollupClient priceBumpPercent: number = 10, ): Promise { if (!Number.isInteger(priceBumpPercent)) { - throw new Error("'priceBumpPercent' must be an integer"); + throw makeBaseError("'priceBumpPercent' must be an integer"); } if (this.mode === "read-only") { - throw new BaseError("'retryTransactionWithHigherFee' function not callable using readOnly mode."); + throw makeBaseError("'retryTransactionWithHigherFee' function not callable using readOnly mode."); } const transaction = await this.provider.getTransaction(transactionHash); if (!transaction) { - throw new BaseError(`Transaction with hash ${transactionHash} not found.`); + throw makeBaseError(`Transaction with hash ${transactionHash} not found.`); } let maxPriorityFeePerGas; diff --git a/sdk/src/clients/ethereum/MerkleTreeService.ts b/sdk/src/clients/ethereum/MerkleTreeService.ts index c26e26bad..de2fcce95 100644 --- a/sdk/src/clients/ethereum/MerkleTreeService.ts +++ b/sdk/src/clients/ethereum/MerkleTreeService.ts @@ -1,6 +1,5 @@ import { Block, TransactionReceipt, TransactionRequest, TransactionResponse } from "ethers"; import { SparseMerkleTreeFactory } from "../../utils/merkleTree/MerkleTreeFactory"; -import { BaseError } from "../../core/errors"; import { ILineaRollupLogClient, FinalizationMessagingInfo, @@ -16,6 +15,7 @@ import { import { LineaRollup, LineaRollup__factory } from "../../contracts/typechain"; import { IProvider } from "../../core/clients/IProvider"; import { BrowserProvider, Provider } from "../providers"; +import { makeBaseError } from "../../core/errors/utils"; export class MerkleTreeService implements IMerkleTreeService { private readonly contract: LineaRollup; @@ -54,7 +54,7 @@ export class MerkleTreeService implements IMerkleTreeService { const [messageEvent] = await this.l2MessageServiceLogClient.getMessageSentEventsByMessageHash({ messageHash }); if (!messageEvent) { - throw new BaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); + throw makeBaseError(`Message hash does not exist on L2. Message hash: ${messageHash}`); } const [l2MessagingBlockAnchoredEvent] = await this.lineaRollupLogClient.getL2MessagingBlockAnchoredEvents({ @@ -62,7 +62,7 @@ export class MerkleTreeService implements IMerkleTreeService { }); if (!l2MessagingBlockAnchoredEvent) { - throw new BaseError(`L2 block number ${messageEvent.blockNumber} has not been finalized on L1.`); + throw makeBaseError(`L2 block number ${messageEvent.blockNumber} has not been finalized on L1.`); } const finalizationInfo = await this.getFinalizationMessagingInfo(l2MessagingBlockAnchoredEvent.transactionHash); @@ -78,7 +78,7 @@ export class MerkleTreeService implements IMerkleTreeService { const tree = merkleTreeFactory.createAndAddLeaves(l2messages); if (!finalizationInfo.l2MerkleRoots.includes(tree.getRoot())) { - throw new BaseError("Merkle tree build failed."); + throw makeBaseError("Merkle tree build failed."); } return tree.getProof(l2messages.indexOf(messageHash)); @@ -93,7 +93,7 @@ export class MerkleTreeService implements IMerkleTreeService { const receipt = await this.provider.getTransactionReceipt(transactionHash); if (!receipt || receipt.logs.length === 0) { - throw new BaseError(`Transaction does not exist or no logs found in this transaction: ${transactionHash}.`); + throw makeBaseError(`Transaction does not exist or no logs found in this transaction: ${transactionHash}.`); } let treeDepth = 0; @@ -114,11 +114,11 @@ export class MerkleTreeService implements IMerkleTreeService { } if (l2MerkleRoots.length === 0) { - throw new BaseError(`No L2MerkleRootAdded events found in this transaction.`); + throw makeBaseError(`No L2MerkleRootAdded events found in this transaction.`); } if (blocksNumber.length === 0) { - throw new BaseError(`No L2MessagingBlocksAnchored events found in this transaction.`); + throw makeBaseError(`No L2MessagingBlocksAnchored events found in this transaction.`); } return { @@ -141,7 +141,7 @@ export class MerkleTreeService implements IMerkleTreeService { const events = await this.l2MessageServiceLogClient.getMessageSentEventsByBlockRange(fromBlock, toBlock); if (events.length === 0) { - throw new BaseError(`No MessageSent events found in this block range on L2.`); + throw makeBaseError(`No MessageSent events found in this block range on L2.`); } return events.map((event) => event.messageHash); @@ -161,7 +161,7 @@ export class MerkleTreeService implements IMerkleTreeService { const messageHashIndex = messageHashes.indexOf(messageHash); if (messageHashIndex === -1) { - throw new BaseError("Message hash not found in messages."); + throw makeBaseError("Message hash not found in messages."); } const start = Math.floor(messageHashIndex / numberOfMessagesInTrees) * numberOfMessagesInTrees; diff --git a/sdk/src/clients/ethereum/__tests__/LineaRollupClient.test.ts b/sdk/src/clients/ethereum/__tests__/LineaRollupClient.test.ts index f804ed896..8da972c19 100644 --- a/sdk/src/clients/ethereum/__tests__/LineaRollupClient.test.ts +++ b/sdk/src/clients/ethereum/__tests__/LineaRollupClient.test.ts @@ -30,8 +30,7 @@ import { import { LineaRollupClient } from "../LineaRollupClient"; import { ZERO_ADDRESS } from "../../../core/constants"; import { OnChainMessageStatus } from "../../../core/enums/message"; -import { GasEstimationError } from "../../../core/errors/GasFeeErrors"; -import { BaseError } from "../../../core/errors"; +import { BaseError, makeBaseError } from "../../../core/errors"; import { EthersL2MessageServiceLogClient } from "../../linea/EthersL2MessageServiceLogClient"; import { EthersLineaRollupLogClient } from "../EthersLineaRollupLogClient"; import { DefaultGasProvider } from "../../gas/DefaultGasProvider"; @@ -231,7 +230,7 @@ describe("TestLineaRollupClient", () => { jest.spyOn(gasFeeProvider, "getGasFees").mockRejectedValue(new Error("Gas fees estimation failed").message); await expect(lineaRollupClient.estimateClaimWithoutProofGas(message)).rejects.toThrow( - new GasEstimationError("Gas fees estimation failed", message), + makeBaseError("Gas fees estimation failed", message), ); }); diff --git a/sdk/src/clients/gas/DefaultGasProvider.ts b/sdk/src/clients/gas/DefaultGasProvider.ts index a9d3bbca3..4ec61547e 100644 --- a/sdk/src/clients/gas/DefaultGasProvider.ts +++ b/sdk/src/clients/gas/DefaultGasProvider.ts @@ -1,8 +1,8 @@ import { Block, TransactionReceipt, TransactionRequest, TransactionResponse } from "ethers"; -import { FeeEstimationError } from "../../core/errors"; import { DefaultGasProviderConfig, FeeHistory, GasFees, IEthereumGasProvider } from "../../core/clients/IGasProvider"; import { IProvider } from "../../core/clients/IProvider"; import { BrowserProvider, LineaBrowserProvider, LineaProvider, Provider } from "../providers"; +import { makeBaseError } from "../../core/errors/utils"; export class DefaultGasProvider implements IEthereumGasProvider { private gasFeesCache: GasFees; @@ -62,7 +62,7 @@ export class DefaultGasProvider implements IEthereumGasProvider this.config.maxFeePerGasCap) { - throw new FeeEstimationError( + throw makeBaseError( `Estimated miner tip of ${maxPriorityFeePerGas} exceeds configured max fee per gas of ${this.config.maxFeePerGasCap}!`, ); } diff --git a/sdk/src/clients/gas/GasProvider.ts b/sdk/src/clients/gas/GasProvider.ts index a13af7676..11e285f7f 100644 --- a/sdk/src/clients/gas/GasProvider.ts +++ b/sdk/src/clients/gas/GasProvider.ts @@ -4,8 +4,8 @@ import { LineaGasProvider } from "./LineaGasProvider"; import { IProvider } from "../../core/clients/IProvider"; import { GasFees, GasProviderConfig, IGasProvider, LineaGasFees } from "../../core/clients/IGasProvider"; import { Direction } from "../../core/enums"; -import { BaseError } from "../../core/errors"; import { BrowserProvider, LineaBrowserProvider, LineaProvider, Provider } from "../providers"; +import { makeBaseError } from "../../core/errors/utils"; export class GasProvider implements IGasProvider { private defaultGasProvider: DefaultGasProvider; @@ -50,7 +50,7 @@ export class GasProvider implements IGasProvider { if (this.config.direction === Direction.L1_TO_L2) { if (this.config.enableLineaEstimateGas) { if (!transactionRequest) { - throw new BaseError( + throw makeBaseError( "You need to provide transaction request as param to call the getGasFees function on Linea.", ); } diff --git a/sdk/src/clients/gas/__tests__/DefaultGasProvider.test.ts b/sdk/src/clients/gas/__tests__/DefaultGasProvider.test.ts index 4bcfb609b..3f3c3ecd5 100644 --- a/sdk/src/clients/gas/__tests__/DefaultGasProvider.test.ts +++ b/sdk/src/clients/gas/__tests__/DefaultGasProvider.test.ts @@ -1,9 +1,9 @@ import { describe, afterEach, jest, it, expect, beforeEach } from "@jest/globals"; import { MockProxy, mock, mockClear } from "jest-mock-extended"; import { DefaultGasProvider } from "../DefaultGasProvider"; -import { FeeEstimationError } from "../../../core/errors/GasFeeErrors"; import { Provider } from "../../providers/provider"; import { DEFAULT_GAS_ESTIMATION_PERCENTILE } from "../../../core/constants"; +import { makeBaseError } from "../../../core/errors"; const MAX_FEE_PER_GAS = 100_000_000n; @@ -48,7 +48,9 @@ describe("DefaultGasProvider", () => { ["0x3b9aca00", "0x59682f00"], ], }); - await expect(eip1559GasProvider.getGasFees()).rejects.toThrow(FeeEstimationError); + await expect(eip1559GasProvider.getGasFees()).rejects.toThrow( + makeBaseError(`Estimated miner tip of ${1271935510} exceeds configured max fee per gas of ${MAX_FEE_PER_GAS}!`), + ); expect(sendSpy).toHaveBeenCalledTimes(1); }); diff --git a/sdk/src/clients/linea/L2MessageServiceClient.ts b/sdk/src/clients/linea/L2MessageServiceClient.ts index 80b138165..ef7e07a6c 100644 --- a/sdk/src/clients/linea/L2MessageServiceClient.ts +++ b/sdk/src/clients/linea/L2MessageServiceClient.ts @@ -9,7 +9,6 @@ import { ErrorDescription, } from "ethers"; import { L2MessageService, L2MessageService__factory } from "../../contracts/typechain"; -import { GasEstimationError, BaseError } from "../../core/errors"; import { Message, SDKMode, MessageSent } from "../../core/types"; import { OnChainMessageStatus } from "../../core/enums"; import { IL2MessageServiceClient, ILineaProvider } from "../../core/clients/linea"; @@ -18,6 +17,7 @@ import { formatMessageStatus, isString } from "../../core/utils"; import { IGasProvider, LineaGasFees } from "../../core/clients/IGasProvider"; import { IMessageRetriever } from "../../core/clients/IMessageRetriever"; import { LineaBrowserProvider, LineaProvider } from "../providers"; +import { makeBaseError } from "../../core/errors/utils"; export class L2MessageServiceClient implements @@ -111,7 +111,7 @@ export class L2MessageServiceClient } if (!signer) { - throw new BaseError("Please provide a signer."); + throw makeBaseError("Please provide a signer."); } return L2MessageService__factory.connect(contractAddress, signer); @@ -141,7 +141,7 @@ export class L2MessageServiceClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'EstimateClaimGasFees' function not callable using readOnly mode."); + throw makeBaseError("'EstimateClaimGasFees' function not callable using readOnly mode."); } try { @@ -155,9 +155,8 @@ export class L2MessageServiceClient data: transactionData, ...overrides, })) as LineaGasFees; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - throw new GasEstimationError(e, message); + } catch (e) { + throw makeBaseError(e, message); } } @@ -173,7 +172,7 @@ export class L2MessageServiceClient overrides: Overrides = {}, ): Promise { if (this.mode === "read-only") { - throw new BaseError("'claim' function not callable using readOnly mode."); + throw makeBaseError("'claim' function not callable using readOnly mode."); } const { messageSender, destination, fee, value, calldata, messageNonce, feeRecipient } = message; @@ -205,17 +204,17 @@ export class L2MessageServiceClient priceBumpPercent: number = 10, ): Promise { if (!Number.isInteger(priceBumpPercent)) { - throw new Error("'priceBumpPercent' must be an integer"); + throw makeBaseError("'priceBumpPercent' must be an integer"); } if (this.mode === "read-only") { - throw new BaseError("'retryTransactionWithHigherFee' function not callable using readOnly mode."); + throw makeBaseError("'retryTransactionWithHigherFee' function not callable using readOnly mode."); } const transaction = await this.provider.getTransaction(transactionHash); if (!transaction) { - throw new BaseError(`Transaction with hash ${transactionHash} not found.`); + throw makeBaseError(`Transaction with hash ${transactionHash} not found.`); } let maxPriorityFeePerGas; diff --git a/sdk/src/clients/linea/__tests__/L2MessageServiceClient.test.ts b/sdk/src/clients/linea/__tests__/L2MessageServiceClient.test.ts index b6943f31c..d51672f6e 100644 --- a/sdk/src/clients/linea/__tests__/L2MessageServiceClient.test.ts +++ b/sdk/src/clients/linea/__tests__/L2MessageServiceClient.test.ts @@ -19,8 +19,7 @@ import { import { L2MessageServiceClient } from "../L2MessageServiceClient"; import { ZERO_ADDRESS } from "../../../core/constants"; import { OnChainMessageStatus } from "../../../core/enums/message"; -import { GasEstimationError } from "../../../core/errors/GasFeeErrors"; -import { BaseError } from "../../../core/errors"; +import { BaseError, makeBaseError } from "../../../core/errors"; import { LineaProvider } from "../../providers"; import { GasProvider } from "../../gas"; @@ -107,7 +106,7 @@ describe("TestL2MessageServiceClient", () => { jest.spyOn(gasFeeProvider, "getGasFees").mockRejectedValue(new Error("Gas fees estimation failed").message); await expect(l2MessageServiceClient.estimateClaimGasFees(message)).rejects.toThrow( - new GasEstimationError("Gas fees estimation failed", message), + makeBaseError("Gas fees estimation failed", message), ); }); diff --git a/sdk/src/clients/providers/lineaProvider.ts b/sdk/src/clients/providers/lineaProvider.ts index 6be28da84..cf810a97b 100644 --- a/sdk/src/clients/providers/lineaProvider.ts +++ b/sdk/src/clients/providers/lineaProvider.ts @@ -1,7 +1,7 @@ import { BlockTag, dataSlice, ethers, toNumber } from "ethers"; import { BlockExtraData } from "../../core/clients/linea"; import { GasFees } from "../../core/clients/IGasProvider"; -import { BaseError } from "../../core/errors"; +import { makeBaseError } from "../../core/errors/utils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Constructor = new (...args: any[]) => T; @@ -21,7 +21,7 @@ function LineaProviderMixIn>(Base: TB const { maxPriorityFeePerGas, maxFeePerGas } = await this.getFeeData(); if (!maxPriorityFeePerGas || !maxFeePerGas) { - throw new BaseError("Error getting fee data"); + throw makeBaseError("Error getting fee data"); } return { maxPriorityFeePerGas, maxFeePerGas }; diff --git a/sdk/src/clients/providers/provider.ts b/sdk/src/clients/providers/provider.ts index 34d659cce..0785093d9 100644 --- a/sdk/src/clients/providers/provider.ts +++ b/sdk/src/clients/providers/provider.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; -import { BaseError } from "../../core/errors"; import { GasFees } from "../../core/clients/IGasProvider"; +import { makeBaseError } from "../../core/errors/utils"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Constructor = new (...args: any[]) => T; @@ -22,7 +22,7 @@ export function ProviderMixIn>(Base: const { maxPriorityFeePerGas, maxFeePerGas } = await this.getFeeData(); if (!maxPriorityFeePerGas || !maxFeePerGas) { - throw new BaseError("Error getting fee data"); + throw makeBaseError("Error getting fee data"); } return { maxPriorityFeePerGas, maxFeePerGas }; diff --git a/sdk/src/core/errors/BaseError.ts b/sdk/src/core/errors/BaseError.ts index f2a1f556e..221333810 100644 --- a/sdk/src/core/errors/BaseError.ts +++ b/sdk/src/core/errors/BaseError.ts @@ -1,11 +1,35 @@ -export class BaseError extends Error { - reason?: BaseError | Error | string; +export type InferErrorType = T extends Error + ? Error + : T extends string + ? string + : T extends number + ? number + : T extends { message: string } + ? { message: string } + : unknown; - override name = "LineaSDKCoreError"; +export class BaseError extends Error { + stack: string = ""; + metadata?: M; + error: InferErrorType; - constructor(message?: string) { - super(); - this.message = message || "An error occurred."; - Error.captureStackTrace(this, this.constructor); + constructor(error: T, metadata?: M) { + const message = BaseError.getMessage(error); + super(message); + this.stack = error instanceof Error && error.stack ? error.stack : message; + this.metadata = metadata; + this.error = error as InferErrorType; + + Object.setPrototypeOf(this, BaseError.prototype); + } + + private static getMessage(error: unknown): string { + if (typeof error === "string") return error; + if (typeof error === "number") return `Error Code: ${error}`; + if (error instanceof Error) return error.message; + if (typeof error === "object" && error !== null && "message" in error) { + return String(error.message); + } + return "Unknown error"; } } diff --git a/sdk/src/core/errors/GasFeeErrors.ts b/sdk/src/core/errors/GasFeeErrors.ts deleted file mode 100644 index 6f7e2c1d3..000000000 --- a/sdk/src/core/errors/GasFeeErrors.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Message } from "../types"; -import { BaseError } from "./BaseError"; - -export class FeeEstimationError extends BaseError { - override name = FeeEstimationError.name; -} - -export class GasEstimationError extends BaseError { - override name = GasEstimationError.name; - public rejectedMessage?: T; - - constructor(message: string, rejectedMessage?: T) { - super(message); - this.rejectedMessage = rejectedMessage; - } -} diff --git a/sdk/src/core/errors/__tests__/BaseError.test.ts b/sdk/src/core/errors/__tests__/BaseError.test.ts index 346b35c57..74f78e769 100644 --- a/sdk/src/core/errors/__tests__/BaseError.test.ts +++ b/sdk/src/core/errors/__tests__/BaseError.test.ts @@ -1,14 +1,8 @@ import { describe, it } from "@jest/globals"; -import { BaseError } from "../BaseError"; -import { serialize } from "../../utils/serialize"; +import { makeBaseError } from "../utils"; describe("BaseError", () => { it("Should log error message when we only pass a short message", () => { - expect(serialize(new BaseError("An error message."))).toStrictEqual( - serialize({ - name: "LineaSDKCoreError", - message: "An error message.", - }), - ); + expect(makeBaseError("An error message.").message).toStrictEqual("An error message."); }); }); diff --git a/sdk/src/core/errors/__tests__/GasFeeErrors.test.ts b/sdk/src/core/errors/__tests__/GasFeeErrors.test.ts deleted file mode 100644 index 89faebc48..000000000 --- a/sdk/src/core/errors/__tests__/GasFeeErrors.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, it } from "@jest/globals"; -import { FeeEstimationError, GasEstimationError } from "../GasFeeErrors"; -import { serialize } from "../../utils/serialize"; -import { Message } from "../../types/message"; -import { ZERO_ADDRESS, ZERO_HASH } from "../../constants"; - -describe("BaseError", () => { - describe("FeeEstimationError", () => { - it("Should log error message", () => { - expect(serialize(new FeeEstimationError("An error message."))).toStrictEqual( - serialize({ - name: "FeeEstimationError", - message: "An error message.", - }), - ); - }); - }); - - describe("GasEstimationError", () => { - it("Should log error message", () => { - const rejectedMessage: Message = { - messageHash: ZERO_HASH, - messageSender: ZERO_ADDRESS, - destination: ZERO_ADDRESS, - fee: 0n, - value: 0n, - messageNonce: 0n, - calldata: "0x", - }; - - const estimationError = new Error("estimation error"); - - expect(serialize(new GasEstimationError(estimationError.message, rejectedMessage))).toStrictEqual( - serialize({ - name: "GasEstimationError", - message: "estimation error", - rejectedMessage: { - messageHash: "0x0000000000000000000000000000000000000000000000000000000000000000", - messageSender: "0x0000000000000000000000000000000000000000", - destination: "0x0000000000000000000000000000000000000000", - fee: 0n, - value: 0n, - messageNonce: 0n, - calldata: "0x", - }, - }), - ); - }); - }); -}); diff --git a/sdk/src/core/errors/index.ts b/sdk/src/core/errors/index.ts index fbc1108b7..170c1ec87 100644 --- a/sdk/src/core/errors/index.ts +++ b/sdk/src/core/errors/index.ts @@ -1,2 +1,2 @@ export { BaseError } from "./BaseError"; -export { GasEstimationError, FeeEstimationError } from "./GasFeeErrors"; +export { makeBaseError, isBaseError } from "./utils"; diff --git a/sdk/src/core/errors/utils.ts b/sdk/src/core/errors/utils.ts new file mode 100644 index 000000000..efada4109 --- /dev/null +++ b/sdk/src/core/errors/utils.ts @@ -0,0 +1,35 @@ +import { isNativeError } from "util/types"; +import { BaseError, InferErrorType } from "./BaseError"; + +/** + * Converts an `unknown` value that was thrown into a `BaseError` object. + * + * @param value - An `unknown` value. + * + * @returns A `BaseError` object. + */ +export const makeBaseError = (value: E extends BaseError ? never : E, metadata?: M): BaseError => { + if (isNativeError(value)) { + return new BaseError(value, metadata); + } else { + try { + return new BaseError( + new Error(`${typeof value === "object" ? JSON.stringify(value) : String(value)}`), + metadata, + ) as BaseError; + } catch { + return new BaseError(new Error(`Unexpected value thrown: non-stringifiable object`), metadata) as BaseError; + } + } +}; + +/** + * Type guard to check if an `unknown` value is a `BaseError` object. + * + * @param value - The value to check. + * + * @returns `true` if the value is a `BaseError` object, otherwise `false`. + */ +export const isBaseError = (value: unknown): value is BaseError, M> => { + return value instanceof BaseError && typeof value.stack === "string" && "metadata" in value && "error" in value; +}; diff --git a/sdk/src/index.ts b/sdk/src/index.ts index 3d81ac029..2c8608ab6 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -25,7 +25,7 @@ export { Provider, BrowserProvider, LineaProvider, LineaBrowserProvider } from " export { DefaultGasProvider, GasProvider, LineaGasProvider } from "./clients/gas"; // Core errors -export { GasEstimationError, FeeEstimationError } from "./core/errors"; +export { makeBaseError, isBaseError } from "./core/errors"; // Contracts types and factories (generated from typechain) export { LineaRollup, LineaRollup__factory, L2MessageService, L2MessageService__factory } from "./contracts/typechain"; diff --git a/sdk/src/utils/merkleTree/SparseMerkleTree.ts b/sdk/src/utils/merkleTree/SparseMerkleTree.ts index 192528c85..b3d9d2dcf 100644 --- a/sdk/src/utils/merkleTree/SparseMerkleTree.ts +++ b/sdk/src/utils/merkleTree/SparseMerkleTree.ts @@ -1,5 +1,5 @@ import { ethers } from "ethers"; -import { BaseError } from "../../core/errors"; +import { makeBaseError } from "../../core/errors"; import { ZERO_HASH } from "../../core/constants"; import { Proof } from "../../core/clients/ethereum"; @@ -27,7 +27,7 @@ export class SparseMerkleTree { */ constructor(depth: number) { if (depth <= 1) { - throw new BaseError("Merkle tree depth must be greater than 1"); + throw makeBaseError("Merkle tree depth must be greater than 1"); } this.depth = depth; this.emptyLeaves = this.generateEmptyLeaves(this.depth); @@ -53,14 +53,14 @@ export class SparseMerkleTree { */ public getProof(key: number): Proof { if (key < 0 || key >= Math.pow(2, this.depth)) { - throw new BaseError(`Leaf index is out of range`); + throw makeBaseError(`Leaf index is out of range`); } const binaryKey = this.keyToBinary(key); const leaf = this.getLeaf(key); if (leaf === this.emptyLeaves[0]) { - throw new BaseError(`Leaf does not exist`); + throw makeBaseError(`Leaf does not exist`); } return { @@ -78,7 +78,7 @@ export class SparseMerkleTree { */ public getLeaf(key: number): string { if (key < 0 || key >= Math.pow(2, this.depth)) { - throw new BaseError("Leaf index is out of range"); + throw makeBaseError("Leaf index is out of range"); } const binaryKey = this.keyToBinary(key); return this.getLeafHelper(this.root, binaryKey, 0);