mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-08 03:43:56 -05:00
fix: improve postman logging and error parsing (#622)
* fix: improve postman logging and error parsing * fix: remove unnecessary casting
This commit is contained in:
@@ -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<void>} A promise that resolves when the processing is complete.
|
||||
*/
|
||||
public async process(): Promise<void> {
|
||||
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<void>} A promise that resolves when the error has been handled.
|
||||
*/
|
||||
private async handleProcessingError(e: unknown, message: Message | null): Promise<void> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void>} A promise that resolves when the error has been handled.
|
||||
*/
|
||||
private async handleProcessingError(e: unknown, message: Message | null): Promise<void> {
|
||||
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 } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -370,6 +370,7 @@ describe("TestMessageClaimingProcessor", () => {
|
||||
expect(loggerWarnOrErrorSpy).toHaveBeenCalledTimes(1);
|
||||
expect(loggerWarnOrErrorSpy).toHaveBeenCalledWith(actionRejectedError, {
|
||||
parsedError: ErrorParser.parseErrorWithMitigation(actionRejectedError as EthersError),
|
||||
messageHash: expectedLoggingMessage.messageHash,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 },
|
||||
};
|
||||
|
||||
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 = {
|
||||
shouldRetry: true,
|
||||
if (!isBaseError(error)) {
|
||||
if (error instanceof DatabaseAccessError) {
|
||||
return {
|
||||
errorCode: "UNKNOWN_ERROR",
|
||||
errorMessage: (error as DatabaseAccessError<MessageProps>).message,
|
||||
mitigation: { shouldRetry: true },
|
||||
};
|
||||
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 = {
|
||||
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 },
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
parsedErrResult.context = error.shortMessage;
|
||||
parsedErrResult.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<MessageProps>).message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof DatabaseAccessError) {
|
||||
parsedErrResult.context = (error as DatabaseAccessError<MessageProps>).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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 NETWORK_ERROR and shouldRetry = true when error code = NETWORK_ERROR", () => {
|
||||
const errorMessage = {
|
||||
code: "NETWORK_ERROR",
|
||||
shortMessage: "any reason",
|
||||
};
|
||||
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,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(ErrorParser.parseErrorWithMitigation(errorMessage as unknown as EthersError)).toStrictEqual({
|
||||
context: "any reason",
|
||||
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 error = makeError("any reason", "NETWORK_ERROR");
|
||||
|
||||
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,10 +345,10 @@ 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 as unknown as EthersError)).toStrictEqual({
|
||||
context: "Gas estimation failed",
|
||||
expect(ErrorParser.parseErrorWithMitigation(gasEstimationError)).toStrictEqual({
|
||||
errorMessage: "Gas estimation failed",
|
||||
errorCode: "UNKNOWN_ERROR",
|
||||
mitigation: {
|
||||
shouldRetry: false,
|
||||
@@ -305,6 +356,25 @@ describe("ErrorParser", () => {
|
||||
});
|
||||
});
|
||||
|
||||
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.parseEthersError(error)).toStrictEqual({
|
||||
errorMessage: "execution reverted",
|
||||
errorCode: "UNKNOWN_ERROR",
|
||||
data: "0x0123456789abcdef",
|
||||
mitigation: {
|
||||
shouldRetry: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return UNKNOWN_ERROR and shouldRetry = false when error = DatabaseAccessError", () => {
|
||||
const databaseAccessError = new DatabaseAccessError(
|
||||
DatabaseRepoName.MessageRepository,
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<bigint> {
|
||||
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<ContractTransactionResponse> {
|
||||
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<bigint> {
|
||||
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<ContractTransactionResponse> {
|
||||
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<TransactionResponse> {
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<TransactionRequest> {
|
||||
private gasFeesCache: GasFees;
|
||||
@@ -62,7 +62,7 @@ export class DefaultGasProvider implements IEthereumGasProvider<TransactionReque
|
||||
const maxPriorityFeePerGas = this.calculateMaxPriorityFee(feeHistory.reward);
|
||||
|
||||
if (maxPriorityFeePerGas > this.config.maxFeePerGasCap) {
|
||||
throw new FeeEstimationError(
|
||||
throw makeBaseError(
|
||||
`Estimated miner tip of ${maxPriorityFeePerGas} exceeds configured max fee per gas of ${this.config.maxFeePerGasCap}!`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<TransactionRequest> {
|
||||
private defaultGasProvider: DefaultGasProvider;
|
||||
@@ -50,7 +50,7 @@ export class GasProvider implements IGasProvider<TransactionRequest> {
|
||||
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.",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<LineaGasFees> {
|
||||
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<ContractTransactionResponse> {
|
||||
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<TransactionResponse> {
|
||||
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;
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -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<T = object> = new (...args: any[]) => T;
|
||||
@@ -21,7 +21,7 @@ function LineaProviderMixIn<TBase extends Constructor<ethers.Provider>>(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 };
|
||||
|
||||
@@ -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<T = object> = new (...args: any[]) => T;
|
||||
@@ -22,7 +22,7 @@ export function ProviderMixIn<TBase extends Constructor<ethers.Provider>>(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 };
|
||||
|
||||
@@ -1,11 +1,35 @@
|
||||
export class BaseError extends Error {
|
||||
reason?: BaseError | Error | string;
|
||||
export type InferErrorType<T> = 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<T = unknown, M = unknown> extends Error {
|
||||
stack: string = "";
|
||||
metadata?: M;
|
||||
error: InferErrorType<T>;
|
||||
|
||||
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<T>;
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Message } from "../types";
|
||||
import { BaseError } from "./BaseError";
|
||||
|
||||
export class FeeEstimationError extends BaseError {
|
||||
override name = FeeEstimationError.name;
|
||||
}
|
||||
|
||||
export class GasEstimationError<T extends Message> extends BaseError {
|
||||
override name = GasEstimationError.name;
|
||||
public rejectedMessage?: T;
|
||||
|
||||
constructor(message: string, rejectedMessage?: T) {
|
||||
super(message);
|
||||
this.rejectedMessage = rejectedMessage;
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,2 +1,2 @@
|
||||
export { BaseError } from "./BaseError";
|
||||
export { GasEstimationError, FeeEstimationError } from "./GasFeeErrors";
|
||||
export { makeBaseError, isBaseError } from "./utils";
|
||||
|
||||
35
sdk/src/core/errors/utils.ts
Normal file
35
sdk/src/core/errors/utils.ts
Normal file
@@ -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 = <E, M>(value: E extends BaseError<E, M> ? never : E, metadata?: M): BaseError<E, M> => {
|
||||
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<E, M>;
|
||||
} catch {
|
||||
return new BaseError(new Error(`Unexpected value thrown: non-stringifiable object`), metadata) as BaseError<E, M>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = <E, M>(value: unknown): value is BaseError<InferErrorType<E>, M> => {
|
||||
return value instanceof BaseError && typeof value.stack === "string" && "metadata" in value && "error" in value;
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user