From 57ca611e12d2e40c3f0f33023544a890be7ccb87 Mon Sep 17 00:00:00 2001 From: Ara Kevonian <=> Date: Fri, 30 Mar 2018 05:45:24 -0700 Subject: Monitor different state layers with OrderWatcher Allow instantiation of stand-alone OrderWatchers that can monitor different blockchain state layers (e.g. pending or latest) --- packages/0x.js/CHANGELOG.md | 3 + packages/0x.js/src/0x.ts | 28 +++---- packages/0x.js/src/order_watcher/event_watcher.ts | 8 +- .../0x.js/src/order_watcher/order_state_watcher.ts | 8 +- packages/0x.js/src/types.ts | 1 + packages/0x.js/test/order_state_watcher_test.ts | 89 ++++++++++++---------- 6 files changed, 77 insertions(+), 60 deletions(-) diff --git a/packages/0x.js/CHANGELOG.md b/packages/0x.js/CHANGELOG.md index ca6b985d3..ab656349f 100644 --- a/packages/0x.js/CHANGELOG.md +++ b/packages/0x.js/CHANGELOG.md @@ -3,6 +3,9 @@ ## v0.34.0 - _TBD_ * Fix the bug causing `zeroEx.exchange.fillOrdersUpToAsync` validation to fail if there were some extra orders passed (#470) + * Remove automatic instantiation of `zeroEx.orderStateWatcher` + * Add `zeroEx.createOrderStateWatcher` to allow creating arbitrary number of OrderStateWatchers + * Added `stateLayer` setting to `OrderStateWatcherConfig` so OrderStateWatcher can be set to monitor different blockchain state layers ## v0.33.2 - _March 18, 2018_ diff --git a/packages/0x.js/src/0x.ts b/packages/0x.js/src/0x.ts index 0dd728ff1..418d0ba34 100644 --- a/packages/0x.js/src/0x.ts +++ b/packages/0x.js/src/0x.ts @@ -15,7 +15,7 @@ import { OrderStateWatcher } from './order_watcher/order_state_watcher'; import { zeroExConfigSchema } from './schemas/zero_ex_config_schema'; import { zeroExPrivateNetworkConfigSchema } from './schemas/zero_ex_private_network_config_schema'; import { zeroExPublicNetworkConfigSchema } from './schemas/zero_ex_public_network_config_schema'; -import { Web3Provider, ZeroExConfig, ZeroExError } from './types'; +import { OrderStateWatcherConfig, ZeroExConfig, ZeroExError } from './types'; import { assert } from './utils/assert'; import { constants } from './utils/constants'; import { decorators } from './utils/decorators'; @@ -57,11 +57,6 @@ export class ZeroEx { * tokenTransferProxy smart contract. */ public proxy: TokenTransferProxyWrapper; - /** - * An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant - * blockchain state changes. - */ - public orderStateWatcher: OrderStateWatcher; private _web3Wrapper: Web3Wrapper; private _abiDecoder: AbiDecoder; /** @@ -197,13 +192,6 @@ export class ZeroEx { config.tokenRegistryContractAddress, ); this.etherToken = new EtherTokenWrapper(this._web3Wrapper, config.networkId, this._abiDecoder, this.token); - this.orderStateWatcher = new OrderStateWatcher( - this._web3Wrapper, - this._abiDecoder, - this.token, - this.exchange, - config.orderWatcherConfig, - ); } /** * Sets a new web3 provider for 0x.js. Updating the provider will stop all @@ -336,6 +324,20 @@ export class ZeroEx { const txReceipt = await txReceiptPromise; return txReceipt; } + /** + * Instantiates and returns a new OrderStateWatcher instance. + * @param config The configuration object. Look up the type for the description. + * @return An instance of the 0x.js OrderStateWatcher class. + */ + public createOrderStateWatcher(config?: OrderStateWatcherConfig) { + return new OrderStateWatcher( + this._web3Wrapper, + this._abiDecoder, + this.token, + this.exchange, + config, + ); + } /* * HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from * an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle. diff --git a/packages/0x.js/src/order_watcher/event_watcher.ts b/packages/0x.js/src/order_watcher/event_watcher.ts index 246ab8292..deb1ffbff 100644 --- a/packages/0x.js/src/order_watcher/event_watcher.ts +++ b/packages/0x.js/src/order_watcher/event_watcher.ts @@ -22,8 +22,10 @@ export class EventWatcher { private _pollingIntervalMs: number; private _intervalIdIfExists?: NodeJS.Timer; private _lastEvents: LogEntry[] = []; - constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number) { + private _stateLayer: BlockParamLiteral; + constructor(web3Wrapper: Web3Wrapper, pollingIntervalIfExistsMs: undefined | number, stateLayer: BlockParamLiteral = BlockParamLiteral.Pending) { this._web3Wrapper = web3Wrapper; + this._stateLayer = stateLayer; this._pollingIntervalMs = _.isUndefined(pollingIntervalIfExistsMs) ? DEFAULT_EVENT_POLLING_INTERVAL_MS : pollingIntervalIfExistsMs; @@ -69,8 +71,8 @@ export class EventWatcher { } private async _getEventsAsync(): Promise { const eventFilter = { - fromBlock: BlockParamLiteral.Pending, - toBlock: BlockParamLiteral.Pending, + fromBlock: this._stateLayer, + toBlock: this._stateLayer, }; const events = await this._web3Wrapper.getLogsAsync(eventFilter); return events; diff --git a/packages/0x.js/src/order_watcher/order_state_watcher.ts b/packages/0x.js/src/order_watcher/order_state_watcher.ts index 9cccadb7f..fcf3c351d 100644 --- a/packages/0x.js/src/order_watcher/order_state_watcher.ts +++ b/packages/0x.js/src/order_watcher/order_state_watcher.ts @@ -76,6 +76,7 @@ export class OrderStateWatcher { private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; private _cleanupJobInterval: number; private _cleanupJobIntervalIdIfExists?: NodeJS.Timer; + private _stateLayer: BlockParamLiteral; constructor( web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, @@ -86,10 +87,13 @@ export class OrderStateWatcher { this._abiDecoder = abiDecoder; this._web3Wrapper = web3Wrapper; const pollingIntervalIfExistsMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; - this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs); + this._stateLayer = _.isUndefined(config) || _.isUndefined(config.stateLayer) + ? BlockParamLiteral.Pending + : config.stateLayer; + this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs, this._stateLayer); this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore( token, - BlockParamLiteral.Pending, + this._stateLayer, ); this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); this._orderStateUtils = new OrderStateUtils( diff --git a/packages/0x.js/src/types.ts b/packages/0x.js/src/types.ts index 38cfb6306..a13004720 100644 --- a/packages/0x.js/src/types.ts +++ b/packages/0x.js/src/types.ts @@ -175,6 +175,7 @@ export interface OrderStateWatcherConfig { eventPollingIntervalMs?: number; expirationMarginMs?: number; cleanupJobIntervalMs?: number; + stateLayer: BlockParamLiteral; } /* diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts index d08272c3b..4f727d495 100644 --- a/packages/0x.js/test/order_state_watcher_test.ts +++ b/packages/0x.js/test/order_state_watcher_test.ts @@ -15,6 +15,9 @@ import { ZeroEx, ZeroExError, } from '../src'; +import { + OrderStateWatcher, +} from '../src/order_watcher/order_state_watcher'; import { DoneCallback } from '../src/types'; import { chaiSetup } from './utils/chai_setup'; @@ -43,6 +46,7 @@ describe('OrderStateWatcher', () => { let maker: string; let taker: string; let signedOrder: SignedOrder; + let orderStateWatcher: OrderStateWatcher; const config = { networkId: constants.TESTRPC_NETWORK_ID, }; @@ -50,6 +54,7 @@ describe('OrderStateWatcher', () => { const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals); before(async () => { zeroEx = new ZeroEx(web3.currentProvider, config); + orderStateWatcher = zeroEx.createOrderStateWatcher(); exchangeContractAddress = zeroEx.exchange.getContractAddress(); userAddresses = await zeroEx.getAvailableAddressesAsync(); [, maker, taker] = userAddresses; @@ -76,17 +81,17 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({ + orderStateWatcher.addOrder(signedOrder); + expect((orderStateWatcher as any)._orderByOrderHash).to.include({ [orderHash]: signedOrder, }); - let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + let dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes; expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash); - zeroEx.orderStateWatcher.removeOrder(orderHash); - expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({ + orderStateWatcher.removeOrder(orderHash); + expect((orderStateWatcher as any)._orderByOrderHash).to.not.include({ [orderHash]: signedOrder, }); - dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes; + dependentOrderHashes = (orderStateWatcher as any)._dependentOrderHashes; expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined(); }); it('should no-op when removing a non-existing order', async () => { @@ -103,23 +108,23 @@ describe('OrderStateWatcher', () => { .split('') .reverse() .join('')}`; - zeroEx.orderStateWatcher.removeOrder(nonExistentOrderHash); + orderStateWatcher.removeOrder(nonExistentOrderHash); }); }); describe('#subscribe', async () => { afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); + orderStateWatcher.unsubscribe(); }); it('should fail when trying to subscribe twice', async () => { - zeroEx.orderStateWatcher.subscribe(_.noop); - expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); + orderStateWatcher.subscribe(_.noop); + expect(() => orderStateWatcher.subscribe(_.noop)).to.throw(ZeroExError.SubscriptionAlreadyPresent); }); }); describe('tests with cleanup', async () => { afterEach(async () => { - zeroEx.orderStateWatcher.unsubscribe(); + orderStateWatcher.unsubscribe(); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.removeOrder(orderHash); + orderStateWatcher.removeOrder(orderHash); }); it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => { (async () => { @@ -131,14 +136,14 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0)); })().catch(done); }); @@ -151,11 +156,11 @@ describe('OrderStateWatcher', () => { taker, fillableAmount, ); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { throw new Error('OrderState callback fired for irrelevant order'); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); const notTheMaker = userAddresses[0]; const anyRecipient = taker; const transferAmount = new BigNumber(2); @@ -175,14 +180,14 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); const invalidOrderState = orderState as OrderStateInvalid; expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); const anyRecipient = taker; const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); @@ -198,7 +203,7 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -206,7 +211,7 @@ describe('OrderStateWatcher', () => { expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); const shouldThrowOnInsufficientBalanceOrAllowance = true; await zeroEx.exchange.fillOrderAsync( @@ -230,7 +235,7 @@ describe('OrderStateWatcher', () => { const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); const fillAmountInBaseUnits = new BigNumber(2); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -247,7 +252,7 @@ describe('OrderStateWatcher', () => { ); expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); const shouldThrowOnInsufficientBalanceOrAllowance = true; await zeroEx.exchange.fillOrderAsync( signedOrder, @@ -272,8 +277,8 @@ describe('OrderStateWatcher', () => { taker, ); const callback = reportNodeCallbackErrors(done)(); - zeroEx.orderStateWatcher.addOrder(signedOrder); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.subscribe(callback); await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0)); })().catch(done); }); @@ -292,7 +297,7 @@ describe('OrderStateWatcher', () => { ); const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); const validOrderState = orderState as OrderStateValid; @@ -305,7 +310,7 @@ describe('OrderStateWatcher', () => { ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals), ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); const shouldThrowOnInsufficientBalanceOrAllowance = true; await zeroEx.exchange.fillOrderAsync( signedOrder, @@ -326,7 +331,7 @@ describe('OrderStateWatcher', () => { ); const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -338,7 +343,7 @@ describe('OrderStateWatcher', () => { changedMakerApprovalAmount, ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount); })().catch(done); }); @@ -356,7 +361,7 @@ describe('OrderStateWatcher', () => { const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals); const transferAmount = makerBalance.sub(remainingAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -369,7 +374,7 @@ describe('OrderStateWatcher', () => { remainingAmount, ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.token.transferAsync(makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount); })().catch(done); }); @@ -391,7 +396,7 @@ describe('OrderStateWatcher', () => { const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -401,7 +406,7 @@ describe('OrderStateWatcher', () => { remainingTokenAmount, ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount); })().catch(done); }); @@ -425,7 +430,7 @@ describe('OrderStateWatcher', () => { const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals); const transferTokenAmount = makerFee.sub(remainingTokenAmount); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -434,7 +439,7 @@ describe('OrderStateWatcher', () => { remainingFeeAmount, ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount); await zeroEx.token.transferAsync( makerToken.address, @@ -460,7 +465,7 @@ describe('OrderStateWatcher', () => { feeRecipient, ); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { const validOrderState = orderState as OrderStateValid; @@ -469,7 +474,7 @@ describe('OrderStateWatcher', () => { fillableAmount, ); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.token.setProxyAllowanceAsync( makerToken.address, maker, @@ -488,7 +493,7 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -496,7 +501,7 @@ describe('OrderStateWatcher', () => { expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount); })().catch(done); @@ -512,7 +517,7 @@ describe('OrderStateWatcher', () => { fillableAmount, ); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.false(); @@ -520,7 +525,7 @@ describe('OrderStateWatcher', () => { expect(invalidOrderState.orderHash).to.be.equal(orderHash); expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.exchange.cancelOrderAsync( signedOrder, fillableAmount.minus(remainingFillableAmountInBaseUnits), @@ -539,7 +544,7 @@ describe('OrderStateWatcher', () => { const cancelAmountInBaseUnits = new BigNumber(2); const orderHash = ZeroEx.getOrderHashHex(signedOrder); - zeroEx.orderStateWatcher.addOrder(signedOrder); + orderStateWatcher.addOrder(signedOrder); const callback = reportNodeCallbackErrors(done)((orderState: OrderState) => { expect(orderState.isValid).to.be.true(); @@ -548,7 +553,7 @@ describe('OrderStateWatcher', () => { const orderRelevantState = validOrderState.orderRelevantState; expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits); }); - zeroEx.orderStateWatcher.subscribe(callback); + orderStateWatcher.subscribe(callback); await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits); })().catch(done); }); -- cgit v1.2.3