Skip to content

Commit 5e86e02

Browse files
committed
Fixed wildcard events and made nested events more robust (#289).
1 parent 15cd0d8 commit 5e86e02

File tree

3 files changed

+70
-32
lines changed

3 files changed

+70
-32
lines changed

src.ts/contract.ts

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ export type EventFilter = {
4141

4242
// The (n + 1)th parameter passed to contract event callbacks
4343
export interface Event extends Log {
44-
args: Array<any>;
45-
decode: (data: string, topics?: Array<string>) => any;
46-
event: string;
47-
eventSignature: string;
44+
args?: Array<any>;
45+
decode?: (data: string, topics?: Array<string>) => any;
46+
event?: string;
47+
eventSignature?: string;
4848

4949
removeListener: () => void;
5050

@@ -266,15 +266,18 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole
266266
}
267267

268268
function getEventTag(filter: EventFilter): string {
269-
return (filter.address || '') + (filter.topics ? filter.topics.join(':'): '');
269+
if (filter.address && (filter.topics == null || filter.topics.length === 0)) {
270+
return '*';
271+
}
272+
return (filter.address || '*') + '@' + (filter.topics ? filter.topics.join(':'): '');
270273
}
271274

272275
interface Bucket<T> {
273276
[name: string]: T;
274277
}
275278

276279
type _EventFilter = {
277-
decode: (log: Log) => Array<any>;
280+
prepareEvent: (event: Event) => Array<any>;
278281
event?: EventDescription;
279282
eventTag: string;
280283
filter: EventFilter;
@@ -287,7 +290,6 @@ type _Event = {
287290
wrappedListener: Listener;
288291
};
289292

290-
291293
export class Contract {
292294
readonly address: string;
293295
readonly interface: Interface;
@@ -470,8 +472,15 @@ export class Contract {
470472
// Listen for any event
471473
if (eventName === '*') {
472474
return {
473-
decode: (log: Log) => {
474-
return [ this.interface.parseLog(log) ];
475+
prepareEvent: (e: Event) => {
476+
let parsed = this.interface.parseLog(e);
477+
if (parsed) {
478+
e.args = parsed.values;
479+
e.decode = parsed.decode;
480+
e.event = parsed.name;
481+
e.eventSignature = parsed.signature;
482+
}
483+
return [ e ];
475484
},
476485
eventTag: '*',
477486
filter: { address: this.address },
@@ -494,8 +503,14 @@ export class Contract {
494503
}
495504

496505
return {
497-
decode: (log: Log) => {
498-
return event.decode(log.data, log.topics)
506+
prepareEvent: (e: Event) => {
507+
let args = event.decode(e.data, e.topics);
508+
e.args = args;
509+
510+
let result = Array.prototype.slice.call(args);
511+
result.push(e);
512+
513+
return result;
499514
},
500515
event: event,
501516
eventTag: getEventTag(filter),
@@ -512,7 +527,7 @@ export class Contract {
512527
let event: EventDescription = null;
513528
if (eventName.topics && eventName.topics[0]) {
514529
filter.topics = eventName.topics;
515-
for (var name in this.interface.events) {
530+
for (let name in this.interface.events) {
516531
if (name.indexOf('(') === -1) { continue; }
517532
let e = this.interface.events[name];
518533
if (e.topic === eventName.topics[0].toLowerCase()) {
@@ -523,9 +538,16 @@ export class Contract {
523538
}
524539

525540
return {
526-
decode: (log: Log) => {
527-
if (event) { return event.decode(log.data, log.topics) }
528-
return [ log ]
541+
prepareEvent: (e: Event) => {
542+
if (!event) { return [ e ]; }
543+
544+
let args = event.decode(e.data, e.topics);
545+
e.args = args;
546+
547+
let result = Array.prototype.slice.call(args);
548+
result.push(e);
549+
550+
return result;
529551
},
530552
event: event,
531553
eventTag: getEventTag(filter),
@@ -539,22 +561,24 @@ export class Contract {
539561
}
540562

541563
let wrappedListener = (log: Log) => {
542-
let decoded = Array.prototype.slice.call(eventFilter.decode(log));
543564

544-
let event = deepCopy(log);
545-
event.args = decoded;
546-
event.decode = eventFilter.event.decode;
547-
event.event = eventFilter.event.name;
548-
event.eventSignature = eventFilter.event.signature;
565+
let event: Event = (<Event>deepCopy(log));
566+
567+
let args = eventFilter.prepareEvent(event);
568+
569+
if (eventFilter.event) {
570+
event.decode = eventFilter.event.decode;
571+
event.event = eventFilter.event.name;
572+
event.eventSignature = eventFilter.event.signature;
573+
}
549574

550575
event.removeListener = () => { this.removeListener(eventFilter.filter, listener); };
551576

552577
event.getBlock = () => { return this.provider.getBlock(log.blockHash); }
553-
event.getTransaction = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
578+
event.getTransaction = () => { return this.provider.getTransaction(log.transactionHash); }
554579
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
555580

556-
decoded.push(event);
557-
this.emit(eventFilter.filter, ...decoded);
581+
this.emit(eventFilter.filter, ...args);
558582
};
559583

560584
this.provider.on(eventFilter.filter, wrappedListener);
@@ -582,11 +606,17 @@ export class Contract {
582606

583607
let eventFilter = this._getEventFilter(eventName);
584608
this._events = this._events.filter((event) => {
609+
610+
// Not this event (keep it for later)
585611
if (event.eventFilter.eventTag !== eventFilter.eventTag) { return true; }
612+
613+
// Call the callback in the next event loop
586614
setTimeout(() => {
587615
event.listener.apply(this, args);
588616
}, 0);
589617
result = true;
618+
619+
// Reschedule it if it not "once"
590620
return !(event.once);
591621
});
592622

src.ts/providers/base-provider.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,8 @@ function serializeTopics(topics: Array<string | Array<string>>): string {
391391
}
392392
});
393393
return topic.join(',');
394+
} else if (topic === null) {
395+
return '';
394396
}
395397
return errors.throwError('invalid topic value', errors.INVALID_ARGUMENT, { argument: 'topic', value: topic });
396398
}).join('&');
@@ -403,7 +405,11 @@ function deserializeTopics(data: string): Array<string | Array<string>> {
403405
if (comps[0] === '') { return null; }
404406
return topic;
405407
}
406-
return comps;
408+
409+
return comps.map((topic) => {
410+
if (topic === '') { return null; }
411+
return topic;
412+
});
407413
});
408414
}
409415

src.ts/utils/properties.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,13 @@ let opaque: { [key: string]: boolean } = { boolean: true, number: true, string:
5252

5353
export function deepCopy(object: any, frozen?: boolean): any {
5454

55+
// Opaque objects are not mutable, so safe to copy by assignment
5556
if (object === undefined || object === null || opaque[typeof(object)]) { return object; }
5657

58+
// Arrays are mutable, so we need to create a copy
5759
if (Array.isArray(object)) {
58-
let result: Array<any> = [];
59-
object.forEach((item) => {
60-
result.push(deepCopy(item, frozen));
61-
});
62-
60+
let result = object.map((item) => deepCopy(item, frozen));
6361
if (frozen) { Object.freeze(result); }
64-
6562
return result
6663
}
6764

@@ -73,7 +70,7 @@ export function deepCopy(object: any, frozen?: boolean): any {
7370
if (isType(object, 'Indexed')) { return object; }
7471

7572
let result: { [ key: string ]: any } = {};
76-
for (var key in object) {
73+
for (let key in object) {
7774
let value = object[key];
7875
if (value === undefined) { continue; }
7976
defineReadOnly(result, key, deepCopy(value, frozen));
@@ -84,6 +81,11 @@ export function deepCopy(object: any, frozen?: boolean): any {
8481
return result;
8582
}
8683

84+
// The function type is also immutable, so safe to copy by assignment
85+
if (typeof(object) === 'function') {
86+
return object;
87+
}
88+
8789
throw new Error('Cannot deepCopy ' + typeof(object));
8890
}
8991

0 commit comments

Comments
 (0)