diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index 5437c9031b..b76128436e 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -15,7 +15,7 @@ "pr": 1742 }, { - "note": "Add `RevertError` and `StringRevertError` types and associated utilities", + "note": "Add `RevertError`, `StringRevertError`, `AnyRevertError` types and associated utilities", "pr": TODO } ], diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 1b851a1365..e138a47801 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -15,4 +15,10 @@ export { signTypedDataUtils } from './sign_typed_data_utils'; export import AbiEncoder = require('./abi_encoder'); export * from './types'; export { generatePseudoRandom256BitNumber } from './random'; -export { decodeRevertError, registerRevertErrorType, RevertError, StringRevertError } from './revert_error'; +export { + decodeRevertError, + registerRevertErrorType, + RevertError, + StringRevertError, + AnyRevertError, +} from './revert_error'; diff --git a/packages/utils/src/revert_error.ts b/packages/utils/src/revert_error.ts index d073b996d2..c5864e6116 100644 --- a/packages/utils/src/revert_error.ts +++ b/packages/utils/src/revert_error.ts @@ -47,7 +47,7 @@ export function decodeRevertError(bytes: string | Buffer): RevertError { export abstract class RevertError extends Error { // Map of types registered via `registerType`. private static readonly _typeRegistry: ObjectMap = {}; - public abi: RevertErrorAbi; + public abi?: RevertErrorAbi; public values: ValueMap = {}; /** @@ -82,6 +82,9 @@ export abstract class RevertError extends Error { if (instance.selector in RevertError._typeRegistry) { throw new Error(`RevertError type with signature "${instance.signature}" is already registered`); } + if (_.isNil(instance.abi)) { + throw new Error(`Attempting to register a RevertError class with no ABI`); + } RevertError._typeRegistry[instance.selector] = { type: revertClass, decoder: createDecoder(instance.abi), @@ -102,11 +105,13 @@ export abstract class RevertError extends Error { * @param declaration Function-style declaration of the revert (e.g., Error(string message)) * @param values Optional mapping of parameters to values. */ - protected constructor(declaration: string, values?: ValueMap) { + protected constructor(declaration?: string, values?: ValueMap) { super(); - this.abi = declarationToAbi(declaration); - if (values !== undefined) { - _.assign(this.values, _.cloneDeep(values)); + if (declaration !== undefined) { + this.abi = declarationToAbi(declaration); + if (values !== undefined) { + _.assign(this.values, _.cloneDeep(values)); + } } // Extending Error is tricky; we need to explicitly set the prototype. Object.setPrototypeOf(this, new.target.prototype); @@ -117,28 +122,40 @@ export abstract class RevertError extends Error { * Get the ABI name for this revert. */ get name(): string { - return this.abi.name; + if (!_.isNil(this.abi)) { + return this.abi.name; + } + return ''; } /** * Get the hex selector for this revert (without leading '0x'). */ get selector(): string { - return toSelector(this.abi); + if (!_.isNil(this.abi)) { + return toSelector(this.abi); + } + return ''; } /** * Get the signature for this revert: e.g., 'Error(string)'. */ get signature(): string { - return toSignature(this.abi); + if (!_.isNil(this.abi)) { + return toSignature(this.abi); + } + return ''; } /** * Get the ABI arguments for this revert. */ get arguments(): DataItem[] { - return this.abi.arguments || []; + if (!_.isNil(this.abi)) { + return this.abi.arguments || []; + } + return []; } /** @@ -156,9 +173,18 @@ export abstract class RevertError extends Error { if (typeof _other === 'string') { _other = RevertError.decode(_other); } + if (!(_other instanceof RevertError)) { + return false; + } + // If either is of the `AnyRevertError` type, always succeed. + if (this._isAnyType || _other._isAnyType) { + return true; + } + // Must be of same type. if (this.constructor !== _other.constructor) { return false; } + // Must share the same parameter values if defined in both instances. for (const name of Object.keys(this.values)) { const a = this.values[name]; const b = _other.values[name]; @@ -188,14 +214,30 @@ export abstract class RevertError extends Error { } return arg; } + + private get _isAnyType(): boolean { + return _.isNil(this.abi); + } } +/** + * RevertError type for standard string reverts. + */ export class StringRevertError extends RevertError { constructor(message?: string) { super('Error(string message)', { message }); } } +/** + * Special RevertError type that matches with any other RevertError instance. + */ +export class AnyRevertError extends RevertError { + constructor() { + super(); + } +} + /** * Parse a solidity function declaration into a RevertErrorAbi object. * @param declaration Function declaration (e.g., 'foo(uint256 bar)'). diff --git a/packages/utils/test/revert_error_test.ts b/packages/utils/test/revert_error_test.ts index 66e18fa3a7..2564e388dd 100644 --- a/packages/utils/test/revert_error_test.ts +++ b/packages/utils/test/revert_error_test.ts @@ -1,6 +1,6 @@ import * as chai from 'chai'; -import { RevertError, StringRevertError } from '../src/revert_error'; +import { AnyRevertError, RevertError, StringRevertError } from '../src/revert_error'; import { chaiSetup } from './utils/chai_setup'; @@ -35,6 +35,11 @@ describe('RevertError', () => { const revert2 = new StringRevertError(); expect(revert1.equals(revert2)).to.be.true(); }); + it('should equate AnyRevertError with a real RevertError', () => { + const revert1 = new StringRevertError(message); + const revert2 = new AnyRevertError(); + expect(revert1.equals(revert2)).to.be.true(); + }); it('should not equate a the same RevertError type with different values', () => { const revert1 = new StringRevertError(message); const revert2 = new StringRevertError(`${message}1`);