Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve handling of errors #191

Merged
merged 31 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3c4a3d7
chore: first pass at a rework ofthe backend errors code
DNR500 Jun 7, 2024
a948e7e
fix: utils index file with correct import
DNR500 Jun 10, 2024
2feaba6
test: adjust tests to new implementation
DNR500 Jun 11, 2024
e1aec31
chore: allow disabling of creation lifi error codes and error types
DNR500 Jun 11, 2024
25ff544
chore: clean up unused code
DNR500 Jun 11, 2024
0639c45
chore: update after discussion
DNR500 Jun 14, 2024
ece51c8
feat: start using the cause property on errors
DNR500 Jun 24, 2024
51ffd71
feat: add top level error wrapper to api code
DNR500 Jun 24, 2024
54559bd
refactor: restructure files
DNR500 Jul 1, 2024
17078c8
test: tests addedd around errors and util root cause functions
DNR500 Jul 1, 2024
0773230
feat: improved root cause and stack handling and more tests
DNR500 Jul 1, 2024
8ce4e95
chore: updates of parse errors tests
DNR500 Jul 11, 2024
e583f1c
chore: remove unsupported error
DNR500 Jul 11, 2024
2079bf3
chore: simplified parse evm step errors function - ethers message rem…
DNR500 Jul 11, 2024
49b4294
chore: updated yarn lock
DNR500 Jul 11, 2024
f0acf97
chore: temp comment out test
DNR500 Jul 11, 2024
097bca4
chore: revert to error classes
DNR500 Jul 11, 2024
ae7e759
test: fix test
DNR500 Jul 12, 2024
3d706c0
chore: clena up
DNR500 Jul 12, 2024
10b1218
chore: restructure and move a few things round
DNR500 Jul 12, 2024
939252a
test: update the tests to use msw
DNR500 Jul 17, 2024
be90901
chore: pass as much through the parse evm step errors function as pos…
DNR500 Jul 17, 2024
3a54acf
Merge branch 'main' into handling-backend-errors
DNR500 Jul 17, 2024
4d75a00
chore: change comment wording
DNR500 Jul 17, 2024
b809522
chore: remove lifi from the name of util functions
DNR500 Jul 17, 2024
1c68832
chore: remove lifi from sdk error references in tests
DNR500 Jul 17, 2024
687a974
chore: remove commented out imports
DNR500 Jul 17, 2024
e65cb18
feat: add signature required error for solana
DNR500 Jul 17, 2024
150daf4
feat: add error response for out of gas in evm provider
DNR500 Jul 17, 2024
effdb4c
feat: improve resilence around tenderly call
DNR500 Jul 18, 2024
3bf64d2
chore: changes following code review
DNR500 Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/core/EVM/EVMStepExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ import { publicActions } from 'viem'
import { config } from '../../config.js'
import { getStepTransaction } from '../../services/api.js'
import {
LiFiErrorCode,
TransactionError,
ValidationError,
getTransactionFailedMessage,
isZeroAddress,
parseError,
} from '../../utils/index.js'
import { ValidationError, TransactionError } from '../../errors/errors.js'
import { LiFiErrorCode } from '../../errors/constants.js'
import { parseEVMErrors } from './parseEVMErrors.js'
import { BaseStepExecutor } from '../BaseStepExecutor.js'
import { checkBalance } from '../checkBalance.js'
import { getSubstatusMessage } from '../processMessages.js'
Expand Down Expand Up @@ -87,9 +86,13 @@ export class EVMStepExecutor extends BaseStepExecutor {
},
})
this.statusManager.updateExecution(step, 'FAILED')
throw new TransactionError(
LiFiErrorCode.WalletChangedDuringExecution,
errorMessage
throw await parseEVMErrors(
new TransactionError(
LiFiErrorCode.WalletChangedDuringExecution,
errorMessage
),
step,
process
)
}
return updatedWalletClient
Expand Down Expand Up @@ -397,20 +400,21 @@ export class EVMStepExecutor extends BaseStepExecutor {
process = this.statusManager.updateProcess(step, process.type, 'DONE')
}
} catch (e: any) {
const error = await parseError(e, step, process)
const error = await parseEVMErrors(e, step, process)
process = this.statusManager.updateProcess(
step,
process.type,
'FAILED',
{
error: {
message: error.message,
htmlMessage: error.htmlMessage,
message: error.cause.message,
htmlMessage: error.cause.htmlMessage,
code: error.code,
},
}
)
this.statusManager.updateExecution(step, 'FAILED')

throw error
}
}
Expand Down Expand Up @@ -479,8 +483,7 @@ export class EVMStepExecutor extends BaseStepExecutor {
},
})
this.statusManager.updateExecution(step, 'FAILED')
console.warn(e)
throw e
throw await parseEVMErrors(e as Error, step, process)
}

// DONE
Expand Down
8 changes: 4 additions & 4 deletions src/core/EVM/checkAllowance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Chain, LiFiStep, Process, ProcessType } from '@lifi/types'
import type { Address, Hash, WalletClient } from 'viem'
import { maxUint256 } from 'viem'
import { parseError } from '../../utils/parseError.js'
import { parseEVMErrors } from './parseEVMErrors.js'
import type { StatusManager } from '../StatusManager.js'
import type { ExecutionOptions } from '../types.js'
import { getAllowance } from './getAllowance.js'
Expand Down Expand Up @@ -100,15 +100,15 @@ export const checkAllowance = async (
}
}
} catch (e: any) {
const error = await parseError(e, step, allowanceProcess)
const error = await parseEVMErrors(e, step, allowanceProcess)
allowanceProcess = statusManager.updateProcess(
step,
allowanceProcess.type,
'FAILED',
{
error: {
message: error.message,
htmlMessage: error.htmlMessage,
message: error.cause.message,
htmlMessage: error.cause.htmlMessage,
code: error.code,
},
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/EVM/multisig.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ExtendedChain, LiFiStep, ProcessType } from '@lifi/types'
import type { Hash } from 'viem'
import { LiFiErrorCode, TransactionError } from '../../utils/errors.js'
import { LiFiErrorCode } from '../../errors/constants.js'
import { TransactionError } from '../../errors/errors.js'
import type { StatusManager } from '../StatusManager.js'
import type { MultisigConfig, MultisigTxDetails } from './types.js'

Expand Down
66 changes: 66 additions & 0 deletions src/core/EVM/parseEVMErrors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { type LiFiStep, type Process } from '@lifi/types'
import { TransactionError, UnknownError } from '../../errors/errors.js'
import { SDKError } from '../../errors/SDKError.js'
import { ErrorMessage, LiFiErrorCode } from '../../errors/constants.js'
import { BaseError } from '../../utils/index.js'
import { fetchTxErrorDetails } from '../../helpers.js'

export const parseEVMErrors = async (
e: Error,
step?: LiFiStep,
process?: Process
): Promise<SDKError> => {
if (e instanceof SDKError) {
e.step = e.step ?? step
e.process = e.process ?? process
return e
}

const baseError = await handleSpecificErrors(e, step, process)

return new SDKError(baseError, step, process)
}

const handleSpecificErrors = async (
e: any,
step?: LiFiStep,
process?: Process
) => {
if (e.cause?.name === 'UserRejectedRequestError') {
return new TransactionError(
LiFiErrorCode.SignatureRejected,
e.message,
undefined,
e
)
}

if (
step &&
process?.txHash &&
e.code === LiFiErrorCode.TransactionFailed &&
e.message === ErrorMessage.TransactionReverted
) {
const response = await fetchTxErrorDetails(
process.txHash,
step.action.fromChainId
)

const errorMessage = response?.error_message

if (errorMessage?.toLowerCase().includes('out of gas')) {
return new TransactionError(
LiFiErrorCode.GasLimitError,
ErrorMessage.GasLimitLow,
undefined,
e
)
}
}

if (e instanceof BaseError) {
return e
}

return new UnknownError(e.message || ErrorMessage.UnknownError, undefined, e)
}
215 changes: 215 additions & 0 deletions src/core/EVM/parseEVMErrors.unit.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { beforeAll, describe, expect, it, vi } from 'vitest'
import { setupTestEnvironment } from '../../../tests/setup.js'
import { parseEVMErrors } from './parseEVMErrors.js'
import {
ErrorName,
BaseError,
LiFiErrorCode,
SDKError,
TransactionError,
ErrorMessage,
} from '../../utils/index.js'
import { buildStepObject } from '../../../tests/fixtures.js'
import type { LiFiStep, Process } from '@lifi/types'
import * as helpers from '../../helpers.js'

beforeAll(setupTestEnvironment)

describe('parseEVMStepErrors', () => {
describe('when a SDKError is passed', async () => {
it('should return the original error', async () => {
const error = new SDKError(
new BaseError(
ErrorName.UnknownError,
LiFiErrorCode.InternalError,
'there was an error'
)
)

const parsedError = await parseEVMErrors(error)

expect(parsedError).toBe(error)

expect(parsedError.step).toBeUndefined()
expect(parsedError.process).toBeUndefined()
})
})

describe('when step and process is passed', () => {
it('should return the original error with step and process added', async () => {
const error = new SDKError(
new BaseError(
ErrorName.UnknownError,
LiFiErrorCode.InternalError,
'there was an error'
)
)

const step = buildStepObject({ includingExecution: true })
const process = step.execution!.process[0]

const parsedError = await parseEVMErrors(error, step, process)

expect(parsedError).toBe(error)

expect(parsedError.step).toBe(step)
expect(parsedError.process).toBe(process)
})
})

describe('when the SDKError already has a step and process', () => {
it('should return the original error with teh existing step and process specified', async () => {
const expectedStep = buildStepObject({ includingExecution: true })
const expectedProcess = expectedStep.execution!.process[0]

const error = new SDKError(
new BaseError(
ErrorName.UnknownError,
LiFiErrorCode.InternalError,
'there was an error'
),
expectedStep,
expectedProcess
)

const step = buildStepObject({ includingExecution: true })
const process = step.execution!.process[0]

const parsedError = await parseEVMErrors(error, step, process)

expect(parsedError).toBe(error)

expect(parsedError.step).toBe(expectedStep)
expect(parsedError.process).toBe(expectedProcess)
})
})

describe('when a BaseError is passed', () => {
it('should return the BaseError as the cause on a SDKError', async () => {
const error = new BaseError(
ErrorName.BalanceError,
LiFiErrorCode.BalanceError,
'there was an error'
)

const parsedError = await parseEVMErrors(error)

expect(parsedError).toBeInstanceOf(SDKError)
expect(parsedError.step).toBeUndefined()
expect(parsedError.process).toBeUndefined()
expect(parsedError.cause).toBe(error)
})

describe('when step and process is passed', () => {
it('should return the SDKError with step and process added', async () => {
const error = new BaseError(
ErrorName.BalanceError,
LiFiErrorCode.BalanceError,
'there was an error'
)

const step = buildStepObject({ includingExecution: true })
const process = step.execution!.process[0]

const parsedError = await parseEVMErrors(error, step, process)

expect(parsedError).toBeInstanceOf(SDKError)
expect(parsedError.step).toBe(step)
expect(parsedError.process).toBe(process)
expect(parsedError.cause).toBe(error)
})
})
})

describe('when a generic Error is passed', () => {
it('should return the Error as he cause on a BaseError which is wrapped in an SDKError', async () => {
const error = new Error('Somethings fishy')

const parsedError = await parseEVMErrors(error)
expect(parsedError).toBeInstanceOf(SDKError)
expect(parsedError.step).toBeUndefined()
expect(parsedError.process).toBeUndefined()

const baseError = parsedError.cause
expect(baseError).toBeInstanceOf(BaseError)

const causeError = baseError.cause
expect(causeError).toBe(error)
})

describe('when step and process is passed', () => {
it('should return an SDKError with step and process added', async () => {
const error = new Error('Somethings fishy')

const step = buildStepObject({ includingExecution: true })
const process = step.execution?.process[0]

const parsedError = await parseEVMErrors(error, step, process)
expect(parsedError).toBeInstanceOf(SDKError)
expect(parsedError.step).toBe(step)
expect(parsedError.process).toBe(process)
})
})
})

describe('when specific Errors are passed', () => {
describe('when the error is the viem UserRejectedRequestError error', () => {
it('should return the BaseError with the SignatureRejected code as the cause on a SDKError', async () => {
const mockViemError = new Error()
const UserRejectedRequestError = new Error()
UserRejectedRequestError.name = 'UserRejectedRequestError'
mockViemError.cause = UserRejectedRequestError

const parsedError = await parseEVMErrors(mockViemError)

expect(parsedError).toBeInstanceOf(SDKError)

const baseError = parsedError.cause
expect(baseError).toBeInstanceOf(TransactionError)
expect(baseError.code).toEqual(LiFiErrorCode.SignatureRejected)

expect(baseError.cause?.cause).toBe(UserRejectedRequestError)
})
})
})

describe('when the error is a Transaction reverted error caused by low gas', () => {
it('should return the TransactionError with the GasLimitError code and GasLimitLow message', async () => {
vi.spyOn(helpers, 'fetchTxErrorDetails').mockResolvedValue({
error_message: 'out of gas',
})

const mockTransactionError = new TransactionError(
LiFiErrorCode.TransactionFailed,
ErrorMessage.TransactionReverted
)

const mockStep = {
action: {
fromChainId: 10,
},
} as LiFiStep

const mockProcess = {
txHash:
'0x5c73f72a72a75d8b716ed42cd620042f53b958f028d0c9ad772908b7791c017b',
} as Process

const parsedError = await parseEVMErrors(
mockTransactionError,
mockStep,
mockProcess
)

expect(parsedError).toBeInstanceOf(SDKError)

const baseError = parsedError.cause
expect(baseError).toBeInstanceOf(TransactionError)
expect(baseError.code).toEqual(LiFiErrorCode.GasLimitError)
expect(baseError.message).toEqual(ErrorMessage.GasLimitLow)
expect(baseError.cause).toBe(mockTransactionError)

vi.clearAllMocks()
})
})
})
3 changes: 2 additions & 1 deletion src/core/EVM/switchChain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { WalletClient } from 'viem'
import { LiFiErrorCode, ProviderError } from '../../utils/errors.js'
import { LiFiErrorCode } from '../../errors/constants.js'
import { ProviderError } from '../../errors/errors.js'
import type { StatusManager } from '../StatusManager.js'
import type { LiFiStepExtended, SwitchChainHook } from '../types.js'

Expand Down
Loading
Loading