-
Notifications
You must be signed in to change notification settings - Fork 165
/
Copy pathrevertedWith.ts
146 lines (136 loc) · 5.01 KB
/
revertedWith.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
134
135
136
137
138
139
140
141
142
143
144
145
146
import {decodeRevertString} from '@ethereum-waffle/provider';
import {ethers} from 'ethers';
import {callPromise} from '../call-promise';
import JSONbig from 'json-bigint';
export function supportRevertedWith(Assertion: Chai.AssertionStatic) {
Assertion.addMethod('revertedWith', function (this: any, revertReason: string | RegExp) {
callPromise(this);
const assertNotReverted = () => this.assert(
false,
'Expected transaction to be reverted',
'Expected transaction NOT to be reverted',
'Transaction reverted.',
'Transaction NOT reverted.'
);
const onError = (error: any) => {
const revertString = error?.receipt?.revertString ??
decodeHardhatError(error, this) ??
decodeOptimismError(error) ??
decodeRevertString(error);
const isReverted = revertReason instanceof RegExp
? revertReason.test(revertString)
: revertString === revertReason;
this.assert(
isReverted,
`Expected transaction to be reverted with "${revertReason}", but other reason was found: "${revertString}"`,
`Expected transaction NOT to be reverted with "${revertReason}"`,
`Transaction reverted with "${revertReason}"`,
error
);
return error;
};
this.callPromise = this.callPromise.then(assertNotReverted, onError);
this.then = this.callPromise.then.bind(this.callPromise);
this.catch = this.callPromise.catch.bind(this.callPromise);
this.txMatcher = 'revertedWith';
return this;
});
}
const errorInterface = new ethers.utils.Interface(['function Error(string)']);
const decodeHardhatError = (error: any, context: any) => {
const tryDecode = (error: any) => {
if (error === undefined) {
return undefined;
}
if (
error?.errorName &&
/**
* Preserve old behaviour for non-custom errors,
* because if the case of regular errors,
* with revertedWith we match against the argument of error (single string),
* not against the error name like in the case of custom errors - because it is always just Error.
* We don't want to require the user to do `expect(tx).to.be.revertedWith('Error').withArgs('Require cause')`.
*/
error?.errorName !== 'Error' &&
error.errorArgs
) {
context.args = [error.errorArgs];
context.txErrorName = error.errorName;
return error.errorName;
}
if (error?.data) {
try {
const decodedReason = errorInterface.decodeFunctionData('Error', error.data);
if (decodedReason[0]) return decodedReason[0];
} catch {}
}
const errorString = String(error);
{
// eslint-disable-next-line max-len
const regexp = /VM Exception while processing transaction: reverted with custom error '([a-zA-Z0-9$_]+)\((.*)\)'/g;
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
// Matches is in a format of string: "arg1, arg2, arg3, ..."
// So it only makes sense in an array:
const matchesList = `[${matches[2]}]`;
// Next, it needs to be wrapped in a list to be consistent with the emit matcher:
context.args = [
// Additionally, we preserve numbers as strings,
// otherwise we face an overflow of bignumber.
JSONbig({storeAsString: true}).parse(matchesList)
];
const errorName = matches[1];
context.txErrorName = errorName;
return errorName;
}
}
{
const regexp = new RegExp('VM Exception while processing transaction: reverted with panic code ([a-zA-Z0-9]*)');
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return 'panic code ' + matches[1];
}
}
{
const regexp = new RegExp('Error: Transaction reverted: (.*)');
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return matches[1];
}
}
{
const regexp = /revert(ed)? with reason (string )?("(?:[^\\"]|\\.)*")/;
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return JSON.parse(matches[matches.length - 1]); // parse escapes
}
}
{
const regexp = new RegExp('revert(ed)? with reason (string )?\'(.*)\'');
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return matches[matches.length - 1];
}
}
return undefined;
};
return tryDecode(error.error?.error) ??
tryDecode(error.error) ??
tryDecode(error); // the error may be wrapped
};
const decodeOptimismError = (error: any) => {
const tryDecode = (error: any) => {
const body = error?.body;
if (body) {
const errorString = JSON.parse(body)?.error?.message;
const regexp = /execution reverted: (.*)/g;
const matches = regexp.exec(errorString);
if (matches && matches.length >= 1) {
return matches[1];
}
}
};
return tryDecode(error) ??
tryDecode(error?.error) ??
tryDecode(error?.error?.error);
};