aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts
blob: e8601e678a36753f360a7a5cd9e06c203f38d434 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import {BigNumber} from 'bignumber.js';

import {SignedOrder} from '../types';

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;
    }
}