diff --git a/packages/utils-evm/src/errors/parser.ts b/packages/utils-evm/src/errors/parser.ts index 8e205dff3..956bc9821 100644 --- a/packages/utils-evm/src/errors/parser.ts +++ b/packages/utils-evm/src/errors/parser.ts @@ -3,6 +3,8 @@ import type { OmniContract, OmniContractFactory } from '@/omnigraph/types' import type { OmniError } from '@layerzerolabs/utils' import { ContractError, CustomError, UnknownError, PanicError, RevertError } from './errors' import { BigNumberishBigintSchema } from '../schema' +import type { Contract } from '@ethersproject/contracts' +import type { OmniContractErrorParser } from './types' /** * Creates an asynchronous error parser for EVM contract errors. @@ -15,32 +17,58 @@ import { BigNumberishBigintSchema } from '../schema' */ export const createErrorParser = (contractFactory: OmniContractFactory) => - async ({ error, point }: OmniError): Promise> => { - try { - // If the error already is a ContractError, we'll continue - if (error instanceof ContractError) return { error, point } - - // If the error is unknown we'll try to decode basic errors - const candidates = getErrorDataCandidates(error) - const [basicError] = candidates.flatMap(basicDecoder) - if (basicError != null) return { point, error: basicError } - - // Then we'll try to decode custom errors - const contract = await contractFactory(point) - const contractDecoder = createContractDecoder(contract) - const [customError] = candidates.flatMap(contractDecoder) - if (customError != null) return { point, error: customError } - - // If none of the decoding works, we'll send a generic error back - return { point, error: new UnknownError(`Unknown error: ${toStringSafe(error)}`) } - } catch { - // If we fail, we send an unknown error back - return { - point, - error: new UnknownError(`Unexpected error: ${toStringSafe(error)}`), - } - } + async ({ error, point }: OmniError): Promise> => ({ + point, + error: createContractErrorParser(await contractFactory(point))(error), + }) + +/** + * Creates an error parser based on a specific `OmniContract` + * + * This call will never fail and will always return an instance of `ContractError` + * + * @param {OmniContract | null | undefined} contract + * @returns {ContractError} + */ +export const createContractErrorParser = + (contract: OmniContract | null | undefined): OmniContractErrorParser => + (error) => + // First we'll try to decode a contract error if we have a contract + (contract ? parseContractError(error, contract.contract) : null) ?? + // Then we'll try decoding a generic one + parseGenericError(error) ?? + // The we throw a generic one + new UnknownError(`Unknown error: ${toStringSafe(error)}`) + +export const parseContractError = (error: unknown, contract: Contract): ContractError | undefined => { + // If the error already is a ContractError, we'll continue + if (error instanceof ContractError) return error + + try { + // If the error is unknown we'll try to decode basic errors + const candidates = getErrorDataCandidates(error) + + const contractDecoder = createContractDecoder(contract) + return candidates.flatMap(contractDecoder).at(0) + } catch { + return undefined + } +} + +export const parseGenericError = (error: unknown): ContractError | undefined => { + // If the error already is a ContractError, we'll continue + if (error instanceof ContractError) return error + + try { + // If the error is unknown we'll try to decode basic errors + const candidates = getErrorDataCandidates(error) + + // And return the first candidate + return candidates.flatMap(basicDecoder).at(0) + } catch { + return undefined } +} // If a contract reverts using revert, the error data will be prefixed with this beauty const REVERT_ERROR_PREFIX = '0x08c379a0' @@ -91,12 +119,12 @@ const basicDecoder = (data: string): ContractError[] => { /** * Contract decoder uses the contract ABIs to decode error revert string * - * @param contract `OmniContract` + * @param contract `Contract` * * @returns `(data: string) => ContractError[]` Custom error decoder */ const createContractDecoder = - ({ contract }: OmniContract) => + (contract: Contract) => (data: string): ContractError[] => { try { const errorDescription = contract.interface.parseError(data) diff --git a/packages/utils-evm/src/errors/types.ts b/packages/utils-evm/src/errors/types.ts new file mode 100644 index 000000000..0c661e2d3 --- /dev/null +++ b/packages/utils-evm/src/errors/types.ts @@ -0,0 +1,3 @@ +import type { ContractError } from './errors' + +export type OmniContractErrorParser = (error: unknown) => ContractError diff --git a/packages/utils-evm/src/omnigraph/sdk.ts b/packages/utils-evm/src/omnigraph/sdk.ts index 6c669f099..047f50316 100644 --- a/packages/utils-evm/src/omnigraph/sdk.ts +++ b/packages/utils-evm/src/omnigraph/sdk.ts @@ -1,13 +1,19 @@ import { OmniPoint, OmniTransaction } from '@layerzerolabs/utils' import { IOmniSDK, OmniContract } from './types' import { omniContractToPoint } from './coordinates' +import { createContractErrorParser } from '@/errors/parser' +import { OmniContractErrorParser } from '@/errors/types' +import { ContractError } from '..' /** * Base class for all EVM SDKs, providing some common functionality * to reduce the boilerplate */ export abstract class OmniSDK implements IOmniSDK { - constructor(public readonly contract: OmniContract) {} + constructor( + public readonly contract: OmniContract, + protected readonly errorParser: OmniContractErrorParser = createContractErrorParser(contract) + ) {} get point(): OmniPoint { return omniContractToPoint(this.contract) @@ -19,4 +25,8 @@ export abstract class OmniSDK implements IOmniSDK { data, } } + + protected parseError(error: unknown): ContractError { + return this.errorParser(error) + } }