Merge pull request #211 from 0xProject/feature/orderWatcherLocalStateStore

Order watcher local state store
This commit is contained in:
Fabio Berger
2017-11-12 20:26:21 -05:00
committed by GitHub
13 changed files with 285 additions and 222 deletions

View File

@@ -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",

View File

@@ -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,
);
}
/**

View File

@@ -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;

View File

@@ -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._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,
);
this._abiDecoder = abiDecoder;
this._orderStateUtils = orderStateUtils;
this._web3Wrapper = web3Wrapper;
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,
);
}
/**
* 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:
case ExchangeEvents.LogCancel:
const orderHash = decodedLog.args.orderHash;
{
// 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], blockNumber);
await this._emitRevalidateOrdersAsync([orderHash]);
}
break;
}
case ExchangeEvents.LogCancel:
{
// 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]);
}
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) {

View File

@@ -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 = {};
}
}

View File

@@ -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 = {};
}
}

View File

@@ -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;
}
/*

View File

@@ -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 = {};
this.store = new BalanceAndProxyAllowanceLazyStore(token);
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
}
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;
}
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> {

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
});
});
});
});

View File

@@ -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"