Merge pull request #170 from 0xProject/addOrderValidation
Add validateOrderFillableOrThrowAsync Method
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# CHANGELOG
|
||||
|
||||
v0.17.0 - _September 26, 2017_
|
||||
* Added `zeroEx.exchange.validateOrderFillableOrThrowAsync` to simplify orderbook pruning (#170)
|
||||
* Made `zeroEx.exchange.getZRXTokenAddressAsync` public (#171)
|
||||
|
||||
v0.16.0 - _September 20, 2017_
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
LogCancelContractEventArgs,
|
||||
LogWithDecodedArgs,
|
||||
MethodOpts,
|
||||
ValidateOrderFillableOpts,
|
||||
} from '../types';
|
||||
import {assert} from '../utils/assert';
|
||||
import {utils} from '../utils/utils';
|
||||
@@ -623,6 +624,25 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
const exchangeAddress = exchangeInstance.address;
|
||||
return exchangeAddress;
|
||||
}
|
||||
/**
|
||||
* Checks if order is still fillable and throws an error otherwise. Useful for orderbook
|
||||
* pruning where you want to remove stale orders without knowing who the taker will be.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
* signedOrder you wish to validate.
|
||||
* @param opts An object that conforms to the ValidateOrderFillableOpts
|
||||
* interface. Allows specifying a specific fillTakerTokenAmount
|
||||
* to validate for.
|
||||
*/
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
signedOrder: SignedOrder, opts?: ValidateOrderFillableOpts,
|
||||
): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined;
|
||||
await this._orderValidationUtils.validateOrderFillableOrThrowAsync(
|
||||
signedOrder, zrxTokenAddress, expectedFillTakerTokenAmount,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Checks if order fill will succeed and throws an error otherwise.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
|
||||
10
src/types.ts
10
src/types.ts
@@ -433,6 +433,16 @@ export interface Artifact {
|
||||
}};
|
||||
}
|
||||
|
||||
/*
|
||||
* expectedFillTakerTokenAmount: If specified, the validation method will ensure that the
|
||||
* supplied order maker has a sufficient allowance/balance to fill this amount of the order's
|
||||
* takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient
|
||||
* allowance/balance to fill the entire remaining order amount.
|
||||
*/
|
||||
export interface ValidateOrderFillableOpts {
|
||||
expectedFillTakerTokenAmount?: BigNumber.BigNumber;
|
||||
}
|
||||
|
||||
/*
|
||||
* defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number
|
||||
* let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as _ from 'lodash';
|
||||
import {ExchangeContractErrs, SignedOrder, Order, ZeroExError} from '../types';
|
||||
import {ZeroEx} from '../0x';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
@@ -12,6 +13,23 @@ export class OrderValidationUtils {
|
||||
this.tokenWrapper = tokenWrapper;
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
}
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
signedOrder: SignedOrder, zrxTokenAddress: string, expectedFillTakerTokenAmount?: BigNumber.BigNumber,
|
||||
): Promise<void> {
|
||||
const orderHash = utils.getOrderHashHex(signedOrder);
|
||||
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
this.validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
|
||||
);
|
||||
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
||||
fillTakerTokenAmount = expectedFillTakerTokenAmount;
|
||||
}
|
||||
await this.validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, zrxTokenAddress,
|
||||
);
|
||||
}
|
||||
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string,
|
||||
@@ -24,16 +42,13 @@ export class OrderValidationUtils {
|
||||
throw new Error(ZeroExError.InvalidSignature);
|
||||
}
|
||||
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
this.validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
|
||||
);
|
||||
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
|
||||
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillExpired);
|
||||
}
|
||||
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
@@ -147,4 +162,17 @@ export class OrderValidationUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
private validateRemainingFillAmountNotZeroOrThrow(
|
||||
takerTokenAmount: BigNumber.BigNumber, unavailableTakerTokenAmount: BigNumber.BigNumber,
|
||||
) {
|
||||
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
}
|
||||
private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber.BigNumber) {
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillExpired);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,46 @@ describe('OrderValidation', () => {
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('validateOrderFillableOrThrowAsync', () => {
|
||||
it('should succeed if the order is fillable', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
);
|
||||
});
|
||||
it('should succeed if the order is asymmetric and fillable', async () => {
|
||||
const makerFillableAmount = fillableAmount;
|
||||
const takerFillableAmount = fillableAmount.minus(4);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
makerFillableAmount, takerFillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
|
||||
});
|
||||
});
|
||||
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
|
||||
@@ -62,7 +62,9 @@ export class FillScenarios {
|
||||
fillableAmount, fillableAmount,
|
||||
);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = false;
|
||||
await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await this.zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
|
||||
Reference in New Issue
Block a user