1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.RawRevertError = exports.AnyRevertError = exports.StringRevertError = exports.getThrownErrorRevertErrorBytes = exports.RevertError = exports.coerceThrownErrorAsRevertError = exports.decodeThrownErrorAsRevertError = exports.decodeBytesAsRevertError = exports.registerRevertErrorType = void 0;
|
4 | const ethUtil = require("ethereumjs-util");
|
5 | const _ = require("lodash");
|
6 | const util_1 = require("util");
|
7 | const AbiEncoder = require("./abi_encoder");
|
8 | const configured_bignumber_1 = require("./configured_bignumber");
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | function registerRevertErrorType(revertClass, force = false) {
|
16 | RevertError.registerType(revertClass, force);
|
17 | }
|
18 | exports.registerRevertErrorType = registerRevertErrorType;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 | function decodeBytesAsRevertError(bytes, coerce = false) {
|
27 | return RevertError.decode(bytes, coerce);
|
28 | }
|
29 | exports.decodeBytesAsRevertError = decodeBytesAsRevertError;
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | function decodeThrownErrorAsRevertError(error, coerce = false) {
|
38 | if (error instanceof RevertError) {
|
39 | return error;
|
40 | }
|
41 | return RevertError.decode(getThrownErrorRevertErrorBytes(error), coerce);
|
42 | }
|
43 | exports.decodeThrownErrorAsRevertError = decodeThrownErrorAsRevertError;
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | function coerceThrownErrorAsRevertError(error) {
|
50 | if (error instanceof RevertError) {
|
51 | return error;
|
52 | }
|
53 | try {
|
54 | return decodeThrownErrorAsRevertError(error, true);
|
55 | }
|
56 | catch (err) {
|
57 | if (isGanacheTransactionRevertError(error)) {
|
58 | throw err;
|
59 | }
|
60 |
|
61 | if (isGethTransactionRevertError(error)) {
|
62 |
|
63 |
|
64 | return new AnyRevertError();
|
65 | }
|
66 |
|
67 | return new StringRevertError(error.message);
|
68 | }
|
69 | }
|
70 | exports.coerceThrownErrorAsRevertError = coerceThrownErrorAsRevertError;
|
71 |
|
72 |
|
73 |
|
74 | class RevertError extends Error {
|
75 | |
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 | constructor(name, declaration, values, raw) {
|
85 | super(createErrorMessage(name, values));
|
86 | this.values = {};
|
87 | if (declaration !== undefined) {
|
88 | this.abi = declarationToAbi(declaration);
|
89 | if (values !== undefined) {
|
90 | _.assign(this.values, _.cloneDeep(values));
|
91 | }
|
92 | }
|
93 | this._raw = raw;
|
94 |
|
95 | Object.setPrototypeOf(this, new.target.prototype);
|
96 | }
|
97 | |
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 | static decode(bytes, coerce = false) {
|
105 | if (bytes instanceof RevertError) {
|
106 | return bytes;
|
107 | }
|
108 | const _bytes = bytes instanceof Buffer ? ethUtil.bufferToHex(bytes) : ethUtil.addHexPrefix(bytes);
|
109 |
|
110 | const selector = _bytes.slice(2, 10);
|
111 | if (!(selector in RevertError._typeRegistry)) {
|
112 | if (coerce) {
|
113 | return new RawRevertError(bytes);
|
114 | }
|
115 | throw new Error(`Unknown selector: ${selector}`);
|
116 | }
|
117 | const { type, decoder } = RevertError._typeRegistry[selector];
|
118 | const instance = new type();
|
119 | try {
|
120 | Object.assign(instance, { values: decoder(_bytes) });
|
121 | instance.message = instance.toString();
|
122 | return instance;
|
123 | }
|
124 | catch (err) {
|
125 | throw new Error(`Bytes ${_bytes} cannot be decoded as a revert error of type ${instance.signature}: ${err.message}`);
|
126 | }
|
127 | }
|
128 | |
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 | static registerType(revertClass, force = false) {
|
135 | const instance = new revertClass();
|
136 | if (!force && instance.selector in RevertError._typeRegistry) {
|
137 | throw new Error(`RevertError type with signature "${instance.signature}" is already registered`);
|
138 | }
|
139 | if (_.isNil(instance.abi)) {
|
140 | throw new Error(`Attempting to register a RevertError class with no ABI`);
|
141 | }
|
142 | RevertError._typeRegistry[instance.selector] = {
|
143 | type: revertClass,
|
144 | decoder: createDecoder(instance.abi),
|
145 | };
|
146 | }
|
147 | |
148 |
|
149 |
|
150 | get name() {
|
151 | if (!_.isNil(this.abi)) {
|
152 | return this.abi.name;
|
153 | }
|
154 | return `<${this.typeName}>`;
|
155 | }
|
156 | |
157 |
|
158 |
|
159 | get typeName() {
|
160 |
|
161 | return this.constructor.name;
|
162 | }
|
163 | |
164 |
|
165 |
|
166 | get selector() {
|
167 | if (!_.isNil(this.abi)) {
|
168 | return toSelector(this.abi);
|
169 | }
|
170 | if (this._isRawType) {
|
171 |
|
172 | return this._raw.slice(2, 10);
|
173 | }
|
174 | return '';
|
175 | }
|
176 | |
177 |
|
178 |
|
179 | get signature() {
|
180 | if (!_.isNil(this.abi)) {
|
181 | return toSignature(this.abi);
|
182 | }
|
183 | return '';
|
184 | }
|
185 | |
186 |
|
187 |
|
188 | get arguments() {
|
189 | if (!_.isNil(this.abi)) {
|
190 | return this.abi.arguments || [];
|
191 | }
|
192 | return [];
|
193 | }
|
194 | get [Symbol.toStringTag]() {
|
195 | return this.toString();
|
196 | }
|
197 | |
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 | equals(other) {
|
205 | let _other = other;
|
206 | if (_other instanceof Buffer) {
|
207 | _other = ethUtil.bufferToHex(_other);
|
208 | }
|
209 | if (typeof _other === 'string') {
|
210 | _other = RevertError.decode(_other);
|
211 | }
|
212 | if (!(_other instanceof RevertError)) {
|
213 | return false;
|
214 | }
|
215 |
|
216 | if (this._isAnyType || _other._isAnyType) {
|
217 | return true;
|
218 | }
|
219 |
|
220 | if (this._isRawType || _other._isRawType) {
|
221 | return this._raw === _other._raw;
|
222 | }
|
223 |
|
224 | if (this.constructor !== _other.constructor) {
|
225 | return false;
|
226 | }
|
227 |
|
228 | for (const name of Object.keys(this.values)) {
|
229 | const a = this.values[name];
|
230 | const b = _other.values[name];
|
231 | if (a === b) {
|
232 | continue;
|
233 | }
|
234 | if (!_.isNil(a) && !_.isNil(b)) {
|
235 | const { type } = this._getArgumentByName(name);
|
236 | if (!checkArgEquality(type, a, b)) {
|
237 | return false;
|
238 | }
|
239 | }
|
240 | }
|
241 | return true;
|
242 | }
|
243 | encode() {
|
244 | if (this._raw !== undefined) {
|
245 | return this._raw;
|
246 | }
|
247 | if (!this._hasAllArgumentValues) {
|
248 | throw new Error(`Instance of ${this.typeName} does not have all its parameter values set.`);
|
249 | }
|
250 | const encoder = createEncoder(this.abi);
|
251 | return encoder(this.values);
|
252 | }
|
253 | toString() {
|
254 | if (this._isRawType) {
|
255 | return `${this.constructor.name}(${this._raw})`;
|
256 | }
|
257 | const values = _.omitBy(this.values, (v) => _.isNil(v));
|
258 |
|
259 | for (const k in values) {
|
260 | const { type: argType } = this._getArgumentByName(k);
|
261 | if (argType === 'bytes') {
|
262 |
|
263 | try {
|
264 | values[k] = RevertError.decode(values[k]);
|
265 | }
|
266 | catch (err) { }
|
267 | }
|
268 | }
|
269 | const inner = _.isEmpty(values) ? '' : util_1.inspect(values);
|
270 | return `${this.constructor.name}(${inner})`;
|
271 | }
|
272 | _getArgumentByName(name) {
|
273 | const arg = _.find(this.arguments, (a) => a.name === name);
|
274 | if (_.isNil(arg)) {
|
275 | throw new Error(`RevertError ${this.signature} has no argument named ${name}`);
|
276 | }
|
277 | return arg;
|
278 | }
|
279 | get _isAnyType() {
|
280 | return _.isNil(this.abi) && _.isNil(this._raw);
|
281 | }
|
282 | get _isRawType() {
|
283 | return !_.isNil(this._raw);
|
284 | }
|
285 | get _hasAllArgumentValues() {
|
286 | if (_.isNil(this.abi) || _.isNil(this.abi.arguments)) {
|
287 | return false;
|
288 | }
|
289 | for (const arg of this.abi.arguments) {
|
290 | if (_.isNil(this.values[arg.name])) {
|
291 | return false;
|
292 | }
|
293 | }
|
294 | return true;
|
295 | }
|
296 | }
|
297 | exports.RevertError = RevertError;
|
298 |
|
299 | RevertError._typeRegistry = {};
|
300 | const PARITY_TRANSACTION_REVERT_ERROR_MESSAGE = /^VM execution error/;
|
301 | const GANACHE_TRANSACTION_REVERT_ERROR_MESSAGE = /^VM Exception while processing transaction: revert/;
|
302 | const GETH_TRANSACTION_REVERT_ERROR_MESSAGE = /always failing transaction$/;
|
303 |
|
304 |
|
305 |
|
306 | function getThrownErrorRevertErrorBytes(error) {
|
307 |
|
308 | if (isGanacheTransactionRevertError(error)) {
|
309 | return error.data;
|
310 | }
|
311 | else if (isParityTransactionRevertError(error)) {
|
312 |
|
313 | const { data } = error;
|
314 | const hexDataIndex = data.indexOf('0x');
|
315 | if (hexDataIndex !== -1) {
|
316 | return data.slice(hexDataIndex);
|
317 | }
|
318 | }
|
319 | else {
|
320 |
|
321 | if (isGethTransactionRevertError(error)) {
|
322 |
|
323 |
|
324 | }
|
325 | }
|
326 | throw new Error(`Cannot decode thrown Error "${error.message}" as a RevertError`);
|
327 | }
|
328 | exports.getThrownErrorRevertErrorBytes = getThrownErrorRevertErrorBytes;
|
329 | function isParityTransactionRevertError(error) {
|
330 | if (PARITY_TRANSACTION_REVERT_ERROR_MESSAGE.test(error.message) && 'code' in error && 'data' in error) {
|
331 | return true;
|
332 | }
|
333 | return false;
|
334 | }
|
335 | function isGanacheTransactionRevertError(error) {
|
336 | if (GANACHE_TRANSACTION_REVERT_ERROR_MESSAGE.test(error.message) && 'data' in error) {
|
337 | return true;
|
338 | }
|
339 | return false;
|
340 | }
|
341 | function isGethTransactionRevertError(error) {
|
342 | return GETH_TRANSACTION_REVERT_ERROR_MESSAGE.test(error.message);
|
343 | }
|
344 |
|
345 |
|
346 |
|
347 | class StringRevertError extends RevertError {
|
348 | constructor(message) {
|
349 | super('StringRevertError', 'Error(string message)', { message });
|
350 | }
|
351 | }
|
352 | exports.StringRevertError = StringRevertError;
|
353 |
|
354 |
|
355 |
|
356 | class AnyRevertError extends RevertError {
|
357 | constructor() {
|
358 | super('AnyRevertError');
|
359 | }
|
360 | }
|
361 | exports.AnyRevertError = AnyRevertError;
|
362 |
|
363 |
|
364 |
|
365 | class RawRevertError extends RevertError {
|
366 | constructor(encoded) {
|
367 | super('RawRevertError', undefined, undefined, typeof encoded === 'string' ? encoded : ethUtil.bufferToHex(encoded));
|
368 | }
|
369 | }
|
370 | exports.RawRevertError = RawRevertError;
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 | function createErrorMessage(name, values) {
|
377 | if (values === undefined) {
|
378 | return `${name}()`;
|
379 | }
|
380 | const _values = _.omitBy(values, (v) => _.isNil(v));
|
381 | const inner = _.isEmpty(_values) ? '' : util_1.inspect(_values);
|
382 | return `${name}(${inner})`;
|
383 | }
|
384 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 | function declarationToAbi(declaration) {
|
390 | let m = /^\s*([_a-z][a-z0-9_]*)\((.*)\)\s*$/i.exec(declaration);
|
391 | if (!m) {
|
392 | throw new Error(`Invalid Revert Error signature: "${declaration}"`);
|
393 | }
|
394 | const [name, args] = m.slice(1);
|
395 | const argList = _.filter(args.split(','));
|
396 | const argData = _.map(argList, (a) => {
|
397 |
|
398 |
|
399 | m = /^\s*(([_a-z][a-z0-9_]*)(\[\d*\])*)\s+([_a-z][a-z0-9_]*)\s*$/i.exec(a);
|
400 | if (!m) {
|
401 | throw new Error(`Invalid Revert Error signature: "${declaration}"`);
|
402 | }
|
403 |
|
404 | return {
|
405 | name: m[4],
|
406 | type: m[1],
|
407 | };
|
408 |
|
409 | });
|
410 | const r = {
|
411 | type: 'error',
|
412 | name,
|
413 | arguments: _.isEmpty(argData) ? [] : argData,
|
414 | };
|
415 | return r;
|
416 | }
|
417 | function checkArgEquality(type, lhs, rhs) {
|
418 |
|
419 | try {
|
420 | return RevertError.decode(lhs).equals(RevertError.decode(rhs));
|
421 | }
|
422 | catch (err) {
|
423 |
|
424 | }
|
425 | if (type === 'address') {
|
426 | return normalizeAddress(lhs) === normalizeAddress(rhs);
|
427 | }
|
428 | else if (type === 'bytes' || /^bytes(\d+)$/.test(type)) {
|
429 | return normalizeBytes(lhs) === normalizeBytes(rhs);
|
430 | }
|
431 | else if (type === 'string') {
|
432 | return lhs === rhs;
|
433 | }
|
434 | else if (/\[\d*\]$/.test(type)) {
|
435 |
|
436 |
|
437 |
|
438 | if (lhs.length !== rhs.length) {
|
439 | return false;
|
440 | }
|
441 | const m = /^(.+)\[(\d*)\]$/.exec(type);
|
442 | const baseType = m[1];
|
443 | const isFixedLength = m[2].length !== 0;
|
444 | if (isFixedLength) {
|
445 | const length = parseInt(m[2], 10);
|
446 |
|
447 | if (lhs.length !== length) {
|
448 | return false;
|
449 | }
|
450 | }
|
451 |
|
452 | for (const [slhs, srhs] of _.zip(lhs, rhs)) {
|
453 | if (!checkArgEquality(baseType, slhs, srhs)) {
|
454 | return false;
|
455 | }
|
456 | }
|
457 | return true;
|
458 |
|
459 | }
|
460 |
|
461 | return new configured_bignumber_1.BigNumber(lhs || 0).eq(rhs);
|
462 | }
|
463 | function normalizeAddress(addr) {
|
464 | const ADDRESS_SIZE = 20;
|
465 | return ethUtil.bufferToHex(ethUtil.setLengthLeft(ethUtil.toBuffer(ethUtil.addHexPrefix(addr)), ADDRESS_SIZE));
|
466 | }
|
467 | function normalizeBytes(bytes) {
|
468 | return ethUtil.addHexPrefix(bytes).toLowerCase();
|
469 | }
|
470 | function createEncoder(abi) {
|
471 | const encoder = AbiEncoder.createMethod(abi.name, abi.arguments || []);
|
472 | return (values) => {
|
473 | const valuesArray = _.map(abi.arguments, (arg) => values[arg.name]);
|
474 | return encoder.encode(valuesArray);
|
475 | };
|
476 | }
|
477 | function createDecoder(abi) {
|
478 | const encoder = AbiEncoder.createMethod(abi.name, abi.arguments || []);
|
479 | return (hex) => {
|
480 | return encoder.decode(hex);
|
481 | };
|
482 | }
|
483 | function toSignature(abi) {
|
484 | const argTypes = _.map(abi.arguments, (a) => a.type);
|
485 | const args = argTypes.join(',');
|
486 | return `${abi.name}(${args})`;
|
487 | }
|
488 | function toSelector(abi) {
|
489 | return (ethUtil
|
490 | .keccak256(Buffer.from(toSignature(abi)))
|
491 |
|
492 | .slice(0, 4)
|
493 | .toString('hex'));
|
494 | }
|
495 |
|
496 | RevertError.registerType(StringRevertError);
|
497 |
|
498 |
|
\ | No newline at end of file |