aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contract-wrappers/src/utils/exchange_transfer_simulator.ts
blob: 4b75ea386056f2ec48158974386dbc8658893e49 (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import { ExchangeContractErrs } from '@0x/types';
import { BigNumber } from '@0x/utils';

import { AbstractBalanceAndProxyAllowanceLazyStore } from '../abstract/abstract_balance_and_proxy_allowance_lazy_store';
import { TradeSide, TransferType } from '../types';
import { constants } from '../utils/constants';

enum FailureReason {
    Balance = 'balance',
    ProxyAllowance = 'proxyAllowance',
}

const ERR_MSG_MAPPING = {
    [FailureReason.Balance]: {
        [TradeSide.Maker]: {
            [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
            [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
        },
        [TradeSide.Taker]: {
            [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
            [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
        },
    },
    [FailureReason.ProxyAllowance]: {
        [TradeSide.Maker]: {
            [TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
            [TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
        },
        [TradeSide.Taker]: {
            [TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
            [TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
        },
    },
};

export class ExchangeTransferSimulator {
    private readonly _store: AbstractBalanceAndProxyAllowanceLazyStore;
    private static _throwValidationError(
        failureReason: FailureReason,
        tradeSide: TradeSide,
        transferType: TransferType,
    ): never {
        const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
        throw new Error(errMsg);
    }
    constructor(store: AbstractBalanceAndProxyAllowanceLazyStore) {
        this._store = store;
    }
    /**
     * Simulates transferFrom call performed by a proxy
     * @param  tokenAddress      Address of the token to be transferred
     * @param  from              Owner of the transferred tokens
     * @param  to                Recipient of the transferred tokens
     * @param  amountInBaseUnits The amount of tokens being transferred
     * @param  tradeSide         Is Maker/Taker transferring
     * @param  transferType      Is it a fee payment or a value transfer
     */
    public async transferFromAsync(
        tokenAddress: string,
        from: string,
        to: string,
        amountInBaseUnits: BigNumber,
        tradeSide: TradeSide,
        transferType: TransferType,
    ): Promise<void> {
        // HACK: When simulating an open order (e.g taker is NULL_ADDRESS), we don't want to adjust balances/
        // allowances for the taker. We do however, want to increase the balance of the maker since the maker
        // might be relying on those funds to fill subsequent orders or pay the order's fees.
        if (from === constants.NULL_ADDRESS && tradeSide === TradeSide.Taker) {
            await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
            return;
        }
        const balance = await this._store.getBalanceAsync(tokenAddress, from);
        const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, from);
        if (proxyAllowance.isLessThan(amountInBaseUnits)) {
            ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
        }
        if (balance.isLessThan(amountInBaseUnits)) {
            ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
        }
        await this._decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
        await this._decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
        await this._increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
    }
    private async _decreaseProxyAllowanceAsync(
        tokenAddress: string,
        userAddress: string,
        amountInBaseUnits: BigNumber,
    ): Promise<void> {
        const proxyAllowance = await this._store.getProxyAllowanceAsync(tokenAddress, userAddress);
        if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
            this._store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
        }
    }
    private async _increaseBalanceAsync(
        tokenAddress: string,
        userAddress: string,
        amountInBaseUnits: BigNumber,
    ): Promise<void> {
        const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
        this._store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
    }
    private async _decreaseBalanceAsync(
        tokenAddress: string,
        userAddress: string,
        amountInBaseUnits: BigNumber,
    ): Promise<void> {
        const balance = await this._store.getBalanceAsync(tokenAddress, userAddress);
        this._store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
    }
}