diff --git a/.changeset/popular-hairs-report.md b/.changeset/popular-hairs-report.md new file mode 100644 index 00000000000..4bc0455d398 --- /dev/null +++ b/.changeset/popular-hairs-report.md @@ -0,0 +1,8 @@ +--- +"@fuel-ts/account": minor +"fuels": minor +"@fuel-ts/program": minor +"@fuel-ts/transactions": minor +--- + +chore!: enhance TX error handling and message formatting diff --git a/apps/docs-snippets/src/guide/errors/debugging-revert-errors.test.ts b/apps/docs-snippets/src/guide/errors/debugging-revert-errors.test.ts index 89448d7087c..4b281b208c6 100644 --- a/apps/docs-snippets/src/guide/errors/debugging-revert-errors.test.ts +++ b/apps/docs-snippets/src/guide/errors/debugging-revert-errors.test.ts @@ -1,56 +1,37 @@ -import { generateTestWallet } from '@fuel-ts/account/test-utils'; -import { BaseAssetId, FUEL_NETWORK_URL, Provider, Script } from 'fuels'; - -import { - DocSnippetProjectsEnum, - getDocsSnippetsForcProject, -} from '../../../test/fixtures/forc-projects'; +import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects'; import { createAndDeployContractFromProject } from '../../utils'; /** * @group node */ -test('logs out custom require messages for error enums when tx reverts', async () => { - const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); - - // #region revert-errors-4 - expect(() => contract.functions.test_function_with_custom_error().call()).rejects.toThrow( - 'The script reverted with reason RequireFailed. (Reason: "InvalidInput")' - ); - // #endregion revert-errors-4 -}); - -test('logs out custom require messages for require statements using str array when tx reverts', async () => { - const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); - - // #region revert-errors-7 - expect(() => contract.functions.test_function_with_str_array_message().call()).rejects.toThrow( - 'The script reverted with reason RequireFailed. (Reason: "This is also a revert error")' - ); - // #endregion revert-errors-7 -}); - -test('logs out a generic error message for require statements with a simple string message', async () => { - const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); - - // #region revert-errors-5 - expect(() => contract.functions.test_function().call()).rejects.toThrow( - 'String slices can not be decoded from logs. Convert the slice to `str[N]` with `__to_str_array`' - ); - // #endregion revert-errors-5 -}); - -test('logs out custom require messages for script calls', async () => { - const { binHexlified, abiContents } = getDocsSnippetsForcProject( - DocSnippetProjectsEnum.REVERT_ERRORS_SCRIPT - ); - - const provider = await Provider.create(FUEL_NETWORK_URL); - const wallet = await generateTestWallet(provider, [[1_000_000, BaseAssetId]]); - - const script = new Script(binHexlified, abiContents, wallet); - - expect(() => script.functions.main().call()).rejects.toThrow( - 'The script reverted with reason RequireFailed. (Reason: "This is a revert error")' - ); +describe(__filename, () => { + it('logs out custom require messages for error enums when tx reverts', async () => { + const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); + + // #region revert-errors-4 + expect(() => contract.functions.test_function_with_custom_error().call()).rejects.toThrow( + 'The transaction reverted because a "require" statement has thrown "InvalidInput".' + ); + // #endregion revert-errors-4 + }); + + it('logs out custom require messages for require statements using str array when tx reverts', async () => { + const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); + + // #region revert-errors-7 + expect(() => contract.functions.test_function_with_str_array_message().call()).rejects.toThrow( + 'The transaction reverted because a "require" statement has thrown "This is also a revert error".' + ); + // #endregion revert-errors-7 + }); + + it('logs out a generic error message for require statements with a simple string message', async () => { + const contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.REVERT_ERRORS); + + // #region revert-errors-5 + expect(() => contract.functions.test_function().call()).rejects.toThrow( + 'String slices can not be decoded from logs. Convert the slice to `str[N]` with `__to_str_array`' + ); + // #endregion revert-errors-5 + }); }); diff --git a/internal/check-imports/src/references.ts b/internal/check-imports/src/references.ts index ea2f8bff7ed..a685a02c219 100644 --- a/internal/check-imports/src/references.ts +++ b/internal/check-imports/src/references.ts @@ -24,9 +24,9 @@ import { BN } from '@fuel-ts/math'; import { DEFAULT_PRECISION, DEFAULT_MIN_PRECISION } from '@fuel-ts/math/configs'; import { SparseMerkleTree, constructTree } from '@fuel-ts/merkle'; import { FunctionInvocationScope } from '@fuel-ts/program'; -import { PANIC_REASONS } from '@fuel-ts/program/configs'; import { Script } from '@fuel-ts/script'; import { InputCoinCoder } from '@fuel-ts/transactions'; +import { PANIC_REASONS } from '@fuel-ts/transactions/configs'; import { versions } from '@fuel-ts/versions'; import { runVersions } from '@fuel-ts/versions/cli'; // TODO: Add `launchNode` and `launchNodeAndGetWallets` here diff --git a/packages/account/src/providers/transaction-response/transaction-response.ts b/packages/account/src/providers/transaction-response/transaction-response.ts index 074efb363cb..21e0f17a54d 100644 --- a/packages/account/src/providers/transaction-response/transaction-response.ts +++ b/packages/account/src/providers/transaction-response/transaction-response.ts @@ -24,12 +24,8 @@ import type Provider from '../provider'; import type { JsonAbisFromAllCalls } from '../transaction-request'; import { assembleTransactionSummary } from '../transaction-summary/assemble-transaction-summary'; import { processGqlReceipt } from '../transaction-summary/receipt'; -import type { - TransactionSummary, - FailureStatus, - GqlTransaction, - AbiMap, -} from '../transaction-summary/types'; +import type { TransactionSummary, GqlTransaction, AbiMap } from '../transaction-summary/types'; +import { extractTxError } from '../utils'; import { getDecodedLogs } from './getDecodedLogs'; @@ -250,8 +246,10 @@ export class TransactionResponse { ...transactionSummary, }; + let logs: Array = []; + if (this.abis) { - const logs = getDecodedLogs( + logs = getDecodedLogs( transactionSummary.receipts, this.abis.main, this.abis.otherContractsAbis @@ -260,6 +258,19 @@ export class TransactionResponse { transactionResult.logs = logs; } + if (transactionResult.isStatusFailure) { + const { + receipts, + gqlTransaction: { status }, + } = transactionResult; + + throw extractTxError({ + receipts, + status, + logs, + }); + } + return transactionResult; } @@ -271,15 +282,6 @@ export class TransactionResponse { async wait( contractsAbiMap?: AbiMap ): Promise> { - const result = await this.waitForResult(contractsAbiMap); - - if (result.isStatusFailure) { - throw new FuelError( - ErrorCode.TRANSACTION_FAILED, - `Transaction failed: ${(result.gqlTransaction.status).reason}` - ); - } - - return result; + return this.waitForResult(contractsAbiMap); } } diff --git a/packages/account/src/providers/utils/extract-tx-error.ts b/packages/account/src/providers/utils/extract-tx-error.ts new file mode 100644 index 00000000000..4f9d2422d47 --- /dev/null +++ b/packages/account/src/providers/utils/extract-tx-error.ts @@ -0,0 +1,133 @@ +import { ErrorCode, FuelError } from '@fuel-ts/errors'; +import { bn } from '@fuel-ts/math'; +import type { ReceiptRevert } from '@fuel-ts/transactions'; +import { ReceiptType } from '@fuel-ts/transactions'; +import { + FAILED_REQUIRE_SIGNAL, + FAILED_ASSERT_EQ_SIGNAL, + FAILED_ASSERT_NE_SIGNAL, + FAILED_ASSERT_SIGNAL, + FAILED_TRANSFER_TO_ADDRESS_SIGNAL, + PANIC_REASONS, + PANIC_DOC_URL, +} from '@fuel-ts/transactions/configs'; + +import type { GqlTransactionStatusFragmentFragment } from '../__generated__/operations'; +import type { TransactionResultReceipt } from '../transaction-response'; +import type { FailureStatus } from '../transaction-summary'; + +/** + * Assembles an error message for a panic status. + * @param status - The transaction failure status. + * @returns The error message. + */ +export const assemblePanicError = (status: FailureStatus) => { + let errorMessage = `The transaction reverted with reason: "${status.reason}".`; + const reason = status.reason; + + if (PANIC_REASONS.includes(status.reason)) { + errorMessage = `${errorMessage}\n\nYou can read more about this error at:\n\n${PANIC_DOC_URL}#variant.${status.reason}`; + } + + return { errorMessage, reason }; +}; + +/** @hidden */ +const stringify = (obj: unknown) => JSON.stringify(obj, null, 2); + +/** + * Assembles an error message for a revert status. + * @param receipts - The transaction result processed receipts. + * @param logs - The transaction decoded logs. + * @returns The error message. + */ +export const assembleRevertError = ( + receipts: Array, + logs: Array +) => { + let errorMessage = 'The transaction reverted with an unknown reason.'; + + const revertReceipt = receipts.find(({ type }) => type === ReceiptType.Revert) as ReceiptRevert; + let reason = ''; + + if (revertReceipt) { + const reasonHex = bn(revertReceipt.val).toHex(); + + switch (reasonHex) { + case FAILED_REQUIRE_SIGNAL: { + reason = 'require'; + errorMessage = `The transaction reverted because a "require" statement has thrown ${ + logs.length ? stringify(logs[0]) : 'an error.' + }.`; + break; + } + + case FAILED_ASSERT_EQ_SIGNAL: { + const sufix = + logs.length >= 2 ? ` comparing ${stringify(logs[1])} and ${stringify(logs[0])}.` : '.'; + + reason = 'assert_eq'; + errorMessage = `The transaction reverted because of an "assert_eq" statement${sufix}`; + break; + } + + case FAILED_ASSERT_NE_SIGNAL: { + const sufix = + logs.length >= 2 ? ` comparing ${stringify(logs[1])} and ${stringify(logs[0])}.` : '.'; + + reason = 'assert_ne'; + errorMessage = `The transaction reverted because of an "assert_ne" statement${sufix}`; + break; + } + + case FAILED_ASSERT_SIGNAL: + reason = 'assert'; + errorMessage = `The transaction reverted because an "assert" statement failed to evaluate to true.`; + break; + + case FAILED_TRANSFER_TO_ADDRESS_SIGNAL: + reason = 'MissingOutputChange'; + errorMessage = `The transaction reverted because it's missing an "OutputChange".`; + break; + + default: + reason = 'unknown'; + errorMessage = `The transaction reverted with an unknown reason: ${revertReceipt.val}`; + } + } + + return { errorMessage, reason }; +}; + +interface IExtractTxError { + receipts: Array; + status?: GqlTransactionStatusFragmentFragment | null; + logs: Array; +} + +/** + * Extracts the transaction error and returns a FuelError object. + * @param IExtractTxError - The parameters for extracting the error. + * @returns The FuelError object. + */ +export const extractTxError = (params: IExtractTxError): FuelError => { + const { receipts, status, logs } = params; + + const isPanic = receipts.some(({ type }) => type === ReceiptType.Panic); + const isRevert = receipts.some(({ type }) => type === ReceiptType.Revert); + + const { errorMessage, reason } = + status?.type === 'FailureStatus' && isPanic + ? assemblePanicError(status) + : assembleRevertError(receipts, logs); + + const metadata = { + logs, + receipts, + panic: isPanic, + revert: isRevert, + reason, + }; + + return new FuelError(ErrorCode.SCRIPT_REVERTED, errorMessage, metadata); +}; diff --git a/packages/account/src/providers/utils/index.ts b/packages/account/src/providers/utils/index.ts index 4fd18fa5c09..e4bc16151f5 100644 --- a/packages/account/src/providers/utils/index.ts +++ b/packages/account/src/providers/utils/index.ts @@ -3,3 +3,4 @@ export * from './block-explorer'; export * from './gas'; export * from './json'; export * from './sleep'; +export * from './extract-tx-error'; diff --git a/packages/errors/src/fuel-error.test.ts b/packages/errors/src/fuel-error.test.ts index d919f531449..7aed8a047a6 100644 --- a/packages/errors/src/fuel-error.test.ts +++ b/packages/errors/src/fuel-error.test.ts @@ -49,6 +49,7 @@ it('converts error to plain object', () => { const code = FuelError.CODES.PARSE_FAILED; const name = 'FuelError'; const message = 'It happens'; - const err = new FuelError(code, message); - expect(err.toObject()).toEqual({ code, name, message, VERSIONS: err.VERSIONS }); + const metadata = { name: 'FuelLabs' }; + const err = new FuelError(code, message, metadata); + expect(err.toObject()).toEqual({ code, name, message, VERSIONS: err.VERSIONS, metadata }); }); diff --git a/packages/errors/src/fuel-error.ts b/packages/errors/src/fuel-error.ts index f5a4f9b5435..1d8dec81f2b 100644 --- a/packages/errors/src/fuel-error.ts +++ b/packages/errors/src/fuel-error.ts @@ -5,6 +5,7 @@ import { ErrorCode } from './error-codes'; export class FuelError extends Error { static readonly CODES = ErrorCode; readonly VERSIONS = versions; + readonly metadata: Record; static parse(e: unknown) { const error = e as FuelError; @@ -31,14 +32,15 @@ export class FuelError extends Error { code: ErrorCode; - constructor(code: ErrorCode, message: string) { + constructor(code: ErrorCode, message: string, metadata: Record = {}) { super(message); this.code = code; this.name = 'FuelError'; + this.metadata = metadata; } toObject() { - const { code, name, message, VERSIONS } = this; - return { code, name, message, VERSIONS }; + const { code, name, message, metadata, VERSIONS } = this; + return { code, name, message, metadata, VERSIONS }; } } diff --git a/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts b/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts index 36ab0187cba..3dd95cb0107 100644 --- a/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts +++ b/packages/errors/src/test-utils/expect-to-throw-fuel-error.ts @@ -37,6 +37,10 @@ export const expectToThrowFuelError = async ( ); } + if (expectedError.metadata) { + expect(thrownError.metadata).toEqual(expect.objectContaining(expectedError.metadata)); + } + expect(thrownError.name).toEqual('FuelError'); expect(thrownError).toMatchObject(expectedError); }; diff --git a/packages/fuel-gauge/src/advanced-logging.test.ts b/packages/fuel-gauge/src/advanced-logging.test.ts index ce6b0262d65..e701c7cc061 100644 --- a/packages/fuel-gauge/src/advanced-logging.test.ts +++ b/packages/fuel-gauge/src/advanced-logging.test.ts @@ -1,6 +1,7 @@ import { generateTestWallet } from '@fuel-ts/account/test-utils'; +import type { FuelError } from '@fuel-ts/errors'; import type { BN, Contract, Provider, WalletUnlocked } from 'fuels'; -import { RequireRevertError, Script, ScriptResultDecoderError, bn } from 'fuels'; +import { Script, bn } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -89,24 +90,24 @@ describe('Advanced Logging', () => { throw new Error('it should have thrown'); } catch (error) { - if (error instanceof RequireRevertError && error.cause instanceof ScriptResultDecoderError) { - const logs = error.cause.logs; - logs[0].game_id = logs[0].game_id.toHex(); - expect(logs).toEqual([ - { - score: 0, - time_left: 100, - ammo: 10, - game_id: '0x18af8', - state: { Playing: 1 }, - contract_Id: { - value: '0xfffffffffffffffffffffffffffffffff00fffffffffffffffffffffffffffff', + if ((error).message) { + expect(JSON.stringify((error).metadata.logs)).toMatch( + JSON.stringify([ + { + score: 0, + time_left: 100, + ammo: 10, + game_id: bn(0x18af8), + state: { Playing: 1 }, + contract_Id: { + value: '0xfffffffffffffffffffffffffffffffff00fffffffffffffffffffffffffffff', + }, + difficulty: { Medium: true }, }, - difficulty: { Medium: true }, - }, - ]); + ]) + ); } else { - throw new Error('it should throw RequireRevertError'); + throw new Error('it should be possible to decode error from "require" statement'); } } }); diff --git a/packages/fuel-gauge/src/auth-testing.test.ts b/packages/fuel-gauge/src/auth-testing.test.ts index 3e27fb3acdb..df377527dae 100644 --- a/packages/fuel-gauge/src/auth-testing.test.ts +++ b/packages/fuel-gauge/src/auth-testing.test.ts @@ -1,13 +1,6 @@ import { generateTestWallet } from '@fuel-ts/account/test-utils'; import type { BN, Contract, WalletUnlocked } from 'fuels'; -import { - AssertFailedRevertError, - ContractFactory, - BaseAssetId, - Provider, - getRandomB256, - FUEL_NETWORK_URL, -} from 'fuels'; +import { ContractFactory, BaseAssetId, Provider, getRandomB256, FUEL_NETWORK_URL } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -49,6 +42,8 @@ describe('Auth Testing', () => { it('can check_msg_sender [with incorrect id]', async () => { await expect( contractInstance.functions.check_msg_sender({ value: getRandomB256() }).call() - ).rejects.toThrow(AssertFailedRevertError); + ).rejects.toThrow( + 'The transaction reverted because an "assert" statement failed to evaluate to true.' + ); }); }); diff --git a/packages/fuel-gauge/src/contract.test.ts b/packages/fuel-gauge/src/contract.test.ts index 38f4ec04b51..738a20f6d1e 100644 --- a/packages/fuel-gauge/src/contract.test.ts +++ b/packages/fuel-gauge/src/contract.test.ts @@ -8,8 +8,6 @@ import type { TransactionType, JsonAbi, ScriptTransactionRequest, - ReceiptScriptResult, - TransactionCost, } from 'fuels'; import { BN, @@ -28,7 +26,6 @@ import { BaseAssetId, FUEL_NETWORK_URL, Predicate, - ReceiptType, } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -1013,36 +1010,18 @@ describe('Contract', () => { expect(finalBalance).toBe(initialBalance + amountToContract); }); - it('should ensure ScriptResultDecoderError works for dryRun and simulate calls', async () => { + it('should ensure TX revert error can be extracted for dryRun and simulate calls', async () => { const contract = await setupContract({ cache: false }); - const invocationScope = contract.functions.return_context_amount().callParams({ - forward: [100, BaseAssetId], - }); + const scope = contract.functions.assert_u8(10, 11); - vi.spyOn(contract.provider, 'getTransactionCost').mockImplementationOnce(async () => - Promise.resolve({ receipts: [] } as unknown as TransactionCost) + await expect(scope.dryRun()).rejects.toThrowError( + `The transaction reverted because an "assert" statement failed to evaluate to true.` ); - await expect(invocationScope.dryRun()).rejects.toThrowError( - `The script call result does not contain a 'scriptResultReceipt'.` + await expect(scope.simulate()).rejects.toThrowError( + `The transaction reverted because an "assert" statement failed to evaluate to true.` ); - - const scriptResultReceipt: ReceiptScriptResult = { - type: ReceiptType.ScriptResult, - result: bn(1), - gasUsed: bn(2), - }; - - vi.spyOn(contract.provider, 'call').mockImplementation(async () => - Promise.resolve({ receipts: [scriptResultReceipt] }) - ); - - await expect(invocationScope.simulate()).rejects.toThrowError( - `The script call result does not contain a 'returnReceipt'.` - ); - - vi.restoreAllMocks(); }); it('should ensure assets can be transfered to wallets (SINGLE TRANSFER)', async () => { diff --git a/packages/fuel-gauge/src/revert-error.test.ts b/packages/fuel-gauge/src/revert-error.test.ts index 525f9555daa..4f4711a6e4c 100644 --- a/packages/fuel-gauge/src/revert-error.test.ts +++ b/packages/fuel-gauge/src/revert-error.test.ts @@ -1,17 +1,8 @@ import { generateTestWallet } from '@fuel-ts/account/test-utils'; -import type { BN, Contract, WalletUnlocked } from 'fuels'; -import { - ScriptResultDecoderError, - SendMessageRevertError, - RequireRevertError, - AssertFailedRevertError, - TransferToAddressRevertError, - bn, - ContractFactory, - Provider, - BaseAssetId, - FUEL_NETWORK_URL, -} from 'fuels'; +import { ErrorCode, FuelError } from '@fuel-ts/errors'; +import { expectToThrowFuelError } from '@fuel-ts/errors/test-utils'; +import type { BN, Contract, WalletUnlocked, TransactionResultReceipt } from 'fuels'; +import { bn, ContractFactory, Provider, BaseAssetId, FUEL_NETWORK_URL, getRandomB256 } from 'fuels'; import { FuelGaugeProjectsEnum, getFuelGaugeForcProject } from '../test/fixtures'; @@ -23,9 +14,10 @@ let wallet: WalletUnlocked; */ describe('Revert Error Testing', () => { let gasPrice: BN; + let provider: Provider; beforeAll(async () => { - const provider = await Provider.create(FUEL_NETWORK_URL); + provider = await Provider.create(FUEL_NETWORK_URL); wallet = await generateTestWallet(provider, [[1_000_000, BaseAssetId]]); const { binHexlified: bytecode, abiContents: FactoryAbi } = getFuelGaugeForcProject( @@ -37,7 +29,7 @@ describe('Revert Error Testing', () => { contractInstance = await factory.deployContract({ gasPrice }); }); - it('can pass required checks [valid]', async () => { + it('can pass require checks [valid]', async () => { const INPUT_PRICE = bn(10); const INPUT_TOKEN_ID = bn(100); @@ -55,67 +47,196 @@ describe('Revert Error Testing', () => { ]); }); - it('can throw RequireRevertError [invalid price]', async () => { + it('should throw for "require" revert TX [PriceCantBeZero]', async () => { const INPUT_PRICE = bn(0); const INPUT_TOKEN_ID = bn(100); - await expect( - contractInstance.functions - .validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE) - - .call() - ).rejects.toThrow(RequireRevertError); + await expectToThrowFuelError( + () => contractInstance.functions.validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE).call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted because a "require" statement has thrown "PriceCantBeZero".`, + { + logs: ['PriceCantBeZero'], + receipts: expect.any(Array), + reason: 'require', + panic: false, + revert: true, + } + ) + ); }); - it('can throw RequireRevertError [invalid token id]', async () => { + it('should throw for "require" revert TX [InvalidTokenId]', async () => { const INPUT_PRICE = bn(10); const INPUT_TOKEN_ID = bn(55); - await expect( - contractInstance.functions - .validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE) + await expectToThrowFuelError( + () => contractInstance.functions.validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE).call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted because a "require" statement has thrown "InvalidTokenId".`, + { + logs: ['InvalidTokenId'], + receipts: expect.any(Array), + reason: 'require', + panic: false, + revert: true, + } + ) + ); + }); - .call() - ).rejects.toThrow(RequireRevertError); + it('should throw for revert TX with reason "TransferZeroCoins"', async () => { + await expectToThrowFuelError( + () => contractInstance.functions.failed_transfer_revert().call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + 'The transaction reverted with reason: "TransferZeroCoins".\n\nYou can read more about this error at:\n\nhttps://docs.rs/fuel-asm/latest/fuel_asm/enum.PanicReason.html#variant.TransferZeroCoins', + { + logs: [], + receipts: expect.any(Array), + reason: 'TransferZeroCoins', + panic: true, + revert: false, + } + ) + ); }); - it('can throw AssertFailedRevertError', async () => { + it('should throw for "assert" revert TX', async () => { const INPUT_PRICE = bn(100); const INPUT_TOKEN_ID = bn(100); - await expect( - contractInstance.functions - .validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE) + await expectToThrowFuelError( + () => contractInstance.functions.validate_inputs(INPUT_TOKEN_ID, INPUT_PRICE).call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + 'The transaction reverted because an "assert" statement failed to evaluate to true.', + { + logs: [], + receipts: expect.any(Array), + panic: false, + revert: true, + reason: 'assert', + } + ) + ); + }); - .call() - ).rejects.toThrow(AssertFailedRevertError); + it('should throw for revert TX with reason "NotEnoughBalance"', async () => { + await expectToThrowFuelError( + () => contractInstance.functions.failed_transfer().call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + 'The transaction reverted with reason: "NotEnoughBalance".\n\nYou can read more about this error at:\n\nhttps://docs.rs/fuel-asm/latest/fuel_asm/enum.PanicReason.html#variant.NotEnoughBalance', + { + logs: [], + receipts: expect.any(Array), + panic: true, + revert: false, + reason: 'NotEnoughBalance', + } + ) + ); + }); + + it('should throw for "assert_eq" revert TX', async () => { + await expectToThrowFuelError( + () => contractInstance.functions.assert_value_eq_10(9).call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted because of an "assert_eq" statement comparing 10 and 9.`, + { + logs: [9, 10], + receipts: expect.any(Array), + panic: false, + revert: true, + reason: 'assert_eq', + } + ) + ); }); - /** - * TODO: fix this - * we could not get this sway function to revert - */ - it.skip('can throw SendMessageRevertError', async () => { - await expect(contractInstance.functions.failed_message().call()).rejects.toThrow( - SendMessageRevertError + it('should throw for "assert_ne" revert TX', async () => { + await expectToThrowFuelError( + () => contractInstance.functions.assert_value_ne_5(5).call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted because of an "assert_ne" statement comparing 5 and 5.`, + { + logs: [5, 5], + receipts: expect.any(Array), + panic: false, + revert: true, + reason: 'assert_ne', + } + ) ); }); - /** - * TODO: fix this - * we could not get this sway function to revert - * according to sway docs: this should revert if amount = 0 - * https://fuellabs.github.io/sway/master/reference/documentation/operations/asset/transfer/address.html - */ - it.skip('can throw TransferToAddressRevertError', async () => { - await expect(contractInstance.functions.failed_transfer_revert().call()).rejects.toThrow( - TransferToAddressRevertError + it('should throw for "assert_ne" revert TX', async () => { + const { binHexlified: tokenBytecode, abiContents: tokenAbi } = getFuelGaugeForcProject( + FuelGaugeProjectsEnum.TOKEN_CONTRACT + ); + + const factory = new ContractFactory(tokenBytecode, tokenAbi, wallet); + const tokenContract = await factory.deployContract(); + + const addresses = [ + { value: getRandomB256() }, + { value: getRandomB256() }, + { value: getRandomB256() }, + ]; + + const request = await tokenContract + .multiCall([ + tokenContract.functions.mint_coins(500), + tokenContract.functions.mint_to_addresses(addresses, 300), + ]) + .txParams({ gasPrice }) + .getTransactionRequest(); + + const { gasUsed, maxFee, requiredQuantities } = await provider.getTransactionCost(request); + + request.gasLimit = gasUsed; + + await wallet.fund(request, requiredQuantities, maxFee); + + const tx = await wallet.sendTransaction(request, { + estimateTxDependencies: false, + }); + + await expectToThrowFuelError( + () => tx.wait(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted because it's missing an "OutputChange".`, + { + logs: [], + receipts: expect.any(Array), + panic: false, + revert: true, + reason: 'MissingOutputChange', + } + ) ); }); - it('can throw ScriptResultDecoderError', async () => { - await expect(contractInstance.functions.failed_transfer().call()).rejects.toThrow( - ScriptResultDecoderError + it('should throw for explicit "revert" call', async () => { + await expectToThrowFuelError( + () => contractInstance.functions.revert_with_0().call(), + new FuelError( + ErrorCode.SCRIPT_REVERTED, + `The transaction reverted with an unknown reason: 0`, + { + logs: [], + receipts: expect.any(Array), + panic: false, + revert: true, + reason: 'unknown', + } + ) ); }); }); diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw index bf13595f5cb..75757f20dac 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/call-test-contract/src/main.sw @@ -70,6 +70,7 @@ abi TestContract { fn take_b256_enum(a: TestB256Enum) -> b256; fn take_bool_enum(enum_arg: TestBoolEnum) -> bool; fn take_string_enum(enum_arg: TestStringEnum) -> str[3]; + fn assert_u8(value_1: u8, value_2: u8); } impl TestContract for Contract { @@ -178,4 +179,7 @@ impl TestContract for Contract { bytes } + fn assert_u8(value_1: u8, value_2: u8) { + assert(value_1 == value_2); + } } diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/revert-error/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/revert-error/src/main.sw index f41a64f8d76..c381bfe219b 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/revert-error/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/revert-error/src/main.sw @@ -18,6 +18,9 @@ abi RevertError { fn failed_message(); fn failed_transfer(); fn failed_transfer_revert(); + fn assert_value_eq_10(value: u8); + fn assert_value_ne_5(value: u8); + fn revert_with_0(); } const BASE_TOKEN_A: AssetId = AssetId { @@ -72,4 +75,16 @@ impl RevertError for Contract { let user = Address::from(address); transfer_to_address(user, BASE_TOKEN_B, amount); } + + fn assert_value_eq_10(value: u8) { + assert_eq(value, 10); + } + + fn assert_value_ne_5(value: u8) { + assert_ne(value, 5); + } + + fn revert_with_0() { + revert(0); + } } diff --git a/packages/fuels/src/index.test.ts b/packages/fuels/src/index.test.ts index ecde17b9279..b9f07e9a18d 100644 --- a/packages/fuels/src/index.test.ts +++ b/packages/fuels/src/index.test.ts @@ -16,7 +16,6 @@ describe('index.js', () => { expect(fuels.Provider).toBeTruthy(); expect(fuels.Wallet).toBeTruthy(); expect(fuels.TransactionType).toBeTruthy(); - expect(fuels.ScriptResultDecoderError).toBeTruthy(); expect(fuels.Script).toBeTruthy(); expect(fuels.FunctionInvocationScope).toBeTruthy(); expect(fuels.arrayify).toBeTruthy(); diff --git a/packages/fuels/src/index.ts b/packages/fuels/src/index.ts index b51f3600ac1..5313927c232 100644 --- a/packages/fuels/src/index.ts +++ b/packages/fuels/src/index.ts @@ -10,7 +10,6 @@ export * from '@fuel-ts/interfaces'; export * from '@fuel-ts/math'; export * from '@fuel-ts/math/configs'; export * from '@fuel-ts/program'; -export * from '@fuel-ts/program/configs'; export * from '@fuel-ts/transactions'; export * from '@fuel-ts/utils'; export * from '@fuel-ts/account'; diff --git a/packages/program/package.json b/packages/program/package.json index b90063bff32..f7077155c0c 100644 --- a/packages/program/package.json +++ b/packages/program/package.json @@ -14,18 +14,6 @@ "require": "./dist/index.js", "import": "./dist/index.mjs", "types": "./dist/index.d.ts" - }, - "./configs": { - "require": "./dist/configs.js", - "import": "./dist/configs.mjs", - "types": "./dist/configs.d.ts" - } - }, - "typesVersions": { - "*": { - "configs": [ - "./dist/configs.d.ts" - ] } }, "files": [ diff --git a/packages/program/src/configs.ts b/packages/program/src/configs.ts deleted file mode 100644 index 6c116df1489..00000000000 --- a/packages/program/src/configs.ts +++ /dev/null @@ -1,38 +0,0 @@ -// From https://github.com/FuelLabs/fuel-asm/blob/eb78378c3b7c22a53b834381c387d89b3c0ef122/src/panic_reason.rs#L13 -export const PANIC_REASONS = [ - 'Success', - 'Revert', - 'OutOfGas', - 'TransactionValidity', - 'MemoryOverflow', - 'ArithmeticOverflow', - 'ContractNotFound', - 'MemoryOwnership', - 'NotEnoughBalance', - 'ExpectedInternalContext', - 'AssetIdNotFound', - 'InputNotFound', - 'OutputNotFound', - 'WitnessNotFound', - 'TransactionMaturity', - 'InvalidMetadataIdentifier', - 'MalformedCallStructure', - 'ReservedRegisterNotWritable', - 'ErrorFlag', - 'InvalidImmediateValue', - 'ExpectedCoinInput', - 'MaxMemoryAccess', - 'MemoryWriteOverlap', - 'ContractNotInInputs', - 'InternalBalanceOverflow', - 'ContractMaxSize', - 'ExpectedUnallocatedStack', - 'MaxStaticContractsReached', - 'TransferAmountCannotBeZero', - 'ExpectedOutputVariable', - 'ExpectedParentInternalContext', - 'IllegalJump', - 'NonZeroMessageOutputRecipient', - 'ZeroedMessageOutputRecipient', -]; -export const PANIC_DOC_URL = 'https://docs.rs/fuel-asm/latest/fuel_asm/enum.PanicReason.html'; diff --git a/packages/program/src/contract-call-script.ts b/packages/program/src/contract-call-script.ts index 6b2d0c35490..f35df0311fc 100644 --- a/packages/program/src/contract-call-script.ts +++ b/packages/program/src/contract-call-script.ts @@ -143,10 +143,7 @@ const getMainCallReceipt = ( const scriptResultDecoder = (contractId: AbstractAddress, isOutputDataHeap: boolean) => (result: ScriptResult) => { if (toNumber(result.code) !== 0) { - throw new FuelError( - ErrorCode.TRANSACTION_ERROR, - `Execution of the script associated with contract ${contractId} resulted in a non-zero exit code: ${result.code}.` - ); + throw new FuelError(ErrorCode.SCRIPT_REVERTED, `Transaction reverted.`); } const mainCallResult = getMainCallReceipt( diff --git a/packages/program/src/errors.ts b/packages/program/src/errors.ts deleted file mode 100644 index eef8ddb9fdb..00000000000 --- a/packages/program/src/errors.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { TransactionResult } from '@fuel-ts/account'; -import { ReceiptType } from '@fuel-ts/transactions'; - -import { RevertErrorCodes } from './revert/revert-error-codes'; -import { getDocs } from './utils'; - -const bigintReplacer = (key: unknown, value: unknown) => - typeof value === 'bigint' ? value.toString() : value; - -/** - * @hidden - */ -export class ScriptResultDecoderError extends Error { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - logs: any[]; - constructor(result: TransactionResult, message: string, logs: Array) { - let docLink = ''; - - if (result?.gqlTransaction?.status) { - docLink = `${JSON.stringify(getDocs(result.gqlTransaction.status), null, 2)}\n\n`; - } - - const logsText = logs.length ? `Logs:\n${JSON.stringify(logs, null, 2)}\n\n` : ''; - - const receiptsText = `Receipts:\n${JSON.stringify( - result.receipts.map(({ type, ...r }) => ({ type: ReceiptType[type], ...r })), - bigintReplacer, - 2 - )}`; - - super(`${message}\n\n${docLink}${logsText}${receiptsText}`); - this.logs = logs; - - new RevertErrorCodes(result.receipts, logs).assert(this); - } -} diff --git a/packages/program/src/index.ts b/packages/program/src/index.ts index 658827bd183..de8b07c7499 100644 --- a/packages/program/src/index.ts +++ b/packages/program/src/index.ts @@ -1,7 +1,5 @@ export * from './types'; export * from './utils'; -export * from './errors'; -export * from './revert/revert-error'; export { FunctionInvocationScope } from './functions/invocation-scope'; export { MultiCallInvocationScope } from './functions/multicall-scope'; export { diff --git a/packages/program/src/revert/revert-error-codes.ts b/packages/program/src/revert/revert-error-codes.ts deleted file mode 100644 index e1761e343ce..00000000000 --- a/packages/program/src/revert/revert-error-codes.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { TransactionResultReceipt, TransactionResultRevertReceipt } from '@fuel-ts/account'; -import { ReceiptType } from '@fuel-ts/transactions'; - -import type { RevertError } from './revert-error'; -import { revertErrorFactory } from './revert-error'; - -const { warn } = console; - -const getRevertReceipts = ( - receipts: TransactionResultReceipt[] -): TransactionResultRevertReceipt[] => - receipts.filter((r) => r.type === ReceiptType.Revert) as TransactionResultRevertReceipt[]; - -export class RevertErrorCodes { - private revertReceipts: TransactionResultRevertReceipt[]; - private logs: Array; - - constructor(receipts: TransactionResultReceipt[], logs: Array) { - this.revertReceipts = getRevertReceipts(receipts); - this.logs = logs; - } - - assert(detailedError: Error): void { - const revertError = this.getError(); - if (revertError) { - revertError.cause = detailedError; - throw revertError; - } - } - - getError(): RevertError | undefined { - if (!this.revertReceipts.length) { - return undefined; - } - - if (this.revertReceipts.length !== 1) { - warn( - 'Multiple revert receipts found, expected one. Receipts:', - JSON.stringify(this.revertReceipts) - ); - } - - return revertErrorFactory(this.revertReceipts[0], this.logs); - } -} diff --git a/packages/program/src/revert/revert-error.ts b/packages/program/src/revert/revert-error.ts deleted file mode 100644 index 5aacd266330..00000000000 --- a/packages/program/src/revert/revert-error.ts +++ /dev/null @@ -1,192 +0,0 @@ -/* eslint-disable max-classes-per-file */ -import type { TransactionResultRevertReceipt } from '@fuel-ts/account'; -import { - FAILED_ASSERT_EQ_SIGNAL, - FAILED_ASSERT_SIGNAL, - FAILED_REQUIRE_SIGNAL, - FAILED_SEND_MESSAGE_SIGNAL, - FAILED_TRANSFER_TO_ADDRESS_SIGNAL, - FAILED_UNKNOWN_SIGNAL, -} from '@fuel-ts/transactions/configs'; - -/** - * Represents the possible reasons for a revert. - */ -export type RevertReason = - | 'RequireFailed' - | 'TransferToAddressFailed' - | 'SendMessageFailed' - | 'AssertEqFailed' - | 'AssertFailed' - | 'Unknown'; - -/** - * A mapping of hex codes to their corresponding revert reasons. - */ -const REVERT_MAP: { [signal: string]: RevertReason } = { - [FAILED_REQUIRE_SIGNAL]: 'RequireFailed', - [FAILED_TRANSFER_TO_ADDRESS_SIGNAL]: 'TransferToAddressFailed', - [FAILED_SEND_MESSAGE_SIGNAL]: 'SendMessageFailed', - [FAILED_ASSERT_EQ_SIGNAL]: 'AssertEqFailed', - [FAILED_ASSERT_SIGNAL]: 'AssertFailed', - [FAILED_UNKNOWN_SIGNAL]: 'Unknown', -}; - -/** - * Decode the revert error code from the given receipt. - * - * @param receipt - The transaction revert receipt. - * @returns The revert reason, or undefined if not found. - */ -const decodeRevertErrorCode = ( - receipt: TransactionResultRevertReceipt -): RevertReason | undefined => { - const signalHex = receipt.val.toHex(); - return REVERT_MAP[signalHex] ? REVERT_MAP[signalHex] : undefined; -}; - -/** - * @hidden - * - * An error class for revert errors. - */ -export class RevertError extends Error { - /** - * The receipt associated with the revert error. - */ - receipt: TransactionResultRevertReceipt; - - /** - * Creates a new instance of RevertError. - * - * @param receipt - The transaction revert receipt. - * @param reason - The revert reason. - */ - constructor(receipt: TransactionResultRevertReceipt, reason: RevertReason, logs: Array) { - super( - `The script reverted with reason ${reason}. (Reason: "${RevertError.extractErrorReasonFromLogs( - logs - )}")` - ); - this.name = 'RevertError'; - this.receipt = receipt; - } - - static extractErrorReasonFromLogs(logs: Array) { - return logs.filter((l) => typeof l === 'string'); - } - - /** - * Returns a string representation of the RevertError. - * - * @returns The string representation of the error. - */ - toString() { - const { id, ...r } = this.receipt; - return `${this.name}: ${this.message} - ${id}: ${JSON.stringify(r)}`; - } -} - -/** - * @hidden - * - * An error class for Require revert errors. - */ -export class RequireRevertError extends RevertError { - /** - * Creates a new instance of RequireRevertError. - * - * @param receipt - The transaction revert receipt. - * @param reason - The revert reason. - */ - constructor(receipt: TransactionResultRevertReceipt, reason: RevertReason, logs: Array) { - super(receipt, reason, logs); - this.name = 'RequireRevertError'; - } -} - -/** - * @hidden - * - * An error class for TransferToAddress revert errors. - */ -export class TransferToAddressRevertError extends RevertError { - /** - * Creates a new instance of TransferToAddressRevertError. - * - * @param receipt - The transaction revert receipt. - * @param reason - The revert reason. - */ - constructor(receipt: TransactionResultRevertReceipt, reason: RevertReason, logs: Array) { - super(receipt, reason, logs); - this.name = 'TransferToAddressRevertError'; - } -} - -/** - * @hidden - * - * An error class for SendMessage revert errors. - */ -export class SendMessageRevertError extends RevertError { - /** - * Creates a new instance of SendMessageRevertError. - * - * @param receipt - The transaction revert receipt. - * @param reason - The revert reason. - */ - constructor(receipt: TransactionResultRevertReceipt, reason: RevertReason, logs: Array) { - super(receipt, reason, logs); - this.name = 'SendMessageRevertError'; - } -} - -/** - * @hidden - * - * An error class for AssertFailed revert errors. - */ -export class AssertFailedRevertError extends RevertError { - /** - * Creates a new instance of AssertFailedRevertError. - * - * @param receipt - The transaction revert receipt. - * @param reason - The revert reason. - */ - constructor(receipt: TransactionResultRevertReceipt, reason: RevertReason, logs: Array) { - super(receipt, reason, logs); - this.name = 'AssertFailedRevertError'; - } -} - -/** - * @hidden - * - * Factory function to create the appropriate RevertError instance based on the given receipt. - * - * @param receipt - The transaction revert receipt. - * @returns The RevertError instance, or undefined if the revert reason is not recognized. - */ -export const revertErrorFactory = ( - receipt: TransactionResultRevertReceipt, - logs: Array -): RevertError | undefined => { - const reason = decodeRevertErrorCode(receipt); - if (!reason) { - return undefined; - } - - switch (reason) { - case 'RequireFailed': - return new RequireRevertError(receipt, reason, logs); - case 'TransferToAddressFailed': - return new TransferToAddressRevertError(receipt, reason, logs); - case 'SendMessageFailed': - return new SendMessageRevertError(receipt, reason, logs); - case 'AssertFailed': - return new AssertFailedRevertError(receipt, reason, logs); - default: - return new RevertError(receipt, reason, logs); - } -}; diff --git a/packages/program/src/script-request.ts b/packages/program/src/script-request.ts index fc1f1797ae9..6f229d7ef0e 100644 --- a/packages/program/src/script-request.ts +++ b/packages/program/src/script-request.ts @@ -15,6 +15,7 @@ import type { TransactionResultScriptResultReceipt, TransactionResult, } from '@fuel-ts/account'; +import { extractTxError } from '@fuel-ts/account'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; import type { BytesLike } from '@fuel-ts/interfaces'; import type { BN } from '@fuel-ts/math'; @@ -22,7 +23,6 @@ import type { ReceiptScriptResult } from '@fuel-ts/transactions'; import { ReceiptType } from '@fuel-ts/transactions'; import { arrayify } from '@fuel-ts/utils'; -import { ScriptResultDecoderError } from './errors'; import type { CallConfig } from './types'; export const calculateScriptDataBaseOffset = (maxInputs: number) => @@ -72,18 +72,8 @@ function callResultToScriptResult(callResult: CallResult): ScriptResult { } }); - if (!scriptResultReceipt) { - throw new FuelError( - ErrorCode.TRANSACTION_ERROR, - `The script call result does not contain a 'scriptResultReceipt'.` - ); - } - - if (!returnReceipt) { - throw new FuelError( - ErrorCode.TRANSACTION_ERROR, - `The script call result does not contain a 'returnReceipt'.` - ); + if (!scriptResultReceipt || !returnReceipt) { + throw new FuelError(ErrorCode.SCRIPT_REVERTED, `Transaction reverted.`); } const scriptResult: ScriptResult = { @@ -116,11 +106,15 @@ export function decodeCallResult( const scriptResult = callResultToScriptResult(callResult); return decoder(scriptResult); } catch (error) { - throw new ScriptResultDecoderError( - callResult as TransactionResult, - (error as Error).message, - logs - ); + if ((error).code === ErrorCode.SCRIPT_REVERTED) { + throw extractTxError({ + logs, + receipts: callResult.receipts, + status: (callResult).gqlTransaction?.status, + }); + } + + throw error; } } diff --git a/packages/program/src/utils.ts b/packages/program/src/utils.ts index fe1d32f3203..7076920ba5d 100644 --- a/packages/program/src/utils.ts +++ b/packages/program/src/utils.ts @@ -1,37 +1,9 @@ -import type { JsonAbisFromAllCalls, TransactionResult } from '@fuel-ts/account'; +import type { JsonAbisFromAllCalls } from '@fuel-ts/account'; import { ErrorCode, FuelError } from '@fuel-ts/errors'; import type { AbstractContract } from '@fuel-ts/interfaces'; -import { PANIC_REASONS, PANIC_DOC_URL } from './configs'; import type { InvocationScopeLike } from './types'; -/** - * @hidden - */ -const getFailureReason = (reason: string): string => { - if (PANIC_REASONS.includes(reason)) { - return reason; - } - - return reason === 'Revert(123)' ? 'MismatchedSelector' : 'unknown'; -}; - -/** - * @hidden - */ -export const getDocs = ( - status: TransactionResult['gqlTransaction']['status'] -): { doc: string; reason: string } => { - if (status?.type === 'FailureStatus') { - const reason = getFailureReason(status.reason); - return { - doc: reason !== 'unknown' ? `${PANIC_DOC_URL}#variant.${reason}` : PANIC_DOC_URL, - reason, - }; - } - return { doc: PANIC_DOC_URL, reason: 'unknown' }; -}; - /** * @hidden * diff --git a/packages/program/tsup.config.ts b/packages/program/tsup.config.ts index bf518b39c4b..4c7f2f0354f 100644 --- a/packages/program/tsup.config.ts +++ b/packages/program/tsup.config.ts @@ -1,3 +1,3 @@ -import { indexAndConfigs } from '@internal/tsup'; +import { index } from '@internal/tsup'; -export default indexAndConfigs; +export default index; diff --git a/packages/transactions/src/configs.ts b/packages/transactions/src/configs.ts index 90b960eedaf..44aab68bbd0 100644 --- a/packages/transactions/src/configs.ts +++ b/packages/transactions/src/configs.ts @@ -35,13 +35,64 @@ export const FAILED_REQUIRE_SIGNAL = '0xffffffffffff0000'; // Revert with this value for a failing call to `std::asset::transfer_to_address`. export const FAILED_TRANSFER_TO_ADDRESS_SIGNAL = '0xffffffffffff0001'; -// Revert with this value for a failing call to `std::message::send_message`. -export const FAILED_SEND_MESSAGE_SIGNAL = '0xffffffffffff0002'; - // Revert with this value for a failing call to `std::assert::assert_eq`. export const FAILED_ASSERT_EQ_SIGNAL = '0xffffffffffff0003'; // Revert with this value for a failing call to `std::assert::assert`. export const FAILED_ASSERT_SIGNAL = '0xffffffffffff0004'; +// Revert with this value for a failing call to `std::assert::assert_ne`. +export const FAILED_ASSERT_NE_SIGNAL = '0xffffffffffff0005'; + export const FAILED_UNKNOWN_SIGNAL = '0x0'; + +export const PANIC_REASONS = [ + 'UnknownPanicReason', + 'Revert', + 'OutOfGas', + 'TransactionValidity', + 'MemoryOverflow', + 'ArithmeticOverflow', + 'ContractNotFound', + 'MemoryOwnership', + 'NotEnoughBalance', + 'ExpectedInternalContext', + 'AssetIdNotFound', + 'InputNotFound', + 'OutputNotFound', + 'WitnessNotFound', + 'TransactionMaturity', + 'InvalidMetadataIdentifier', + 'MalformedCallStructure', + 'ReservedRegisterNotWritable', + 'InvalidFlags', + 'InvalidImmediateValue', + 'ExpectedCoinInput', + 'EcalError', + 'MemoryWriteOverlap', + 'ContractNotInInputs', + 'InternalBalanceOverflow', + 'ContractMaxSize', + 'ExpectedUnallocatedStack', + 'MaxStaticContractsReached', + 'TransferAmountCannotBeZero', + 'ExpectedOutputVariable', + 'ExpectedParentInternalContext', + 'PredicateReturnedNonOne', + 'ContractIdAlreadyDeployed', + 'ContractMismatch', + 'MessageDataTooLong', + 'ArithmeticError', + 'ContractInstructionNotAllowed', + 'TransferZeroCoins', + 'InvalidInstruction', + 'MemoryNotExecutable', + 'PolicyIsNotSet', + 'PolicyNotFound', + 'TooManyReceipts', + 'BalanceOverflow', + 'InvalidBlockHeight', + 'TooManySlots', +]; + +export const PANIC_DOC_URL = 'https://docs.rs/fuel-asm/latest/fuel_asm/enum.PanicReason.html';