Added support for Contract event parsing error recovery.
This commit is contained in:
parent
4ef0e4f765
commit
cc72f76695
@ -51,6 +51,9 @@ export interface Event extends Log {
|
|||||||
// The parsed arguments to the event
|
// The parsed arguments to the event
|
||||||
args?: Result;
|
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
|
// A function that can be used to decode event data and topics
|
||||||
decode?: (data: string, topics?: Array<string>) => any;
|
decode?: (data: string, topics?: Array<string>) => any;
|
||||||
|
|
||||||
@ -346,6 +349,11 @@ class RunningEvent {
|
|||||||
|
|
||||||
prepareEvent(event: Event): void {
|
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 {
|
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 {
|
class FragmentRunningEvent extends RunningEvent {
|
||||||
readonly address: string;
|
readonly address: string;
|
||||||
readonly interface: Interface;
|
readonly interface: Interface;
|
||||||
@ -389,10 +403,27 @@ class FragmentRunningEvent extends RunningEvent {
|
|||||||
return this.interface.decodeEventLog(this.fragment, data, topics);
|
return this.interface.decodeEventLog(this.fragment, data, topics);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
event.args = this.interface.decodeEventLog(this.fragment, event.data, event.topics);
|
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 {
|
class WildcardRunningEvent extends RunningEvent {
|
||||||
readonly address: string;
|
readonly address: string;
|
||||||
readonly interface: Interface;
|
readonly interface: Interface;
|
||||||
@ -406,8 +437,8 @@ class WildcardRunningEvent extends RunningEvent {
|
|||||||
prepareEvent(event: Event): void {
|
prepareEvent(event: Event): void {
|
||||||
super.prepareEvent(event);
|
super.prepareEvent(event);
|
||||||
|
|
||||||
|
try {
|
||||||
const parsed = this.interface.parseLog(event);
|
const parsed = this.interface.parseLog(event);
|
||||||
if (parsed) {
|
|
||||||
event.event = parsed.name;
|
event.event = parsed.name;
|
||||||
event.eventSignature = parsed.signature;
|
event.eventSignature = parsed.signature;
|
||||||
|
|
||||||
@ -416,6 +447,8 @@ class WildcardRunningEvent extends RunningEvent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
event.args = parsed.args;
|
event.args = parsed.args;
|
||||||
|
} catch (error) {
|
||||||
|
// No matching event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -674,7 +707,6 @@ export class Contract {
|
|||||||
private _getRunningEvent(eventName: EventFilter | string): RunningEvent {
|
private _getRunningEvent(eventName: EventFilter | string): RunningEvent {
|
||||||
if (typeof(eventName) === "string") {
|
if (typeof(eventName) === "string") {
|
||||||
|
|
||||||
|
|
||||||
// Listen for "error" events (if your contract has an error event, include
|
// Listen for "error" events (if your contract has an error event, include
|
||||||
// the full signature to bypass this special event keyword)
|
// the full signature to bypass this special event keyword)
|
||||||
if (eventName === "error") {
|
if (eventName === "error") {
|
||||||
@ -686,34 +718,32 @@ export class Contract {
|
|||||||
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
|
return this._normalizeRunningEvent(new WildcardRunningEvent(this.address, this.interface));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the event Fragment (throws if ambiguous/unknown event)
|
||||||
const fragment = this.interface.getEvent(eventName)
|
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));
|
return this._normalizeRunningEvent(new FragmentRunningEvent(this.address, this.interface, fragment));
|
||||||
}
|
}
|
||||||
|
|
||||||
const filter: EventFilter = {
|
// We have topics to filter by...
|
||||||
address: this.address
|
if (eventName.topics && eventName.topics.length > 0) {
|
||||||
}
|
|
||||||
|
|
||||||
// Find the matching event in the ABI; if none, we still allow filtering
|
// Is it a known topichash? (throws if no matching topichash)
|
||||||
// since it may be a filter for an otherwise unknown event
|
try {
|
||||||
if (eventName.topics) {
|
|
||||||
if (eventName.topics[0]) {
|
|
||||||
const fragment = this.interface.getEvent(eventName.topics[0]);
|
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.topics = eventName.topics;
|
// Filter by the unknown topichash
|
||||||
|
const filter: EventFilter = {
|
||||||
|
address: this.address,
|
||||||
|
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 {
|
_checkRunningEvents(runningEvent: RunningEvent): void {
|
||||||
if (runningEvent.listenerCount() === 0) {
|
if (runningEvent.listenerCount() === 0) {
|
||||||
delete this._runningEvents[runningEvent.tag];
|
delete this._runningEvents[runningEvent.tag];
|
||||||
@ -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);
|
const event = <Event>deepCopy(log);
|
||||||
|
|
||||||
try {
|
|
||||||
runningEvent.prepareEvent(event);
|
|
||||||
} catch (error) {
|
|
||||||
this.emit("error", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
event.removeListener = () => {
|
event.removeListener = () => {
|
||||||
if (!listener) { return; }
|
if (!listener) { return; }
|
||||||
runningEvent.removeListener(listener);
|
runningEvent.removeListener(listener);
|
||||||
@ -747,6 +772,9 @@ export class Contract {
|
|||||||
event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); }
|
event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); }
|
||||||
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(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;
|
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)
|
// Track this running event and its listeners (may already be there; but no hard in updating)
|
||||||
this._runningEvents[runningEvent.tag] = runningEvent;
|
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]) {
|
if (!this._wrappedEmits[runningEvent.tag]) {
|
||||||
const wrappedEmit = (log: Log) => {
|
const wrappedEmit = (log: Log) => {
|
||||||
const event = this._wrapEvent(runningEvent, log, listener);
|
let event = null;
|
||||||
const args = (event.args || []).slice();
|
try {
|
||||||
args.push(event);
|
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.emit(runningEvent.filter, ...args);
|
||||||
};
|
};
|
||||||
this._wrappedEmits[runningEvent.tag] = wrappedEmit;
|
this._wrappedEmits[runningEvent.tag] = wrappedEmit;
|
||||||
|
Loading…
Reference in New Issue
Block a user