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
},
{
"note": "Add `RevertError` and `StringRevertError` types and associated utilities",
"note": "Add `RevertError`, `StringRevertError`, `AnyRevertError` types and associated utilities",
"pr": TODO
}
],

View File

@ -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';

View File

@ -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<RevertErrorRegistryItem> = {};
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 '<AnyRevertError>';
}
/**
* 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)').

View File

@ -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`);