/
extract-tx-error.ts
133 lines (110 loc) · 4.16 KB
/
extract-tx-error.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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<TransactionResultReceipt>,
logs: Array<unknown>
) => {
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<TransactionResultReceipt>;
status?: GqlTransactionStatusFragmentFragment | null;
logs: Array<unknown>;
}
/**
* 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);
};