From e37a3155cd52d35da3eef9a8dc450b9b3df0b888 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 4 Oct 2017 15:36:57 +0300 Subject: Instantiate logAndBlockStreamer --- package.json | 3 ++- src/contract_wrappers/contract_wrapper.ts | 7 +++++++ src/web3_wrapper.ts | 8 ++++++-- yarn.lock | 22 ++++++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 82bca114d..011e974c0 100644 --- a/package.json +++ b/package.json @@ -89,12 +89,13 @@ "webpack": "^3.1.0" }, "dependencies": { - "@types/bignumber.js": "^4.0.2", "0x-json-schemas": "^0.6.1", + "@types/bignumber.js": "^4.0.2", "bignumber.js": "^4.0.2", "compare-versions": "^3.0.1", "es6-promisify": "^5.0.0", "ethereumjs-abi": "^0.6.4", + "ethereumjs-blockstream": "^2.0.6", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", "lodash": "^4.17.4", diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 743dfc9b2..927a09b52 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -1,6 +1,7 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import * as ethUtil from 'ethereumjs-util'; +import {BlockAndLogStreamer} from 'ethereumjs-blockstream'; import {Web3Wrapper} from '../web3_wrapper'; import {AbiDecoder} from '../utils/abi_decoder'; import { @@ -19,9 +20,15 @@ const TOPIC_LENGTH = 32; export class ContractWrapper { protected _web3Wrapper: Web3Wrapper; private _abiDecoder?: AbiDecoder; + private _blockAndLogStreamer: BlockAndLogStreamer; constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) { this._web3Wrapper = web3Wrapper; this._abiDecoder = abiDecoder; + const getBlockAsync = async (hash: string) => this._web3Wrapper.getBlockAsync(hash); + this._blockAndLogStreamer = new BlockAndLogStreamer( + this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), + this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + ); } protected async _getLogsAsync(address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts, indexFilterValues: IndexedFilterValues, diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index 7576f3d40..fd9b74b8b 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -94,8 +94,12 @@ export class Web3Wrapper { const signData = await promisify(this.web3.eth.sign)(address, message); return signData; } - public async getBlockTimestampAsync(blockHash: string): Promise { - const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash); + public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise { + const block = await promisify(this.web3.eth.getBlock)(blockParam); + return block; + } + public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise { + const {timestamp} = await this.getBlockAsync(blockParam); return timestamp; } public async getAvailableAddressesAsync(): Promise { diff --git a/yarn.lock b/yarn.lock index 2d3fbb2fc..4d27d246c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1675,6 +1675,14 @@ ethereumjs-block@^1.2.2: ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" +ethereumjs-blockstream@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-2.0.6.tgz#12c37ad5ac138d1d0abd60e7f4fa4344739f7e8b" + dependencies: + immutable "3.8.1" + source-map-support "0.4.14" + uuid "3.0.1" + ethereumjs-testrpc@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/ethereumjs-testrpc/-/ethereumjs-testrpc-4.0.1.tgz#af23babff4c36008418bc6de4c80f81606896cad" @@ -2297,6 +2305,10 @@ immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" +immutable@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -4270,6 +4282,12 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-support@0.4.14: + version "0.4.14" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.14.tgz#9d4463772598b86271b4f523f6c1f4e02a7d6aef" + dependencies: + source-map "^0.5.6" + source-map-support@^0.4.15, source-map-support@^0.4.2: version "0.4.15" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" @@ -4860,6 +4878,10 @@ util@0.10.3, util@^0.10.3: dependencies: inherits "2.0.1" +uuid@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -- cgit v1.2.3 From 7dd63523939822203d938511472c84b8ff418aaf Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 14:34:30 +0300 Subject: Implement subscriptions based on ethereumjs-blockstream --- package.json | 2 + src/contract_wrappers/contract_wrapper.ts | 122 +++++++++++++------- src/contract_wrappers/exchange_wrapper.ts | 82 ++++++-------- src/contract_wrappers/token_wrapper.ts | 60 +++++----- src/index.ts | 2 +- src/types.ts | 24 +--- src/utils/constants.ts | 1 + src/utils/event_utils.ts | 41 ------- src/utils/filter_utils.ts | 80 +++++++++++++ test/exchange_wrapper_test.ts | 95 ++++++---------- test/token_wrapper_test.ts | 88 ++++++--------- yarn.lock | 180 +++++++++++++----------------- 12 files changed, 376 insertions(+), 401 deletions(-) delete mode 100644 src/utils/event_utils.ts create mode 100644 src/utils/filter_utils.ts diff --git a/package.json b/package.json index 011e974c0..59b0c9cf9 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@types/mocha": "^2.2.41", "@types/node": "^8.0.1", "@types/sinon": "^2.2.2", + "@types/uuid": "^3.4.2", "awesome-typescript-loader": "^3.1.3", "bignumber.js": "^4.0.2", "chai": "^4.0.1", @@ -100,6 +101,7 @@ "find-versions": "^2.0.0", "lodash": "^4.17.4", "publish-release": "^1.3.3", + "uuid": "^3.1.0", "web3": "^0.20.0" } } diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 927a09b52..6f22f5bdb 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -1,10 +1,10 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; -import * as ethUtil from 'ethereumjs-util'; -import {BlockAndLogStreamer} from 'ethereumjs-blockstream'; +import {BlockAndLogStreamer, Block} from 'ethereumjs-blockstream'; import {Web3Wrapper} from '../web3_wrapper'; import {AbiDecoder} from '../utils/abi_decoder'; import { + ZeroExError, InternalZeroExError, Artifact, LogWithDecodedArgs, @@ -12,38 +12,73 @@ import { ContractEvents, SubscriptionOpts, IndexedFilterValues, + EventCallback, } from '../types'; import {utils} from '../utils/utils'; - -const TOPIC_LENGTH = 32; +import {constants} from '../utils/constants'; +import {intervalUtils} from '../utils/interval_utils'; +import {filterUtils} from '../utils/filter_utils'; export class ContractWrapper { protected _web3Wrapper: Web3Wrapper; private _abiDecoder?: AbiDecoder; private _blockAndLogStreamer: BlockAndLogStreamer; + private _blockAndLogStreamInterval: NodeJS.Timer; + private _activeFilters: number; + private _filters: {[filterToken: string]: Web3.FilterObject}; + private _filterCallbacks: {[filterToken: string]: EventCallback}; + private _onLogAddedSubscriptionToken: string|undefined; + private _onLogRemovedSubscriptionToken: string|undefined; constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) { this._web3Wrapper = web3Wrapper; this._abiDecoder = abiDecoder; - const getBlockAsync = async (hash: string) => this._web3Wrapper.getBlockAsync(hash); - this._blockAndLogStreamer = new BlockAndLogStreamer( - this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), - this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + this._activeFilters = 0; + this._filters = {}; + this._filterCallbacks = {}; + this._onLogAddedSubscriptionToken = undefined; + this._onLogRemovedSubscriptionToken = undefined; + } + protected _subscribe(address: string, eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi, + callback: EventCallback): string { + const filter = filterUtils.getFilter( + this._web3Wrapper.keccak256.bind(this._web3Wrapper), address, eventName, indexFilterValues, abi, ); + if (_.isEmpty(this._filters)) { + this._startBlockAndLogStream(); + let removed = false; + this._onLogAddedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogAdded( + this._onLogStateChanged.bind(this, removed), + ); + removed = true; + this._onLogRemovedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogRemoved( + this._onLogStateChanged.bind(this, removed), + ); + } + const filterToken = filterUtils.generateUUID(); + this._filters[filterToken] = filter; + this._filterCallbacks[filterToken] = callback; + return filterToken; + } + protected _unsubscribe(filterToken: string): void { + if (_.isUndefined(this._filters[filterToken])) { + throw new Error(ZeroExError.SubscriptionNotFound); + } + delete this._filters[filterToken]; + delete this._filterCallbacks[filterToken]; + if (_.isEmpty(this._filters)) { + this._blockAndLogStreamer.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); + this._blockAndLogStreamer.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); + this._stopBlockAndLogStream(); + } } protected async _getLogsAsync(address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi): Promise { - const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi; - const eventSignature = this._getEventSignatureFromAbiByName(eventAbi, eventName); - const topicForEventSignature = this._web3Wrapper.keccak256(eventSignature); - const topicsForIndexedArgs = this._getTopicsForIndexedArgs(eventAbi, indexFilterValues); - const topics = [topicForEventSignature, ...topicsForIndexedArgs]; - const filter = { - fromBlock: subscriptionOpts.fromBlock, - toBlock: subscriptionOpts.toBlock, - address, - topics, - }; + const filter = filterUtils.getFilter( + this._web3Wrapper.keccak256.bind(this._web3Wrapper), address, eventName, indexFilterValues, abi, + subscriptionOpts, + ); const logs = await this._web3Wrapper.getLogsAsync(filter); const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); return logsWithDecodedArguments; @@ -62,27 +97,34 @@ export class ContractWrapper { await this._web3Wrapper.getContractInstanceFromArtifactAsync(artifact, addressIfExists); return contractInstance; } - protected _getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { - const types = _.map(eventAbi.inputs, 'type'); - const signature = `${eventAbi.name}(${types.join(',')})`; - return signature; - } - private _getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array { - const topics: Array = []; - for (const eventInput of abi.inputs) { - if (!eventInput.indexed) { - continue; + private _onLogStateChanged(removed: boolean, log: Web3.LogEntry): void { + _.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => { + if (filterUtils.matchesFilter(log, filter)) { + const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs; + const logEvent = { + ...decodedLog, + removed, + }; + this._filterCallbacks[filterToken](logEvent); } - if (_.isUndefined(indexFilterValues[eventInput.name])) { - topics.push(null); - } else { - const value = indexFilterValues[eventInput.name] as string; - const buffer = ethUtil.toBuffer(value); - const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); - const topic = ethUtil.bufferToHex(paddedBuffer); - topics.push(topic); - } - } - return topics; + }); + } + private _startBlockAndLogStream(): void { + this._blockAndLogStreamer = new BlockAndLogStreamer( + this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), + this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), + ); + this._blockAndLogStreamer.addLogFilter({}); + this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( + this._reconcileBlockAsync.bind(this), constants.DEFAULT_BLOCK_POLLING_INTERVAL, + ); + } + private _stopBlockAndLogStream(): void { + intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); + delete this._blockAndLogStreamer; + } + private async _reconcileBlockAsync(): Promise { + const latestBlock = await this._web3Wrapper.getBlockAsync('latest'); + this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block); } } diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 32eaa590c..e5f190864 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -16,11 +16,8 @@ import { SignedOrder, ContractEvent, ExchangeEvents, - ContractEventEmitter, SubscriptionOpts, IndexedFilterValues, - CreateContractEvent, - ContractEventObj, OrderCancellationRequest, OrderFillRequest, LogErrorContractEventArgs, @@ -31,10 +28,10 @@ import { ValidateOrderFillableOpts, OrderTransactionOpts, RawLog, + EventCallback, } from '../types'; import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; -import {eventUtils} from '../utils/event_utils'; import {OrderValidationUtils} from '../utils/order_validation_utils'; import {ContractWrapper} from './contract_wrapper'; import {constants} from '../utils/constants'; @@ -51,7 +48,7 @@ const SHOULD_VALIDATE_BY_DEFAULT = true; */ export class ExchangeWrapper extends ContractWrapper { private _exchangeContractIfExists?: ExchangeContract; - private _exchangeLogEventEmitters: ContractEventEmitter[]; + private _activeSubscriptions: string[]; private _orderValidationUtils: OrderValidationUtils; private _tokenWrapper: TokenWrapper; private _exchangeContractErrCodesToMsg = { @@ -86,7 +83,7 @@ export class ExchangeWrapper extends ContractWrapper { super(web3Wrapper, abiDecoder); this._tokenWrapper = tokenWrapper; this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this); - this._exchangeLogEventEmitters = []; + this._activeSubscriptions = []; this._contractAddressIfExists = contractAddressIfExists; } /** @@ -622,41 +619,32 @@ export class ExchangeWrapper extends ContractWrapper { return txHash; } /** - * Subscribe to an event type emitted by the Exchange smart contract - * @param eventName The exchange contract event you would like to subscribe to. - * @param subscriptionOpts Subscriptions options that let you configure the subscription. - * @param indexFilterValues An object where the keys are indexed args returned by the event and - * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` - * @param exchangeContractAddress The hex encoded address of the Exchange contract to call. - * @return ContractEventEmitter object + * Subscribe to an event type emitted by the Exchange contract. + * @param tokenAddress The hex encoded address where the ERC20 token is deployed. + * @param eventName The exchange contract event you would like to subscribe to. + * @param indexFilterValues An object where the keys are indexed args returned by the event and + * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed + * @return ContractEventEmitter object */ - public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts, - indexFilterValues: IndexedFilterValues, exchangeContractAddress: string): - Promise { - assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress); + public async subscribeAsync(eventName: ExchangeEvents, indexFilterValues: IndexedFilterValues, + callback: EventCallback): Promise { assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); - assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const exchangeContract = await this._getExchangeContractAsync(); - let createLogEvent: CreateContractEvent; - switch (eventName) { - case ExchangeEvents.LogFill: - createLogEvent = exchangeContract.LogFill; - break; - case ExchangeEvents.LogError: - createLogEvent = exchangeContract.LogError; - break; - case ExchangeEvents.LogCancel: - createLogEvent = exchangeContract.LogCancel; - break; - default: - throw utils.spawnSwitchErr('ExchangeEvents', eventName); - } - - const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts); - const eventEmitter = eventUtils.wrapEventEmitter(logEventObj); - this._exchangeLogEventEmitters.push(eventEmitter); - return eventEmitter; + const exchangeContractAddress = await this.getContractAddressAsync(); + const subscriptionToken = this._subscribe( + exchangeContractAddress, eventName, indexFilterValues, artifacts.ExchangeArtifact.abi, callback, + ); + this._activeSubscriptions.push(subscriptionToken); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + _.pull(this._activeSubscriptions, subscriptionToken); + this._unsubscribe(subscriptionToken); } /** * Gets historical logs without creating a subscription @@ -677,15 +665,6 @@ export class ExchangeWrapper extends ContractWrapper { ); return logs; } - /** - * Stops watching for all exchange events - */ - public async stopWatchingAllEventsAsync(): Promise { - const stopWatchingPromises = _.map(this._exchangeLogEventEmitters, - logEventObj => logEventObj.stopWatchingAsync()); - await Promise.all(stopWatchingPromises); - this._exchangeLogEventEmitters = []; - } /** * Retrieves the Ethereum address of the Exchange contract deployed on the network * that the user-passed web3 provider is connected to. @@ -809,8 +788,15 @@ export class ExchangeWrapper extends ContractWrapper { const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync(); return ZRXtokenAddress; } + /** + * Cancels all existing subscriptions + */ + public unsubscribeAll(): void { + _.forEach(this._activeSubscriptions, this._unsubscribe.bind(this)); + this._activeSubscriptions = []; + } private async _invalidateContractInstancesAsync(): Promise { - await this.stopWatchingAllEventsAsync(); + this.unsubscribeAll(); delete this._exchangeContractIfExists; } private async _isValidSignatureUsingContractCallAsync(dataHex: string, ecSignature: ECSignature, diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index f988e6ece..d2b1ccf96 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -4,7 +4,6 @@ import {SchemaValidator, schemas} from '0x-json-schemas'; import {Web3Wrapper} from '../web3_wrapper'; import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; -import {eventUtils} from '../utils/event_utils'; import {constants} from '../utils/constants'; import {ContractWrapper} from './contract_wrapper'; import {AbiDecoder} from '../utils/abi_decoder'; @@ -15,12 +14,10 @@ import { TokenEvents, IndexedFilterValues, SubscriptionOpts, - CreateContractEvent, - ContractEventEmitter, - ContractEventObj, MethodOpts, LogWithDecodedArgs, RawLog, + EventCallback, } from '../types'; const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155; @@ -33,13 +30,13 @@ const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155; export class TokenWrapper extends ContractWrapper { public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS; private _tokenContractsByAddress: {[address: string]: TokenContract}; - private _tokenLogEventEmitters: ContractEventEmitter[]; + private _activeSubscriptions: string[]; private _tokenTransferProxyContractAddressFetcher: () => Promise; constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, tokenTransferProxyContractAddressFetcher: () => Promise) { super(web3Wrapper, abiDecoder); this._tokenContractsByAddress = {}; - this._tokenLogEventEmitters = []; + this._activeSubscriptions = []; this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher; } /** @@ -251,34 +248,29 @@ export class TokenWrapper extends ContractWrapper { * Subscribe to an event type emitted by the Token contract. * @param tokenAddress The hex encoded address where the ERC20 token is deployed. * @param eventName The token contract event you would like to subscribe to. - * @param subscriptionOpts Subscriptions options that let you configure the subscription. * @param indexFilterValues An object where the keys are indexed args returned by the event and * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` + * @param callback Callback that gets called when a log is added/removed * @return ContractEventEmitter object */ - public async subscribeAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts, - indexFilterValues: IndexedFilterValues): Promise { + public subscribe(tokenAddress: string, eventName: TokenEvents, indexFilterValues: IndexedFilterValues, + callback: EventCallback): string { assert.isETHAddressHex('tokenAddress', tokenAddress); assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); - assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); - const tokenContract = await this._getTokenContractAsync(tokenAddress); - let createLogEvent: CreateContractEvent; - switch (eventName) { - case TokenEvents.Approval: - createLogEvent = tokenContract.Approval; - break; - case TokenEvents.Transfer: - createLogEvent = tokenContract.Transfer; - break; - default: - throw utils.spawnSwitchErr('TokenEvents', eventName); - } - - const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts); - const eventEmitter = eventUtils.wrapEventEmitter(logEventObj); - this._tokenLogEventEmitters.push(eventEmitter); - return eventEmitter; + const subscriptionToken = this._subscribe( + tokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, callback, + ); + this._activeSubscriptions.push(subscriptionToken); + return subscriptionToken; + } + /** + * Cancel a subscription + * @param subscriptionToken Subscription token returned by `subscribe()` + */ + public unsubscribe(subscriptionToken: string): void { + _.pull(this._activeSubscriptions, subscriptionToken); + this._unsubscribe(subscriptionToken); } /** * Gets historical logs without creating a subscription @@ -301,16 +293,14 @@ export class TokenWrapper extends ContractWrapper { return logs; } /** - * Stops watching for all token events + * Cancels all existing subscriptions */ - public async stopWatchingAllEventsAsync(): Promise { - const stopWatchingPromises = _.map(this._tokenLogEventEmitters, - logEventObj => logEventObj.stopWatchingAsync()); - await Promise.all(stopWatchingPromises); - this._tokenLogEventEmitters = []; + public unsubscribeAll(): void { + _.forEach(this._activeSubscriptions, this._unsubscribe.bind(this)); + this._activeSubscriptions = []; } - private async _invalidateContractInstancesAsync(): Promise { - await this.stopWatchingAllEventsAsync(); + private _invalidateContractInstancesAsync(): void { + this.unsubscribeAll(); this._tokenContractsByAddress = {}; } private async _getTokenContractAsync(tokenAddress: string): Promise { diff --git a/src/index.ts b/src/index.ts index 3359743e9..97ab084b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,6 @@ export { OrderFillOrKillRequest, OrderCancellationRequest, OrderFillRequest, - ContractEventEmitter, LogErrorContractEventArgs, LogCancelContractEventArgs, LogFillContractEventArgs, @@ -36,4 +35,5 @@ export { MethodOpts, OrderTransactionOpts, FilterObject, + LogEvent, } from './types'; diff --git a/src/types.ts b/src/types.ts index 35bb6af78..f0f37bfca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,6 +14,7 @@ export enum ZeroExError { InvalidJump = 'INVALID_JUMP', OutOfGas = 'OUT_OF_GAS', NoNetworkId = 'NO_NETWORK_ID', + SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', } export enum InternalZeroExError { @@ -35,23 +36,17 @@ export type OrderAddresses = [string, string, string, string, string]; export type OrderValues = [BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber, BigNumber.BigNumber]; -export type EventCallbackAsync = (err: Error, event: ContractEvent) => Promise; -export type EventCallbackSync = (err: Error, event: ContractEvent) => void; -export type EventCallback = EventCallbackSync|EventCallbackAsync; -export interface ContractEventObj { - watch: (eventWatch: EventCallback) => void; - stopWatching: () => void; +export interface LogEvent extends LogWithDecodedArgs { + removed: boolean; } -export type CreateContractEvent = (indexFilterValues: IndexedFilterValues, - subscriptionOpts: SubscriptionOpts) => ContractEventObj; +export type EventCallbackAsync = (log: LogEvent) => Promise; +export type EventCallbackSync = (log: LogEvent) => void; +export type EventCallback = EventCallbackSync|EventCallbackAsync; export interface ExchangeContract extends Web3.ContractInstance { isValidSignature: { callAsync: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string, txOpts?: TxOpts) => Promise; }; - LogFill: CreateContractEvent; - LogCancel: CreateContractEvent; - LogError: CreateContractEvent; ZRX_TOKEN_CONTRACT: { callAsync: () => Promise; }; @@ -137,8 +132,6 @@ export interface ExchangeContract extends Web3.ContractInstance { } export interface TokenContract extends Web3.ContractInstance { - Transfer: CreateContractEvent; - Approval: CreateContractEvent; balanceOf: { callAsync: (address: string, defaultBlock?: Web3.BlockParam) => Promise; }; @@ -378,11 +371,6 @@ export interface OrderFillRequest { export type AsyncMethod = (...args: any[]) => Promise; -export interface ContractEventEmitter { - watch: (eventCallback: EventCallback) => void; - stopWatchingAsync: () => Promise; -} - /** * We re-export the `Web3.Provider` type specified in the Web3 Typescript typings * since it is the type of the `provider` argument to the `ZeroEx` constructor. diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 1b11d7055..a066fe869 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -7,4 +7,5 @@ export const constants = { INVALID_JUMP_PATTERN: 'invalid JUMP at', OUT_OF_GAS_PATTERN: 'out of gas', UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1), + DEFAULT_BLOCK_POLLING_INTERVAL: 1000, }; diff --git a/src/utils/event_utils.ts b/src/utils/event_utils.ts deleted file mode 100644 index e8f30e1a8..000000000 --- a/src/utils/event_utils.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as _ from 'lodash'; -import {EventCallback, ContractEventArg, ContractEvent, ContractEventObj, ContractEventEmitter} from '../types'; -import * as BigNumber from 'bignumber.js'; -import promisify = require('es6-promisify'); - -export const eventUtils = { - wrapEventEmitter(event: ContractEventObj): ContractEventEmitter { - const watch = (eventCallback: EventCallback) => { - const bignumberWrappingEventCallback = eventUtils._getBigNumberWrappingEventCallback(eventCallback); - event.watch(bignumberWrappingEventCallback); - }; - const zeroExEvent = { - watch, - stopWatchingAsync: async () => { - await promisify(event.stopWatching, event)(); - }, - }; - return zeroExEvent; - }, - /** - * Wraps eventCallback function so that all the BigNumber arguments are wrapped in a newer version of BigNumber. - * @param eventCallback Event callback function to be wrapped - * @return Wrapped event callback function - */ - _getBigNumberWrappingEventCallback(eventCallback: EventCallback): EventCallback { - const bignumberWrappingEventCallback = (err: Error, event: ContractEvent) => { - if (_.isNull(err)) { - const wrapIfBigNumber = (value: ContractEventArg): ContractEventArg => { - // HACK: The old version of BigNumber used by Web3@0.19.0 does not support the `isBigNumber` - // and checking for a BigNumber instance using `instanceof` does not work either. We therefore - // check if the value constructor is a bignumber constructor. - const isWeb3BigNumber = _.startsWith(value.constructor.toString(), 'function BigNumber('); - return isWeb3BigNumber ? new BigNumber(value) : value; - }; - event.args = _.mapValues(event.args, wrapIfBigNumber); - } - eventCallback(err, event); - }; - return bignumberWrappingEventCallback; - }, -}; diff --git a/src/utils/filter_utils.ts b/src/utils/filter_utils.ts new file mode 100644 index 000000000..ee39b6836 --- /dev/null +++ b/src/utils/filter_utils.ts @@ -0,0 +1,80 @@ +import * as _ from 'lodash'; +import * as Web3 from 'web3'; +import * as uuid from 'uuid/v4'; +import * as ethUtil from 'ethereumjs-util'; +import {ContractEvents, IndexedFilterValues, SubscriptionOpts} from '../types'; + +const TOPIC_LENGTH = 32; + +export const filterUtils = { + generateUUID(): string { + return uuid(); + }, + getFilter(keccak256: (data: string) => string, address: string, eventName: ContractEvents, + indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi, + subscriptionOpts?: SubscriptionOpts): Web3.FilterObject { + const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi; + const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); + const topicForEventSignature = keccak256(eventSignature); + const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); + const topics = [topicForEventSignature, ...topicsForIndexedArgs]; + let filter: Web3.FilterObject = { + address, + topics, + }; + if (!_.isUndefined(subscriptionOpts)) { + filter = { + ...subscriptionOpts, + ...filter, + }; + } + return filter; + }, + getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string { + const types = _.map(eventAbi.inputs, 'type'); + const signature = `${eventAbi.name}(${types.join(',')})`; + return signature; + }, + getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array { + const topics: Array = []; + for (const eventInput of abi.inputs) { + if (!eventInput.indexed) { + continue; + } + if (_.isUndefined(indexFilterValues[eventInput.name])) { + topics.push(null); + } else { + const value = indexFilterValues[eventInput.name] as string; + const buffer = ethUtil.toBuffer(value); + const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH); + const topic = ethUtil.bufferToHex(paddedBuffer); + topics.push(topic); + } + } + return topics; + }, + matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean { + if (!_.isUndefined(filter.address) && log.address !== filter.address) { + return false; + } + if (!_.isUndefined(filter.topics)) { + return filterUtils.matchesTopics(log.topics, filter.topics); + } + return true; + }, + matchesTopics(logTopics: string[], filterTopics: Array): boolean { + const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils)); + const matchesTopics = _.every(matchesTopic); + return matchesTopics; + }, + matchesTopic(logTopic: string, filterTopic: string[]|string|null): boolean { + if (_.isArray(filterTopic)) { + return _.includes(filterTopic, logTopic); + } + if (_.isString(filterTopic)) { + return filterTopic === logTopic; + } + // null topic is a wildcard + return true; + }, +}; diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 71c5713ad..51d6957d5 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -19,6 +19,7 @@ import { OrderFillRequest, LogFillContractEventArgs, OrderFillOrKillRequest, + LogEvent, } from '../src'; import {DoneCallback} from '../src/types'; import {FillScenarios} from './utils/fill_scenarios'; @@ -616,7 +617,7 @@ describe('ExchangeWrapper', () => { }); }); }); - describe('#subscribeAsync', () => { + describe('#subscribe', () => { const indexFilterValues = {}; const shouldThrowOnInsufficientBalanceOrAllowance = true; let makerTokenAddress: string; @@ -626,10 +627,6 @@ describe('ExchangeWrapper', () => { let makerAddress: string; let fillableAmount: BigNumber.BigNumber; let signedOrder: SignedOrder; - const subscriptionOpts: SubscriptionOpts = { - fromBlock: 0, - toBlock: 'latest', - }; const fillTakerAmountInBaseUnits = new BigNumber(1); const cancelTakerAmountInBaseUnits = new BigNumber(1); before(() => { @@ -645,24 +642,22 @@ describe('ExchangeWrapper', () => { ); }); afterEach(async () => { - await zeroEx.exchange.stopWatchingAllEventsAsync(); + zeroEx.exchange.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribeAsync` callback, + // Since we need to await the receipt of the event in the `subscribe` callback, // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then // wrap the rest of the test in an async block // Source: https://github.com/mochajs/mocha/issues/2407 it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => { (async () => { - const zeroExEvent = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress, - ); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - expect(event.event).to.be.equal('LogFill'); + const callback = (logEvent: LogEvent) => { + expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill); done(); - }); + }; + await zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, callback, + ); await zeroEx.exchange.fillOrderAsync( signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, ); @@ -670,75 +665,53 @@ describe('ExchangeWrapper', () => { }); it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => { (async () => { - const zeroExEvent = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogCancel, subscriptionOpts, indexFilterValues, exchangeContractAddress, + const callback = (logEvent: LogEvent) => { + expect(logEvent.event).to.be.equal(ExchangeEvents.LogCancel); + done(); + }; + await zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogCancel, indexFilterValues, callback, ); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - expect(event.event).to.be.equal('LogCancel'); - done(); - }); await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits); })().catch(done); }); it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => { (async () => { - const eventSubscriptionToBeCancelled = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress, - ); - eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => { + const callbackNeverToBeCalled = (logEvent: LogEvent) => { done(new Error('Expected this subscription to have been cancelled')); - }); + }; + await zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled, + ); const newProvider = web3Factory.getRpcProvider(); await zeroEx.setProviderAsync(newProvider); - const eventSubscriptionToStay = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress, - ); - eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - expect(event.event).to.be.equal('LogFill'); + const callback = (logEvent: LogEvent) => { + expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill); done(); - }); - await zeroEx.exchange.fillOrderAsync( - signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, - ); - })().catch(done); - }); - it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => { - (async () => { - const eventSubscriptionToBeStopped = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress, + }; + await zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, callback, ); - eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => { - done(new Error('Expected this subscription to have been stopped')); - }); - await eventSubscriptionToBeStopped.stopWatchingAsync(); await zeroEx.exchange.fillOrderAsync( signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, ); - done(); })().catch(done); }); - it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => { + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { (async () => { - const zeroExEvent = await zeroEx.exchange.subscribeAsync( - ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress, + const callbackNeverToBeCalled = (logEvent: LogEvent) => { + done(new Error('Expected this subscription to have been cancelled')); + }; + const subscriptionToken = await zeroEx.exchange.subscribeAsync( + ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled, ); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - const args = event.args as LogFillContractEventArgs; - expect(args.filledMakerTokenAmount.isBigNumber).to.be.true(); - expect(args.filledTakerTokenAmount.isBigNumber).to.be.true(); - expect(args.paidMakerFee.isBigNumber).to.be.true(); - expect(args.paidTakerFee.isBigNumber).to.be.true(); - done(); - }); + zeroEx.exchange.unsubscribe(subscriptionToken); await zeroEx.exchange.fillOrderAsync( signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, ); + done(); })().catch(done); }); }); diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index da020f714..be97496e0 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -15,6 +15,7 @@ import { TransferContractEventArgs, ApprovalContractEventArgs, LogWithDecodedArgs, + LogEvent, } from '../src'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; import {TokenUtils} from './utils/token_utils'; @@ -336,22 +337,18 @@ describe('TokenWrapper', () => { return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); }); }); - describe('#subscribeAsync', () => { + describe('#subscribe', () => { const indexFilterValues = {}; const shouldThrowOnInsufficientBalanceOrAllowance = true; let tokenAddress: string; - const subscriptionOpts: SubscriptionOpts = { - fromBlock: 0, - toBlock: 'latest', - }; const transferAmount = new BigNumber(42); const allowanceAmount = new BigNumber(42); before(() => { const token = tokens[0]; tokenAddress = token.address; }); - afterEach(async () => { - await zeroEx.token.stopWatchingAllEventsAsync(); + afterEach(() => { + zeroEx.token.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback // Since we need to await the receipt of the event in the `subscribeAsync` callback, @@ -360,81 +357,66 @@ describe('TokenWrapper', () => { // Source: https://github.com/mochajs/mocha/issues/2407 it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => { (async () => { - const zeroExEvent = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - const args = event.args as TransferContractEventArgs; + const callback = (logEvent: LogEvent) => { + expect(logEvent).to.not.be.undefined(); + const args = logEvent.args as any as TransferContractEventArgs; expect(args._from).to.be.equal(coinbase); expect(args._to).to.be.equal(addressWithoutFunds); expect(args._value).to.be.bignumber.equal(transferAmount); done(); - }); + }; + zeroEx.token.subscribe( + tokenAddress, TokenEvents.Transfer, indexFilterValues, callback); await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); })().catch(done); }); it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => { (async () => { - const zeroExEvent = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Approval, subscriptionOpts, indexFilterValues); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - const args = event.args as ApprovalContractEventArgs; + const callback = (logEvent: LogEvent) => { + expect(logEvent).to.not.be.undefined(); + const args = logEvent.args as any as ApprovalContractEventArgs; expect(args._owner).to.be.equal(coinbase); expect(args._spender).to.be.equal(addressWithoutFunds); expect(args._value).to.be.bignumber.equal(allowanceAmount); done(); - }); + }; + zeroEx.token.subscribe( + tokenAddress, TokenEvents.Approval, indexFilterValues, callback); await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount); })().catch(done); }); it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => { (async () => { - const eventSubscriptionToBeCancelled = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues); - eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => { + const callbackNeverToBeCalled = (logEvent: LogEvent) => { done(new Error('Expected this subscription to have been cancelled')); - }); - + }; + zeroEx.token.subscribe( + tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled, + ); + const callbackToBeCalled = (logEvent: LogEvent) => { + done(); + }; const newProvider = web3Factory.getRpcProvider(); await zeroEx.setProviderAsync(newProvider); - - const eventSubscriptionToStay = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues); - eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => { - expect(err).to.be.null(); - expect(event).to.not.be.undefined(); - done(); - }); + zeroEx.token.subscribe( + tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled, + ); await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); })().catch(done); }); - it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => { + it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => { (async () => { - const eventSubscriptionToBeStopped = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues); - eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => { - done(new Error('Expected this subscription to have been stopped')); - }); - await eventSubscriptionToBeStopped.stopWatchingAsync(); + const callbackNeverToBeCalled = (logEvent: LogEvent) => { + done(new Error('Expected this subscription to have been cancelled')); + }; + const subscriptionToken = zeroEx.token.subscribe( + tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled); + zeroEx.token.unsubscribe(subscriptionToken); await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); done(); })().catch(done); }); - it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => { - (async () => { - const zeroExEvent = await zeroEx.token.subscribeAsync( - tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues); - zeroExEvent.watch((err: Error, event: ContractEvent) => { - const args = event.args as TransferContractEventArgs; - expect(args._value.isBigNumber).to.be.true(); - done(); - }); - await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount); - })().catch(done); - }); + // TODO test block reorgs }); describe('#getLogsAsync', () => { let tokenAddress: string; diff --git a/yarn.lock b/yarn.lock index 4d27d246c..577aa03c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -62,6 +62,12 @@ version "2.3.2" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.3.2.tgz#1e1e99e67162d78e2db17816892bf93bf5209885" +"@types/uuid@^3.4.2": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.2.tgz#b8cde3c32c273d3fc658b96f810e8ff091e1b723" + dependencies: + "@types/node" "*" + abbrev@1: version "1.1.0" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" @@ -1155,11 +1161,9 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2.9.0, commander@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" +commander@2.11.0, commander@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" commondir@^1.0.1: version "1.0.1" @@ -1321,11 +1325,11 @@ debug-log@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debug-log/-/debug-log-1.0.1.tgz#2307632d4c04382b8df8a32f70b895046d52745f" -debug@2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: - ms "0.7.2" + ms "2.0.0" debug@^2.1.1, debug@^2.2.0, debug@^2.3.3, debug@^2.6.3: version "2.6.8" @@ -1407,9 +1411,9 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -diff@3.2.0, diff@^3.1.0, diff@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" +diff@3.3.1, diff@^3.1.0, diff@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" diffie-hellman@^5.0.0: version "5.0.2" @@ -1604,6 +1608,15 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-plugin-node@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-5.2.0.tgz#e1efca04a385516cff3f2f04027ce8c5ae6db749" + dependencies: + ignore "^3.3.3" + minimatch "^3.0.4" + resolve "^1.3.3" + semver "5.3.0" + esprima@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" @@ -2120,18 +2133,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.2" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.0.0, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.2: +glob@7.1.2, glob@^7.0.0, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -2157,13 +2159,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" +growl@1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.2.tgz#8f16dfcd8fb7c04cfc1f4e0012e0ea858726979a" + dependencies: + eslint-plugin-node "^5.1.0" handlebars@^4.0.3, handlebars@^4.0.6: version "4.0.10" @@ -2196,6 +2196,10 @@ has-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2247,6 +2251,10 @@ hdkey@^0.7.0: coinstring "^2.0.0" secp256k1 "^3.0.1" +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + highlight.js@^9.0.0: version "9.12.0" resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" @@ -2301,6 +2309,10 @@ ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +ignore@^3.3.3: + version "3.3.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.5.tgz#c4e715455f6073a8d7e5dae72d2fc9d71663dba6" + immediate@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.2.3.tgz#d140fa8f614659bd6541233097ddaac25cdd991c" @@ -2680,10 +2692,6 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json3@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" - json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" @@ -2852,61 +2860,14 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash._baseassign@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" - dependencies: - lodash._basecopy "^3.0.0" - lodash.keys "^3.0.0" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - -lodash._basecreate@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - lodash.assign@^4.0.3, lodash.assign@^4.0.6: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" -lodash.create@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" - dependencies: - lodash._baseassign "^3.0.0" - lodash._basecreate "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - lodash@^3.3.1, lodash@^3.6.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" @@ -3155,25 +3116,20 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: dependencies: minimist "0.0.8" -mocha@^3.4.1: - version "3.4.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.4.2.tgz#d0ef4d332126dbf18d0d640c9b382dd48be97594" +mocha@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.0.0.tgz#3da718ccd76e93b9d82afb065e17086bdbe352bf" dependencies: browser-stdout "1.3.0" - commander "2.9.0" - debug "2.6.0" - diff "3.2.0" + commander "2.11.0" + debug "3.1.0" + diff "3.3.1" escape-string-regexp "1.0.5" - glob "7.1.1" - growl "1.9.2" - json3 "3.3.2" - lodash.create "3.1.1" + glob "7.1.2" + growl "1.10.2" + he "1.1.1" mkdirp "0.5.1" - supports-color "3.1.2" - -ms@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" + supports-color "4.4.0" ms@2.0.0: version "2.0.0" @@ -4017,7 +3973,7 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@^1.1.6, resolve@^1.3.2, resolve@~1.3.3: +resolve@^1.1.6, resolve@^1.3.2, resolve@^1.3.3, resolve@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" dependencies: @@ -4105,7 +4061,7 @@ semver-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-1.0.0.tgz#92a4969065f9c70c694753d55248fc68f8f652c9" -"semver@2 || 3 || 4 || 5", semver@^5.3.0: +"semver@2 || 3 || 4 || 5", semver@5.3.0, semver@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -4294,6 +4250,12 @@ source-map-support@^0.4.15, source-map-support@^0.4.2: dependencies: source-map "^0.5.6" +source-map-support@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab" + dependencies: + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -4308,6 +4270,10 @@ source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, sour version "0.5.6" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + spawn-wrap@^1.3.7: version "1.3.8" resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.8.tgz#fa2a79b990cbb0bb0018dca6748d88367b19ec31" @@ -4500,16 +4466,22 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -supports-color@3.1.2, supports-color@^3.1.0, supports-color@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" dependencies: - has-flag "^1.0.0" + has-flag "^2.0.0" supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" +supports-color@^3.1.0, supports-color@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" + dependencies: + has-flag "^1.0.0" + tapable@^0.2.5, tapable@~0.2.5: version "0.2.6" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" @@ -4886,7 +4858,7 @@ uuid@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" -uuid@^3.0.0: +uuid@^3.0.0, uuid@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -- cgit v1.2.3 From 1b6d3b0f0b193f5237210b92e72b898db8a92232 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 14:39:22 +0300 Subject: Add missing comments --- test/exchange_wrapper_test.ts | 3 ++- test/token_wrapper_test.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 51d6957d5..4bd216757 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -617,7 +617,7 @@ describe('ExchangeWrapper', () => { }); }); }); - describe('#subscribe', () => { + describe('#subscribeAsync', () => { const indexFilterValues = {}; const shouldThrowOnInsufficientBalanceOrAllowance = true; let makerTokenAddress: string; @@ -714,6 +714,7 @@ describe('ExchangeWrapper', () => { done(); })().catch(done); }); + // TODO test block reorgs and backfills }); describe('#getOrderHashHexUsingContractCallAsync', () => { let makerTokenAddress: string; diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index be97496e0..6ada6588c 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -416,7 +416,7 @@ describe('TokenWrapper', () => { done(); })().catch(done); }); - // TODO test block reorgs + // TODO test block reorgs and backfills }); describe('#getLogsAsync', () => { let tokenAddress: string; -- cgit v1.2.3 From a537b2e40ca08187138851094c781394af4a2f2d Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 14:47:45 +0300 Subject: Add missing comment --- src/contract_wrappers/contract_wrapper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 6f22f5bdb..b6b4aa4be 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -125,6 +125,7 @@ export class ContractWrapper { } private async _reconcileBlockAsync(): Promise { const latestBlock = await this._web3Wrapper.getBlockAsync('latest'); + // We need to coerce to Block type cause Web3.Block includes types for mempool bloks this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block); } } -- cgit v1.2.3 From f2100fa36dae740735f9a2e2811cc7977f4c286c Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 14:48:45 +0300 Subject: Remove missing comment --- src/contract_wrappers/exchange_wrapper.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index e5f190864..ab5bc15ee 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -620,7 +620,6 @@ export class ExchangeWrapper extends ContractWrapper { } /** * Subscribe to an event type emitted by the Exchange contract. - * @param tokenAddress The hex encoded address where the ERC20 token is deployed. * @param eventName The exchange contract event you would like to subscribe to. * @param indexFilterValues An object where the keys are indexed args returned by the event and * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` -- cgit v1.2.3 From 118381c1d10315c82a526d94c86e60b569ad3b45 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 15:16:51 +0300 Subject: Move more logic into _stopBlockAndLogStream and _startBlockAndLogStream --- src/contract_wrappers/contract_wrapper.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index b6b4aa4be..ee989c700 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -46,14 +46,6 @@ export class ContractWrapper { ); if (_.isEmpty(this._filters)) { this._startBlockAndLogStream(); - let removed = false; - this._onLogAddedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogAdded( - this._onLogStateChanged.bind(this, removed), - ); - removed = true; - this._onLogRemovedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogRemoved( - this._onLogStateChanged.bind(this, removed), - ); } const filterToken = filterUtils.generateUUID(); this._filters[filterToken] = filter; @@ -67,8 +59,6 @@ export class ContractWrapper { delete this._filters[filterToken]; delete this._filterCallbacks[filterToken]; if (_.isEmpty(this._filters)) { - this._blockAndLogStreamer.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); - this._blockAndLogStreamer.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); this._stopBlockAndLogStream(); } } @@ -114,12 +104,23 @@ export class ContractWrapper { this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper), this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper), ); - this._blockAndLogStreamer.addLogFilter({}); + const catchAllLogFilter = {}; + this._blockAndLogStreamer.addLogFilter(catchAllLogFilter); this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval( this._reconcileBlockAsync.bind(this), constants.DEFAULT_BLOCK_POLLING_INTERVAL, ); + let removed = false; + this._onLogAddedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogAdded( + this._onLogStateChanged.bind(this, removed), + ); + removed = true; + this._onLogRemovedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogRemoved( + this._onLogStateChanged.bind(this, removed), + ); } private _stopBlockAndLogStream(): void { + this._blockAndLogStreamer.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); + this._blockAndLogStreamer.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); delete this._blockAndLogStreamer; } -- cgit v1.2.3 From 553cbb25f4d8b8ae43156eb21d3358d5b1350c68 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 15:26:38 +0300 Subject: Fix comments --- src/contract_wrappers/exchange_wrapper.ts | 2 +- src/contract_wrappers/token_wrapper.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index ab5bc15ee..0eb600ff6 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -624,7 +624,7 @@ export class ExchangeWrapper extends ContractWrapper { * @param indexFilterValues An object where the keys are indexed args returned by the event and * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` * @param callback Callback that gets called when a log is added/removed - * @return ContractEventEmitter object + * @return Subscription token used later to unsubscribe */ public async subscribeAsync(eventName: ExchangeEvents, indexFilterValues: IndexedFilterValues, callback: EventCallback): Promise { diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index d2b1ccf96..05852742a 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -251,7 +251,7 @@ export class TokenWrapper extends ContractWrapper { * @param indexFilterValues An object where the keys are indexed args returned by the event and * the value is the value you are interested in. E.g `{maker: aUserAddressHex}` * @param callback Callback that gets called when a log is added/removed - * @return ContractEventEmitter object + * @return Subscription token used later to unsubscribe */ public subscribe(tokenAddress: string, eventName: TokenEvents, indexFilterValues: IndexedFilterValues, callback: EventCallback): string { -- cgit v1.2.3 From 7bcedc27b8ed4d710ffc8c4f589211106475d899 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 15:43:46 +0300 Subject: Update CHANGELOG --- CHANGELOG.md | 16 +++++++++++++++- test/token_wrapper_test.ts | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f19bfe03..6b6a5386a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,20 @@ # CHANGELOG -v0.20.0 - _October 3, 2017_ +v0.21.0 - _TBD, 2017_ +------------------------ + * Complete rewrite of subscription logic (#182) + * Subscriptions now can't return historical logs. If you want them - use `getLogsAsync` + * Subscriptions now use [ethereumjs-blockstream](https://github.com/ethereumjs/ethereumjs-blockstream) under the hood + * Subscriptions now correctly handle block re-orgs (forks) + * Subscriptions now correctly backfill logs (connection problems) + * They don't setup filters on the underlying nodes, so you can use them with infura without a filter Subprovider + * Removed `ContractEventEmitter` and added `LogEvent` + * Renamed `zeroEx.token.subscribeAsync` to `zeroEx.token.subscribe` + * Added `zeroEx.token.unsubscribe` and `zeroEx.exchange.unsubscribe` + * Renamed `zeroEx.exchange.stopWatchingAllEventsAsync` to `zeroEx.exhange.unsubscribeAll` + * Renamed `zeroEx.token.stopWatchingAllEventsAsync` to `zeroEx.token.unsubscribeAll` + +v0.20.0 - _October 5, 2017_ ------------------------ * Add `zeroEx.token.getLogsAsync` (#178) * Add `zeroEx.exchange.getLogsAsync` (#178) diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index 6ada6588c..eac540d03 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -351,7 +351,7 @@ describe('TokenWrapper', () => { zeroEx.token.unsubscribeAll(); }); // Hack: Mocha does not allow a test to be both async and have a `done` callback - // Since we need to await the receipt of the event in the `subscribeAsync` callback, + // Since we need to await the receipt of the event in the `subscribe` callback, // we do need both. A hack is to make the top-level a sync fn w/ a done callback and then // wrap the rest of the test in an async block // Source: https://github.com/mochajs/mocha/issues/2407 -- cgit v1.2.3 From 721d969a85f9a3f19542e22c71ec3d43c73510c2 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 15:56:28 +0300 Subject: Make it possible to have multiple layers of snapshots --- test/utils/blockchain_lifecycle.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/utils/blockchain_lifecycle.ts b/test/utils/blockchain_lifecycle.ts index 50eb57b95..9fdf0e856 100644 --- a/test/utils/blockchain_lifecycle.ts +++ b/test/utils/blockchain_lifecycle.ts @@ -2,19 +2,22 @@ import {RPC} from './rpc'; export class BlockchainLifecycle { private rpc: RPC; - private snapshotId: number; + private snapshotIdsStack: number[]; constructor() { this.rpc = new RPC(); + this.snapshotIdsStack = []; } // TODO: In order to run these tests on an actual node, we should check if we are running against // TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test public async startAsync(): Promise { - this.snapshotId = await this.rpc.takeSnapshotAsync(); + const snapshotId = await this.rpc.takeSnapshotAsync(); + this.snapshotIdsStack.push(snapshotId); } public async revertAsync(): Promise { - const didRevert = await this.rpc.revertSnapshotAsync(this.snapshotId); + const snapshotId = this.snapshotIdsStack.pop() as number; + const didRevert = await this.rpc.revertSnapshotAsync(snapshotId); if (!didRevert) { - throw new Error(`Snapshot with id #${this.snapshotId} failed to revert`); + throw new Error(`Snapshot with id #${snapshotId} failed to revert`); } } } -- cgit v1.2.3 From 209c31f361743bd511e6bb2c494ceeb02e52a7d9 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 16:28:20 +0300 Subject: Remove TODOs --- test/exchange_wrapper_test.ts | 1 - test/token_wrapper_test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 4bd216757..371f96954 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -714,7 +714,6 @@ describe('ExchangeWrapper', () => { done(); })().catch(done); }); - // TODO test block reorgs and backfills }); describe('#getOrderHashHexUsingContractCallAsync', () => { let makerTokenAddress: string; diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index eac540d03..17f4ac875 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -416,7 +416,6 @@ describe('TokenWrapper', () => { done(); })().catch(done); }); - // TODO test block reorgs and backfills }); describe('#getLogsAsync', () => { let tokenAddress: string; -- cgit v1.2.3 From 1414b8ee8bdb1be901371c4cf7b8a5150d8fb771 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 16:32:01 +0300 Subject: Add type assertions for callback parameters --- src/contract_wrappers/exchange_wrapper.ts | 1 + src/contract_wrappers/token_wrapper.ts | 1 + src/utils/assert.ts | 3 +++ 3 files changed, 5 insertions(+) diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index 0eb600ff6..b2e48b36e 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -630,6 +630,7 @@ export class ExchangeWrapper extends ContractWrapper { callback: EventCallback): Promise { assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); const exchangeContractAddress = await this.getContractAddressAsync(); const subscriptionToken = this._subscribe( exchangeContractAddress, eventName, indexFilterValues, artifacts.ExchangeArtifact.abi, callback, diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index 05852742a..ee49e8539 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -258,6 +258,7 @@ export class TokenWrapper extends ContractWrapper { assert.isETHAddressHex('tokenAddress', tokenAddress); assert.doesBelongToStringEnum('eventName', eventName, TokenEvents); assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema); + assert.isFunction('callback', callback); const subscriptionToken = this._subscribe( tokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, callback, ); diff --git a/src/utils/assert.ts b/src/utils/assert.ts index eb084129c..0b7a11939 100644 --- a/src/utils/assert.ts +++ b/src/utils/assert.ts @@ -17,6 +17,9 @@ export const assert = { isString(variableName: string, value: string): void { this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value)); }, + isFunction(variableName: string, value: any): void { + this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value)); + }, isHexString(variableName: string, value: string): void { this.assert(_.isString(value) && HEX_REGEX.test(value), this.typeAssertionMessage(variableName, 'HexString', value)); -- cgit v1.2.3 From a406b4d134e729bceba81a98f5756a8759f18655 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 5 Oct 2017 17:34:30 +0300 Subject: Remove unused imports --- src/0x.ts | 9 --------- src/contract_wrappers/contract_wrapper.ts | 1 - src/contract_wrappers/exchange_wrapper.ts | 4 +--- src/contract_wrappers/token_wrapper.ts | 4 +--- src/subproviders/empty_wallet_subprovider.ts | 1 - src/utils/abi_decoder.ts | 2 +- 6 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/0x.ts b/src/0x.ts index 3180c52f6..9f955c807 100644 --- a/src/0x.ts +++ b/src/0x.ts @@ -1,12 +1,8 @@ import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import * as Web3 from 'web3'; -import * as abiDecoder from 'abi-decoder'; import {SchemaValidator, schemas} from '0x-json-schemas'; import {bigNumberConfigs} from './bignumber_config'; import * as ethUtil from 'ethereumjs-util'; -import findVersions = require('find-versions'); -import compareVersions = require('compare-versions'); import {Web3Wrapper} from './web3_wrapper'; import {constants} from './utils/constants'; import {utils} from './utils/utils'; @@ -27,12 +23,7 @@ import { SignedOrder, Web3Provider, ZeroExConfig, - TransactionReceipt, - DecodedLogArgs, TransactionReceiptWithDecodedLogs, - LogWithDecodedArgs, - FilterObject, - RawLog, } from './types'; import {zeroExConfigSchema} from './schemas/zero_ex_config_schema'; diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index ee989c700..4ae3d1449 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -14,7 +14,6 @@ import { IndexedFilterValues, EventCallback, } from '../types'; -import {utils} from '../utils/utils'; import {constants} from '../utils/constants'; import {intervalUtils} from '../utils/interval_utils'; import {filterUtils} from '../utils/filter_utils'; diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index b2e48b36e..5f02903ce 100644 --- a/src/contract_wrappers/exchange_wrapper.ts +++ b/src/contract_wrappers/exchange_wrapper.ts @@ -1,7 +1,6 @@ import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import {SchemaValidator, schemas} from '0x-json-schemas'; -import promisify = require('es6-promisify'); +import {schemas} from '0x-json-schemas'; import {Web3Wrapper} from '../web3_wrapper'; import { ECSignature, @@ -34,7 +33,6 @@ import {assert} from '../utils/assert'; import {utils} from '../utils/utils'; import {OrderValidationUtils} from '../utils/order_validation_utils'; import {ContractWrapper} from './contract_wrapper'; -import {constants} from '../utils/constants'; import {TokenWrapper} from './token_wrapper'; import {decorators} from '../utils/decorators'; import {AbiDecoder} from '../utils/abi_decoder'; diff --git a/src/contract_wrappers/token_wrapper.ts b/src/contract_wrappers/token_wrapper.ts index ee49e8539..abd090f7e 100644 --- a/src/contract_wrappers/token_wrapper.ts +++ b/src/contract_wrappers/token_wrapper.ts @@ -1,9 +1,8 @@ import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import {SchemaValidator, schemas} from '0x-json-schemas'; +import {schemas} from '0x-json-schemas'; import {Web3Wrapper} from '../web3_wrapper'; import {assert} from '../utils/assert'; -import {utils} from '../utils/utils'; import {constants} from '../utils/constants'; import {ContractWrapper} from './contract_wrapper'; import {AbiDecoder} from '../utils/abi_decoder'; @@ -16,7 +15,6 @@ import { SubscriptionOpts, MethodOpts, LogWithDecodedArgs, - RawLog, EventCallback, } from '../types'; diff --git a/src/subproviders/empty_wallet_subprovider.ts b/src/subproviders/empty_wallet_subprovider.ts index 0d037042d..2f260217c 100644 --- a/src/subproviders/empty_wallet_subprovider.ts +++ b/src/subproviders/empty_wallet_subprovider.ts @@ -1,4 +1,3 @@ -import * as Web3 from 'web3'; import {JSONRPCPayload} from '../types'; /* diff --git a/src/utils/abi_decoder.ts b/src/utils/abi_decoder.ts index 542591251..52b114c12 100644 --- a/src/utils/abi_decoder.ts +++ b/src/utils/abi_decoder.ts @@ -1,7 +1,7 @@ import * as Web3 from 'web3'; import * as _ from 'lodash'; import * as BigNumber from 'bignumber.js'; -import {AbiType, DecodedLogArgs, DecodedArgs, LogWithDecodedArgs, RawLog, SolidityTypes} from '../types'; +import {AbiType, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes} from '../types'; import * as SolidityCoder from 'web3/lib/solidity/coder'; export class AbiDecoder { -- cgit v1.2.3 From 6af2ba5cffa582130b1c50639cabd9d6385dbd9e Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 12:30:27 +0300 Subject: Remove _activeFilters --- src/contract_wrappers/contract_wrapper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 4ae3d1449..f3ebae871 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -23,7 +23,6 @@ export class ContractWrapper { private _abiDecoder?: AbiDecoder; private _blockAndLogStreamer: BlockAndLogStreamer; private _blockAndLogStreamInterval: NodeJS.Timer; - private _activeFilters: number; private _filters: {[filterToken: string]: Web3.FilterObject}; private _filterCallbacks: {[filterToken: string]: EventCallback}; private _onLogAddedSubscriptionToken: string|undefined; @@ -31,7 +30,6 @@ export class ContractWrapper { constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) { this._web3Wrapper = web3Wrapper; this._abiDecoder = abiDecoder; - this._activeFilters = 0; this._filters = {}; this._filterCallbacks = {}; this._onLogAddedSubscriptionToken = undefined; -- cgit v1.2.3 From 1043def46c137c9d308b3201eddc0d08afa34b10 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 12:58:17 +0300 Subject: Install js-sha3 and use it for keccak256 --- package.json | 1 + src/contract_wrappers/contract_wrapper.ts | 9 ++------- src/utils/filter_utils.ts | 5 +++-- src/web3_wrapper.ts | 4 ---- yarn.lock | 4 ++++ 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 59b0c9cf9..941ccc504 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "ethereumjs-blockstream": "^2.0.6", "ethereumjs-util": "^5.1.1", "find-versions": "^2.0.0", + "js-sha3": "^0.6.1", "lodash": "^4.17.4", "publish-release": "^1.3.3", "uuid": "^3.1.0", diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index f3ebae871..0bd7cc8b1 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -38,9 +38,7 @@ export class ContractWrapper { protected _subscribe(address: string, eventName: ContractEvents, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi, callback: EventCallback): string { - const filter = filterUtils.getFilter( - this._web3Wrapper.keccak256.bind(this._web3Wrapper), address, eventName, indexFilterValues, abi, - ); + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); if (_.isEmpty(this._filters)) { this._startBlockAndLogStream(); } @@ -62,10 +60,7 @@ export class ContractWrapper { protected async _getLogsAsync(address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi): Promise { - const filter = filterUtils.getFilter( - this._web3Wrapper.keccak256.bind(this._web3Wrapper), address, eventName, indexFilterValues, abi, - subscriptionOpts, - ); + const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, subscriptionOpts); const logs = await this._web3Wrapper.getLogsAsync(filter); const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this)); return logsWithDecodedArguments; diff --git a/src/utils/filter_utils.ts b/src/utils/filter_utils.ts index ee39b6836..2bd745815 100644 --- a/src/utils/filter_utils.ts +++ b/src/utils/filter_utils.ts @@ -2,6 +2,7 @@ import * as _ from 'lodash'; import * as Web3 from 'web3'; import * as uuid from 'uuid/v4'; import * as ethUtil from 'ethereumjs-util'; +import * as jsSHA3 from 'js-sha3'; import {ContractEvents, IndexedFilterValues, SubscriptionOpts} from '../types'; const TOPIC_LENGTH = 32; @@ -10,12 +11,12 @@ export const filterUtils = { generateUUID(): string { return uuid(); }, - getFilter(keccak256: (data: string) => string, address: string, eventName: ContractEvents, + getFilter(address: string, eventName: ContractEvents, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi, subscriptionOpts?: SubscriptionOpts): Web3.FilterObject { const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi; const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); - const topicForEventSignature = keccak256(eventSignature); + const topicForEventSignature = jsSHA3.keccak256(eventSignature); const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); const topics = [topicForEventSignature, ...topicsForIndexedArgs]; let filter: Web3.FilterObject = { diff --git a/src/web3_wrapper.ts b/src/web3_wrapper.ts index fd9b74b8b..9de75c809 100644 --- a/src/web3_wrapper.ts +++ b/src/web3_wrapper.ts @@ -116,10 +116,6 @@ export class Web3Wrapper { const logs = await this.sendRawPayloadAsync(payload); return logs; } - public keccak256(data: string): string { - const hash = this.web3.sha3(data); - return hash; - } private getContractInstance(abi: Web3.ContractAbi, address: string): A { const web3ContractInstance = this.web3.eth.contract(abi).at(address); const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A; diff --git a/yarn.lock b/yarn.lock index 577aa03c1..ab9b520f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2637,6 +2637,10 @@ js-sha3@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" +js-sha3@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" + js-tokens@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -- cgit v1.2.3 From 44e2929a4c3cec6666a2e3ad340028aff2884f69 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:01:08 +0300 Subject: Check for blockAndLogStreamer to be undefined instead of th filters object to be empty --- src/contract_wrappers/contract_wrapper.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 0bd7cc8b1..925537173 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -21,7 +21,7 @@ import {filterUtils} from '../utils/filter_utils'; export class ContractWrapper { protected _web3Wrapper: Web3Wrapper; private _abiDecoder?: AbiDecoder; - private _blockAndLogStreamer: BlockAndLogStreamer; + private _blockAndLogStreamer: BlockAndLogStreamer|undefined; private _blockAndLogStreamInterval: NodeJS.Timer; private _filters: {[filterToken: string]: Web3.FilterObject}; private _filterCallbacks: {[filterToken: string]: EventCallback}; @@ -32,6 +32,7 @@ export class ContractWrapper { this._abiDecoder = abiDecoder; this._filters = {}; this._filterCallbacks = {}; + this._blockAndLogStreamer = undefined; this._onLogAddedSubscriptionToken = undefined; this._onLogRemovedSubscriptionToken = undefined; } @@ -39,7 +40,7 @@ export class ContractWrapper { indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi, callback: EventCallback): string { const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi); - if (_.isEmpty(this._filters)) { + if (_.isUndefined(this._blockAndLogStreamer)) { this._startBlockAndLogStream(); } const filterToken = filterUtils.generateUUID(); @@ -111,14 +112,16 @@ export class ContractWrapper { ); } private _stopBlockAndLogStream(): void { - this._blockAndLogStreamer.unsubscribeFromOnLogAdded(this._onLogAddedSubscriptionToken as string); - this._blockAndLogStreamer.unsubscribeFromOnLogRemoved(this._onLogRemovedSubscriptionToken as string); + (this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogAdded( + this._onLogAddedSubscriptionToken as string); + (this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogRemoved( + this._onLogRemovedSubscriptionToken as string); intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval); delete this._blockAndLogStreamer; } private async _reconcileBlockAsync(): Promise { const latestBlock = await this._web3Wrapper.getBlockAsync('latest'); // We need to coerce to Block type cause Web3.Block includes types for mempool bloks - this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block); + (this._blockAndLogStreamer as BlockAndLogStreamer).reconcileNewBlock(latestBlock as any as Block); } } -- cgit v1.2.3 From 637183e4b214cc7a215ebf83d18c1a65263d8e7e Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:04:51 +0300 Subject: introduce BlockParamLiteral --- src/contract_wrappers/contract_wrapper.ts | 3 ++- src/types.ts | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 925537173..0b65445c1 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -13,6 +13,7 @@ import { SubscriptionOpts, IndexedFilterValues, EventCallback, + BlockParamLiteral, } from '../types'; import {constants} from '../utils/constants'; import {intervalUtils} from '../utils/interval_utils'; @@ -120,7 +121,7 @@ export class ContractWrapper { delete this._blockAndLogStreamer; } private async _reconcileBlockAsync(): Promise { - const latestBlock = await this._web3Wrapper.getBlockAsync('latest'); + const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); // We need to coerce to Block type cause Web3.Block includes types for mempool bloks (this._blockAndLogStreamer as BlockAndLogStreamer).reconcileNewBlock(latestBlock as any as Block); } diff --git a/src/types.ts b/src/types.ts index f0f37bfca..44094f442 100644 --- a/src/types.ts +++ b/src/types.ts @@ -345,7 +345,13 @@ export interface IndexedFilterValues { [index: string]: ContractEventArg; } -export type BlockParam = 'latest'|'earliest'|'pending'|number; +export enum BlockParamLiteral { + Latest = 'latest', + Earliest = 'earliest', + Pending = 'pending', +} + +export type BlockParam = BlockParamLiteral|number; export interface SubscriptionOpts { fromBlock: BlockParam; -- cgit v1.2.3 From 292aab9b1820a3eae52089e92672f12d5dfe1899 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:07:01 +0300 Subject: Use BlockParamLiteral types --- test/exchange_wrapper_test.ts | 6 +++--- test/token_wrapper_test.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts index 371f96954..afc33f1f4 100644 --- a/test/exchange_wrapper_test.ts +++ b/test/exchange_wrapper_test.ts @@ -21,7 +21,7 @@ import { OrderFillOrKillRequest, LogEvent, } from '../src'; -import {DoneCallback} from '../src/types'; +import {DoneCallback, BlockParamLiteral} from '../src/types'; import {FillScenarios} from './utils/fill_scenarios'; import {TokenUtils} from './utils/token_utils'; import {assert} from '../src/utils/assert'; @@ -752,8 +752,8 @@ describe('ExchangeWrapper', () => { const fillableAmount = new BigNumber(5); const shouldThrowOnInsufficientBalanceOrAllowance = true; const subscriptionOpts: SubscriptionOpts = { - fromBlock: 'earliest', - toBlock: 'latest', + fromBlock: BlockParamLiteral.Earliest, + toBlock: BlockParamLiteral.Latest, }; let txHash: string; before(async () => { diff --git a/test/token_wrapper_test.ts b/test/token_wrapper_test.ts index 17f4ac875..50f2db2ac 100644 --- a/test/token_wrapper_test.ts +++ b/test/token_wrapper_test.ts @@ -19,7 +19,7 @@ import { } from '../src'; import {BlockchainLifecycle} from './utils/blockchain_lifecycle'; import {TokenUtils} from './utils/token_utils'; -import {DoneCallback} from '../src/types'; +import {DoneCallback, BlockParamLiteral} from '../src/types'; chaiSetup.configure(); const expect = chai.expect; @@ -421,8 +421,8 @@ describe('TokenWrapper', () => { let tokenAddress: string; let tokenTransferProxyAddress: string; const subscriptionOpts: SubscriptionOpts = { - fromBlock: 'earliest', - toBlock: 'latest', + fromBlock: BlockParamLiteral.Earliest, + toBlock: BlockParamLiteral.Latest, }; let txHash: string; before(async () => { -- cgit v1.2.3 From 498cf5333dbd6990d156926b80016bf4fc7b473c Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:08:07 +0300 Subject: Fix a typo --- src/contract_wrappers/contract_wrapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 0b65445c1..492d40a28 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -122,7 +122,7 @@ export class ContractWrapper { } private async _reconcileBlockAsync(): Promise { const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); - // We need to coerce to Block type cause Web3.Block includes types for mempool bloks + // We need to coerce to Block type cause Web3.Block includes types for mempool blocks (this._blockAndLogStreamer as BlockAndLogStreamer).reconcileNewBlock(latestBlock as any as Block); } } -- cgit v1.2.3 From cfa75ed36c47a1180f338bf52c8c91544bcd8a9d Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:09:14 +0300 Subject: Add a comment --- src/utils/filter_utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/filter_utils.ts b/src/utils/filter_utils.ts index 2bd745815..c056467c7 100644 --- a/src/utils/filter_utils.ts +++ b/src/utils/filter_utils.ts @@ -43,6 +43,7 @@ export const filterUtils = { continue; } if (_.isUndefined(indexFilterValues[eventInput.name])) { + // Null is a wildcard topic in a JSON-RPC call topics.push(null); } else { const value = indexFilterValues[eventInput.name] as string; -- cgit v1.2.3 From a2cc127ea97907cd83acbd2cc49fb7892c6b11de Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:10:45 +0300 Subject: Fix comments --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6a5386a..2c160ee47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,11 @@ v0.21.0 - _TBD, 2017_ ------------------------ * Complete rewrite of subscription logic (#182) - * Subscriptions now can't return historical logs. If you want them - use `getLogsAsync` + * Subscriptions no longer return historical logs. If you want them - use `getLogsAsync` * Subscriptions now use [ethereumjs-blockstream](https://github.com/ethereumjs/ethereumjs-blockstream) under the hood - * Subscriptions now correctly handle block re-orgs (forks) - * Subscriptions now correctly backfill logs (connection problems) - * They don't setup filters on the underlying nodes, so you can use them with infura without a filter Subprovider + * Subscriptions correctly handle block re-orgs (forks) + * Subscriptions correctly backfill logs (connection problems) + * They no longer setup filters on the underlying nodes, so you can use them with infura without a filter Subprovider * Removed `ContractEventEmitter` and added `LogEvent` * Renamed `zeroEx.token.subscribeAsync` to `zeroEx.token.subscribe` * Added `zeroEx.token.unsubscribe` and `zeroEx.exchange.unsubscribe` -- cgit v1.2.3 From 81297b44c6392e1cb995848a5f81bdc3ce6108be Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 13:15:11 +0300 Subject: Add undefined check --- src/contract_wrappers/contract_wrapper.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/contract_wrappers/contract_wrapper.ts b/src/contract_wrappers/contract_wrapper.ts index 492d40a28..f6ccfdee4 100644 --- a/src/contract_wrappers/contract_wrapper.ts +++ b/src/contract_wrappers/contract_wrapper.ts @@ -123,6 +123,9 @@ export class ContractWrapper { private async _reconcileBlockAsync(): Promise { const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest); // We need to coerce to Block type cause Web3.Block includes types for mempool blocks - (this._blockAndLogStreamer as BlockAndLogStreamer).reconcileNewBlock(latestBlock as any as Block); + if (!_.isUndefined(this._blockAndLogStreamer)) { + // If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined + this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block); + } } } -- cgit v1.2.3 From 0c112a2a1c1811e1fc7c4b03881f960b952c788c Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 6 Oct 2017 15:13:31 +0300 Subject: Add a hex prefix --- src/utils/filter_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/filter_utils.ts b/src/utils/filter_utils.ts index c056467c7..e09a95a6e 100644 --- a/src/utils/filter_utils.ts +++ b/src/utils/filter_utils.ts @@ -16,7 +16,7 @@ export const filterUtils = { subscriptionOpts?: SubscriptionOpts): Web3.FilterObject { const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi; const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName); - const topicForEventSignature = jsSHA3.keccak256(eventSignature); + const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature)); const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues); const topics = [topicForEventSignature, ...topicsForIndexedArgs]; let filter: Web3.FilterObject = { -- cgit v1.2.3