Merge pull request #510 from avonian/development

Implement `zeroEx.exchange.getOrderStateAsync`
This commit is contained in:
Fabio Berger
2018-04-17 23:09:05 +09:00
committed by GitHub
11 changed files with 165 additions and 32 deletions

View File

@@ -13,6 +13,10 @@
{
"note": "Moved Web3.Provider to `@0xproject/types:Provider`",
"pr": 501
},
{
"note": "Add `zeroEx.exchange.getOrderStateAsync` to allow obtaining current OrderState for a signedOrder",
"pr": 510
}
],
"timestamp": 1523462196

View File

@@ -0,0 +1,6 @@
import { BigNumber } from '@0xproject/utils';
export abstract class BalanceAndProxyAllowanceFetcher {
public abstract async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
public abstract async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber>;
}

View File

@@ -0,0 +1,6 @@
import { BigNumber } from '@0xproject/utils';
export abstract class OrderFilledCancelledFetcher {
public abstract async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
public abstract async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber>;
}

View File

@@ -13,6 +13,8 @@ import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import { artifacts } from '../artifacts';
import { SimpleBalanceAndProxyAllowanceFetcher } from '../fetchers/simple_balance_and_proxy_allowance_fetcher';
import { SimpleOrderFilledCancelledFetcher } from '../fetchers/simple_order_filled_cancelled_fetcher';
import {
BlockRange,
EventCallback,
@@ -23,6 +25,7 @@ import {
OrderAddresses,
OrderCancellationRequest,
OrderFillRequest,
OrderState,
OrderTransactionOpts,
OrderValues,
ValidateOrderFillableOpts,
@@ -30,6 +33,7 @@ import {
import { assert } from '../utils/assert';
import { decorators } from '../utils/decorators';
import { ExchangeTransferSimulator } from '../utils/exchange_transfer_simulator';
import { OrderStateUtils } from '../utils/order_state_utils';
import { OrderValidationUtils } from '../utils/order_validation_utils';
import { utils } from '../utils/utils';
@@ -41,7 +45,6 @@ import {
LogErrorContractEventArgs,
} from './generated/exchange';
import { TokenWrapper } from './token_wrapper';
const SHOULD_VALIDATE_BY_DEFAULT = true;
interface ExchangeContractErrCodesToMsgs {
@@ -873,6 +876,22 @@ export class ExchangeWrapper extends ContractWrapper {
throw new Error(errMessage);
}
}
/**
* Gets the latest OrderState of a signedOrder
* @param signedOrder The signedOrder
* @param stateLayer Optional, desired blockchain state layer (defaults to latest).
* @return OrderState of the signedOrder
*/
public async getOrderStateAsync(signedOrder: SignedOrder, stateLayer: BlockParamLiteral = BlockParamLiteral.Latest): Promise<OrderState> {
const simpleBalanceAndProxyAllowanceFetcher = new SimpleBalanceAndProxyAllowanceFetcher(
this._tokenWrapper,
stateLayer,
);
const simpleOrderFilledCancelledFetcher = new SimpleOrderFilledCancelledFetcher(this, stateLayer);
const orderStateUtils = new OrderStateUtils(simpleBalanceAndProxyAllowanceFetcher, simpleOrderFilledCancelledFetcher);
const orderState = orderStateUtils.getOrderStateAsync(signedOrder);
return orderState;
}
/**
* Returns the ZRX token address used by the exchange contract.
* @return Address of ZRX token

View File

@@ -0,0 +1,28 @@
import { BlockParamLiteral } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import {BalanceAndProxyAllowanceFetcher} from '../abstract/balance_and_proxy_allowance_fetcher';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
export class SimpleBalanceAndProxyAllowanceFetcher implements BalanceAndProxyAllowanceFetcher {
private _tokenWrapper: TokenWrapper;
private _defaultBlock: BlockParamLiteral;
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._tokenWrapper = token;
this._defaultBlock = defaultBlock;
}
public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const balance = this._tokenWrapper.getBalanceAsync(tokenAddress, userAddress, methodOpts);
return balance;
}
public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const proxyAllowance = this._tokenWrapper.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
return proxyAllowance;
}
}

View File

@@ -0,0 +1,28 @@
import { BlockParamLiteral } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import {OrderFilledCancelledFetcher} from '../abstract/order_filled_cancelled_fetcher';
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
export class SimpleOrderFilledCancelledFetcher implements OrderFilledCancelledFetcher {
private _exchangeWrapper: ExchangeWrapper;
private _defaultBlock: BlockParamLiteral;
constructor(exchange: ExchangeWrapper, defaultBlock: BlockParamLiteral) {
this._exchangeWrapper = exchange;
this._defaultBlock = defaultBlock;
}
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const filledTakerAmount = this._exchangeWrapper.getFilledTakerAmountAsync(orderHash, methodOpts);
return filledTakerAmount;
}
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const cancelledTakerAmount = this._exchangeWrapper.getCancelledTakerAmountAsync(orderHash, methodOpts);
return cancelledTakerAmount;
}
}

View File

@@ -87,7 +87,7 @@ export class OrderStateWatcher {
_.isUndefined(config) || _.isUndefined(config.stateLayer) ? BlockParamLiteral.Latest : config.stateLayer;
this._eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalIfExistsMs, stateLayer);
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token, stateLayer);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange, stateLayer);
this._orderStateUtils = new OrderStateUtils(
this._balanceAndProxyAllowanceLazyStore,
this._orderFilledCancelledLazyStore,
@@ -131,7 +131,7 @@ export class OrderStateWatcher {
}
delete this._orderByOrderHash[orderHash];
delete this._orderStateByOrderHashCache[orderHash];
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const exchange = (this._orderFilledCancelledLazyStore as any)._exchangeWrapper as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
this._removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
@@ -374,7 +374,7 @@ export class OrderStateWatcher {
}
}
private _getZRXTokenAddress(): string {
const exchange = (this._orderFilledCancelledLazyStore as any)._exchange as ExchangeWrapper;
const exchange = (this._orderFilledCancelledLazyStore as any)._exchangeWrapper as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
return zrxTokenAddress;
}

View File

@@ -3,12 +3,13 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { TokenWrapper } from '../contract_wrappers/token_wrapper';
import { BalanceAndProxyAllowanceFetcher } from '../abstract/balance_and_proxy_allowance_fetcher';
/**
* Copy on read store for balances/proxyAllowances of tokens/accounts
*/
export class BalanceAndProxyAllowanceLazyStore {
private _token: TokenWrapper;
export class BalanceAndProxyAllowanceLazyStore implements BalanceAndProxyAllowanceFetcher {
private _tokenWrapper: TokenWrapper;
private _defaultBlock: BlockParamLiteral;
private _balance: {
[tokenAddress: string]: {
@@ -21,7 +22,7 @@ export class BalanceAndProxyAllowanceLazyStore {
};
};
constructor(token: TokenWrapper, defaultBlock: BlockParamLiteral) {
this._token = token;
this._tokenWrapper = token;
this._defaultBlock = defaultBlock;
this._balance = {};
this._proxyAllowance = {};
@@ -31,7 +32,7 @@ export class BalanceAndProxyAllowanceLazyStore {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const balance = await this._token.getBalanceAsync(tokenAddress, userAddress, methodOpts);
const balance = await this._tokenWrapper.getBalanceAsync(tokenAddress, userAddress, methodOpts);
this.setBalance(tokenAddress, userAddress, balance);
}
const cachedBalance = this._balance[tokenAddress][userAddress];
@@ -59,7 +60,7 @@ export class BalanceAndProxyAllowanceLazyStore {
const methodOpts = {
defaultBlock: this._defaultBlock,
};
const proxyAllowance = await this._token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
const proxyAllowance = await this._tokenWrapper.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
}
const cachedProxyAllowance = this._proxyAllowance[tokenAddress][userAddress];

View File

@@ -3,29 +3,32 @@ import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
import { OrderFilledCancelledFetcher } from '../abstract/order_filled_cancelled_fetcher';
/**
* Copy on read store for filled/cancelled taker amounts
*/
export class OrderFilledCancelledLazyStore {
private _exchange: ExchangeWrapper;
export class OrderFilledCancelledLazyStore implements OrderFilledCancelledFetcher {
private _exchangeWrapper: ExchangeWrapper;
private _defaultBlock: BlockParamLiteral;
private _filledTakerAmount: {
[orderHash: string]: BigNumber;
};
private _cancelledTakerAmount: {
[orderHash: string]: BigNumber;
};
constructor(exchange: ExchangeWrapper) {
this._exchange = exchange;
constructor(exchange: ExchangeWrapper, defaultBlock: BlockParamLiteral) {
this._exchangeWrapper = exchange;
this._defaultBlock = defaultBlock;
this._filledTakerAmount = {};
this._cancelledTakerAmount = {};
}
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._filledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
defaultBlock: this._defaultBlock,
};
const filledTakerAmount = await this._exchange.getFilledTakerAmountAsync(orderHash, methodOpts);
const filledTakerAmount = await this._exchangeWrapper.getFilledTakerAmountAsync(orderHash, methodOpts);
this.setFilledTakerAmount(orderHash, filledTakerAmount);
}
const cachedFilled = this._filledTakerAmount[orderHash];
@@ -40,9 +43,9 @@ export class OrderFilledCancelledLazyStore {
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
if (_.isUndefined(this._cancelledTakerAmount[orderHash])) {
const methodOpts = {
defaultBlock: BlockParamLiteral.Pending,
defaultBlock: this._defaultBlock,
};
const cancelledTakerAmount = await this._exchange.getCancelledTakerAmountAsync(orderHash, methodOpts);
const cancelledTakerAmount = await this._exchangeWrapper.getCancelledTakerAmountAsync(orderHash, methodOpts);
this.setCancelledTakerAmount(orderHash, cancelledTakerAmount);
}
const cachedCancelled = this._cancelledTakerAmount[orderHash];

View File

@@ -4,16 +4,16 @@ import * as _ from 'lodash';
import { ZeroEx } from '../0x';
import { ExchangeWrapper } from '../contract_wrappers/exchange_wrapper';
import { BalanceAndProxyAllowanceFetcher } from '../abstract/balance_and_proxy_allowance_fetcher';
import { OrderFilledCancelledFetcher } from '../abstract/order_filled_cancelled_fetcher';
import { RemainingFillableCalculator } from '../order_watcher/remaining_fillable_calculator';
import { BalanceAndProxyAllowanceLazyStore } from '../stores/balance_proxy_allowance_lazy_store';
import { OrderFilledCancelledLazyStore } from '../stores/order_filled_cancelled_lazy_store';
import { ExchangeContractErrs, OrderRelevantState, OrderState, OrderStateInvalid, OrderStateValid } from '../types';
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
export class OrderStateUtils {
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
private _balanceAndProxyAllowanceFetcher: BalanceAndProxyAllowanceFetcher;
private _orderFilledCancelledFetcher: OrderFilledCancelledFetcher;
private static _validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
orderRelevantState.filledTakerTokenAmount,
@@ -49,11 +49,11 @@ export class OrderStateUtils {
}
}
constructor(
balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore,
orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore,
balanceAndProxyAllowanceFetcher: BalanceAndProxyAllowanceFetcher,
orderFilledCancelledFetcher: OrderFilledCancelledFetcher,
) {
this._balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore;
this._orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;
this._balanceAndProxyAllowanceFetcher = balanceAndProxyAllowanceFetcher;
this._orderFilledCancelledFetcher = orderFilledCancelledFetcher;
}
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
@@ -80,27 +80,27 @@ export class OrderStateUtils {
// 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 exchange = (this._orderFilledCancelledFetcher as any)._exchangeWrapper as ExchangeWrapper;
const zrxTokenAddress = exchange.getZRXTokenAddress();
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
const makerBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
const makerBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
const makerProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
signedOrder.makerTokenAddress,
signedOrder.maker,
);
const makerFeeBalance = await this._balanceAndProxyAllowanceLazyStore.getBalanceAsync(
const makerFeeBalance = await this._balanceAndProxyAllowanceFetcher.getBalanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
const makerFeeProxyAllowance = await this._balanceAndProxyAllowanceFetcher.getProxyAllowanceAsync(
zrxTokenAddress,
signedOrder.maker,
);
const filledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash);
const cancelledTakerTokenAmount = await this._orderFilledCancelledLazyStore.getCancelledTakerAmountAsync(
const filledTakerTokenAmount = await this._orderFilledCancelledFetcher.getFilledTakerAmountAsync(orderHash);
const cancelledTakerTokenAmount = await this._orderFilledCancelledFetcher.getCancelledTakerAmountAsync(
orderHash,
);
const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);

View File

@@ -14,6 +14,7 @@ import {
LogFillContractEventArgs,
OrderCancellationRequest,
OrderFillRequest,
OrderState,
SignedOrder,
Token,
ZeroEx,
@@ -1154,4 +1155,41 @@ describe('ExchangeWrapper', () => {
expect(args.maker).to.be.equal(differentMakerAddress);
});
});
describe('#getOrderStateAsync', () => {
let maker: string;
let taker: string;
let makerToken: Token;
let takerToken: Token;
let signedOrder: SignedOrder;
let orderState: OrderState;
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), constants.ZRX_DECIMALS);
before(async () => {
[, maker, taker] = userAddresses;
tokens = await zeroEx.tokenRegistry.getTokensAsync();
[makerToken, takerToken] = tokenUtils.getDummyTokens();
});
it('should report orderStateValid when order is fillable', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerToken.address,
takerToken.address,
maker,
taker,
fillableAmount,
);
orderState = await zeroEx.exchange.getOrderStateAsync(signedOrder);
expect(orderState.isValid).to.be.true();
});
it('should report orderStateInvalid when maker allowance set to 0', async () => {
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
makerToken.address,
takerToken.address,
maker,
taker,
fillableAmount,
);
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0));
orderState = await zeroEx.exchange.getOrderStateAsync(signedOrder);
expect(orderState.isValid).to.be.false();
});
});
}); // tslint:disable:max-file-line-count