Skip to content

Commit

Permalink
Added support for Contract event parsing error recovery.
Browse files Browse the repository at this point in the history
  • Loading branch information
ricmoo committed Apr 17, 2020
1 parent 4ef0e4f commit cc72f76
Showing 1 changed file with 67 additions and 33 deletions.
100 changes: 67 additions & 33 deletions packages/contracts/src.ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand All @@ -416,6 +447,8 @@ class WildcardRunningEvent extends RunningEvent {
};

event.args = parsed.args;
} catch (error) {
// No matching event
}
}
}
Expand Down Expand Up @@ -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") {
Expand All @@ -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 {
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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;
Expand Down

0 comments on commit cc72f76

Please sign in to comment.