aboutsummaryrefslogtreecommitdiffstats
path: root/packages/0x.js/src/order_watcher/remaining_fillable_calculator.ts
blob: 20b09d6066fd630856614e3425870965845d5b11 (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
88
89
90
91
92
93
94
95
96
import { BigNumber } from '@0xproject/utils';

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