aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonid <logvinov.leon@gmail.com>2017-08-24 15:56:34 +0800
committerGitHub <noreply@github.com>2017-08-24 15:56:34 +0800
commite216f2ff6de9b2dd998c37c919b8786f7dff1101 (patch)
treeb6fc54d6a328cc4c307bcf95deea36a609c88b94
parent18f2a93950500e3efec7ae25e32ff644ebb7efcc (diff)
parent8d6045c1d5737ff2e62f6f3b906cf639da1aa2ba (diff)
downloaddexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.gz
dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.tar.zst
dexon-sol-tools-e216f2ff6de9b2dd998c37c919b8786f7dff1101.zip
Merge pull request #128 from 0xProject/verifyOrder
Add implementation for public order validation functions
-rw-r--r--CHANGELOG.md10
-rw-r--r--src/contract_wrappers/exchange_wrapper.ts151
-rw-r--r--src/types.ts1
-rw-r--r--src/utils/order_validation_utils.ts69
-rw-r--r--test/exchange_wrapper_test.ts83
-rw-r--r--test/order_validation_test.ts161
6 files changed, 291 insertions, 184 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3ca451d85..b5fef398c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,14 @@
# CHANGELOG
-v0.9.4 - _TBD_
+v0.10.0 - _TBD_
------------------------
- * Add clear error message when checksummed address is passed to a public method (#124)
+ * Added `zeroEx.exchange.validateFillOrderThrowIfInvalidAsync` (#128)
+ * Added `zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync` (#128)
+ * Added `zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync` (#128)
+ * Added `zeroEx.exchange.isRoundingErrorAsync` (#128)
+ * Added `zeroEx.proxy.getContractAddressAsync` (#130)
+ * Added clear error message when checksummed address is passed to a public method (#124)
* Fixes the description of `shouldThrowOnInsufficientBalanceOrAllowance` in docs (#127)
- * Add `zeroEx.proxy.getContractAddressAsync` to public interface (#130)
v0.9.3 - _Aug 22, 2017_
------------------------
diff --git a/src/contract_wrappers/exchange_wrapper.ts b/src/contract_wrappers/exchange_wrapper.ts
index 7994ee72f..66cf06dee 100644
--- a/src/contract_wrappers/exchange_wrapper.ts
+++ b/src/contract_wrappers/exchange_wrapper.ts
@@ -84,7 +84,7 @@ export class ExchangeWrapper extends ContractWrapper {
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
super(web3Wrapper);
this._tokenWrapper = tokenWrapper;
- this._orderValidationUtils = new OrderValidationUtils(tokenWrapper);
+ this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this);
this._exchangeLogEventEmitters = [];
}
/**
@@ -161,7 +161,7 @@ export class ExchangeWrapper extends ContractWrapper {
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this._validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
+ await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
@@ -226,7 +226,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
for (const signedOrder of signedOrders) {
- await this._validateFillOrderAndThrowIfInvalidAsync(
+ await this.validateFillOrderThrowIfInvalidAsync(
signedOrder, fillTakerTokenAmount, takerAddress);
}
if (_.isEmpty(signedOrders)) {
@@ -311,7 +311,7 @@ export class ExchangeWrapper extends ContractWrapper {
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
for (const orderFillRequest of orderFillRequests) {
- await this._validateFillOrderAndThrowIfInvalidAsync(
+ await this.validateFillOrderThrowIfInvalidAsync(
orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
}
if (_.isEmpty(orderFillRequests)) {
@@ -377,9 +377,8 @@ export class ExchangeWrapper extends ContractWrapper {
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this._validateFillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
- await this._validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount);
+ await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
@@ -431,7 +430,8 @@ export class ExchangeWrapper extends ContractWrapper {
}
const exchangeInstance = await this._getExchangeContractAsync();
for (const request of orderFillOrKillRequests) {
- await this._validateFillOrKillOrderAndThrowIfInvalidAsync(request.signedOrder, request.fillTakerAmount);
+ await this.validateFillOrKillOrderThrowIfInvalidAsync(
+ request.signedOrder, request.fillTakerAmount, takerAddress);
}
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => {
@@ -488,7 +488,7 @@ export class ExchangeWrapper extends ContractWrapper {
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
const exchangeInstance = await this._getExchangeContractAsync();
- await this._validateCancelOrderAndThrowIfInvalidAsync(order, cancelTakerTokenAmount);
+ await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
const gas = await exchangeInstance.cancelOrder.estimateGas(
@@ -534,7 +534,7 @@ export class ExchangeWrapper extends ContractWrapper {
const maker = makers[0];
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
for (const cancellationRequest of orderCancellationRequests) {
- await this._validateCancelOrderAndThrowIfInvalidAsync(
+ await this.validateCancelOrderThrowIfInvalidAsync(
cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
);
}
@@ -626,6 +626,78 @@ export class ExchangeWrapper extends ContractWrapper {
const exchangeAddress = exchangeInstance.address;
return exchangeAddress;
}
+ /**
+ * Checks if order fill will succeed and throws an error otherwise.
+ * @param signedOrder An object that conforms to the SignedOrder interface. The
+ * signedOrder you wish to fill.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Web3.Provider passed to 0x.js.
+ */
+ public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber.BigNumber,
+ takerAddress: string): Promise<void> {
+ assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
+ assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const zrxTokenAddress = await this._getZRXTokenAddressAsync();
+ await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
+ }
+ /**
+ * Checks if cancelling a given order will succeed and throws an informative error if it won't.
+ * @param order An object that conforms to the Order or SignedOrder interface.
+ * The order you would like to cancel.
+ * @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
+ */
+ public async validateCancelOrderThrowIfInvalidAsync(
+ order: Order, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<void> {
+ assert.doesConformToSchema('order', order, orderSchema);
+ assert.isBigNumber('cancelTakerTokenAmount', cancelTakerTokenAmount);
+ const orderHash = utils.getOrderHashHex(order);
+ const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
+ await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
+ order, cancelTakerTokenAmount, unavailableTakerTokenAmount);
+ }
+ /**
+ * Checks if calling fillOrKill on a given order will succeed and throws an informative error if it won't.
+ * @param signedOrder An object that conforms to the SignedOrder interface. The
+ * signedOrder you wish to fill.
+ * @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
+ * @param takerAddress The user Ethereum address who would like to fill this order.
+ * Must be available via the supplied Web3.Provider passed to 0x.js.
+ */
+ public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber.BigNumber,
+ takerAddress: string): Promise<void> {
+ assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
+ assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
+ await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
+ const zrxTokenAddress = await this._getZRXTokenAddressAsync();
+ await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
+ }
+ /**
+ * Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing:
+ * `(fillTakerTokenAmount * makerTokenAmount) / takerTokenAmount`.
+ * 0x Protocol does not accept any trades that result in large rounding errors. This means that tokens with few or
+ * no decimals can only be filled in quantities and ratios that avoid large rounding errors.
+ * @param fillTakerTokenAmount The amount of the order (in taker tokens baseUnits) that you wish to fill.
+ * @param takerTokenAmount The order size on the taker side
+ * @param makerTokenAmount The order size on the maker side
+ */
+ public async isRoundingErrorAsync(fillTakerTokenAmount: BigNumber.BigNumber,
+ takerTokenAmount: BigNumber.BigNumber,
+ makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
+ assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
+ assert.isBigNumber('takerTokenAmount', takerTokenAmount);
+ assert.isBigNumber('makerTokenAmount', makerTokenAmount);
+ const exchangeInstance = await this._getExchangeContractAsync();
+ const isRoundingError = await exchangeInstance.isRoundingError.call(
+ fillTakerTokenAmount, takerTokenAmount, makerTokenAmount,
+ );
+ return isRoundingError;
+ }
private async _invalidateContractInstancesAsync(): Promise<void> {
await this.stopWatchingAllEventsAsync();
delete this._exchangeContractIfExists;
@@ -653,56 +725,6 @@ export class ExchangeWrapper extends ContractWrapper {
const orderHashHex = await exchangeInstance.getOrderHash.call(orderAddresses, orderValues);
return orderHashHex;
}
- private async _validateFillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
- fillTakerAmount: BigNumber.BigNumber,
- senderAddress: string): Promise<void> {
- if (fillTakerAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
- }
- if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== senderAddress) {
- throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
- }
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
- if (signedOrder.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderFillExpired);
- }
- const zrxTokenAddress = await this._getZRXTokenAddressAsync(signedOrder.exchangeContractAddress);
- await this._orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
- signedOrder, fillTakerAmount, senderAddress, zrxTokenAddress,
- );
-
- const wouldRoundingErrorOccur = await this._isRoundingErrorAsync(
- fillTakerAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
- );
- if (wouldRoundingErrorOccur) {
- throw new Error(ExchangeContractErrs.OrderFillRoundingError);
- }
- }
- private async _validateCancelOrderAndThrowIfInvalidAsync(
- order: Order, takerTokenCancelAmount: BigNumber.BigNumber): Promise<void> {
- if (takerTokenCancelAmount.eq(0)) {
- throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
- }
- const orderHash = utils.getOrderHashHex(order);
- const unavailableAmount = await this.getUnavailableTakerAmountAsync(orderHash);
- if (order.takerTokenAmount.minus(unavailableAmount).eq(0)) {
- throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
- }
- const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
- if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
- throw new Error(ExchangeContractErrs.OrderCancelExpired);
- }
- }
- private async _validateFillOrKillOrderAndThrowIfInvalidAsync(signedOrder: SignedOrder,
- fillTakerAmount: BigNumber.BigNumber) {
- // Check that fillValue available >= fillTakerAmount
- const orderHashHex = utils.getOrderHashHex(signedOrder);
- const unavailableTakerAmount = await this.getUnavailableTakerAmountAsync(orderHashHex);
- const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount);
- if (remainingTakerAmount < fillTakerAmount) {
- throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
- }
- }
private _throwErrorLogsAsErrors(logs: ContractEvent[]): void {
const errEvent = _.find(logs, {event: 'LogError'});
if (!_.isUndefined(errEvent)) {
@@ -711,15 +733,6 @@ export class ExchangeWrapper extends ContractWrapper {
throw new Error(errMessage);
}
}
- private async _isRoundingErrorAsync(numerator: BigNumber.BigNumber,
- demoninator: BigNumber.BigNumber,
- makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
- const exchangeInstance = await this._getExchangeContractAsync();
- const isRoundingError = await exchangeInstance.isRoundingError.call(
- numerator, demoninator, makerTokenAmount,
- );
- return isRoundingError;
- }
private async _getExchangeContractAsync(): Promise<ExchangeContract> {
if (!_.isUndefined(this._exchangeContractIfExists)) {
return this._exchangeContractIfExists;
@@ -728,7 +741,7 @@ export class ExchangeWrapper extends ContractWrapper {
this._exchangeContractIfExists = contractInstance as ExchangeContract;
return this._exchangeContractIfExists;
}
- private async _getZRXTokenAddressAsync(exchangeContractAddress: string): Promise<string> {
+ private async _getZRXTokenAddressAsync(): Promise<string> {
const exchangeInstance = await this._getExchangeContractAsync();
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call();
return ZRXtokenAddress;
diff --git a/src/types.ts b/src/types.ts
index 0de87ca71..5ba85a7d5 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -180,6 +180,7 @@ export enum ExchangeContractErrs {
OrderCancelExpired = 'ORDER_CANCEL_EXPIRED',
OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO',
OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
+ OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO',
OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
diff --git a/src/utils/order_validation_utils.ts b/src/utils/order_validation_utils.ts
index 8a737040c..445ad43f9 100644
--- a/src/utils/order_validation_utils.ts
+++ b/src/utils/order_validation_utils.ts
@@ -1,10 +1,75 @@
-import {ExchangeContractErrs, SignedOrder} from '../types';
+import {ExchangeContractErrs, SignedOrder, Order} from '../types';
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
+import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
+import {utils} from '../utils/utils';
+import {constants} from '../utils/constants';
export class OrderValidationUtils {
private tokenWrapper: TokenWrapper;
- constructor(tokenWrapper: TokenWrapper) {
+ private exchangeWrapper: ExchangeWrapper;
+ constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) {
this.tokenWrapper = tokenWrapper;
+ this.exchangeWrapper = exchangeWrapper;
+ }
+ public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber.BigNumber,
+ takerAddress: string,
+ zrxTokenAddress: string): Promise<void> {
+ if (fillTakerTokenAmount.eq(0)) {
+ throw new Error(ExchangeContractErrs.OrderFillAmountZero);
+ }
+ const orderHash = utils.getOrderHashHex(signedOrder);
+ const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
+ if (signedOrder.makerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ }
+ 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);
+ }
+ await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
+ );
+
+ const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync(
+ fillTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
+ );
+ if (wouldRoundingErrorOccur) {
+ throw new Error(ExchangeContractErrs.OrderFillRoundingError);
+ }
+ }
+ public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
+ fillTakerTokenAmount: BigNumber.BigNumber,
+ takerAddress: string,
+ zrxTokenAddress: string): Promise<void> {
+ await this.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
+ );
+ // Check that fillValue available >= fillTakerAmount
+ const orderHashHex = utils.getOrderHashHex(signedOrder);
+ const unavailableTakerAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHashHex);
+ const remainingTakerAmount = signedOrder.takerTokenAmount.minus(unavailableTakerAmount);
+ if (remainingTakerAmount < fillTakerTokenAmount) {
+ throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
+ }
+ }
+ public async validateCancelOrderThrowIfInvalidAsync(order: Order,
+ cancelTakerTokenAmount: BigNumber.BigNumber,
+ unavailableTakerTokenAmount: BigNumber.BigNumber,
+ ): Promise<void> {
+ if (cancelTakerTokenAmount.eq(0)) {
+ throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
+ }
+ if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
+ throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
+ }
+ const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
+ if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
+ throw new Error(ExchangeContractErrs.OrderCancelExpired);
+ }
}
public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
signedOrder: SignedOrder, fillTakerAmount: BigNumber.BigNumber, senderAddress: string, zrxTokenAddress: string,
diff --git a/test/exchange_wrapper_test.ts b/test/exchange_wrapper_test.ts
index a9c237d0e..080be774a 100644
--- a/test/exchange_wrapper_test.ts
+++ b/test/exchange_wrapper_test.ts
@@ -95,24 +95,6 @@ describe('ExchangeWrapper', () => {
});
});
describe('#fillOrKillOrderAsync', () => {
- describe('failed fillOrKill', () => {
- it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
- const fillableAmount = new BigNumber(5);
- const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
- );
- const tooLargeFillAmount = new BigNumber(7);
- const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
- await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
- await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
- await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
- await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
-
- return expect(zeroEx.exchange.fillOrKillOrderAsync(
- signedOrder, tooLargeFillAmount, takerAddress,
- )).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
- });
- });
describe('successful fills', () => {
it('should fill a valid order', async () => {
const fillableAmount = new BigNumber(5);
@@ -174,49 +156,6 @@ describe('ExchangeWrapper', () => {
takerTokenAddress = takerToken.address;
});
describe('#fillOrderAsync', () => {
- describe('failed fills', () => {
- it('should throw when the fill amount is zero', async () => {
- const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
- );
- const zeroFillAmount = new BigNumber(0);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, zeroFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
- )).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
- });
- it('should throw when sender is not a taker', async () => {
- const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
- );
- const nonTakerAddress = userAddresses[6];
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, nonTakerAddress,
- )).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
- });
- 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.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
- )).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
- });
- it('should throw when there a rounding error would have occurred', async () => {
- const makerAmount = new BigNumber(3);
- const takerAmount = new BigNumber(5);
- const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
- makerAmount, takerAmount,
- );
- const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmountThatCausesRoundingError,
- shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
- )).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
- });
- });
describe('successful fills', () => {
it('should fill a valid order', async () => {
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
@@ -403,28 +342,6 @@ describe('ExchangeWrapper', () => {
orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
});
describe('#cancelOrderAsync', () => {
- describe('failed cancels', () => {
- it('should throw when cancel amount is zero', async () => {
- const zeroCancelAmount = new BigNumber(0);
- return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, zeroCancelAmount))
- .to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
- });
- it('should throw when order is expired', async () => {
- const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
- const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
- makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
- fillableAmount, expirationInPast,
- );
- orderHashHex = ZeroEx.getOrderHashHex(expiredSignedOrder);
- return expect(zeroEx.exchange.cancelOrderAsync(expiredSignedOrder, cancelAmount))
- .to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
- });
- it('should throw when order is already cancelled or filled', async () => {
- await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
- return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount))
- .to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
- });
- });
describe('successful cancels', () => {
it('should cancel an order', async () => {
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
diff --git a/test/order_validation_test.ts b/test/order_validation_test.ts
index 826b44077..8a6303f3a 100644
--- a/test/order_validation_test.ts
+++ b/test/order_validation_test.ts
@@ -8,12 +8,13 @@ import {ZeroEx, SignedOrder, Token, ExchangeContractErrs} from '../src';
import {TokenUtils} from './utils/token_utils';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {FillScenarios} from './utils/fill_scenarios';
+import {OrderValidationUtils} from '../src/utils/order_validation_utils';
chaiSetup.configure();
const expect = chai.expect;
const blockchainLifecycle = new BlockchainLifecycle();
-describe('OrderValidationUtils', () => {
+describe('OrderValidation', () => {
let web3: Web3;
let zeroEx: ZeroEx;
let userAddresses: string[];
@@ -28,6 +29,7 @@ describe('OrderValidationUtils', () => {
let makerAddress: string;
let takerAddress: string;
let feeRecipient: string;
+ let orderValidationUtils: OrderValidationUtils;
const fillableAmount = new BigNumber(5);
const fillTakerAmount = new BigNumber(5);
const shouldThrowOnInsufficientBalanceOrAllowance = false;
@@ -44,6 +46,7 @@ describe('OrderValidationUtils', () => {
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
makerTokenAddress = makerToken.address;
takerTokenAddress = takerToken.address;
+ orderValidationUtils = new OrderValidationUtils(zeroEx.token, zeroEx.exchange);
});
beforeEach(async () => {
await blockchainLifecycle.startAsync();
@@ -51,9 +54,113 @@ describe('OrderValidationUtils', () => {
afterEach(async () => {
await blockchainLifecycle.revertAsync();
});
- describe('#validateFillOrderBalancesAndAllowancesAndThrowIfInvalidAsync', () => {
+ describe('validateFillOrderAndThrowIfInvalidAsync', () => {
+ it('should throw when the fill amount is zero', async () => {
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ const zeroFillAmount = new BigNumber(0);
+ return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, zeroFillAmount, takerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
+ });
+ 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.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillableAmount, takerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
+ });
+ it('should throw when sender is not a taker', async () => {
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ const nonTakerAddress = userAddresses[6];
+ return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, nonTakerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
+ });
+ 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.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
+ });
+ it('should throw when there a rounding error would have occurred', async () => {
+ const makerAmount = new BigNumber(3);
+ const takerAmount = new BigNumber(5);
+ const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
+ makerAmount, takerAmount,
+ );
+ const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
+ return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
+ signedOrder, fillTakerAmountThatCausesRoundingError, takerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
+ });
+ });
+ describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
+ it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
+ const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ const tooLargeFillAmount = new BigNumber(7);
+ const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
+ await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
+ await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
+ await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
+ await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
+
+ return expect(zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
+ signedOrder, tooLargeFillAmount, takerAddress,
+ )).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
+ });
+ });
+ describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
+ let signedOrder: SignedOrder;
+ let orderHashHex: string;
+ const cancelAmount = new BigNumber(3);
+ beforeEach(async () => {
+ [coinbase, makerAddress, takerAddress] = userAddresses;
+ const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
+ makerTokenAddress = makerToken.address;
+ takerTokenAddress = takerToken.address;
+ signedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
+ );
+ orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
+ });
+ it('should throw when cancel amount is zero', async () => {
+ const zeroCancelAmount = new BigNumber(0);
+ return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount))
+ .to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
+ });
+ it('should throw when order is expired', async () => {
+ const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
+ const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
+ makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
+ fillableAmount, expirationInPast,
+ );
+ orderHashHex = ZeroEx.getOrderHashHex(expiredSignedOrder);
+ return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount))
+ .to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
+ });
+ it('should throw when order is already cancelled or filled', async () => {
+ await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
+ return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount))
+ .to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
+ });
+ });
+ describe('#validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync', () => {
describe('should throw when not enough balance or allowance to fulfill the order', () => {
const balanceToSubtractFromMaker = new BigNumber(3);
+ const balanceToSubtractFromTaker = new BigNumber(3);
const lackingAllowance = new BigNumber(3);
let signedOrder: SignedOrder;
beforeEach('create fillable signed order', async () => {
@@ -63,34 +170,34 @@ describe('OrderValidationUtils', () => {
});
it('should throw when taker balance is less than fill amount', async () => {
await zeroEx.token.transferAsync(
- takerTokenAddress, takerAddress, coinbase, balanceToSubtractFromMaker,
+ takerTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerBalance);
});
it('should throw when taker allowance is less than fill amount', async () => {
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress,
newAllowanceWhichIsLessThanFillAmount);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
it('should throw when maker balance is less than maker fill amount', async () => {
await zeroEx.token.transferAsync(
makerTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('should throw when maker allowance is less than maker fill amount', async () => {
const newAllowanceWhichIsLessThanFillAmount = fillTakerAmount.minus(lackingAllowance);
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress,
newAllowanceWhichIsLessThanFillAmount);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerAllowance);
});
});
@@ -109,16 +216,16 @@ describe('OrderValidationUtils', () => {
await zeroEx.token.transferAsync(
zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerFeeBalance);
});
it('should throw when maker doesn\'t have enough allowance to pay fees', async () => {
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, makerAddress,
newAllowanceWhichIsLessThanFees);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerFeeAllowance);
});
it('should throw when taker doesn\'t have enough balance to pay fees', async () => {
@@ -126,16 +233,16 @@ describe('OrderValidationUtils', () => {
await zeroEx.token.transferAsync(
zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerFeeBalance);
});
it('should throw when taker doesn\'t have enough allowance to pay fees', async () => {
const newAllowanceWhichIsLessThanFees = makerFee.minus(1);
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, takerAddress,
newAllowanceWhichIsLessThanFees);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerFeeAllowance);
});
});
@@ -155,8 +262,8 @@ describe('OrderValidationUtils', () => {
await zeroEx.token.transferAsync(
zrxTokenAddress, makerAddress, coinbase, balanceToSubtractFromMaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
});
it('should throw on insufficient allowance when makerToken is ZRX', async () => {
@@ -164,8 +271,8 @@ describe('OrderValidationUtils', () => {
const newAllowanceWhichIsInsufficient = oldAllowance.minus(1);
await zeroEx.token.setProxyAllowanceAsync(
zrxTokenAddress, makerAddress, newAllowanceWhichIsInsufficient);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderMakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerAllowance);
});
});
@@ -185,8 +292,8 @@ describe('OrderValidationUtils', () => {
await zeroEx.token.transferAsync(
zrxTokenAddress, takerAddress, coinbase, balanceToSubtractFromTaker,
);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerBalance);
});
it('should throw on insufficient allowance when takerToken is ZRX', async () => {
@@ -194,8 +301,8 @@ describe('OrderValidationUtils', () => {
const newAllowanceWhichIsInsufficient = oldAllowance.minus(1);
await zeroEx.token.setProxyAllowanceAsync(
zrxTokenAddress, takerAddress, newAllowanceWhichIsInsufficient);
- return expect(zeroEx.exchange.fillOrderAsync(
- signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
+ return expect((orderValidationUtils as any).validateFillOrderTakerBalancesAllowancesThrowIfInvalidAsync(
+ signedOrder, fillTakerAmount, takerAddress, zrxTokenAddress,
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
});
});