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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
import { ExchangeContractErrs } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { AbstractBalanceAndProxyAllowanceLazyStore } from './abstract/abstract_balance_and_proxy_allowance_lazy_store';
import { constants } from './constants';
import { TradeSide, TransferType } from './types';
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,
},
},
};
/**
* An exchange transfer simulator which simulates asset transfers exactly how the
* 0x exchange contract would do them.
*/
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);
}
/**
* Instantiate a ExchangeTransferSimulator
* @param store A class that implements AbstractBalanceAndProxyAllowanceLazyStore
* @return an instance of ExchangeTransferSimulator
*/
constructor(store: AbstractBalanceAndProxyAllowanceLazyStore) {
this._store = store;
}
/**
* Simulates transferFrom call performed by a proxy
* @param assetData Data of the asset being transferred. Includes
* it's identifying information and assetType,
* e.g address for ERC20, address & tokenId for ERC721
* @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(
assetData: 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(assetData, to, amountInBaseUnits);
return;
}
const balance = await this._store.getBalanceAsync(assetData, from);
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, from);
if (proxyAllowance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
}
if (balance.lessThan(amountInBaseUnits)) {
ExchangeTransferSimulator._throwValidationError(FailureReason.Balance, tradeSide, transferType);
}
await this._decreaseProxyAllowanceAsync(assetData, from, amountInBaseUnits);
await this._decreaseBalanceAsync(assetData, from, amountInBaseUnits);
await this._increaseBalanceAsync(assetData, to, amountInBaseUnits);
}
private async _decreaseProxyAllowanceAsync(
assetData: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const proxyAllowance = await this._store.getProxyAllowanceAsync(assetData, userAddress);
// HACK: This code assumes that all tokens with an UNLIMITED_ALLOWANCE_IN_BASE_UNITS set,
// are UnlimitedAllowanceTokens. This is however not true, it just so happens that all
// DummyERC20Tokens we use in tests are.
if (!proxyAllowance.eq(constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
this._store.setProxyAllowance(assetData, userAddress, proxyAllowance.minus(amountInBaseUnits));
}
}
private async _increaseBalanceAsync(
assetData: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(assetData, userAddress);
this._store.setBalance(assetData, userAddress, balance.plus(amountInBaseUnits));
}
private async _decreaseBalanceAsync(
assetData: string,
userAddress: string,
amountInBaseUnits: BigNumber,
): Promise<void> {
const balance = await this._store.getBalanceAsync(assetData, userAddress);
this._store.setBalance(assetData, userAddress, balance.minus(amountInBaseUnits));
}
}
|