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
/
DecreasePositionUtils.sol
334 lines (285 loc) · 14.7 KB
/
DecreasePositionUtils.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
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../utils/Precision.sol";
import "../data/DataStore.sol";
import "../event/EventEmitter.sol";
import "../oracle/Oracle.sol";
import "../pricing/PositionPricingUtils.sol";
import "./Position.sol";
import "./PositionStoreUtils.sol";
import "./PositionUtils.sol";
import "./PositionEventUtils.sol";
import "../order/BaseOrderUtils.sol";
import "../order/OrderEventUtils.sol";
import "./DecreasePositionCollateralUtils.sol";
// @title DecreasePositionUtils
// @dev Library for functions to help with decreasing a position
library DecreasePositionUtils {
using SafeCast for uint256;
using SafeCast for int256;
using Position for Position.Props;
using Order for Order.Props;
using Price for Price.Props;
// @dev DecreasePositionResult struct for the results of decreasePosition
// @param outputToken the output token
// @param outputAmount the output amount
// @param secondaryOutputToken the secondary output token
// @param secondaryOutputAmount the secondary output amount
struct DecreasePositionResult {
address outputToken;
uint256 outputAmount;
address secondaryOutputToken;
uint256 secondaryOutputAmount;
}
// @dev decreases a position
// The decreasePosition function decreases the size of an existing position
// in a market. It takes a PositionUtils.UpdatePositionParams object as an input, which
// includes information about the position to be decreased, the market in
// which the position exists, and the order that is being used to decrease the position.
//
// The function first calculates the prices of the tokens in the market, and then
// checks whether the position is liquidatable based on the current market prices.
// If the order is a liquidation order and the position is not liquidatable, the function reverts.
//
// If there is not enough collateral in the position to complete the decrease,
// the function reverts. Otherwise, the function updates the position's size and
// collateral amount, and increments the claimable funding amount for
// the market if necessary.
//
// Finally, the function returns a DecreasePositionResult object containing
// information about the outcome of the decrease operation, including the amount
// of collateral removed from the position and any fees that were paid.
// @param params PositionUtils.UpdatePositionParams
function decreasePosition(
PositionUtils.UpdatePositionParams memory params
) external returns (DecreasePositionResult memory) {
PositionUtils.DecreasePositionCache memory cache;
cache.prices = MarketUtils.getMarketPricesForPosition(
params.contracts.oracle,
params.market
);
// cap the order size to the position size
if (params.order.sizeDeltaUsd() > params.position.sizeInUsd()) {
if (params.order.orderType() == Order.OrderType.LimitDecrease ||
params.order.orderType() == Order.OrderType.StopLossDecrease) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
} else {
revert Errors.InvalidDecreaseOrderSize(params.order.sizeDeltaUsd(), params.position.sizeInUsd());
}
}
// if the position will be partially decreased then do a check on the
// remaining collateral amount and update the order attributes if needed
if (params.order.sizeDeltaUsd() < params.position.sizeInUsd()) {
// estimate pnl based on indexTokenPrice
(cache.estimatedPositionPnlUsd, /* uint256 sizeDeltaInTokens */) = PositionUtils.getPositionPnlUsd(
params.contracts.dataStore,
params.market,
cache.prices,
params.position,
cache.prices.indexTokenPrice.pickPriceForPnl(params.position.isLong(), false),
params.position.sizeInUsd()
);
cache.estimatedRealizedPnlUsd = cache.estimatedPositionPnlUsd * params.order.sizeDeltaUsd().toInt256() / params.position.sizeInUsd().toInt256();
cache.estimatedRemainingPnlUsd = cache.estimatedPositionPnlUsd - cache.estimatedRealizedPnlUsd;
PositionUtils.WillPositionCollateralBeSufficientValues memory positionValues = PositionUtils.WillPositionCollateralBeSufficientValues(
params.position.sizeInUsd() - params.order.sizeDeltaUsd(), // positionSizeInUsd
params.position.collateralAmount() - params.order.initialCollateralDeltaAmount(), // positionCollateralAmount
cache.estimatedRealizedPnlUsd, // realizedPnlUsd
-params.order.sizeDeltaUsd().toInt256() // openInterestDelta
);
(bool willBeSufficient, int256 estimatedRemainingCollateralUsd) = PositionUtils.willPositionCollateralBeSufficient(
params.contracts.dataStore,
params.market,
cache.prices,
params.position.collateralToken(),
params.position.isLong(),
positionValues
);
// do not allow withdrawal of collateral if it would lead to the position
// having an insufficient amount of collateral
// this helps to prevent gaming by opening a position then reducing collateral
// to increase the leverage of the position
if (!willBeSufficient) {
if (params.order.sizeDeltaUsd() == 0) {
revert Errors.UnableToWithdrawCollateralDueToLeverage(estimatedRemainingCollateralUsd);
}
OrderEventUtils.emitOrderCollateralDeltaAmountAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.initialCollateralDeltaAmount(),
0
);
// the estimatedRemainingCollateralUsd subtracts the initialCollateralDeltaAmount
// since the initialCollateralDeltaAmount will be set to zero, the initialCollateralDeltaAmount
// should be added back to the estimatedRemainingCollateralUsd
estimatedRemainingCollateralUsd += params.order.initialCollateralDeltaAmount().toInt256();
params.order.setInitialCollateralDeltaAmount(0);
}
// if the remaining collateral including position pnl will be below
// the min collateral usd value, then close the position
//
// if the position has sufficient remaining collateral including pnl
// then allow the position to be partially closed and the updated
// position to remain open
if ((estimatedRemainingCollateralUsd + cache.estimatedRemainingPnlUsd) < params.contracts.dataStore.getUint(Keys.MIN_COLLATERAL_USD).toInt256()) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
}
if (
params.position.sizeInUsd() > params.order.sizeDeltaUsd() &&
params.position.sizeInUsd() - params.order.sizeDeltaUsd() < params.contracts.dataStore.getUint(Keys.MIN_POSITION_SIZE_USD)
) {
OrderEventUtils.emitOrderSizeDeltaAutoUpdated(
params.contracts.eventEmitter,
params.orderKey,
params.order.sizeDeltaUsd(),
params.position.sizeInUsd()
);
params.order.setSizeDeltaUsd(params.position.sizeInUsd());
}
}
// if the position will be closed, set the initial collateral delta amount
// to zero to help ensure that the order can be executed
if (params.order.sizeDeltaUsd() == params.position.sizeInUsd() && params.order.initialCollateralDeltaAmount() > 0) {
params.order.setInitialCollateralDeltaAmount(0);
}
cache.pnlToken = params.position.isLong() ? params.market.longToken : params.market.shortToken;
cache.pnlTokenPrice = params.position.isLong() ? cache.prices.longTokenPrice : cache.prices.shortTokenPrice;
if (params.order.decreasePositionSwapType() != Order.DecreasePositionSwapType.NoSwap &&
cache.pnlToken == params.position.collateralToken()) {
params.order.setDecreasePositionSwapType(Order.DecreasePositionSwapType.NoSwap);
}
PositionUtils.updateFundingAndBorrowingState(params, cache.prices);
if (BaseOrderUtils.isLiquidationOrder(params.order.orderType()) && !PositionUtils.isPositionLiquidatable(
params.contracts.dataStore,
params.contracts.referralStorage,
params.position,
params.market,
cache.prices,
false, // isIncrease
true // shouldValidateMinCollateralUsd
)) {
revert Errors.PositionShouldNotBeLiquidated();
}
cache.initialCollateralAmount = params.position.collateralAmount();
(
PositionUtils.DecreasePositionCollateralValues memory values,
PositionPricingUtils.PositionFees memory fees
) = DecreasePositionCollateralUtils.processCollateral(
params,
cache
);
cache.nextPositionSizeInUsd = params.position.sizeInUsd() - params.order.sizeDeltaUsd();
cache.nextPositionBorrowingFactor = MarketUtils.getCumulativeBorrowingFactor(params.contracts.dataStore, params.market.marketToken, params.position.isLong());
PositionUtils.updateTotalBorrowing(
params,
cache.nextPositionSizeInUsd,
cache.nextPositionBorrowingFactor
);
params.position.setSizeInUsd(cache.nextPositionSizeInUsd);
params.position.setSizeInTokens(params.position.sizeInTokens() - values.sizeDeltaInTokens);
params.position.setCollateralAmount(values.remainingCollateralAmount.toUint256());
params.position.setDecreasedAtBlock(Chain.currentBlockNumber());
PositionUtils.incrementClaimableFundingAmount(params, fees);
if (params.position.sizeInUsd() == 0 || params.position.sizeInTokens() == 0) {
// withdraw all collateral if the position will be closed
values.output.outputAmount += params.position.collateralAmount();
params.position.setSizeInUsd(0);
params.position.setSizeInTokens(0);
params.position.setCollateralAmount(0);
PositionStoreUtils.remove(params.contracts.dataStore, params.positionKey, params.order.account());
} else {
params.position.setLongTokenFundingAmountPerSize(fees.funding.latestLongTokenFundingAmountPerSize);
params.position.setShortTokenFundingAmountPerSize(fees.funding.latestShortTokenFundingAmountPerSize);
params.position.setBorrowingFactor(cache.nextPositionBorrowingFactor);
// validate position which validates liquidation state is only called
// if the remaining position size is not zero
// due to this, a user can still manually close their position if
// it is in a partially liquidatable state
// this should not cause any issues as a liquidation is the same
// as automatically closing a position
// the only difference is that if the position has insufficient / negative
// collateral a liquidation transaction should still complete
// while a manual close transaction should revert
PositionUtils.validatePosition(
params.contracts.dataStore,
params.contracts.referralStorage,
params.position,
params.market,
cache.prices,
false, // isIncrease
false, // shouldValidateMinPositionSize
false // shouldValidateMinCollateralUsd
);
PositionStoreUtils.set(params.contracts.dataStore, params.positionKey, params.position);
}
MarketUtils.applyDeltaToCollateralSum(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.position.market(),
params.position.collateralToken(),
params.position.isLong(),
-(cache.initialCollateralAmount - params.position.collateralAmount()).toInt256()
);
PositionUtils.updateOpenInterest(
params,
-params.order.sizeDeltaUsd().toInt256(),
-values.sizeDeltaInTokens.toInt256()
);
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
values.pnlTokenForPool,
values.pnlAmountForPool
);
MarketUtils.applyDeltaToPoolAmount(
params.contracts.dataStore,
params.contracts.eventEmitter,
params.market.marketToken,
params.position.collateralToken(),
fees.feeAmountForPool.toInt256()
);
// affiliate rewards are still distributed even if the order is a liquidation order
// this is expected as a partial liquidation is considered the same as an automatic
// closing of a position
PositionUtils.handleReferral(params, fees);
PositionEventUtils.emitPositionFeesCollected(
params.contracts.eventEmitter,
params.orderKey,
params.market.marketToken,
params.position.collateralToken(),
params.order.sizeDeltaUsd(),
false,
fees
);
PositionEventUtils.emitPositionDecrease(
params.contracts.eventEmitter,
params.orderKey,
params.positionKey,
params.position,
params.order.sizeDeltaUsd(),
cache.initialCollateralAmount - params.position.collateralAmount(),
params.order.orderType(),
values
);
values = DecreasePositionCollateralUtils.swapWithdrawnCollateralToPnlToken(params, values);
return DecreasePositionResult(
values.output.outputToken,
values.output.outputAmount,
values.output.secondaryOutputToken,
values.output.secondaryOutputAmount
);
}
}