In @0x/utils: Add AnyRevertError type that matches with any revert error

This commit is contained in:
Lawrence Forman 2019-04-04 15:12:44 -04:00 committed by Amir Bandeali
parent abb71cd074
commit 703a0fde3c
4 changed files with 65 additions and 12 deletions

View File

@ -15,7 +15,7 @@
"pr": 1742 "pr": 1742
}, },
{ {
"note": "Add `RevertError` and `StringRevertError` types and associated utilities", "note": "Add `RevertError`, `StringRevertError`, `AnyRevertError` types and associated utilities",
"pr": TODO "pr": TODO
} }
], ],

View File

@ -15,4 +15,10 @@ export { signTypedDataUtils } from './sign_typed_data_utils';
export import AbiEncoder = require('./abi_encoder'); export import AbiEncoder = require('./abi_encoder');
export * from './types'; export * from './types';
export { generatePseudoRandom256BitNumber } from './random'; export { generatePseudoRandom256BitNumber } from './random';
export { decodeRevertError, registerRevertErrorType, RevertError, StringRevertError } from './revert_error'; export {
decodeRevertError,
registerRevertErrorType,
RevertError,
StringRevertError,
AnyRevertError,
} from './revert_error';

View File

@ -47,7 +47,7 @@ export function decodeRevertError(bytes: string | Buffer): RevertError {
export abstract class RevertError extends Error { export abstract class RevertError extends Error {
// Map of types registered via `registerType`. // Map of types registered via `registerType`.
private static readonly _typeRegistry: ObjectMap<RevertErrorRegistryItem> = {}; private static readonly _typeRegistry: ObjectMap<RevertErrorRegistryItem> = {};
public abi: RevertErrorAbi; public abi?: RevertErrorAbi;
public values: ValueMap = {}; public values: ValueMap = {};
/** /**
@ -82,6 +82,9 @@ export abstract class RevertError extends Error {
if (instance.selector in RevertError._typeRegistry) { if (instance.selector in RevertError._typeRegistry) {
throw new Error(`RevertError type with signature "${instance.signature}" is already registered`); 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] = { RevertError._typeRegistry[instance.selector] = {
type: revertClass, type: revertClass,
decoder: createDecoder(instance.abi), decoder: createDecoder(instance.abi),
@ -102,12 +105,14 @@ export abstract class RevertError extends Error {
* @param declaration Function-style declaration of the revert (e.g., Error(string message)) * @param declaration Function-style declaration of the revert (e.g., Error(string message))
* @param values Optional mapping of parameters to values. * @param values Optional mapping of parameters to values.
*/ */
protected constructor(declaration: string, values?: ValueMap) { protected constructor(declaration?: string, values?: ValueMap) {
super(); super();
if (declaration !== undefined) {
this.abi = declarationToAbi(declaration); this.abi = declarationToAbi(declaration);
if (values !== undefined) { if (values !== undefined) {
_.assign(this.values, _.cloneDeep(values)); _.assign(this.values, _.cloneDeep(values));
} }
}
// Extending Error is tricky; we need to explicitly set the prototype. // Extending Error is tricky; we need to explicitly set the prototype.
Object.setPrototypeOf(this, new.target.prototype); Object.setPrototypeOf(this, new.target.prototype);
this.message = this.toString(); this.message = this.toString();
@ -117,29 +122,41 @@ export abstract class RevertError extends Error {
* Get the ABI name for this revert. * Get the ABI name for this revert.
*/ */
get name(): string { get name(): string {
if (!_.isNil(this.abi)) {
return this.abi.name; return this.abi.name;
} }
return '<AnyRevertError>';
}
/** /**
* Get the hex selector for this revert (without leading '0x'). * Get the hex selector for this revert (without leading '0x').
*/ */
get selector(): string { get selector(): string {
if (!_.isNil(this.abi)) {
return toSelector(this.abi); return toSelector(this.abi);
} }
return '';
}
/** /**
* Get the signature for this revert: e.g., 'Error(string)'. * Get the signature for this revert: e.g., 'Error(string)'.
*/ */
get signature(): string { get signature(): string {
if (!_.isNil(this.abi)) {
return toSignature(this.abi); return toSignature(this.abi);
} }
return '';
}
/** /**
* Get the ABI arguments for this revert. * Get the ABI arguments for this revert.
*/ */
get arguments(): DataItem[] { get arguments(): DataItem[] {
if (!_.isNil(this.abi)) {
return this.abi.arguments || []; return this.abi.arguments || [];
} }
return [];
}
/** /**
* Compares this instance with another. * Compares this instance with another.
@ -156,9 +173,18 @@ export abstract class RevertError extends Error {
if (typeof _other === 'string') { if (typeof _other === 'string') {
_other = RevertError.decode(_other); _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) { if (this.constructor !== _other.constructor) {
return false; return false;
} }
// Must share the same parameter values if defined in both instances.
for (const name of Object.keys(this.values)) { for (const name of Object.keys(this.values)) {
const a = this.values[name]; const a = this.values[name];
const b = _other.values[name]; const b = _other.values[name];
@ -188,14 +214,30 @@ export abstract class RevertError extends Error {
} }
return arg; return arg;
} }
private get _isAnyType(): boolean {
return _.isNil(this.abi);
}
} }
/**
* RevertError type for standard string reverts.
*/
export class StringRevertError extends RevertError { export class StringRevertError extends RevertError {
constructor(message?: string) { constructor(message?: string) {
super('Error(string message)', { message }); 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. * Parse a solidity function declaration into a RevertErrorAbi object.
* @param declaration Function declaration (e.g., 'foo(uint256 bar)'). * @param declaration Function declaration (e.g., 'foo(uint256 bar)').

View File

@ -1,6 +1,6 @@
import * as chai from 'chai'; 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'; import { chaiSetup } from './utils/chai_setup';
@ -35,6 +35,11 @@ describe('RevertError', () => {
const revert2 = new StringRevertError(); const revert2 = new StringRevertError();
expect(revert1.equals(revert2)).to.be.true(); 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', () => { it('should not equate a the same RevertError type with different values', () => {
const revert1 = new StringRevertError(message); const revert1 = new StringRevertError(message);
const revert2 = new StringRevertError(`${message}1`); const revert2 = new StringRevertError(`${message}1`);