UNPKG

@ethereum-waffle/chai

Version:

A sweet set of chai matchers for your blockchain testing needs.

122 lines 6.47 kB
import { decodeRevertString } from '@ethereum-waffle/provider'; import { ethers } from 'ethers'; import { callPromise } from '../call-promise'; import JSONbig from 'json-bigint'; export function supportRevertedWith(Assertion) { Assertion.addMethod('revertedWith', function (revertReason) { 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) => { var _a, _b, _c, _d; const revertString = (_d = (_c = (_b = (_a = error === null || error === void 0 ? void 0 : error.receipt) === null || _a === void 0 ? void 0 : _a.revertString) !== null && _b !== void 0 ? _b : decodeHardhatError(error, this)) !== null && _c !== void 0 ? _c : decodeOptimismError(error)) !== null && _d !== void 0 ? _d : 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, context) => { var _a, _b, _c; const tryDecode = (error) => { if (error === undefined) { return undefined; } if ((error === null || error === void 0 ? void 0 : 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 === null || error === void 0 ? void 0 : error.errorName) !== 'Error' && error.errorArgs) { context.args = [error.errorArgs]; context.txErrorName = error.errorName; return error.errorName; } if (error === null || error === void 0 ? void 0 : error.data) { try { const decodedReason = errorInterface.decodeFunctionData('Error', error.data); if (decodedReason[0]) return decodedReason[0]; } catch (_a) { } } 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 (_c = (_b = tryDecode((_a = error.error) === null || _a === void 0 ? void 0 : _a.error)) !== null && _b !== void 0 ? _b : tryDecode(error.error)) !== null && _c !== void 0 ? _c : tryDecode(error); // the error may be wrapped }; const decodeOptimismError = (error) => { var _a, _b, _c; const tryDecode = (error) => { var _a, _b; const body = error === null || error === void 0 ? void 0 : error.body; if (body) { const errorString = (_b = (_a = JSON.parse(body)) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.message; const regexp = /execution reverted: (.*)/g; const matches = regexp.exec(errorString); if (matches && matches.length >= 1) { return matches[1]; } } }; return (_b = (_a = tryDecode(error)) !== null && _a !== void 0 ? _a : tryDecode(error === null || error === void 0 ? void 0 : error.error)) !== null && _b !== void 0 ? _b : tryDecode((_c = error === null || error === void 0 ? void 0 : error.error) === null || _c === void 0 ? void 0 : _c.error); }; //# sourceMappingURL=revertedWith.js.map