This repository has been archived by the owner on Dec 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
SwapPricingUtils.sol
336 lines (281 loc) · 13.8 KB
/
SwapPricingUtils.sol
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/math/SignedMath.sol";
import "../market/MarketUtils.sol";
import "../utils/Precision.sol";
import "../utils/Calc.sol";
import "./PricingUtils.sol";
// @title SwapPricingUtils
// @dev Library for pricing functions
library SwapPricingUtils {
using SignedMath for int256;
using SafeCast for uint256;
using SafeCast for int256;
using EventUtils for EventUtils.AddressItems;
using EventUtils for EventUtils.UintItems;
using EventUtils for EventUtils.IntItems;
using EventUtils for EventUtils.BoolItems;
using EventUtils for EventUtils.Bytes32Items;
using EventUtils for EventUtils.BytesItems;
using EventUtils for EventUtils.StringItems;
// @dev GetPriceImpactUsdParams struct used in getPriceImpactUsd to
// avoid stack too deep errors
// @param dataStore DataStore
// @param market the market to check
// @param tokenA the token to check balance for
// @param tokenB the token to check balance for
// @param priceForTokenA the price for tokenA
// @param priceForTokenB the price for tokenB
// @param usdDeltaForTokenA the USD change in amount of tokenA
// @param usdDeltaForTokenB the USD change in amount of tokenB
struct GetPriceImpactUsdParams {
DataStore dataStore;
Market.Props market;
address tokenA;
address tokenB;
uint256 priceForTokenA;
uint256 priceForTokenB;
int256 usdDeltaForTokenA;
int256 usdDeltaForTokenB;
}
// @dev PoolParams struct to contain pool values
// @param poolUsdForTokenA the USD value of tokenA in the pool
// @param poolUsdForTokenB the USD value of tokenB in the pool
// @param nextPoolUsdForTokenA the next USD value of tokenA in the pool
// @param nextPoolUsdForTokenB the next USD value of tokenB in the pool
struct PoolParams {
uint256 poolUsdForTokenA;
uint256 poolUsdForTokenB;
uint256 nextPoolUsdForTokenA;
uint256 nextPoolUsdForTokenB;
}
// @dev SwapFees struct to contain swap fee values
// @param feeReceiverAmount the fee amount for the fee receiver
// @param feeAmountForPool the fee amount for the pool
// @param amountAfterFees the output amount after fees
struct SwapFees {
uint256 feeReceiverAmount;
uint256 feeAmountForPool;
uint256 amountAfterFees;
address uiFeeReceiver;
uint256 uiFeeReceiverFactor;
uint256 uiFeeAmount;
}
// @dev get the price impact in USD
//
// note that there will be some difference between the pool amounts used for
// calculating the price impact and fees vs the actual pool amounts after the
// swap is done, since the pool amounts will be increased / decreased by an amount
// after factoring in the calculated price impact and fees
//
// since the calculations are based on the real-time prices values of the tokens
// if a token price increases, the pool will incentivise swapping out more of that token
// this is useful if prices are ranging, if prices are strongly directional, the pool may
// be selling tokens as the token price increases
//
// @param params GetPriceImpactUsdParams
//
// @return the price impact in USD
function getPriceImpactUsd(GetPriceImpactUsdParams memory params) internal view returns (int256) {
PoolParams memory poolParams = getNextPoolAmountsUsd(params);
int256 priceImpactUsd = _getPriceImpactUsd(params.dataStore, params.market, poolParams);
// the virtual price impact calculation is skipped if the price impact
// is positive since the action is helping to balance the pool
//
// in case two virtual pools are unbalanced in a different direction
// e.g. pool0 has more WNT than USDC while pool1 has less WNT
// than USDT
// not skipping the virtual price impact calculation would lead to
// a negative price impact for any trade on either pools and would
// disincentivise the balancing of pools
if (priceImpactUsd >= 0) { return priceImpactUsd; }
(bool hasVirtualInventoryTokenA, uint256 virtualPoolAmountForTokenA) = MarketUtils.getVirtualInventoryForSwaps(
params.dataStore,
params.market.marketToken,
params.tokenA
);
(bool hasVirtualInventoryTokenB, uint256 virtualPoolAmountForTokenB) = MarketUtils.getVirtualInventoryForSwaps(
params.dataStore,
params.market.marketToken,
params.tokenB
);
if (!hasVirtualInventoryTokenA || !hasVirtualInventoryTokenB) {
return priceImpactUsd;
}
PoolParams memory poolParamsForVirtualInventory = getNextPoolAmountsParams(
params,
virtualPoolAmountForTokenA,
virtualPoolAmountForTokenB
);
int256 priceImpactUsdForVirtualInventory = _getPriceImpactUsd(params.dataStore, params.market, poolParamsForVirtualInventory);
return priceImpactUsdForVirtualInventory < priceImpactUsd ? priceImpactUsdForVirtualInventory : priceImpactUsd;
}
// @dev get the price impact in USD
// @param dataStore DataStore
// @param market the trading market
// @param poolParams PoolParams
// @return the price impact in USD
function _getPriceImpactUsd(DataStore dataStore, Market.Props memory market, PoolParams memory poolParams) internal view returns (int256) {
uint256 initialDiffUsd = Calc.diff(poolParams.poolUsdForTokenA, poolParams.poolUsdForTokenB);
uint256 nextDiffUsd = Calc.diff(poolParams.nextPoolUsdForTokenA, poolParams.nextPoolUsdForTokenB);
// check whether an improvement in balance comes from causing the balance to switch sides
// for example, if there is $2000 of ETH and $1000 of USDC in the pool
// adding $1999 USDC into the pool will reduce absolute balance from $1000 to $999 but it does not
// help rebalance the pool much, the isSameSideRebalance value helps avoid gaming using this case
bool isSameSideRebalance = (poolParams.poolUsdForTokenA <= poolParams.poolUsdForTokenB) == (poolParams.nextPoolUsdForTokenA <= poolParams.nextPoolUsdForTokenB);
uint256 impactExponentFactor = dataStore.getUint(Keys.swapImpactExponentFactorKey(market.marketToken));
if (isSameSideRebalance) {
bool hasPositiveImpact = nextDiffUsd < initialDiffUsd;
uint256 impactFactor = MarketUtils.getAdjustedSwapImpactFactor(dataStore, market.marketToken, hasPositiveImpact);
return PricingUtils.getPriceImpactUsdForSameSideRebalance(
initialDiffUsd,
nextDiffUsd,
impactFactor,
impactExponentFactor
);
} else {
(uint256 positiveImpactFactor, uint256 negativeImpactFactor) = MarketUtils.getAdjustedSwapImpactFactors(dataStore, market.marketToken);
return PricingUtils.getPriceImpactUsdForCrossoverRebalance(
initialDiffUsd,
nextDiffUsd,
positiveImpactFactor,
negativeImpactFactor,
impactExponentFactor
);
}
}
// @dev get the next pool amounts in USD
// @param params GetPriceImpactUsdParams
// @return PoolParams
function getNextPoolAmountsUsd(
GetPriceImpactUsdParams memory params
) internal view returns (PoolParams memory) {
uint256 poolAmountForTokenA = MarketUtils.getPoolAmount(params.dataStore, params.market, params.tokenA);
uint256 poolAmountForTokenB = MarketUtils.getPoolAmount(params.dataStore, params.market, params.tokenB);
return getNextPoolAmountsParams(
params,
poolAmountForTokenA,
poolAmountForTokenB
);
}
function getNextPoolAmountsParams(
GetPriceImpactUsdParams memory params,
uint256 poolAmountForTokenA,
uint256 poolAmountForTokenB
) internal view returns (PoolParams memory) {
uint256 poolUsdForTokenA = poolAmountForTokenA * params.priceForTokenA;
uint256 poolUsdForTokenB = poolAmountForTokenB * params.priceForTokenB;
if (params.usdDeltaForTokenA < 0 && (-params.usdDeltaForTokenA).toUint256() > poolUsdForTokenA) {
revert Errors.UsdDeltaExceedsPoolValue(params.usdDeltaForTokenA, poolUsdForTokenA);
}
if (params.usdDeltaForTokenB < 0 && (-params.usdDeltaForTokenB).toUint256() > poolUsdForTokenB) {
revert Errors.UsdDeltaExceedsPoolValue(params.usdDeltaForTokenB, poolUsdForTokenB);
}
uint256 nextPoolUsdForTokenA = Calc.sumReturnUint256(poolUsdForTokenA, params.usdDeltaForTokenA);
uint256 nextPoolUsdForTokenB = Calc.sumReturnUint256(poolUsdForTokenB, params.usdDeltaForTokenB);
int256 poolUsdAdjustmentForTokenA = params.dataStore.getInt(Keys.poolAmountAdjustmentKey(params.market.marketToken, params.tokenA)) * params.priceForTokenA.toInt256();
int256 poolUsdAdjustmentForTokenB = params.dataStore.getInt(Keys.poolAmountAdjustmentKey(params.market.marketToken, params.tokenB)) * params.priceForTokenB.toInt256();
if (poolUsdAdjustmentForTokenA < 0 && poolUsdAdjustmentForTokenA.abs() > nextPoolUsdForTokenA) {
revert Errors.InvalidPoolAdjustment(params.tokenA, nextPoolUsdForTokenA, poolUsdAdjustmentForTokenA);
}
if (poolUsdAdjustmentForTokenB < 0 && poolUsdAdjustmentForTokenB.abs() > nextPoolUsdForTokenB) {
revert Errors.InvalidPoolAdjustment(params.tokenB, nextPoolUsdForTokenB, poolUsdAdjustmentForTokenB);
}
nextPoolUsdForTokenA = Calc.sumReturnUint256(nextPoolUsdForTokenA, poolUsdAdjustmentForTokenA);
nextPoolUsdForTokenB = Calc.sumReturnUint256(nextPoolUsdForTokenB, poolUsdAdjustmentForTokenB);
PoolParams memory poolParams = PoolParams(
poolUsdForTokenA,
poolUsdForTokenB,
nextPoolUsdForTokenA,
nextPoolUsdForTokenB
);
return poolParams;
}
// @dev get the swap fees
// @param dataStore DataStore
// @param marketToken the address of the market token
// @param amount the total swap fee amount
function getSwapFees(
DataStore dataStore,
address marketToken,
uint256 amount,
address uiFeeReceiver
) internal view returns (SwapFees memory) {
SwapFees memory fees;
uint256 feeFactor = dataStore.getUint(Keys.swapFeeFactorKey(marketToken));
uint256 swapFeeReceiverFactor = dataStore.getUint(Keys.SWAP_FEE_RECEIVER_FACTOR);
uint256 feeAmount = Precision.applyFactor(amount, feeFactor);
fees.feeReceiverAmount = Precision.applyFactor(feeAmount, swapFeeReceiverFactor);
fees.feeAmountForPool = feeAmount - fees.feeReceiverAmount;
fees.uiFeeReceiver = uiFeeReceiver;
fees.uiFeeReceiverFactor = MarketUtils.getUiFeeFactor(dataStore, uiFeeReceiver);
fees.uiFeeAmount = Precision.applyFactor(amount, fees.uiFeeReceiverFactor);
fees.amountAfterFees = amount - feeAmount - fees.uiFeeAmount;
return fees;
}
function emitSwapInfo(
EventEmitter eventEmitter,
bytes32 orderKey,
address market,
address receiver,
address tokenIn,
address tokenOut,
uint256 tokenInPrice,
uint256 tokenOutPrice,
uint256 amountIn,
uint256 amountInAfterFees,
uint256 amountOut,
int256 priceImpactUsd
) internal {
EventUtils.EventLogData memory eventData;
eventData.bytes32Items.initItems(1);
eventData.bytes32Items.setItem(0, "orderKey", orderKey);
eventData.addressItems.initItems(4);
eventData.addressItems.setItem(0, "market", market);
eventData.addressItems.setItem(1, "receiver", receiver);
eventData.addressItems.setItem(2, "tokenIn", tokenIn);
eventData.addressItems.setItem(3, "tokenOut", tokenOut);
eventData.uintItems.initItems(5);
eventData.uintItems.setItem(0, "tokenInPrice", tokenInPrice);
eventData.uintItems.setItem(1, "tokenOutPrice", tokenOutPrice);
eventData.uintItems.setItem(2, "amountIn", amountIn);
// note that amountInAfterFees includes negative price impact
eventData.uintItems.setItem(3, "amountInAfterFees", amountInAfterFees);
eventData.uintItems.setItem(4, "amountOut", amountOut);
eventData.intItems.initItems(1);
eventData.intItems.setItem(0, "priceImpactUsd", priceImpactUsd);
eventEmitter.emitEventLog1(
"SwapInfo",
Cast.toBytes32(market),
eventData
);
}
function emitSwapFeesCollected(
EventEmitter eventEmitter,
address market,
address token,
uint256 tokenPrice,
string memory action,
SwapFees memory fees
) internal {
EventUtils.EventLogData memory eventData;
eventData.addressItems.initItems(3);
eventData.addressItems.setItem(0, "uiFeeReceiver", fees.uiFeeReceiver);
eventData.addressItems.setItem(1, "market", market);
eventData.addressItems.setItem(2, "token", token);
eventData.stringItems.initItems(1);
eventData.stringItems.setItem(0, "action", action);
eventData.uintItems.initItems(6);
eventData.uintItems.setItem(0, "tokenPrice", tokenPrice);
eventData.uintItems.setItem(1, "feeReceiverAmount", fees.feeReceiverAmount);
eventData.uintItems.setItem(2, "feeAmountForPool", fees.feeAmountForPool);
eventData.uintItems.setItem(3, "amountAfterFees", fees.amountAfterFees);
eventData.uintItems.setItem(4, "uiFeeReceiverFactor", fees.uiFeeReceiverFactor);
eventData.uintItems.setItem(5, "uiFeeAmount", fees.uiFeeAmount);
eventEmitter.emitEventLog1(
"SwapFeesCollected",
Cast.toBytes32(market),
eventData
);
}
}