aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts86
-rw-r--r--packages/0x.js/src/utils/order_state_utils.ts18
-rw-r--r--packages/0x.js/test/order_state_watcher_test.ts115
-rw-r--r--packages/0x.js/test/remaining_fillable_calculator_test.ts176
-rw-r--r--packages/0x.js/test/utils/constants.ts1
5 files changed, 375 insertions, 21 deletions
diff --git a/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts
new file mode 100644
index 000000000..c77d4428c
--- /dev/null
+++ b/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts
@@ -0,0 +1,86 @@
+import {SignedOrder} from '../types';
+import {BigNumber} from 'bignumber.js';
+
+export class RemainingFillableCalculator {
+ private signedOrder: SignedOrder;
+ private isMakerTokenZRX: boolean;
+ // Transferrable Amount is the minimum of Approval and Balance
+ private transferrableMakerTokenAmount: BigNumber;
+ private transferrableMakerFeeTokenAmount: BigNumber;
+ private remainingMakerTokenAmount: BigNumber;
+ private remainingMakerFeeAmount: BigNumber;
+ constructor(signedOrder: SignedOrder,
+ isMakerTokenZRX: boolean,
+ transferrableMakerTokenAmount: BigNumber,
+ transferrableMakerFeeTokenAmount: BigNumber,
+ remainingMakerTokenAmount: BigNumber) {
+ this.signedOrder = signedOrder;
+ this.isMakerTokenZRX = isMakerTokenZRX;
+ this.transferrableMakerTokenAmount = transferrableMakerTokenAmount;
+ this.transferrableMakerFeeTokenAmount = transferrableMakerFeeTokenAmount;
+ this.remainingMakerTokenAmount = remainingMakerTokenAmount;
+ this.remainingMakerFeeAmount = remainingMakerTokenAmount.times(signedOrder.makerFee)
+ .dividedToIntegerBy(signedOrder.makerTokenAmount);
+ }
+ public computeRemainingMakerFillable(): BigNumber {
+ if (this.hasSufficientFundsForFeeAndTransferAmount()) {
+ return this.remainingMakerTokenAmount;
+ }
+ if (this.signedOrder.makerFee.isZero()) {
+ return BigNumber.min(this.remainingMakerTokenAmount, this.transferrableMakerTokenAmount);
+ }
+ return this.calculatePartiallyFillableMakerTokenAmount();
+ }
+ public computeRemainingTakerFillable(): BigNumber {
+ return this.computeRemainingMakerFillable().times(this.signedOrder.takerTokenAmount)
+ .dividedToIntegerBy(this.signedOrder.makerTokenAmount);
+ }
+ private hasSufficientFundsForFeeAndTransferAmount(): boolean {
+ if (this.isMakerTokenZRX) {
+ const totalZRXTransferAmountRequired = this.remainingMakerTokenAmount.plus(this.remainingMakerFeeAmount);
+ const hasSufficientFunds = this.transferrableMakerTokenAmount.greaterThanOrEqualTo(
+ totalZRXTransferAmountRequired);
+ return hasSufficientFunds;
+ } else {
+ const hasSufficientFundsForTransferAmount = this.transferrableMakerTokenAmount.greaterThanOrEqualTo(
+ this.remainingMakerTokenAmount);
+ const hasSufficientFundsForFeeAmount = this.transferrableMakerFeeTokenAmount.greaterThanOrEqualTo(
+ this.remainingMakerFeeAmount);
+ const hasSufficientFunds = hasSufficientFundsForTransferAmount && hasSufficientFundsForFeeAmount;
+ return hasSufficientFunds;
+ }
+ }
+ private calculatePartiallyFillableMakerTokenAmount(): BigNumber {
+ // Given an order for 200 wei for 2 ZRXwei fee, find 100 wei for 1 ZRXwei. Order ratio is then 100:1
+ const orderToFeeRatio = this.signedOrder.makerTokenAmount.dividedBy(this.signedOrder.makerFee);
+ // The number of times the maker can fill the order, if each fill only required the transfer of a single
+ // baseUnit of fee tokens.
+ // Given 2 ZRXwei, the maximum amount of times Maker can fill this order, in terms of fees, is 2
+ const fillableTimesInFeeTokenBaseUnits = BigNumber.min(this.transferrableMakerFeeTokenAmount,
+ this.remainingMakerFeeAmount);
+ // The number of times the Maker can fill the order, given the Maker Token Balance
+ // Assuming a balance of 150 wei, and an orderToFeeRatio of 100:1, maker can fill this order 1 time.
+ let fillableTimesInMakerTokenUnits = this.transferrableMakerTokenAmount.dividedBy(orderToFeeRatio);
+ if (this.isMakerTokenZRX) {
+ // If ZRX is the maker token, the Fee and the Maker amount need to be removed from the same pool;
+ // 200 ZRXwei for 2ZRXwei fee can only be filled once (need 202 ZRXwei)
+ const totalZRXTokenPooled = this.transferrableMakerTokenAmount;
+ // The purchasing power here is less as the tokens are taken from the same Pool
+ // For every one number of fills, we have to take an extra ZRX out of the pool
+ fillableTimesInMakerTokenUnits = totalZRXTokenPooled.dividedBy(
+ orderToFeeRatio.plus(new BigNumber(1)));
+
+ }
+ // When Ratio is not fully divisible there can be remainders which cannot be represented, so they are floored.
+ // This can result in a RoundingError being thrown by the Exchange Contract.
+ const partiallyFillableMakerTokenAmount = fillableTimesInMakerTokenUnits
+ .times(this.signedOrder.makerTokenAmount)
+ .dividedToIntegerBy(this.signedOrder.makerFee);
+ const partiallyFillableFeeTokenAmount = fillableTimesInFeeTokenBaseUnits
+ .times(this.signedOrder.makerTokenAmount)
+ .dividedToIntegerBy(this.signedOrder.makerFee);
+ const partiallyFillableAmount = BigNumber.min(partiallyFillableMakerTokenAmount,
+ partiallyFillableFeeTokenAmount);
+ return partiallyFillableAmount;
+ }
+}
diff --git a/packages/0x.js/src/utils/order_state_utils.ts b/packages/0x.js/src/utils/order_state_utils.ts
index 123584f90..1d8f02a18 100644
--- a/packages/0x.js/src/utils/order_state_utils.ts
+++ b/packages/0x.js/src/utils/order_state_utils.ts
@@ -17,6 +17,7 @@ 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';
+import {RemainingFillableCalculator} from '../order_watcher/remaining_fillable_calculator';
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
@@ -78,12 +79,17 @@ export class OrderStateUtils {
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
const remainingMakerTokenAmount = remainingTakerTokenAmount.times(totalMakerTokenAmount)
.dividedToIntegerBy(totalTakerTokenAmount);
- const fillableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
- const remainingFillableMakerTokenAmount = BigNumber.min(fillableMakerTokenAmount, remainingMakerTokenAmount);
- const remainingFillableTakerTokenAmount = remainingFillableMakerTokenAmount
- .times(totalTakerTokenAmount)
- .dividedToIntegerBy(totalMakerTokenAmount);
- // TODO: Handle edge case where maker token is ZRX with fee
+ const transferrableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
+ const transferrableFeeTokenAmount = BigNumber.min([makerFeeProxyAllowance, makerFeeBalance]);
+
+ const isMakerTokenZRX = signedOrder.makerTokenAddress === zrxTokenAddress;
+ const remainingFillableCalculator = new RemainingFillableCalculator(signedOrder,
+ isMakerTokenZRX,
+ transferrableMakerTokenAmount,
+ transferrableFeeTokenAmount,
+ remainingMakerTokenAmount);
+ const remainingFillableMakerTokenAmount = remainingFillableCalculator.computeRemainingMakerFillable();
+ const remainingFillableTakerTokenAmount = remainingFillableCalculator.computeRemainingTakerFillable();
const orderRelevantState = {
makerBalance,
makerProxyAllowance,
diff --git a/packages/0x.js/test/order_state_watcher_test.ts b/packages/0x.js/test/order_state_watcher_test.ts
index 00b290252..834099ef6 100644
--- a/packages/0x.js/test/order_state_watcher_test.ts
+++ b/packages/0x.js/test/order_state_watcher_test.ts
@@ -3,10 +3,10 @@ import * as chai from 'chai';
import * as _ from 'lodash';
import * as Web3 from 'web3';
import BigNumber from 'bignumber.js';
-import { chaiSetup } from './utils/chai_setup';
-import { web3Factory } from './utils/web3_factory';
-import { Web3Wrapper } from '../src/web3_wrapper';
-import { OrderStateWatcher } from '../src/order_watcher/order_state_watcher';
+import {chaiSetup} from './utils/chai_setup';
+import {web3Factory} from './utils/web3_factory';
+import {Web3Wrapper} from '../src/web3_wrapper';
+import {OrderStateWatcher} from '../src/order_watcher/order_state_watcher';
import {
Token,
ZeroEx,
@@ -20,11 +20,12 @@ import {
OrderStateInvalid,
ExchangeContractErrs,
} from '../src';
-import { TokenUtils } from './utils/token_utils';
-import { FillScenarios } from './utils/fill_scenarios';
-import { DoneCallback } from '../src/types';
+import {TokenUtils} from './utils/token_utils';
+import {FillScenarios} from './utils/fill_scenarios';
+import {DoneCallback} from '../src/types';
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
import {reportCallbackErrors} from './utils/report_callback_errors';
+import {constants as constants} from './utils/constants';
const TIMEOUT_MS = 150;
@@ -47,7 +48,8 @@ describe('OrderStateWatcher', () => {
let taker: string;
let web3Wrapper: Web3Wrapper;
let signedOrder: SignedOrder;
- const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), 18);
+ const decimals = constants.ZRX_DECIMALS;
+ const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
before(async () => {
web3 = web3Factory.create();
zeroEx = new ZeroEx(web3.currentProvider);
@@ -254,15 +256,15 @@ describe('OrderStateWatcher', () => {
describe('remainingFillable(M|T)akerTokenAmount', () => {
it('should calculate correct remaining fillable', (done: DoneCallback) => {
(async () => {
- const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), 18);
- const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), 18);
+ const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), decimals);
+ const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), decimals);
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
makerToken.address, takerToken.address, maker, taker, makerFillableAmount,
takerFillableAmount,
);
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
- const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
+ const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
let eventCount = 0;
@@ -273,9 +275,9 @@ describe('OrderStateWatcher', () => {
expect(validOrderState.orderHash).to.be.equal(orderHash);
const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
- ZeroEx.toBaseUnitAmount(new BigNumber(16), 18));
+ ZeroEx.toBaseUnitAmount(new BigNumber(16), decimals));
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
- ZeroEx.toBaseUnitAmount(new BigNumber(8), 18));
+ ZeroEx.toBaseUnitAmount(new BigNumber(8), decimals));
if (eventCount === 2) {
done();
}
@@ -295,7 +297,7 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
- const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), 18);
+ const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
@@ -319,11 +321,12 @@ describe('OrderStateWatcher', () => {
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
- const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
+ const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
const transferAmount = makerBalance.sub(remainingAmount);
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
+ expect(orderState.isValid).to.be.true();
const validOrderState = orderState as OrderStateValid;
const orderRelevantState = validOrderState.orderRelevantState;
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
@@ -337,6 +340,88 @@ describe('OrderStateWatcher', () => {
makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount);
})().catch(done);
});
+ it('should equal remaining amount when partially cancelled and order has fees', (done: DoneCallback) => {
+ (async () => {
+ const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
+ const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
+ const feeRecipient = taker;
+ signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerToken.address, takerToken.address, makerFee, takerFee, maker,
+ taker, fillableAmount, feeRecipient);
+
+ const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
+
+ const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
+ const transferTokenAmount = makerFee.sub(remainingTokenAmount);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
+
+ const callback = reportCallbackErrors(done)((orderState: OrderState) => {
+ expect(orderState.isValid).to.be.true();
+ const validOrderState = orderState as OrderStateValid;
+ const orderRelevantState = validOrderState.orderRelevantState;
+ expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
+ remainingTokenAmount);
+ done();
+ });
+ zeroEx.orderStateWatcher.subscribe(callback);
+ await zeroEx.exchange.cancelOrderAsync(signedOrder, transferTokenAmount);
+ })().catch(done);
+ });
+ it('should equal ratio amount when fee balance is lowered', (done: DoneCallback) => {
+ (async () => {
+ const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
+ const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals);
+ const feeRecipient = taker;
+ signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerToken.address, takerToken.address, makerFee, takerFee, maker,
+ taker, fillableAmount, feeRecipient);
+
+ const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
+
+ const remainingFeeAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals);
+ const transferFeeAmount = makerFee.sub(remainingFeeAmount);
+
+ const remainingTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(4), decimals);
+ const transferTokenAmount = makerFee.sub(remainingTokenAmount);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
+
+ const callback = reportCallbackErrors(done)((orderState: OrderState) => {
+ const validOrderState = orderState as OrderStateValid;
+ const orderRelevantState = validOrderState.orderRelevantState;
+ expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
+ remainingFeeAmount);
+ done();
+ });
+ zeroEx.orderStateWatcher.subscribe(callback);
+ await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, remainingFeeAmount);
+ await zeroEx.token.transferAsync(
+ makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferTokenAmount);
+ })().catch(done);
+ });
+ it('should calculate full amount when all available and non-divisible', (done: DoneCallback) => {
+ (async () => {
+ const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), decimals);
+ const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
+ const feeRecipient = taker;
+ signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
+ makerToken.address, takerToken.address, makerFee, takerFee, maker,
+ taker, fillableAmount, feeRecipient);
+
+ const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
+ await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
+
+ const callback = reportCallbackErrors(done)((orderState: OrderState) => {
+ const validOrderState = orderState as OrderStateValid;
+ const orderRelevantState = validOrderState.orderRelevantState;
+ expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
+ fillableAmount);
+ done();
+ });
+ zeroEx.orderStateWatcher.subscribe(callback);
+ await zeroEx.token.setProxyAllowanceAsync(
+ makerToken.address, maker, ZeroEx.toBaseUnitAmount(new BigNumber(100), decimals));
+ })().catch(done);
+ });
});
it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
(async () => {
diff --git a/packages/0x.js/test/remaining_fillable_calculator_test.ts b/packages/0x.js/test/remaining_fillable_calculator_test.ts
new file mode 100644
index 000000000..65b65efd8
--- /dev/null
+++ b/packages/0x.js/test/remaining_fillable_calculator_test.ts
@@ -0,0 +1,176 @@
+import 'mocha';
+import * as chai from 'chai';
+import BigNumber from 'bignumber.js';
+import { chaiSetup } from './utils/chai_setup';
+import { RemainingFillableCalculator } from '../src/order_watcher/remaining_fillable_calculator';
+import { SignedOrder, ECSignature } from '../src/types';
+import { TokenUtils } from './utils/token_utils';
+import { ZeroEx } from '../src/0x';
+
+chaiSetup.configure();
+const expect = chai.expect;
+
+describe('RemainingFillableCalculator', () => {
+ let calculator: RemainingFillableCalculator;
+ let signedOrder: SignedOrder;
+ let transferrableMakerTokenAmount: BigNumber;
+ let transferrableMakerFeeTokenAmount: BigNumber;
+ let remainingMakerTokenAmount: BigNumber;
+ let makerAmount: BigNumber;
+ let takerAmount: BigNumber;
+ let makerFeeAmount: BigNumber;
+ let isMakerTokenZRX: boolean;
+ const makerToken: string = '0x1';
+ const takerToken: string = '0x2';
+ const decimals: number = 4;
+ const zero: BigNumber = new BigNumber(0);
+ const zeroAddress = '0x0';
+ const signature: ECSignature = { v: 27, r: '', s: ''};
+ beforeEach(async () => {
+ [makerAmount, takerAmount, makerFeeAmount] = [ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals)];
+ [transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount] = [
+ ZeroEx.toBaseUnitAmount(new BigNumber(50), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(5), decimals)];
+ });
+ function buildSignedOrder(): SignedOrder {
+ return { ecSignature: signature,
+ exchangeContractAddress: zeroAddress,
+ feeRecipient: zeroAddress,
+ maker: zeroAddress,
+ taker: zeroAddress,
+ makerFee: makerFeeAmount,
+ takerFee: zero,
+ makerTokenAmount: makerAmount,
+ takerTokenAmount: takerAmount,
+ makerTokenAddress: makerToken,
+ takerTokenAddress: takerToken,
+ salt: zero,
+ expirationUnixTimestampSec: zero };
+ }
+ describe('Maker token is NOT ZRX', () => {
+ before(async () => {
+ isMakerTokenZRX = false;
+ });
+ it('calculates the correct amount when unfilled and funds available', () => {
+ signedOrder = buildSignedOrder();
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
+ });
+ it('calculates the correct amount when partially filled and funds available', () => {
+ signedOrder = buildSignedOrder();
+ remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
+ });
+ it('calculates the amount to be 0 when all fee funds are transferred', () => {
+ signedOrder = buildSignedOrder();
+ transferrableMakerFeeTokenAmount = zero;
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
+ });
+ it('calculates the correct amount when balance is less than remaining fillable', () => {
+ signedOrder = buildSignedOrder();
+ const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
+ transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
+ });
+ describe('Order to Fee Ratio is < 1', () => {
+ beforeEach(async () => {
+ [makerAmount, takerAmount, makerFeeAmount] = [ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(6), decimals)];
+ });
+ it('calculates the correct amount when funds unavailable', () => {
+ signedOrder = buildSignedOrder();
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
+ transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount,
+ remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(transferrableMakerTokenAmount);
+ });
+ });
+ describe('Ratio is not evenly divisble', () => {
+ beforeEach(async () => {
+ [makerAmount, takerAmount, makerFeeAmount] = [ZeroEx.toBaseUnitAmount(new BigNumber(3), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals),
+ ZeroEx.toBaseUnitAmount(new BigNumber(7), decimals)];
+ });
+ it('calculates the correct amount when funds unavailable', () => {
+ signedOrder = buildSignedOrder();
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ const transferredAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
+ transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(transferredAmount);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount,
+ remainingMakerTokenAmount);
+ const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
+ expect(calculatedFillableAmount.lessThanOrEqualTo(transferrableMakerTokenAmount)).to.be.true();
+ expect(calculatedFillableAmount).to.be.bignumber.greaterThan(new BigNumber(0));
+ const orderToFeeRatio = signedOrder.makerTokenAmount.dividedBy(signedOrder.makerFee);
+ const calculatedFeeAmount = calculatedFillableAmount.dividedBy(orderToFeeRatio);
+ expect(calculatedFeeAmount).to.be.bignumber.lessThan(transferrableMakerFeeTokenAmount);
+ });
+ });
+ });
+ describe('Maker Token is ZRX', () => {
+ before(async () => {
+ isMakerTokenZRX = true;
+ });
+ it('calculates the correct amount when unfilled and funds available', () => {
+ signedOrder = buildSignedOrder();
+ transferrableMakerTokenAmount = makerAmount.plus(makerFeeAmount);
+ transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
+ });
+ it('calculates the correct amount when partially filled and funds available', () => {
+ signedOrder = buildSignedOrder();
+ remainingMakerTokenAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), decimals);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(remainingMakerTokenAmount);
+ });
+ it('calculates the amount to be 0 when all fee funds are transferred', () => {
+ signedOrder = buildSignedOrder();
+ transferrableMakerTokenAmount = zero;
+ transferrableMakerFeeTokenAmount = zero;
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount;
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ expect(calculator.computeRemainingMakerFillable()).to.be.bignumber.equal(zero);
+ });
+ it('calculates the correct amount when balance is less than remaining fillable', () => {
+ signedOrder = buildSignedOrder();
+ const partiallyFilledAmount = ZeroEx.toBaseUnitAmount(new BigNumber(2), decimals);
+ remainingMakerTokenAmount = signedOrder.makerTokenAmount.minus(partiallyFilledAmount);
+ transferrableMakerTokenAmount = remainingMakerTokenAmount.minus(partiallyFilledAmount);
+ transferrableMakerFeeTokenAmount = transferrableMakerTokenAmount;
+
+ const orderToFeeRatio = signedOrder.makerTokenAmount.dividedToIntegerBy(signedOrder.makerFee);
+ const expectedFillableAmount = new BigNumber(450980);
+ calculator = new RemainingFillableCalculator(signedOrder, isMakerTokenZRX,
+ transferrableMakerTokenAmount, transferrableMakerFeeTokenAmount, remainingMakerTokenAmount);
+ const calculatedFillableAmount = calculator.computeRemainingMakerFillable();
+ const numberOfFillsInRatio = calculatedFillableAmount.dividedToIntegerBy(orderToFeeRatio);
+ const calculatedFillableAmountPlusFees = calculatedFillableAmount.plus(numberOfFillsInRatio);
+ expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(transferrableMakerTokenAmount);
+ expect(calculatedFillableAmountPlusFees).to.be.bignumber.lessThan(remainingMakerTokenAmount);
+ expect(calculatedFillableAmount).to.be.bignumber.equal(expectedFillableAmount);
+ expect(numberOfFillsInRatio.decimalPlaces()).to.be.equal(0);
+ });
+ });
+});
diff --git a/packages/0x.js/test/utils/constants.ts b/packages/0x.js/test/utils/constants.ts
index c7d3aebca..5992c226e 100644
--- a/packages/0x.js/test/utils/constants.ts
+++ b/packages/0x.js/test/utils/constants.ts
@@ -5,4 +5,5 @@ export const constants = {
TESTRPC_NETWORK_ID: 50,
KOVAN_RPC_URL: 'https://kovan.infura.io',
ROPSTEN_RPC_URL: 'https://ropsten.infura.io',
+ ZRX_DECIMALS: 18,
};