Added new-style events (removed old-style) to contracts and added filters to contracts and interfaces.

This commit is contained in:
Richard Moore 2018-07-12 02:52:43 -04:00
parent b7e143b4f3
commit 979e374270
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
5 changed files with 531 additions and 151 deletions

@ -12,8 +12,8 @@ var provider_1 = require("../providers/provider");
var wallet_1 = require("../wallet/wallet"); var wallet_1 = require("../wallet/wallet");
var abi_coder_1 = require("../utils/abi-coder"); var abi_coder_1 = require("../utils/abi-coder");
var address_1 = require("../utils/address"); var address_1 = require("../utils/address");
var bytes_1 = require("../utils/bytes");
var bignumber_1 = require("../utils/bignumber"); var bignumber_1 = require("../utils/bignumber");
var bytes_1 = require("../utils/bytes");
var properties_1 = require("../utils/properties"); var properties_1 = require("../utils/properties");
var web_1 = require("../utils/web"); var web_1 = require("../utils/web");
var errors = __importStar(require("../utils/errors")); var errors = __importStar(require("../utils/errors"));
@ -151,6 +151,9 @@ function runMethod(contract, functionName, estimateOnly) {
}); });
}; };
} }
function getEventTag(filter) {
return (filter.address || '') + (filter.topics ? filter.topics.join(':') : '');
}
var Contract = /** @class */ (function () { var Contract = /** @class */ (function () {
// https://github.com/Microsoft/TypeScript/issues/5453 // https://github.com/Microsoft/TypeScript/issues/5453
// Once this issue is resolved (there are open PR) we can do this nicer // Once this issue is resolved (there are open PR) we can do this nicer
@ -178,14 +181,28 @@ var Contract = /** @class */ (function () {
errors.throwError('invalid signer or provider', errors.INVALID_ARGUMENT, { arg: 'signerOrProvider', value: signerOrProvider }); errors.throwError('invalid signer or provider', errors.INVALID_ARGUMENT, { arg: 'signerOrProvider', value: signerOrProvider });
} }
properties_1.defineReadOnly(this, 'estimate', {}); properties_1.defineReadOnly(this, 'estimate', {});
properties_1.defineReadOnly(this, 'events', {});
properties_1.defineReadOnly(this, 'functions', {}); properties_1.defineReadOnly(this, 'functions', {});
properties_1.defineReadOnly(this, 'filters', {});
Object.keys(this.interface.events).forEach(function (eventName) {
var event = _this.interface.events[eventName];
properties_1.defineReadOnly(_this.filters, eventName, function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return {
address: _this.address,
topics: event.encodeTopics(args)
};
});
});
// Not connected to an on-chain instance, so do not connect functions and events // Not connected to an on-chain instance, so do not connect functions and events
if (!addressOrName) { if (!addressOrName) {
properties_1.defineReadOnly(this, 'address', null); properties_1.defineReadOnly(this, 'address', null);
properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null)); properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null));
return; return;
} }
this._events = [];
properties_1.defineReadOnly(this, 'address', addressOrName); properties_1.defineReadOnly(this, 'address', addressOrName);
properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then(function (address) { properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then(function (address) {
if (address == null) { if (address == null) {
@ -209,68 +226,6 @@ var Contract = /** @class */ (function () {
properties_1.defineReadOnly(_this.estimate, name, runMethod(_this, name, true)); properties_1.defineReadOnly(_this.estimate, name, runMethod(_this, name, true));
} }
}); });
Object.keys(this.interface.events).forEach(function (eventName) {
var eventInfo = _this.interface.events[eventName];
var eventCallback = null;
var contract = _this;
function handleEvent(log) {
contract.addressPromise.then(function (address) {
// Not meant for us (the topics just has the same name)
if (address != log.address) {
return null;
}
try {
var result = eventInfo.decode(log.data, log.topics);
// Some useful things to have with the log
log.args = result;
log.event = eventName;
log.decode = eventInfo.decode;
log.removeListener = function () {
contract.provider.removeListener([eventInfo.topic], handleEvent);
};
log.getBlock = function () { return contract.provider.getBlock(log.blockHash); ; };
log.getTransaction = function () { return contract.provider.getTransaction(log.transactionHash); };
log.getTransactionReceipt = function () { return contract.provider.getTransactionReceipt(log.transactionHash); };
log.eventSignature = eventInfo.signature;
eventCallback.apply(log, Array.prototype.slice.call(result));
}
catch (error) {
console.log(error);
var onerror_1 = contract._onerror;
if (onerror_1) {
setTimeout(function () { onerror_1(error); });
}
}
return null;
}).catch(function (error) { });
}
var property = {
enumerable: true,
get: function () {
return eventCallback;
},
set: function (value) {
if (!value) {
value = null;
}
if (!contract.provider) {
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'events' });
}
if (!value && eventCallback) {
contract.provider.removeListener([eventInfo.topic], handleEvent);
}
else if (value && !eventCallback) {
contract.provider.on([eventInfo.topic], handleEvent);
}
eventCallback = value;
}
};
var propertyName = 'on' + eventName.toLowerCase();
if (_this[propertyName] == null) {
Object.defineProperty(_this, propertyName, property);
}
Object.defineProperty(_this.events, eventName, property);
}, this);
} }
Object.defineProperty(Contract.prototype, "onerror", { Object.defineProperty(Contract.prototype, "onerror", {
get: function () { return this._onerror; }, get: function () { return this._onerror; },
@ -371,6 +326,181 @@ var Contract = /** @class */ (function () {
return contract; return contract;
}); });
}; };
Contract.prototype._getEventFilter = function (eventName) {
var _this = this;
if (typeof (eventName) === 'string') {
// Listen for any event
if (eventName === '*') {
return {
decode: function (log) {
return [_this.interface.parseLog(log)];
},
eventTag: '*',
filter: { address: this.address },
};
}
// Normalize the eventName
if (eventName.indexOf('(') !== -1) {
eventName = abi_coder_1.formatSignature(abi_coder_1.parseSignature('event ' + eventName));
}
var event_1 = this.interface.events[eventName];
if (!event_1) {
errors.throwError('unknown event - ' + eventName, errors.INVALID_ARGUMENT, { argumnet: 'eventName', value: eventName });
}
var filter_1 = {
address: this.address,
topics: [event_1.topic]
};
return {
decode: function (log) {
return event_1.decode(log.data, log.topics);
},
event: event_1,
eventTag: getEventTag(filter_1),
filter: filter_1
};
}
var filter = {
address: this.address
};
// Find the matching event in the ABI; if none, we still allow filtering
// since it may be a filter for an otherwise unknown event
var event = null;
if (eventName.topics && eventName.topics[0]) {
filter.topics = eventName.topics;
for (var name in this.interface.events) {
if (name.indexOf('(') === -1) {
continue;
}
var e = this.interface.events[name];
if (e.topic === eventName.topics[0].toLowerCase()) {
event = e;
break;
}
}
}
return {
decode: function (log) {
if (event) {
return event.decode(log.data, log.topics);
}
return [log];
},
event: event,
eventTag: getEventTag(filter),
filter: filter
};
};
Contract.prototype._addEventListener = function (eventFilter, listener, once) {
var _this = this;
if (!this.provider) {
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'once' });
}
var wrappedListener = function (log) {
var decoded = Array.prototype.slice.call(eventFilter.decode(log));
var event = properties_1.jsonCopy(log);
event.args = decoded;
event.decode = eventFilter.event.decode;
event.event = eventFilter.event.name;
event.eventSignature = eventFilter.event.signature;
event.removeListener = function () { _this.removeListener(eventFilter.filter, listener); };
event.getBlock = function () { return _this.provider.getBlock(log.blockHash); };
event.getTransaction = function () { return _this.provider.getTransactionReceipt(log.transactionHash); };
event.getTransactionReceipt = function () { return _this.provider.getTransactionReceipt(log.transactionHash); };
decoded.push(event);
_this.emit.apply(_this, [eventFilter.filter].concat(decoded));
};
this.provider.on(eventFilter.filter, wrappedListener);
this._events.push({ eventFilter: eventFilter, listener: listener, wrappedListener: wrappedListener, once: once });
};
Contract.prototype.on = function (event, listener) {
this._addEventListener(this._getEventFilter(event), listener, false);
return this;
};
Contract.prototype.once = function (event, listener) {
this._addEventListener(this._getEventFilter(event), listener, true);
return this;
};
Contract.prototype.addEventLisener = function (eventName, listener) {
return this.on(eventName, listener);
};
Contract.prototype.emit = function (eventName) {
var _this = this;
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!this.provider) {
return false;
}
var result = false;
var eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter(function (event) {
if (event.eventFilter.eventTag !== eventFilter.eventTag) {
return true;
}
setTimeout(function () {
event.listener.apply(_this, args);
}, 0);
result = true;
return !(event.once);
});
return result;
};
Contract.prototype.listenerCount = function (eventName) {
if (!this.provider) {
return 0;
}
var eventFilter = this._getEventFilter(eventName);
return this._events.filter(function (event) {
return event.eventFilter.eventTag === eventFilter.eventTag;
}).length;
};
Contract.prototype.listeners = function (eventName) {
if (!this.provider) {
return [];
}
var eventFilter = this._getEventFilter(eventName);
return this._events.filter(function (event) {
return event.eventFilter.eventTag === eventFilter.eventTag;
}).map(function (event) { return event.listener; });
};
Contract.prototype.removeAllListeners = function (eventName) {
if (!this.provider) {
return this;
}
var eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter(function (event) {
return event.eventFilter.eventTag !== eventFilter.eventTag;
});
return this;
};
Contract.prototype.removeListener = function (eventName, listener) {
var _this = this;
if (!this.provider) {
return this;
}
var found = false;
var eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter(function (event) {
// Make sure this event and listener match
if (event.eventFilter.eventTag !== eventFilter.eventTag) {
return true;
}
if (event.listener !== listener) {
return true;
}
_this.provider.removeListener(event.eventFilter.filter, event.wrappedListener);
// Already found a matching event in a previous loop
if (found) {
return true;
}
// REmove this event (returning false filters us out)
found = true;
return false;
});
return this;
};
return Contract; return Contract;
}()); }());
exports.Contract = Contract; exports.Contract = Contract;

@ -18,10 +18,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
var address_1 = require("../utils/address");
var abi_coder_1 = require("../utils/abi-coder"); var abi_coder_1 = require("../utils/abi-coder");
var bignumber_1 = require("../utils/bignumber"); var bignumber_1 = require("../utils/bignumber");
var bytes_1 = require("../utils/bytes"); var bytes_1 = require("../utils/bytes");
var hash_1 = require("../utils/hash"); var hash_1 = require("../utils/hash");
var keccak256_1 = require("../utils/keccak256");
var properties_1 = require("../utils/properties"); var properties_1 = require("../utils/properties");
var errors = __importStar(require("../utils/errors")); var errors = __importStar(require("../utils/errors"));
var Description = /** @class */ (function () { var Description = /** @class */ (function () {
@ -123,6 +125,46 @@ var EventDescription = /** @class */ (function (_super) {
function EventDescription() { function EventDescription() {
return _super !== null && _super.apply(this, arguments) || this; return _super !== null && _super.apply(this, arguments) || this;
} }
EventDescription.prototype.encodeTopics = function (params) {
var _this = this;
if (params.length > this.inputs.length) {
errors.throwError('too many arguments for ' + this.name, errors.UNEXPECTED_ARGUMENT, { maxCount: params.length, expectedCount: this.inputs.length });
}
var topics = [];
if (!this.anonymous) {
topics.push(this.topic);
}
params.forEach(function (arg, index) {
if (arg === null) {
topics.push(null);
return;
}
var param = _this.inputs[index];
if (!param.indexed) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
}
if (param.type === 'string') {
topics.push(hash_1.id(arg));
}
else if (param.type === 'bytes') {
topics.push(keccak256_1.keccak256(arg));
}
else if (param.type.indexOf('[') !== -1 || param.type.substring(0, 5) === 'tuple') {
errors.throwError('filtering with tuples or arrays not implemented yet; bug us on GitHub', errors.NOT_IMPLEMENTED, { operation: 'filter(array|tuple)' });
}
else {
if (param.type === 'address') {
address_1.getAddress(arg);
}
topics.push(bytes_1.hexZeroPad(bytes_1.hexlify(arg), 32).toLowerCase());
}
});
// Trim off trailing nulls
while (topics.length && topics[topics.length - 1] === null) {
topics.pop();
}
return topics;
};
EventDescription.prototype.decode = function (data, topics) { EventDescription.prototype.decode = function (data, topics) {
// Strip the signature off of non-anonymous topics // Strip the signature off of non-anonymous topics
if (topics != null && !this.anonymous) { if (topics != null && !this.anonymous) {

@ -2,15 +2,14 @@
import { EventDescription, Interface } from './interface'; import { EventDescription, Interface } from './interface';
import { Provider, TransactionRequest, TransactionResponse } from '../providers/provider'; import { Block, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse } from '../providers/provider';
import { Signer } from '../wallet/wallet'; import { Signer } from '../wallet/wallet';
import { defaultAbiCoder } from '../utils/abi-coder'; import { defaultAbiCoder, formatSignature, ParamType, parseSignature } from '../utils/abi-coder';
import { getContractAddress } from '../utils/address'; import { getContractAddress } from '../utils/address';
import { hexDataLength, hexDataSlice, isHexString } from '../utils/bytes';
import { ParamType } from '../utils/abi-coder';
import { BigNumber, ConstantZero } from '../utils/bignumber'; import { BigNumber, ConstantZero } from '../utils/bignumber';
import { defineReadOnly, shallowCopy } from '../utils/properties'; import { hexDataLength, hexDataSlice, isHexString } from '../utils/bytes';
import { defineReadOnly, jsonCopy, shallowCopy } from '../utils/properties';
import { poll } from '../utils/web'; import { poll } from '../utils/web';
import * as errors from '../utils/errors'; import * as errors from '../utils/errors';
@ -48,7 +47,6 @@ function resolveAddresses(provider: Provider, value: any, paramType: ParamType |
return Promise.resolve(value); return Promise.resolve(value);
} }
type RunFunction = (...params: Array<any>) => Promise<any>; type RunFunction = (...params: Array<any>) => Promise<any>;
function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction { function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction {
@ -173,14 +171,55 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole
} }
} }
export type Listener = (...args: Array<any>) => void;
export type EventFilter = {
address?: string;
topics?: Array<string>;
// @TODO: Support OR-style topcis; backwards compatible to make this change
//topics?: Array<string | Array<string>>
};
export type ContractEstimate = (...params: Array<any>) => Promise<BigNumber>; export type ContractEstimate = (...params: Array<any>) => Promise<BigNumber>;
export type ContractFunction = (...params: Array<any>) => Promise<any>; export type ContractFunction = (...params: Array<any>) => Promise<any>;
export type ContractEvent = (...params: Array<any>) => void; export type ContractFilter = (...params: Array<any>) => EventFilter;
export interface Event extends Log {
args: Array<any>;
decode: (data: string, topics?: Array<string>) => any;
event: string;
eventSignature: string;
removeListener: () => void;
getBlock: () => Promise<Block>;
getTransaction: () => Promise<TransactionResponse>;
getTransactionReceipt: () => Promise<TransactionReceipt>;
}
function getEventTag(filter: EventFilter): string {
return (filter.address || '') + (filter.topics ? filter.topics.join(':'): '');
}
interface Bucket<T> { interface Bucket<T> {
[name: string]: T; [name: string]: T;
} }
type _EventFilter = {
decode: (log: Log) => Array<any>;
event?: EventDescription;
eventTag: string;
filter: EventFilter;
};
type _Event = {
eventFilter: _EventFilter;
listener: Listener;
once: boolean;
wrappedListener: Listener;
};
export type ErrorCallback = (error: Error) => void; export type ErrorCallback = (error: Error) => void;
export type Contractish = Array<string | ParamType> | Interface | string; export type Contractish = Array<string | ParamType> | Interface | string;
export class Contract { export class Contract {
@ -192,7 +231,8 @@ export class Contract {
readonly estimate: Bucket<ContractEstimate>; readonly estimate: Bucket<ContractEstimate>;
readonly functions: Bucket<ContractFunction>; readonly functions: Bucket<ContractFunction>;
readonly events: Bucket<ContractEvent>;
readonly filters: Bucket<ContractFilter>;
readonly addressPromise: Promise<string>; readonly addressPromise: Promise<string>;
@ -227,9 +267,20 @@ export class Contract {
} }
defineReadOnly(this, 'estimate', { }); defineReadOnly(this, 'estimate', { });
defineReadOnly(this, 'events', { });
defineReadOnly(this, 'functions', { }); defineReadOnly(this, 'functions', { });
defineReadOnly(this, 'filters', { });
Object.keys(this.interface.events).forEach((eventName) => {
let event = this.interface.events[eventName];
defineReadOnly(this.filters, eventName, (...args: Array<any>) => {
return {
address: this.address,
topics: event.encodeTopics(args)
}
});
});
// Not connected to an on-chain instance, so do not connect functions and events // Not connected to an on-chain instance, so do not connect functions and events
if (!addressOrName) { if (!addressOrName) {
defineReadOnly(this, 'address', null); defineReadOnly(this, 'address', null);
@ -237,6 +288,8 @@ export class Contract {
return; return;
} }
this._events = [];
defineReadOnly(this, 'address', addressOrName); defineReadOnly(this, 'address', addressOrName);
defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then((address) => { defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then((address) => {
if (address == null) { throw new Error('name not found'); } if (address == null) { throw new Error('name not found'); }
@ -260,77 +313,6 @@ export class Contract {
defineReadOnly(this.estimate, name, runMethod(this, name, true)); defineReadOnly(this.estimate, name, runMethod(this, name, true));
} }
}); });
Object.keys(this.interface.events).forEach((eventName) => {
let eventInfo: EventDescription = this.interface.events[eventName];
type Callback = (...args: Array<any>) => void;
let eventCallback: Callback = null;
let contract = this;
function handleEvent(log: any): void {
contract.addressPromise.then((address) => {
// Not meant for us (the topics just has the same name)
if (address != log.address) { return null; }
try {
let result = eventInfo.decode(log.data, log.topics);
// Some useful things to have with the log
log.args = result;
log.event = eventName;
log.decode = eventInfo.decode;
log.removeListener = function() {
contract.provider.removeListener([ eventInfo.topic ], handleEvent);
}
log.getBlock = function() { return contract.provider.getBlock(log.blockHash);; }
log.getTransaction = function() { return contract.provider.getTransaction(log.transactionHash); }
log.getTransactionReceipt = function() { return contract.provider.getTransactionReceipt(log.transactionHash); }
log.eventSignature = eventInfo.signature;
eventCallback.apply(log, Array.prototype.slice.call(result));
} catch (error) {
console.log(error);
let onerror = contract._onerror;
if (onerror) { setTimeout(() => { onerror(error); }); }
}
return null;
}).catch((error) => { });
}
var property = {
enumerable: true,
get: function() {
return eventCallback;
},
set: function(value: Callback) {
if (!value) { value = null; }
if (!contract.provider) {
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'events' })
}
if (!value && eventCallback) {
contract.provider.removeListener([ eventInfo.topic ], handleEvent);
} else if (value && !eventCallback) {
contract.provider.on([ eventInfo.topic ], handleEvent);
}
eventCallback = value;
}
};
var propertyName = 'on' + eventName.toLowerCase();
if ((<any>this)[propertyName] == null) {
Object.defineProperty(this, propertyName, property);
}
Object.defineProperty(this.events, eventName, property);
}, this);
} }
get onerror() { return this._onerror; } get onerror() { return this._onerror; }
@ -436,4 +418,189 @@ export class Contract {
return contract; return contract;
}); });
} }
private _events: Array<_Event>;
_getEventFilter(eventName: EventFilter | string): _EventFilter {
if (typeof(eventName) === 'string') {
// Listen for any event
if (eventName === '*') {
return {
decode: (log: Log) => {
return [ this.interface.parseLog(log) ];
},
eventTag: '*',
filter: { address: this.address },
};
}
// Normalize the eventName
if (eventName.indexOf('(') !== -1) {
eventName = formatSignature(parseSignature('event ' + eventName));
}
let event = this.interface.events[eventName];
if (!event) {
errors.throwError('unknown event - ' + eventName, errors.INVALID_ARGUMENT, { argumnet: 'eventName', value: eventName });
}
let filter = {
address: this.address,
topics: [ event.topic ]
}
return {
decode: (log: Log) => {
return event.decode(log.data, log.topics)
},
event: event,
eventTag: getEventTag(filter),
filter: filter
};
}
let filter: EventFilter = {
address: this.address
}
// Find the matching event in the ABI; if none, we still allow filtering
// since it may be a filter for an otherwise unknown event
let event: EventDescription = null;
if (eventName.topics && eventName.topics[0]) {
filter.topics = eventName.topics;
for (var name in this.interface.events) {
if (name.indexOf('(') === -1) { continue; }
let e = this.interface.events[name];
if (e.topic === eventName.topics[0].toLowerCase()) {
event = e;
break;
}
}
}
return {
decode: (log: Log) => {
if (event) { return event.decode(log.data, log.topics) }
return [ log ]
},
event: event,
eventTag: getEventTag(filter),
filter: filter
}
}
_addEventListener(eventFilter: _EventFilter, listener: Listener, once: boolean): void {
if (!this.provider) {
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'once' })
}
let wrappedListener = (log: Log) => {
let decoded = Array.prototype.slice.call(eventFilter.decode(log));
let event = jsonCopy(log);
event.args = decoded;
event.decode = eventFilter.event.decode;
event.event = eventFilter.event.name;
event.eventSignature = eventFilter.event.signature;
event.removeListener = () => { this.removeListener(eventFilter.filter, listener); };
event.getBlock = () => { return this.provider.getBlock(log.blockHash); }
event.getTransaction = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
decoded.push(event);
this.emit(eventFilter.filter, ...decoded);
};
this.provider.on(eventFilter.filter, wrappedListener);
this._events.push({ eventFilter: eventFilter, listener: listener, wrappedListener: wrappedListener, once: once });
}
on(event: EventFilter | string, listener: Listener): Contract {
this._addEventListener(this._getEventFilter(event), listener, false);
return this;
}
once(event: EventFilter | string, listener: Listener): Contract {
this._addEventListener(this._getEventFilter(event), listener, true);
return this;
}
addEventLisener(eventName: EventFilter | string, listener: Listener): Contract {
return this.on(eventName, listener);
}
emit(eventName: EventFilter | string, ...args: Array<any>): boolean {
if (!this.provider) { return false; }
let result = false;
let eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter((event) => {
if (event.eventFilter.eventTag !== eventFilter.eventTag) { return true; }
setTimeout(() => {
event.listener.apply(this, args);
}, 0);
result = true;
return !(event.once);
});
return result;
}
listenerCount(eventName?: EventFilter | string): number {
if (!this.provider) { return 0; }
let eventFilter = this._getEventFilter(eventName);
return this._events.filter((event) => {
return event.eventFilter.eventTag === eventFilter.eventTag
}).length;
}
listeners(eventName: EventFilter | string): Array<Listener> {
if (!this.provider) { return []; }
let eventFilter = this._getEventFilter(eventName);
return this._events.filter((event) => {
return event.eventFilter.eventTag === eventFilter.eventTag
}).map((event) => { return event.listener; });
}
removeAllListeners(eventName: EventFilter | string): Contract {
if (!this.provider) { return this; }
let eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter((event) => {
return event.eventFilter.eventTag !== eventFilter.eventTag
});
return this;
}
removeListener(eventName: any, listener: Listener): Contract {
if (!this.provider) { return this; }
let found = false;
let eventFilter = this._getEventFilter(eventName);
this._events = this._events.filter((event) => {
// Make sure this event and listener match
if (event.eventFilter.eventTag !== eventFilter.eventTag) { return true; }
if (event.listener !== listener) { return true; }
this.provider.removeListener(event.eventFilter.filter, event.wrappedListener);
// Already found a matching event in a previous loop
if (found) { return true; }
// REmove this event (returning false filters us out)
found = true;
return false;
});
return this;
}
} }

@ -2,10 +2,12 @@
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
import { getAddress } from '../utils/address';
import { defaultAbiCoder, EventFragment, formatSignature, FunctionFragment, ParamType, parseSignature } from '../utils/abi-coder'; import { defaultAbiCoder, EventFragment, formatSignature, FunctionFragment, ParamType, parseSignature } from '../utils/abi-coder';
import { BigNumber, bigNumberify, BigNumberish } from '../utils/bignumber'; import { BigNumber, bigNumberify, BigNumberish } from '../utils/bignumber';
import { arrayify, concat, isHexString } from '../utils/bytes'; import { arrayify, concat, hexlify, hexZeroPad, isHexString } from '../utils/bytes';
import { id } from '../utils/hash'; import { id } from '../utils/hash';
import { keccak256 } from '../utils/keccak256';
import { defineReadOnly, defineFrozen } from '../utils/properties'; import { defineReadOnly, defineFrozen } from '../utils/properties';
import * as errors from '../utils/errors'; import * as errors from '../utils/errors';
@ -109,6 +111,45 @@ export class EventDescription extends Description {
readonly anonymous: boolean; readonly anonymous: boolean;
readonly topic: string; readonly topic: string;
encodeTopics(params: Array<any>): Array<string> {
if (params.length > this.inputs.length) {
errors.throwError('too many arguments for ' + this.name, errors.UNEXPECTED_ARGUMENT, { maxCount: params.length, expectedCount: this.inputs.length })
}
let topics: Array<string> = [];
if (!this.anonymous) { topics.push(this.topic); }
params.forEach((arg, index) => {
if (arg === null) {
topics.push(null);
return;
}
let param = this.inputs[index];
if (!param.indexed) {
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
}
if (param.type === 'string') {
topics.push(id(arg));
} else if (param.type === 'bytes') {
topics.push(keccak256(arg));
} else if (param.type.indexOf('[') !== -1 || param.type.substring(0, 5) === 'tuple') {
errors.throwError('filtering with tuples or arrays not implemented yet; bug us on GitHub', errors.NOT_IMPLEMENTED, { operation: 'filter(array|tuple)' });
} else {
if (param.type === 'address') { getAddress(arg); }
topics.push(hexZeroPad(hexlify(arg), 32).toLowerCase());
}
});
// Trim off trailing nulls
while (topics.length && topics[topics.length - 1] === null) {
topics.pop();
}
return topics;
}
decode(data: string, topics?: Array<string>): any { decode(data: string, topics?: Array<string>): any {
// Strip the signature off of non-anonymous topics // Strip the signature off of non-anonymous topics
if (topics != null && !this.anonymous) { topics = topics.slice(1); } if (topics != null && !this.anonymous) { topics = topics.slice(1); }

@ -49,13 +49,13 @@ function TestContractEvents() {
function waitForEvent(eventName, expected) { function waitForEvent(eventName, expected) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
contract['on' + eventName.toLowerCase()] = function() { contract.on(eventName, function() {
//console.dir(this, { depth: null }); var args = Array.prototype.slice.call(arguments);
//console.log(this.event); var event = args.pop();
this.removeListener(); event.removeListener();
equals(this.event, Array.prototype.slice.call(arguments), expected); equals(event.event, args, expected);
resolve(); resolve();
}; });
}); });
} }