From fc3be810a25ce98402c686bd41606e08e7b5c2ac Mon Sep 17 00:00:00 2001 From: Fabio Berger Date: Fri, 2 Jun 2017 12:31:16 +0200 Subject: Implement zeroEx.exchange.subscribeAsync made sure to clean up subscriptions if user updates the provider --- src/0x.js.ts | 4 +-- src/contract_wrappers/exchange_wrapper.ts | 46 ++++++++++++++++++++++++++++++- src/types.ts | 41 +++++++++++++++++++++++++++ src/utils/utils.ts | 3 ++ test/0x.js_test.ts | 6 ++-- 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/0x.js.ts b/src/0x.js.ts index 967c81ed8..0cda7732d 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/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts index f32cb88a6..b394edb06 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, @@ -11,8 +12,16 @@ import { OrderAddresses, SignedOrder, ContractEvent, + ZeroExError, + ExchangeEvents, + SubscriptionOpts, + IndexFilterValues, + CreateContractEvent, + ContractEventObj, + EventCallback, } 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'; @@ -32,12 +41,15 @@ export class ExchangeWrapper extends ContractWrapper { [ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.ORDER_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 { + await this.stopWatchingExchangeLogEventsAsync(); delete this.exchangeContractIfExists; } public async isValidSignatureAsync(dataHex: string, ecSignature: ECSignature, @@ -159,6 +171,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 validateFillOrderAsync(signedOrder: SignedOrder, fillAmount: BigNumber.BigNumber, senderAddress: string) { if (fillAmount.eq(0)) { diff --git a/src/types.ts b/src/types.ts index 29f7e0ee4..c6ad68d3f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -51,6 +51,22 @@ export interface ExchangeContract { }; } +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 type CreateContractEvent = (indexFilterValues: IndexFilterValues, + subscriptionOpts: SubscriptionOpts) => ContractEventObj; +export interface ExchangeContract { + isValidSignature: any; + LogFill: CreateContractEvent; + LogCancel: CreateContractEvent; + LogError: CreateContractEvent; +} + export interface TokenContract { balanceOf: { call: (address: string) => Promise; @@ -110,6 +126,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; } @@ -151,3 +174,21 @@ export interface TxOpts { 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 efc703ea1..52580171b 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.use(ChaiBigNumber()); chai.use(dirtyChai); @@ -16,7 +16,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 @@ -28,7 +28,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(); -- cgit v1.2.3