diff options
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/0x.ts | 3 | ||||
| -rw-r--r-- | src/order_watcher/event_watcher.ts | 15 | ||||
| -rw-r--r-- | src/order_watcher/order_state_watcher.ts | 108 | ||||
| -rw-r--r-- | src/stores/balance_proxy_allowance_lazy_store.ts | 82 | ||||
| -rw-r--r-- | src/stores/order_filled_cancelled_lazy_store.ts | 61 | ||||
| -rw-r--r-- | src/types.ts | 6 | ||||
| -rw-r--r-- | src/utils/exchange_transfer_simulator.ts | 76 | ||||
| -rw-r--r-- | src/utils/order_state_utils.ts | 53 | ||||
| -rw-r--r-- | test/event_watcher_test.ts | 2 | ||||
| -rw-r--r-- | test/exchange_transfer_simulator_test.ts | 18 | ||||
| -rw-r--r-- | test/order_state_watcher_test.ts | 73 | ||||
| -rw-r--r-- | yarn.lock | 4 | 
13 files changed, 283 insertions, 220 deletions
| diff --git a/package.json b/package.json index 171cc7706..1d3b0c7d2 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@      "chai-as-promised": "^7.1.0",      "chai-as-promised-typescript-typings": "0.0.3",      "chai-bignumber": "^2.0.1", -    "chai-typescript-typings": "^0.0.0", +    "chai-typescript-typings": "^0.0.1",      "copyfiles": "^1.2.0",      "coveralls": "^3.0.0",      "dirty-chai": "^2.0.1", @@ -205,9 +205,8 @@ export class ZeroEx {          const etherTokenContractAddressIfExists = _.isUndefined(config) ? undefined : config.etherTokenContractAddress;          this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, etherTokenContractAddressIfExists);          const orderWatcherConfig = _.isUndefined(config) ? undefined : config.orderWatcherConfig; -        const orderStateUtils = new OrderStateUtils(this.token, this.exchange);          this.orderStateWatcher = new OrderStateWatcher( -            this._web3Wrapper, this._abiDecoder, orderStateUtils, orderWatcherConfig, +            this._web3Wrapper, this._abiDecoder, this.token, this.exchange, orderWatcherConfig,          );      }      /** diff --git a/src/order_watcher/event_watcher.ts b/src/order_watcher/event_watcher.ts index c9e72281c..81529a98c 100644 --- a/src/order_watcher/event_watcher.ts +++ b/src/order_watcher/event_watcher.ts @@ -28,10 +28,8 @@ export class EventWatcher {      private _pollingIntervalMs: number;      private _intervalIdIfExists?: NodeJS.Timer;      private _lastEvents: Web3.LogEntry[] = []; -    private _numConfirmations: number; -    constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number, numConfirmations: number) { +    constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number) {          this._web3Wrapper = web3Wrapper; -        this._numConfirmations = numConfirmations;          this._pollingIntervalMs = _.isUndefined(pollingIntervalMs) ?                                      DEFAULT_EVENT_POLLING_INTERVAL :                                      pollingIntervalMs; @@ -67,16 +65,9 @@ export class EventWatcher {          this._lastEvents = pendingEvents;      }      private async _getEventsAsync(): Promise<Web3.LogEntry[]> { -        let latestBlock: BlockParamLiteral|number; -        if (this._numConfirmations === 0) { -            latestBlock = BlockParamLiteral.Pending; -        } else { -            const currentBlock = await this._web3Wrapper.getBlockNumberAsync(); -            latestBlock = currentBlock - this._numConfirmations; -        }          const eventFilter = { -            fromBlock: latestBlock, -            toBlock: latestBlock, +            fromBlock: BlockParamLiteral.Pending, +            toBlock: BlockParamLiteral.Pending,          };          const events = await this._web3Wrapper.getLogsAsync(eventFilter);          return events; diff --git a/src/order_watcher/order_state_watcher.ts b/src/order_watcher/order_state_watcher.ts index 4866f8409..139f13fdf 100644 --- a/src/order_watcher/order_state_watcher.ts +++ b/src/order_watcher/order_state_watcher.ts @@ -1,6 +1,5 @@  import * as _ from 'lodash';  import {schemas} from '0x-json-schemas'; -import * as ethUtil from 'ethereumjs-util';  import {ZeroEx} from '../0x';  import {EventWatcher} from './event_watcher';  import {assert} from '../utils/assert'; @@ -15,13 +14,22 @@ import {      Web3Provider,      BlockParamLiteral,      LogWithDecodedArgs, +    ContractEventArgs,      OnOrderStateChangeCallback,      OrderStateWatcherConfig, +    ApprovalContractEventArgs, +    TransferContractEventArgs, +    LogFillContractEventArgs, +    LogCancelContractEventArgs,      ExchangeEvents,      TokenEvents,      ZeroExError,  } from '../types';  import {Web3Wrapper} from '../web3_wrapper'; +import {TokenWrapper} from '../contract_wrappers/token_wrapper'; +import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper'; +import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store'; +import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';  const DEFAULT_NUM_CONFIRMATIONS = 0; @@ -44,26 +52,26 @@ interface OrderByOrderHash {  export class OrderStateWatcher {      private _orderByOrderHash: OrderByOrderHash = {};      private _dependentOrderHashes: DependentOrderHashes = {}; -    private _web3Wrapper: Web3Wrapper;      private _callbackIfExistsAsync?: OnOrderStateChangeCallback;      private _eventWatcher: EventWatcher; +    private _web3Wrapper: Web3Wrapper;      private _abiDecoder: AbiDecoder;      private _orderStateUtils: OrderStateUtils; -    private _numConfirmations: number; +    private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; +    private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;      constructor( -        web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, orderStateUtils: OrderStateUtils, +        web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, token: TokenWrapper, exchange: ExchangeWrapper,          config?: OrderStateWatcherConfig,      ) { +        this._abiDecoder = abiDecoder;          this._web3Wrapper = web3Wrapper; -        const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.pollingIntervalMs; -        this._numConfirmations = _.isUndefined(config) ? -                                    DEFAULT_NUM_CONFIRMATIONS -                                    : config.numConfirmations; -        this._eventWatcher = new EventWatcher( -            this._web3Wrapper, eventPollingIntervalMs, this._numConfirmations, +        const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs; +        this._eventWatcher = new EventWatcher(web3Wrapper, eventPollingIntervalMs); +        this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token); +        this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange); +        this._orderStateUtils = new OrderStateUtils( +            this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore,          ); -        this._abiDecoder = abiDecoder; -        this._orderStateUtils = orderStateUtils;      }      /**       * Add an order to the orderStateWatcher. Before the order is added, it's @@ -108,6 +116,11 @@ export class OrderStateWatcher {       * Ends an orderStateWatcher subscription.       */      public unsubscribe(): void { +        if (_.isUndefined(this._callbackIfExistsAsync)) { +            throw new Error(ZeroExError.SubscriptionNotFound); +        } +        this._balanceAndProxyAllowanceLazyStore.deleteAll(); +        this._orderFilledCancelledLazyStore.deleteAll();          delete this._callbackIfExistsAsync;          this._eventWatcher.unsubscribe();      } @@ -117,45 +130,68 @@ export class OrderStateWatcher {          if (!isLogDecoded) {              return; // noop          } -        // Unfortunately blockNumber is returned as a hex-encoded string, so we -        // convert it to a number here. -        const blockNumberBuff = ethUtil.toBuffer(maybeDecodedLog.blockNumber); -        const blockNumber = ethUtil.bufferToInt(blockNumberBuff); - -        const decodedLog = maybeDecodedLog as LogWithDecodedArgs<any>; +        const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>;          let makerToken: string;          let makerAddress: string;          let orderHashesSet: Set<string>;          switch (decodedLog.event) {              case TokenEvents.Approval: +            { +                // Invalidate cache +                const args = decodedLog.args as ApprovalContractEventArgs; +                this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner); +                // Revalidate orders                  makerToken = decodedLog.address; -                makerAddress = decodedLog.args._owner; +                makerAddress = args._owner;                  orderHashesSet = _.get(this._dependentOrderHashes, [makerAddress, makerToken]);                  if (!_.isUndefined(orderHashesSet)) {                      const orderHashes = Array.from(orderHashesSet); -                    await this._emitRevalidateOrdersAsync(orderHashes, blockNumber); +                    await this._emitRevalidateOrdersAsync(orderHashes);                  }                  break; - +            }              case TokenEvents.Transfer: +            { +                // Invalidate cache +                const args = decodedLog.args as TransferContractEventArgs; +                this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from); +                this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to); +                // Revalidate orders                  makerToken = decodedLog.address; -                makerAddress = decodedLog.args._from; +                makerAddress = args._from;                  orderHashesSet = _.get(this._dependentOrderHashes, [makerAddress, makerToken]);                  if (!_.isUndefined(orderHashesSet)) {                      const orderHashes = Array.from(orderHashesSet); -                    await this._emitRevalidateOrdersAsync(orderHashes, blockNumber); +                    await this._emitRevalidateOrdersAsync(orderHashes);                  }                  break; - +            }              case ExchangeEvents.LogFill: +            { +                // Invalidate cache +                const args = decodedLog.args as LogFillContractEventArgs; +                this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash); +                // Revalidate orders +                const orderHash = args.orderHash; +                const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]); +                if (isOrderWatched) { +                    await this._emitRevalidateOrdersAsync([orderHash]); +                } +                break; +            }              case ExchangeEvents.LogCancel: -                const orderHash = decodedLog.args.orderHash; +            { +                // Invalidate cache +                const args = decodedLog.args as LogCancelContractEventArgs; +                this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash); +                // Revalidate orders +                const orderHash = args.orderHash;                  const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);                  if (isOrderWatched) { -                    await this._emitRevalidateOrdersAsync([orderHash], blockNumber); +                    await this._emitRevalidateOrdersAsync([orderHash]);                  }                  break; - +            }              case ExchangeEvents.LogError:                  return; // noop @@ -163,22 +199,16 @@ export class OrderStateWatcher {                  throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event);          }      } -    private async _emitRevalidateOrdersAsync(orderHashes: string[], blockNumber: number): Promise<void> { -        const defaultBlock = this._numConfirmations === 0 ? -                                BlockParamLiteral.Pending : -                                blockNumber; -        const methodOpts = { -            defaultBlock, -        }; - +    private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> {          for (const orderHash of orderHashes) {              const signedOrder = this._orderByOrderHash[orderHash] as SignedOrder; -            const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder, methodOpts); -            if (!_.isUndefined(this._callbackIfExistsAsync)) { -                await this._callbackIfExistsAsync(orderState); -            } else { +            // Most of these calls will never reach the network because the data is fetched from stores +            // and only updated when cache is invalidated +            const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder); +            if (_.isUndefined(this._callbackIfExistsAsync)) {                  break; // Unsubscribe was called              } +            await this._callbackIfExistsAsync(orderState);          }      }      private addToDependentOrderHashes(signedOrder: SignedOrder, orderHash: string) { diff --git a/src/stores/balance_proxy_allowance_lazy_store.ts b/src/stores/balance_proxy_allowance_lazy_store.ts new file mode 100644 index 000000000..c83e61606 --- /dev/null +++ b/src/stores/balance_proxy_allowance_lazy_store.ts @@ -0,0 +1,82 @@ +import * as _ from 'lodash'; +import * as Web3 from 'web3'; +import {BigNumber} from 'bignumber.js'; +import {TokenWrapper} from '../contract_wrappers/token_wrapper'; +import {BlockParamLiteral} from '../types'; + +/** + * Copy on read store for balances/proxyAllowances of tokens/accounts + */ +export class BalanceAndProxyAllowanceLazyStore { +    private token: TokenWrapper; +    private balance: { +        [tokenAddress: string]: { +            [userAddress: string]: BigNumber, +        }, +    }; +    private proxyAllowance: { +        [tokenAddress: string]: { +            [userAddress: string]: BigNumber, +        }, +    }; +    constructor(token: TokenWrapper) { +        this.token = token; +        this.balance = {}; +        this.proxyAllowance = {}; +    } +    public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { +        if (_.isUndefined(this.balance[tokenAddress]) || _.isUndefined(this.balance[tokenAddress][userAddress])) { +            const methodOpts = { +                defaultBlock: BlockParamLiteral.Pending, +            }; +            const balance = await this.token.getBalanceAsync(tokenAddress, userAddress, methodOpts); +            this.setBalance(tokenAddress, userAddress, balance); +        } +        const cachedBalance = this.balance[tokenAddress][userAddress]; +        return cachedBalance; +    } +    public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { +        if (_.isUndefined(this.balance[tokenAddress])) { +            this.balance[tokenAddress] = {}; +        } +        this.balance[tokenAddress][userAddress] = balance; +    } +    public deleteBalance(tokenAddress: string, userAddress: string): void { +        if (!_.isUndefined(this.balance[tokenAddress])) { +            delete this.balance[tokenAddress][userAddress]; +            if (_.isEmpty(this.balance[tokenAddress])) { +                delete this.balance[tokenAddress]; +            } +        } +    } +    public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { +        if (_.isUndefined(this.proxyAllowance[tokenAddress]) || +            _.isUndefined(this.proxyAllowance[tokenAddress][userAddress])) { +            const methodOpts = { +                defaultBlock: BlockParamLiteral.Pending, +            }; +            const proxyAllowance = await this.token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts); +            this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); +        } +        const cachedProxyAllowance = this.proxyAllowance[tokenAddress][userAddress]; +        return cachedProxyAllowance; +    } +    public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { +        if (_.isUndefined(this.proxyAllowance[tokenAddress])) { +            this.proxyAllowance[tokenAddress] = {}; +        } +        this.proxyAllowance[tokenAddress][userAddress] = proxyAllowance; +    } +    public deleteProxyAllowance(tokenAddress: string, userAddress: string): void { +        if (!_.isUndefined(this.proxyAllowance[tokenAddress])) { +            delete this.proxyAllowance[tokenAddress][userAddress]; +            if (_.isEmpty(this.proxyAllowance[tokenAddress])) { +                delete this.proxyAllowance[tokenAddress]; +            } +        } +    } +    public deleteAll(): void { +        this.balance = {}; +        this.proxyAllowance = {}; +    } +} diff --git a/src/stores/order_filled_cancelled_lazy_store.ts b/src/stores/order_filled_cancelled_lazy_store.ts new file mode 100644 index 000000000..9d74da096 --- /dev/null +++ b/src/stores/order_filled_cancelled_lazy_store.ts @@ -0,0 +1,61 @@ +import * as _ from 'lodash'; +import * as Web3 from 'web3'; +import {BigNumber} from 'bignumber.js'; +import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper'; +import {BlockParamLiteral} from '../types'; + +/** + * Copy on read store for filled/cancelled taker amounts + */ +export class OrderFilledCancelledLazyStore { +    private exchange: ExchangeWrapper; +    private filledTakerAmount: { +        [orderHash: string]: BigNumber, +    }; +    private cancelledTakerAmount: { +        [orderHash: string]: BigNumber, +    }; +    constructor(exchange: ExchangeWrapper) { +        this.exchange = exchange; +        this.filledTakerAmount = {}; +        this.cancelledTakerAmount = {}; +    } +    public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> { +        if (_.isUndefined(this.filledTakerAmount[orderHash])) { +            const methodOpts = { +                defaultBlock: BlockParamLiteral.Pending, +            }; +            const filledTakerAmount = await this.exchange.getFilledTakerAmountAsync(orderHash, methodOpts); +            this.setFilledTakerAmount(orderHash, filledTakerAmount); +        } +        const cachedFilled = this.filledTakerAmount[orderHash]; +        return cachedFilled; +    } +    public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void { +        this.filledTakerAmount[orderHash] = filledTakerAmount; +    } +    public deleteFilledTakerAmount(orderHash: string): void { +        delete this.filledTakerAmount[orderHash]; +    } +    public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> { +        if (_.isUndefined(this.cancelledTakerAmount[orderHash])) { +            const methodOpts = { +                defaultBlock: BlockParamLiteral.Pending, +            }; +            const cancelledTakerAmount = await this.exchange.getCanceledTakerAmountAsync(orderHash, methodOpts); +            this.setCancelledTakerAmount(orderHash, cancelledTakerAmount); +        } +        const cachedCancelled = this.cancelledTakerAmount[orderHash]; +        return cachedCancelled; +    } +    public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void { +        this.cancelledTakerAmount[orderHash] = cancelledTakerAmount; +    } +    public deleteCancelledTakerAmount(orderHash: string): void { +        delete this.cancelledTakerAmount[orderHash]; +    } +    public deleteAll(): void { +        this.filledTakerAmount = {}; +        this.cancelledTakerAmount = {}; +    } +} diff --git a/src/types.ts b/src/types.ts index 09b611019..20f994ac7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -397,12 +397,10 @@ export interface JSONRPCPayload {  }  /* - * pollingIntervalMs: How often to poll the Ethereum node for new events. - * numConfirmations: How many confirmed blocks deep you wish to listen for events at. + * eventPollingIntervalMs: How often to poll the Ethereum node for new events   */  export interface OrderStateWatcherConfig { -    pollingIntervalMs?: number; -    numConfirmations: number; +    eventPollingIntervalMs?: number;  }  /* diff --git a/src/utils/exchange_transfer_simulator.ts b/src/utils/exchange_transfer_simulator.ts index 89b23c8ab..308ef06db 100644 --- a/src/utils/exchange_transfer_simulator.ts +++ b/src/utils/exchange_transfer_simulator.ts @@ -1,7 +1,8 @@  import * as _ from 'lodash';  import BigNumber from 'bignumber.js'; -import {ExchangeContractErrs, TradeSide, TransferType} from '../types'; +import {ExchangeContractErrs, TradeSide, TransferType, BlockParamLiteral} from '../types';  import {TokenWrapper} from '../contract_wrappers/token_wrapper'; +import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';  enum FailureReason {      Balance = 'balance', @@ -31,58 +32,13 @@ const ERR_MSG_MAPPING = {      },  }; -/** - * Copy on read store for balances/proxyAllowances of tokens/accounts touched in trades - */ -export class BalanceAndProxyAllowanceLazyStore { -    protected _token: TokenWrapper; -    private _balance: { -        [tokenAddress: string]: { -            [userAddress: string]: BigNumber, -        }, -    }; -    private _proxyAllowance: { -        [tokenAddress: string]: { -            [userAddress: string]: BigNumber, -        }, -    }; +export class ExchangeTransferSimulator { +    private store: BalanceAndProxyAllowanceLazyStore; +    private UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;      constructor(token: TokenWrapper) { -        this._token = token; -        this._balance = {}; -        this._proxyAllowance = {}; -    } -    protected async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { -        if (_.isUndefined(this._balance[tokenAddress]) || _.isUndefined(this._balance[tokenAddress][userAddress])) { -            const balance = await this._token.getBalanceAsync(tokenAddress, userAddress); -            this.setBalance(tokenAddress, userAddress, balance); -        } -        const cachedBalance = this._balance[tokenAddress][userAddress]; -        return cachedBalance; -    } -    protected setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void { -        if (_.isUndefined(this._balance[tokenAddress])) { -            this._balance[tokenAddress] = {}; -        } -        this._balance[tokenAddress][userAddress] = balance; +        this.store = new BalanceAndProxyAllowanceLazyStore(token); +        this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;      } -    protected async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> { -        if (_.isUndefined(this._proxyAllowance[tokenAddress]) || -            _.isUndefined(this._proxyAllowance[tokenAddress][userAddress])) { -            const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress); -            this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance); -        } -        const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress]; -        return cachedProxyAllowance; -    } -    protected setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void { -        if (_.isUndefined(this._proxyAllowance[tokenAddress])) { -            this._proxyAllowance[tokenAddress] = {}; -        } -        this._proxyAllowance[tokenAddress][userAddress] = proxyAllowance; -    } -} - -export class ExchangeTransferSimulator extends BalanceAndProxyAllowanceLazyStore {      /**       * Simulates transferFrom call performed by a proxy       * @param  tokenAddress      Address of the token to be transferred @@ -95,8 +51,8 @@ export class ExchangeTransferSimulator extends BalanceAndProxyAllowanceLazyStore      public async transferFromAsync(tokenAddress: string, from: string, to: string,                                     amountInBaseUnits: BigNumber, tradeSide: TradeSide,                                     transferType: TransferType): Promise<void> { -        const balance = await this.getBalanceAsync(tokenAddress, from); -        const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, from); +        const balance = await this.store.getBalanceAsync(tokenAddress, from); +        const proxyAllowance = await this.store.getProxyAllowanceAsync(tokenAddress, from);          if (proxyAllowance.lessThan(amountInBaseUnits)) {              this.throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);          } @@ -109,20 +65,20 @@ export class ExchangeTransferSimulator extends BalanceAndProxyAllowanceLazyStore      }      private async decreaseProxyAllowanceAsync(tokenAddress: string, userAddress: string,                                                amountInBaseUnits: BigNumber): Promise<void> { -        const proxyAllowance = await this.getProxyAllowanceAsync(tokenAddress, userAddress); -        if (!proxyAllowance.eq(this._token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { -            this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits)); +        const proxyAllowance = await this.store.getProxyAllowanceAsync(tokenAddress, userAddress); +        if (!proxyAllowance.eq(this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) { +            this.store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));          }      }      private async increaseBalanceAsync(tokenAddress: string, userAddress: string,                                         amountInBaseUnits: BigNumber): Promise<void> { -        const balance = await this.getBalanceAsync(tokenAddress, userAddress); -        this.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits)); +        const balance = await this.store.getBalanceAsync(tokenAddress, userAddress); +        this.store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));      }      private async decreaseBalanceAsync(tokenAddress: string, userAddress: string,                                         amountInBaseUnits: BigNumber): Promise<void> { -        const balance = await this.getBalanceAsync(tokenAddress, userAddress); -        this.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits)); +        const balance = await this.store.getBalanceAsync(tokenAddress, userAddress); +        this.store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));      }      private throwValidationError(failureReason: FailureReason, tradeSide: TradeSide,                                   transferType: TransferType): Promise<never> { diff --git a/src/utils/order_state_utils.ts b/src/utils/order_state_utils.ts index 36a4b68d6..f82601cae 100644 --- a/src/utils/order_state_utils.ts +++ b/src/utils/order_state_utils.ts @@ -1,4 +1,5 @@  import * as _ from 'lodash'; +import * as Web3 from 'web3';  import BigNumber from 'bignumber.js';  import {      ExchangeContractErrs, @@ -14,16 +15,19 @@ import {TokenWrapper} from '../contract_wrappers/token_wrapper';  import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';  import {utils} from '../utils/utils';  import {constants} from '../utils/constants'; +import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store'; +import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';  export class OrderStateUtils { -    private tokenWrapper: TokenWrapper; -    private exchangeWrapper: ExchangeWrapper; -    constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) { -        this.tokenWrapper = tokenWrapper; -        this.exchangeWrapper = exchangeWrapper; +    private balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore; +    private orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore; +    constructor(balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore, +                orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore) { +        this.balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore; +        this.orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;      } -    public async getOrderStateAsync(signedOrder: SignedOrder, methodOpts?: MethodOpts): Promise<OrderState> { -        const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder, methodOpts); +    public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> { +        const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);          const orderHash = ZeroEx.getOrderHashHex(signedOrder);          try {              this.validateIfOrderIsValid(signedOrder, orderRelevantState); @@ -42,26 +46,31 @@ export class OrderStateUtils {              return orderState;          }      } -    public async getOrderRelevantStateAsync( -        signedOrder: SignedOrder, methodOpts?: MethodOpts): Promise<OrderRelevantState> { -        const zrxTokenAddress = await this.exchangeWrapper.getZRXTokenAddressAsync(); +    public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> { +        // HACK: We access the private property here but otherwise the interface will be less nice. +        // If we pass it from the instantiator - there is no opportunity to get it there +        // because JS doesn't support async constructors. +        // Moreover - it's cached under the hood so it's equivalent to an async constructor. +        const exchange = (this.orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper; +        const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();          const orderHash = ZeroEx.getOrderHashHex(signedOrder); -        const makerBalance = await this.tokenWrapper.getBalanceAsync( -            signedOrder.makerTokenAddress, signedOrder.maker, methodOpts, +        const makerBalance = await this.balanceAndProxyAllowanceLazyStore.getBalanceAsync( +            signedOrder.makerTokenAddress, signedOrder.maker,          ); -        const makerProxyAllowance = await this.tokenWrapper.getProxyAllowanceAsync( -            signedOrder.makerTokenAddress, signedOrder.maker, methodOpts, +        const makerProxyAllowance = await this.balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( +            signedOrder.makerTokenAddress, signedOrder.maker,          ); -        const makerFeeBalance = await this.tokenWrapper.getBalanceAsync( -            zrxTokenAddress, signedOrder.maker, methodOpts, +        const makerFeeBalance = await this.balanceAndProxyAllowanceLazyStore.getBalanceAsync( +            zrxTokenAddress, signedOrder.maker,          ); -        const makerFeeProxyAllowance = await this.tokenWrapper.getProxyAllowanceAsync( -            zrxTokenAddress, signedOrder.maker, methodOpts, +        const makerFeeProxyAllowance = await this.balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync( +            zrxTokenAddress, signedOrder.maker,          ); -        const filledTakerTokenAmount = await this.exchangeWrapper.getFilledTakerAmountAsync(orderHash, methodOpts); -        const canceledTakerTokenAmount = await this.exchangeWrapper.getCanceledTakerAmountAsync(orderHash, methodOpts); -        const unavailableTakerTokenAmount = -          await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash, methodOpts); +        const filledTakerTokenAmount = await this.orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash); +        const canceledTakerTokenAmount = await this.orderFilledCancelledLazyStore.getCancelledTakerAmountAsync( +            orderHash, +        ); +        const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);          const totalMakerTokenAmount = signedOrder.makerTokenAmount;          const totalTakerTokenAmount = signedOrder.takerTokenAmount;          const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount); diff --git a/test/event_watcher_test.ts b/test/event_watcher_test.ts index 98dab93b5..b4164fe63 100644 --- a/test/event_watcher_test.ts +++ b/test/event_watcher_test.ts @@ -58,7 +58,7 @@ describe('EventWatcher', () => {          web3 = web3Factory.create();          const pollingIntervalMs = 10;          web3Wrapper = new Web3Wrapper(web3.currentProvider); -        eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs, numConfirmations); +        eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);      });      afterEach(() => {          // clean up any stubs after the test has completed diff --git a/test/exchange_transfer_simulator_test.ts b/test/exchange_transfer_simulator_test.ts index 3373ebf03..99cb7fb4f 100644 --- a/test/exchange_transfer_simulator_test.ts +++ b/test/exchange_transfer_simulator_test.ts @@ -59,11 +59,10 @@ describe('ExchangeTransferSimulator', () => {              await exchangeTransferSimulator.transferFromAsync(                  exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,              ); -            const senderBalance = await (exchangeTransferSimulator as any).getBalanceAsync(exampleTokenAddress, sender); -            const recipientBalance = await (exchangeTransferSimulator as any).getBalanceAsync( -                exampleTokenAddress, recipient); -            const senderProxyAllowance = await (exchangeTransferSimulator as any).getProxyAllowanceAsync( -                exampleTokenAddress, sender); +            const store = (exchangeTransferSimulator as any).store; +            const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); +            const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); +            const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);              expect(senderBalance).to.be.bignumber.equal(0);              expect(recipientBalance).to.be.bignumber.equal(transferAmount);              expect(senderProxyAllowance).to.be.bignumber.equal(0); @@ -76,11 +75,10 @@ describe('ExchangeTransferSimulator', () => {              await exchangeTransferSimulator.transferFromAsync(                  exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,              ); -            const senderBalance = await (exchangeTransferSimulator as any).getBalanceAsync(exampleTokenAddress, sender); -            const recipientBalance = await (exchangeTransferSimulator as any).getBalanceAsync( -                exampleTokenAddress, recipient); -            const senderProxyAllowance = await (exchangeTransferSimulator as any).getProxyAllowanceAsync( -                exampleTokenAddress, sender); +            const store = (exchangeTransferSimulator as any).store; +            const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender); +            const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient); +            const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);              expect(senderBalance).to.be.bignumber.equal(0);              expect(recipientBalance).to.be.bignumber.equal(transferAmount);              expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS); diff --git a/test/order_state_watcher_test.ts b/test/order_state_watcher_test.ts index 41f938584..c8a4a8064 100644 --- a/test/order_state_watcher_test.ts +++ b/test/order_state_watcher_test.ts @@ -15,6 +15,7 @@ import {      ZeroExConfig,      OrderState,      SignedOrder, +    ZeroExError,      OrderStateValid,      OrderStateInvalid,      ExchangeContractErrs, @@ -92,14 +93,10 @@ describe('OrderStateWatcher', () => {          afterEach(async () => {              zeroEx.orderStateWatcher.unsubscribe();          }); -        it('should fail when trying to subscribe twice', (done: DoneCallback) => { +        it('should fail when trying to subscribe twice', async () => {              zeroEx.orderStateWatcher.subscribe(_.noop); -            try { -                zeroEx.orderStateWatcher.subscribe(_.noop); -                done(new Error('Expected the second subscription to fail')); -            } catch (err) { -                done(); -            } +            expect(() => zeroEx.orderStateWatcher.subscribe(_.noop)) +                .to.throw(ZeroExError.SubscriptionAlreadyPresent);          });      });      describe('tests with cleanup', async () => { @@ -355,67 +352,5 @@ describe('OrderStateWatcher', () => {                  await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits);              })().catch(done);          }); -        describe('check numConfirmations behavior', () => { -            before(() => { -                const configs: ZeroExConfig = { -                    orderWatcherConfig: { -                        numConfirmations: 1, -                    }, -                }; -                zeroEx = new ZeroEx(web3.currentProvider, configs); -            }); -            it('should emit orderState when watching at 1 confirmation deep and event is one block deep', -                (done: DoneCallback) => { -                (async () => { -                    fillScenarios = new FillScenarios( -                        zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress, -                    ); - -                    signedOrder = await fillScenarios.createFillableSignedOrderAsync( -                        makerToken.address, takerToken.address, maker, taker, fillableAmount, -                    ); -                    const orderHash = ZeroEx.getOrderHashHex(signedOrder); -                    zeroEx.orderStateWatcher.addOrder(signedOrder); -                    const callback = reportCallbackErrors(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); -                        done(); -                    }); -                    zeroEx.orderStateWatcher.subscribe(callback); - -                    const anyRecipient = taker; -                    const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); -                    await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); -                    blockchainLifecycle.mineABlock(); -                })().catch(done); -            }); -            it('shouldn\'t emit orderState when watching at 1 confirmation deep and event is in mempool', -                (done: DoneCallback) => { -                (async () => { -                    fillScenarios = new FillScenarios( -                        zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress, -                    ); - -                    signedOrder = await fillScenarios.createFillableSignedOrderAsync( -                        makerToken.address, takerToken.address, maker, taker, fillableAmount, -                    ); -                    const orderHash = ZeroEx.getOrderHashHex(signedOrder); -                    zeroEx.orderStateWatcher.addOrder(signedOrder); -                    const callback = reportCallbackErrors(done)((orderState: OrderState) => { -                        throw new Error('OrderState callback fired when it shouldn\'t have'); -                    }); -                    zeroEx.orderStateWatcher.subscribe(callback); - -                    const anyRecipient = taker; -                    const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker); -                    await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance); -                    setTimeout(() => { -                        done(); -                    }, TIMEOUT_MS); -                })().catch(done); -            }); -        });      });  }); @@ -1041,6 +1041,10 @@ chai-typescript-typings@^0.0.0:    version "0.0.0"    resolved "https://registry.yarnpkg.com/chai-typescript-typings/-/chai-typescript-typings-0.0.0.tgz#52e076d72cf29129c94ab1dba6e33ce3828a0724" +chai-typescript-typings@^0.0.1: +  version "0.0.1" +  resolved "https://registry.yarnpkg.com/chai-typescript-typings/-/chai-typescript-typings-0.0.1.tgz#433dee303b0b2978ad0dd03129df0a5afb791274" +  chai@^4.0.1:    version "4.0.2"    resolved "https://registry.yarnpkg.com/chai/-/chai-4.0.2.tgz#2f7327c4de6f385dd7787999e2ab02697a32b83b" | 
