Added support for Contract event parsing error recovery.

This commit is contained in:
Richard Moore 2020-04-16 21:33:24 -04:00
parent 4ef0e4f765
commit cc72f76695
No known key found for this signature in database
GPG Key ID: 665176BE8E9DC651

@ -51,6 +51,9 @@ export interface Event extends Log {
// The parsed arguments to the event
args?: Result;
// If parsing the arguments failed, this is the error
decodeError?: Error;
// A function that can be used to decode event data and topics
decode?: (data: string, topics?: Array<string>) => any;
@ -346,6 +349,11 @@ class RunningEvent {
prepareEvent(event: Event): void {
}
// Returns the array that will be applied to an emit
getEmit(event: Event): Array<any> {
return [ event ];
}
}
class ErrorRunningEvent extends RunningEvent {
@ -354,6 +362,12 @@ class ErrorRunningEvent extends RunningEvent {
}
}
// @TODO Fragment should inherit Wildcard? and just override getEmit?
// or have a common abstract super class, with enough constructor
// options to configure both.
// A Fragment Event will populate all the properties that Wildcard
// will, and additioanlly dereference the arguments when emitting
class FragmentRunningEvent extends RunningEvent {
readonly address: string;
readonly interface: Interface;
@ -389,15 +403,32 @@ class FragmentRunningEvent extends RunningEvent {
return this.interface.decodeEventLog(this.fragment, data, topics);
};
event.args = this.interface.decodeEventLog(this.fragment, event.data, event.topics);
try {
event.args = this.interface.decodeEventLog(this.fragment, event.data, event.topics);
} catch (error) {
event.args = null;
event.decodeError = error;
throw error;
}
}
getEmit(event: Event): Array<any> {
const args = (event.args || []).slice();
args.push(event);
return args;
}
}
// A Wildard Event will attempt to populate:
// - event The name of the event name
// - eventSignature The full signature of the event
// - decode A function to decode data and topics
// - args The decoded data and topics
class WildcardRunningEvent extends RunningEvent {
readonly address: string;
readonly interface: Interface;
constructor(address :string, contractInterface: Interface) {
constructor(address: string, contractInterface: Interface) {
super("*", { address: address });
defineReadOnly(this, "address", address);
defineReadOnly(this, "interface", contractInterface);
@ -406,8 +437,8 @@ class WildcardRunningEvent extends RunningEvent {
prepareEvent(event: Event): void {
super.prepareEvent(event);
const parsed = this.interface.parseLog(event);
if (parsed) {
try {
const parsed = this.interface.parseLog(event);
event.event = parsed.name;
event.eventSignature = parsed.signature;
@ -416,6 +447,8 @@ class WildcardRunningEvent extends RunningEvent {
};
event.args = parsed.args;
} catch (error) {
// No matching event
}
}
}
@ -674,7 +707,6 @@ export class Contract {
private _getRunningEvent(eventName: EventFilter | string): RunningEvent {
if (typeof(eventName) === "string") {
// Listen for "error" events (if your contract has an error event, include
// the full signature to bypass this special event keyword)
if (eventName === "error") {
@ -686,32 +718,30 @@ export class Contract {
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
}
// Get the event Fragment (throws if ambiguous/unknown event)
const fragment = this.interface.getEvent(eventName)
if (!fragment) {
logger.throwArgumentError("unknown event - " + eventName, "eventName", eventName);
}
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment));
}
const filter: EventFilter = {
address: this.address
}
// We have topics to filter by...
if (eventName.topics && eventName.topics.length > 0) {
// Find the matching event in the ABI; if none, we still allow filtering
// since it may be a filter for an otherwise unknown event
if (eventName.topics) {
if (eventName.topics[0]) {
// Is it a known topichash? (throws if no matching topichash)
try {
const fragment = this.interface.getEvent(eventName.topics[0]);
if (fragment) {
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment, eventName.topics));
}
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment, eventName.topics));
} catch (error) { }
// Filter by the unknown topichash
const filter: EventFilter = {
address: this.address,
topics: eventName.topics
}
filter.topics = eventName.topics;
return this._normalizeRunningEvent(new RunningEvent(getEventTag(filter), filter));
}
return this._normalizeRunningEvent(new RunningEvent(getEventTag(filter), filter));
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
}
_checkRunningEvents(runningEvent: RunningEvent): void {
@ -727,16 +757,11 @@ export class Contract {
}
}
private _wrapEvent(runningEvent: RunningEvent, log: Log, listener: Listener): Event {
// Subclasses can override this to gracefully recover
// from parse errors if they wish
_wrapEvent(runningEvent: RunningEvent, log: Log, listener: Listener): Event {
const event = <Event>deepCopy(log);
try {
runningEvent.prepareEvent(event);
} catch (error) {
this.emit("error", error);
throw error;
}
event.removeListener = () => {
if (!listener) { return; }
runningEvent.removeListener(listener);
@ -747,6 +772,9 @@ export class Contract {
event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); }
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
// This may throw if the topics and data mismatch the signature
runningEvent.prepareEvent(event);
return event;
}
@ -760,12 +788,18 @@ export class Contract {
// Track this running event and its listeners (may already be there; but no hard in updating)
this._runningEvents[runningEvent.tag] = runningEvent;
// If we are not polling the provider, start
// If we are not polling the provider, start polling
if (!this._wrappedEmits[runningEvent.tag]) {
const wrappedEmit = (log: Log) => {
const event = this._wrapEvent(runningEvent, log, listener);
const args = (event.args || []).slice();
args.push(event);
let event = null;
try {
event = this._wrapEvent(runningEvent, log, listener);
} catch (error) {
// There was an error decoding the data and topics
this.emit("error", error, event);
return;
}
const args = runningEvent.getEmit(event);
this.emit(runningEvent.filter, ...args);
};
this._wrappedEmits[runningEvent.tag] = wrappedEmit;