aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/0x.js/src')
-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
2 files changed, 98 insertions, 6 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,