Merge branch 'orderWatcher' of github.com:0xProject/0x.js into orderWatcher
* 'orderWatcher' of github.com:0xProject/0x.js: (33 commits) Remove old tests Remove unused code Fix tests Remove redundant spaces Don't store empty objects Fix a typo Remove duplicate operations Remove redundant instance variables Fix tests Remove blockStore and default to numConfirmations === 0 Add a comment Store number of confirmations in a blockStore Remove tautology check Pass blockStore to eventWatcher Fix last merge conflicts Clear cache on unsubscribe Clear store cache on events Add more configs for order watcher Make subscribe function async and make blockStore operational Adjust tests to new interface ...
This commit is contained in:
@@ -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,
|
||||
);
|
||||
}
|
||||
/**
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
82
src/stores/balance_proxy_allowance_lazy_store.ts
Normal file
82
src/stores/balance_proxy_allowance_lazy_store.ts
Normal 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 = {};
|
||||
}
|
||||
}
|
61
src/stores/order_filled_cancelled_lazy_store.ts
Normal file
61
src/stores/order_filled_cancelled_lazy_store.ts
Normal 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 = {};
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -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> {
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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"
|
||||
|
Reference in New Issue
Block a user