aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonid <logvinov.leon@gmail.com>2017-10-06 20:24:42 +0800
committerGitHub <noreply@github.com>2017-10-06 20:24:42 +0800
commitf38d2f80a6e3af4ff7e2454d42abdf2a389e4303 (patch)
tree9e2f94e7d8236f7612628288d7eab3333d240b6b
parentcd5327bc31618b4ea268de1902a74cf862987ee6 (diff)
parent0c112a2a1c1811e1fc7c4b03881f960b952c788c (diff)
downloaddexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar.gz
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar.bz2
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar.lz
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar.xz
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.tar.zst
dexon-sol-tools-f38d2f80a6e3af4ff7e2454d42abdf2a389e4303.zip
Merge pull request #182 from 0xProject/feature/ethereumjs-blockstream
Rewrite subscriptions
-rw-r--r--CHANGELOG.md16
-rw-r--r--package.json6
-rw-r--r--src/0x.ts9
-rw-r--r--src/contract_wrappers/contract_wrapper.ts120
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts86
-rw-r--r--src/contract_wrappers/token_wrapper.ts67
-rw-r--r--src/index.ts2
-rw-r--r--src/subproviders/empty_wallet_subprovider.ts1
-rw-r--r--src/types.ts32
-rw-r--r--src/utils/abi_decoder.ts2
-rw-r--r--src/utils/assert.ts3
-rw-r--r--src/utils/constants.ts1
-rw-r--r--src/utils/event_utils.ts41
-rw-r--r--src/utils/filter_utils.ts82
-rw-r--r--src/web3_wrapper.ts12
-rw-r--r--test/exchange_wrapper_test.ts99
-rw-r--r--test/token_wrapper_test.ts95
-rw-r--r--test/utils/blockchain_lifecycle.ts11
-rw-r--r--yarn.lock206
19 files changed, 458 insertions, 433 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f19bfe03..2c160ee47 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 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 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`
+ * 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/package.json b/package.json
index e4d47d594..99dfcc9fa 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",
@@ -89,16 +90,19 @@
"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",
+ "js-sha3": "^0.6.1",
"lodash": "^4.17.4",
"publish-release": "^1.3.3",
+ "uuid": "^3.1.0",
"web3": "^0.20.0"
}
}
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 743dfc9b2..f6ccfdee4 100644
--- a/src/contract_wrappers/contract_wrapper.ts
+++ b/src/contract_wrappers/contract_wrapper.ts
@@ -1,9 +1,10 @@
import * as _ from 'lodash';
import * as Web3 from 'web3';
-import * as ethUtil from 'ethereumjs-util';
+import {BlockAndLogStreamer, Block} from 'ethereumjs-blockstream';
import {Web3Wrapper} from '../web3_wrapper';
import {AbiDecoder} from '../utils/abi_decoder';
import {
+ ZeroExError,
InternalZeroExError,
Artifact,
LogWithDecodedArgs,
@@ -11,32 +12,57 @@ import {
ContractEvents,
SubscriptionOpts,
IndexedFilterValues,
+ EventCallback,
+ BlockParamLiteral,
} 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|undefined;
+ private _blockAndLogStreamInterval: NodeJS.Timer;
+ 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;
+ this._filters = {};
+ this._filterCallbacks = {};
+ this._blockAndLogStreamer = undefined;
+ this._onLogAddedSubscriptionToken = undefined;
+ this._onLogRemovedSubscriptionToken = undefined;
+ }
+ protected _subscribe(address: string, eventName: ContractEvents,
+ indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
+ callback: EventCallback): string {
+ const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
+ if (_.isUndefined(this._blockAndLogStreamer)) {
+ this._startBlockAndLogStream();
+ }
+ 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._stopBlockAndLogStream();
+ }
}
protected async _getLogsAsync(address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts,
indexFilterValues: IndexedFilterValues,
abi: Web3.ContractAbi): Promise<LogWithDecodedArgs[]> {
- 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(address, eventName, indexFilterValues, abi, subscriptionOpts);
const logs = await this._web3Wrapper.getLogsAsync(filter);
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
return logsWithDecodedArguments;
@@ -55,27 +81,51 @@ export class ContractWrapper {
await this._web3Wrapper.getContractInstanceFromArtifactAsync<A>(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<string|null> {
- const topics: Array<string|null> = [];
- 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);
+ 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);
}
+ });
+ }
+ private _startBlockAndLogStream(): void {
+ this._blockAndLogStreamer = new BlockAndLogStreamer(
+ this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
+ this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
+ );
+ 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 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<void> {
+ const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
+ // We need to coerce to Block type cause Web3.Block includes types for mempool blocks
+ 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);
}
- return topics;
}
}
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index 32eaa590c..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,
@@ -16,11 +15,8 @@ import {
SignedOrder,
ContractEvent,
ExchangeEvents,
- ContractEventEmitter,
SubscriptionOpts,
IndexedFilterValues,
- CreateContractEvent,
- ContractEventObj,
OrderCancellationRequest,
OrderFillRequest,
LogErrorContractEventArgs,
@@ -31,13 +27,12 @@ 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';
import {TokenWrapper} from './token_wrapper';
import {decorators} from '../utils/decorators';
import {AbiDecoder} from '../utils/abi_decoder';
@@ -51,7 +46,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 +81,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 +617,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 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 Subscription token used later to unsubscribe
*/
- public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts,
- indexFilterValues: IndexedFilterValues, exchangeContractAddress: string):
- Promise<ContractEventEmitter> {
- assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
+ public async subscribeAsync(eventName: ExchangeEvents, indexFilterValues: IndexedFilterValues,
+ callback: EventCallback): Promise<string> {
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;
+ assert.isFunction('callback', callback);
+ 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
@@ -678,15 +664,6 @@ export class ExchangeWrapper extends ContractWrapper {
return logs;
}
/**
- * Stops watching for all exchange events
- */
- public async stopWatchingAllEventsAsync(): Promise<void> {
- 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.
* @returns The Ethereum address of the Exchange contract being used.
@@ -809,8 +786,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<void> {
- 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..abd090f7e 100644
--- a/src/contract_wrappers/token_wrapper.ts
+++ b/src/contract_wrappers/token_wrapper.ts
@@ -1,10 +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 {eventUtils} from '../utils/event_utils';
import {constants} from '../utils/constants';
import {ContractWrapper} from './contract_wrapper';
import {AbiDecoder} from '../utils/abi_decoder';
@@ -15,12 +13,9 @@ import {
TokenEvents,
IndexedFilterValues,
SubscriptionOpts,
- CreateContractEvent,
- ContractEventEmitter,
- ContractEventObj,
MethodOpts,
LogWithDecodedArgs,
- RawLog,
+ EventCallback,
} from '../types';
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
@@ -33,13 +28,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<string>;
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
super(web3Wrapper, abiDecoder);
this._tokenContractsByAddress = {};
- this._tokenLogEventEmitters = [];
+ this._activeSubscriptions = [];
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
}
/**
@@ -251,34 +246,30 @@ 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}`
- * @return ContractEventEmitter object
+ * @param callback Callback that gets called when a log is added/removed
+ * @return Subscription token used later to unsubscribe
*/
- public async subscribeAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
- indexFilterValues: IndexedFilterValues): Promise<ContractEventEmitter> {
+ 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;
+ assert.isFunction('callback', callback);
+ 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 +292,14 @@ export class TokenWrapper extends ContractWrapper {
return logs;
}
/**
- * Stops watching for all token events
+ * Cancels all existing subscriptions
*/
- public async stopWatchingAllEventsAsync(): Promise<void> {
- 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<void> {
- await this.stopWatchingAllEventsAsync();
+ private _invalidateContractInstancesAsync(): void {
+ this.unsubscribeAll();
this._tokenContractsByAddress = {};
}
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
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/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/types.ts b/src/types.ts
index 35bb6af78..44094f442 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<void>;
-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<void>;
+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<boolean>;
};
- LogFill: CreateContractEvent;
- LogCancel: CreateContractEvent;
- LogError: CreateContractEvent;
ZRX_TOKEN_CONTRACT: {
callAsync: () => Promise<string>;
};
@@ -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<BigNumber.BigNumber>;
};
@@ -352,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;
@@ -378,11 +377,6 @@ export interface OrderFillRequest {
export type AsyncMethod = (...args: any[]) => Promise<any>;
-export interface ContractEventEmitter {
- watch: (eventCallback: EventCallback) => void;
- stopWatchingAsync: () => Promise<void>;
-}
-
/**
* 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/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 {
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));
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..e09a95a6e
--- /dev/null
+++ b/src/utils/filter_utils.ts
@@ -0,0 +1,82 @@
+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;
+
+export const filterUtils = {
+ generateUUID(): string {
+ return uuid();
+ },
+ 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 = ethUtil.addHexPrefix(jsSHA3.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<string|null> {
+ const topics: Array<string|null> = [];
+ for (const eventInput of abi.inputs) {
+ if (!eventInput.indexed) {
+ 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;
+ 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<string[]|string|null>): 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/src/web3_wrapper.ts b/src/web3_wrapper.ts
index 7576f3d40..9de75c809 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<number> {
- const {timestamp} = await promisify(this.web3.eth.getBlock)(blockHash);
+ public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
+ const block = await promisify(this.web3.eth.getBlock)(blockParam);
+ return block;
+ }
+ public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise<number> {
+ const {timestamp} = await this.getBlockAsync(blockParam);
return timestamp;
}
public async getAvailableAddressesAsync(): Promise<string[]> {
@@ -112,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<A extends Web3.ContractInstance>(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/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts
index 71c5713ad..afc33f1f4 100644
--- a/test/exchange_wrapper_test.ts
+++ b/test/exchange_wrapper_test.ts
@@ -19,8 +19,9 @@ import {
OrderFillRequest,
LogFillContractEventArgs,
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';
@@ -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);
});
});
@@ -779,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 da020f714..50f2db2ac 100644
--- a/test/token_wrapper_test.ts
+++ b/test/token_wrapper_test.ts
@@ -15,10 +15,11 @@ import {
TransferContractEventArgs,
ApprovalContractEventArgs,
LogWithDecodedArgs,
+ LogEvent,
} 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;
@@ -336,112 +337,92 @@ 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,
+ // 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 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);
- });
});
describe('#getLogsAsync', () => {
let tokenAddress: string;
let tokenTransferProxyAddress: string;
const subscriptionOpts: SubscriptionOpts = {
- fromBlock: 'earliest',
- toBlock: 'latest',
+ fromBlock: BlockParamLiteral.Earliest,
+ toBlock: BlockParamLiteral.Latest,
};
let txHash: string;
before(async () => {
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<void> {
- this.snapshotId = await this.rpc.takeSnapshotAsync();
+ const snapshotId = await this.rpc.takeSnapshotAsync();
+ this.snapshotIdsStack.push(snapshotId);
}
public async revertAsync(): Promise<void> {
- 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`);
}
}
}
diff --git a/yarn.lock b/yarn.lock
index 2d3fbb2fc..ab9b520f0 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"
@@ -1675,6 +1688,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"
@@ -2112,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:
@@ -2149,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"
@@ -2188,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"
@@ -2239,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"
@@ -2293,10 +2309,18 @@ 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"
+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"
@@ -2613,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"
@@ -2668,10 +2696,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"
@@ -2840,61 +2864,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"
@@ -3143,25 +3120,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"
@@ -4005,7 +3977,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:
@@ -4093,7 +4065,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"
@@ -4270,12 +4242,24 @@ 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"
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"
@@ -4290,6 +4274,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"
@@ -4482,16 +4470,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"
@@ -4860,11 +4854,15 @@ 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"
-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"