aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/0x.js.ts4
-rw-r--r--src/artifacts/EtherToken.json2
-rw-r--r--src/artifacts/Mintable.json2
-rw-r--r--src/artifacts/Proxy.json2
-rw-r--r--src/artifacts/Token.json2
-rw-r--r--src/artifacts/TokenRegistry.json2
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts46
-rw-r--r--src/types.ts45
-rw-r--r--src/utils/utils.ts3
-rw-r--r--test/0x.js_test.ts6
-rw-r--r--test/exchange_wrapper_test.ts86
11 files changed, 187 insertions, 13 deletions
diff --git a/src/0x.js.ts b/src/0x.js.ts
index 40290467a..850827fee 100644
--- a/src/0x.js.ts
+++ b/src/0x.js.ts
@@ -115,9 +115,9 @@ export class ZeroEx {
/**
* Sets a new provider for the web3 instance used by 0x.js
*/
- public setProvider(provider: Web3.Provider) {
+ public async setProviderAsync(provider: Web3.Provider) {
this.web3Wrapper.setProvider(provider);
- this.exchange.invalidateContractInstance();
+ await this.exchange.invalidateContractInstanceAsync();
this.tokenRegistry.invalidateContractInstance();
this.token.invalidateContractInstances();
}
diff --git a/src/artifacts/EtherToken.json b/src/artifacts/EtherToken.json
index 0593cdc95..ae79d6bf5 100644
--- a/src/artifacts/EtherToken.json
+++ b/src/artifacts/EtherToken.json
@@ -336,4 +336,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1495042008609
-} \ No newline at end of file
+}
diff --git a/src/artifacts/Mintable.json b/src/artifacts/Mintable.json
index 026f294f2..b508eaee7 100644
--- a/src/artifacts/Mintable.json
+++ b/src/artifacts/Mintable.json
@@ -186,4 +186,4 @@
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1495030728122
-} \ No newline at end of file
+}
diff --git a/src/artifacts/Proxy.json b/src/artifacts/Proxy.json
index 0d6faef35..d02804096 100644
--- a/src/artifacts/Proxy.json
+++ b/src/artifacts/Proxy.json
@@ -264,4 +264,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1495042008598
-} \ No newline at end of file
+}
diff --git a/src/artifacts/Token.json b/src/artifacts/Token.json
index 3bbcead41..4843cc2ca 100644
--- a/src/artifacts/Token.json
+++ b/src/artifacts/Token.json
@@ -173,4 +173,4 @@
"networks": {},
"schema_version": "0.0.5",
"updated_at": 1495030728125
-} \ No newline at end of file
+}
diff --git a/src/artifacts/TokenRegistry.json b/src/artifacts/TokenRegistry.json
index f9f4fafbc..6ecdfa079 100644
--- a/src/artifacts/TokenRegistry.json
+++ b/src/artifacts/TokenRegistry.json
@@ -999,4 +999,4 @@
},
"schema_version": "0.0.5",
"updated_at": 1495042008599
-} \ No newline at end of file
+}
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index cb869b498..b67fd33ac 100644
--- a/src/contract_wrappers/exchange_wrapper.ts
+++ b/src/contract_wrappers/exchange_wrapper.ts
@@ -1,5 +1,6 @@
import * as _ from 'lodash';
import * as BigNumber from 'bignumber.js';
+import promisify = require('es6-promisify');
import {Web3Wrapper} from '../web3_wrapper';
import {
ECSignature,
@@ -10,9 +11,17 @@ import {
OrderAddresses,
SignedOrder,
ContractEvent,
+ ZeroExError,
+ ExchangeEvents,
+ SubscriptionOpts,
+ IndexFilterValues,
+ CreateContractEvent,
+ ContractEventObj,
+ EventCallback,
ContractResponse,
} from '../types';
import {assert} from '../utils/assert';
+import {utils} from '../utils/utils';
import {ContractWrapper} from './contract_wrapper';
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
@@ -31,12 +40,15 @@ export class ExchangeWrapper extends ContractWrapper {
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FILL_BALANCE_ALLOWANCE_ERROR,
};
private exchangeContractIfExists?: ExchangeContract;
+ private exchangeLogEventObjs: ContractEventObj[];
private tokenWrapper: TokenWrapper;
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
super(web3Wrapper);
this.tokenWrapper = tokenWrapper;
+ this.exchangeLogEventObjs = [];
}
- public invalidateContractInstance(): void {
+ public async invalidateContractInstanceAsync(): Promise<void> {
+ await this.stopWatchingExchangeLogEventsAsync();
delete this.exchangeContractIfExists;
}
public async isValidSignatureAsync(dataHex: string, ecSignature: ECSignature,
@@ -160,6 +172,38 @@ export class ExchangeWrapper extends ContractWrapper {
);
this.throwErrorLogsAsErrors(response.logs);
}
+ /**
+ * Subscribe to an event type emitted by the Exchange smart contract
+ */
+ public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts,
+ indexFilterValues: IndexFilterValues, callback: EventCallback) {
+ 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:
+ utils.spawnSwitchErr('ExchangeEvents', eventName);
+ return;
+ }
+
+ const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
+ logEventObj.watch(callback);
+ this.exchangeLogEventObjs.push(logEventObj);
+ }
+ private async stopWatchingExchangeLogEventsAsync() {
+ for (const logEventObj of this.exchangeLogEventObjs) {
+ await promisify(logEventObj.stopWatching, logEventObj)();
+ }
+ this.exchangeLogEventObjs = [];
+ }
private async validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
fillTakerAmount: BigNumber.BigNumber,
senderAddress: string): Promise<void> {
diff --git a/src/types.ts b/src/types.ts
index 46156b155..b5430a783 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -54,6 +54,22 @@ export interface ExchangeContract {
cancelled: {
call: (orderHash: string) => 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 type CreateContractEvent = (indexFilterValues: IndexFilterValues,
+ subscriptionOpts: SubscriptionOpts) => ContractEventObj;
+export interface ExchangeContract {
+ isValidSignature: any;
+ LogFill: CreateContractEvent;
+ LogCancel: CreateContractEvent;
+ LogError: CreateContractEvent;
ZRX: {
call: () => Promise<string>;
};
@@ -117,6 +133,13 @@ export interface ContractResponse {
}
export interface ContractEvent {
+ logIndex: number;
+ transactionIndex: number;
+ transactionHash: string;
+ blockHash: string;
+ blockNumber: number;
+ address: string;
+ type: string;
event: string;
args: any;
}
@@ -154,3 +177,25 @@ export interface TxOpts {
from: string;
gas?: number;
}
+
+export interface TokenAddressBySymbol {
+ [symbol: string]: string;
+}
+
+export const ExchangeEvents = strEnum([
+ 'LogFill',
+ 'LogCancel',
+ 'LogError',
+]);
+export type ExchangeEvents = keyof typeof ExchangeEvents;
+
+export interface IndexFilterValues {
+ [index: string]: any;
+}
+
+export interface SubscriptionOpts {
+ fromBlock: string|number;
+ toBlock: string|number;
+}
+
+export type DoneCallback = (err?: Error) => void;
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index e6840a624..114b46f6c 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -22,4 +22,7 @@ export const utils = {
const isValid = /^0x[0-9A-F]{64}$/i.test(orderHashHex);
return isValid;
},
+ spawnSwitchErr(name: string, value: any) {
+ return new Error(`Unexpected switch value: ${value} encountered for ${name}`);
+ },
};
diff --git a/test/0x.js_test.ts b/test/0x.js_test.ts
index 9196d3df3..8686b42eb 100644
--- a/test/0x.js_test.ts
+++ b/test/0x.js_test.ts
@@ -8,7 +8,7 @@ import * as Sinon from 'sinon';
import {ZeroEx} from '../src/0x.js';
import {constants} from './utils/constants';
import {web3Factory} from './utils/web3_factory';
-import {Order} from '../src/types';
+import {Order, DoneCallback} from '../src/types';
chai.config.includeStack = true;
chai.use(ChaiBigNumber());
@@ -17,7 +17,7 @@ const expect = chai.expect;
describe('ZeroEx library', () => {
describe('#setProvider', () => {
- it('overrides the provider in the nested web3 instance and invalidates contractInstances', async () => {
+ it('overrides provider in nested web3s and invalidates contractInstances', async () => {
const web3 = web3Factory.create();
const zeroEx = new ZeroEx(web3);
// Instantiate the contract instances with the current provider
@@ -29,7 +29,7 @@ describe('ZeroEx library', () => {
const newProvider = web3Factory.getRpcProvider();
// Add property to newProvider so that we can differentiate it from old provider
(newProvider as any).zeroExTestId = 1;
- zeroEx.setProvider(newProvider);
+ await zeroEx.setProviderAsync(newProvider);
// Check that contractInstances with old provider are removed after provider update
expect((zeroEx.exchange as any).exchangeContractIfExists).to.be.undefined();
diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts
index 0f683f811..a44d2728a 100644
--- a/test/exchange_wrapper_test.ts
+++ b/test/exchange_wrapper_test.ts
@@ -9,8 +9,15 @@ import promisify = require('es6-promisify');
import {web3Factory} from './utils/web3_factory';
import {ZeroEx} from '../src/0x.js';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
-import {orderFactory} from './utils/order_factory';
-import {Token, SignedOrder, ExchangeContractErrs} from '../src/types';
+import {
+ Token,
+ SignedOrder,
+ SubscriptionOpts,
+ ExchangeEvents,
+ ContractEvent,
+ DoneCallback,
+ ExchangeContractErrs,
+} from '../src/types';
import {FillScenarios} from './utils/fill_scenarios';
import {TokenUtils} from './utils/token_utils';
@@ -126,6 +133,7 @@ describe('ExchangeWrapper', () => {
const shouldCheckTransfer = false;
before(async () => {
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
+ tokens = await zeroEx.tokenRegistry.getTokensAsync();
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
@@ -396,4 +404,78 @@ describe('ExchangeWrapper', () => {
});
});
});
+ describe('#subscribeAsync', () => {
+ const indexFilterValues = {};
+ const shouldCheckTransfer = false;
+ let makerTokenAddress: string;
+ let takerTokenAddress: string;
+ let coinbase: string;
+ let takerAddress: string;
+ let makerAddress: string;
+ let fillableAmount: BigNumber.BigNumber;
+ let signedOrder: SignedOrder;
+ before(() => {
+ [coinbase, makerAddress, takerAddress] = userAddresses;
+ const [makerToken, takerToken] = tokens;
+ makerTokenAddress = makerToken.address;
+ takerTokenAddress = takerToken.address;
+ });
+ beforeEach(async () => {
+ fillableAmount = new BigNumber(5);
+ signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ });
+ afterEach(async () => {
+ (zeroEx.exchange as any).stopWatchingExchangeLogEventsAsync();
+ });
+ // 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,
+ // 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 subscriptionOpts: SubscriptionOpts = {
+ fromBlock: 0,
+ toBlock: 'latest',
+ };
+ await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
+ indexFilterValues, (err: Error, event: ContractEvent) => {
+ expect(err).to.be.null();
+ expect(event).to.not.be.undefined();
+ done();
+ });
+ const fillTakerAmountInBaseUnits = new BigNumber(1);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer);
+ })();
+ });
+ it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
+ (async () => {
+ const subscriptionOpts: SubscriptionOpts = {
+ fromBlock: 0,
+ toBlock: 'latest',
+ };
+ await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
+ indexFilterValues, (err: Error, event: ContractEvent) => {
+ done(new Error('Expected this subscription to have been cancelled'));
+ });
+
+ const newProvider = web3Factory.getRpcProvider();
+ await zeroEx.setProviderAsync(newProvider);
+
+ await zeroEx.exchange.subscribeAsync(ExchangeEvents.LogFill, subscriptionOpts,
+ indexFilterValues, (err: Error, event: ContractEvent) => {
+ expect(err).to.be.null();
+ expect(event).to.not.be.undefined();
+ done();
+ });
+
+ const fillTakerAmountInBaseUnits = new BigNumber(1);
+ zeroEx.setTransactionSenderAccount(takerAddress);
+ await zeroEx.exchange.fillOrderAsync(signedOrder, fillTakerAmountInBaseUnits, shouldCheckTransfer);
+ })();
+ });
+ });
});