forked from overlay-market/v1-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Position.sol
288 lines (254 loc) · 11.1 KB
/
Position.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./FixedPoint.sol";
library Position {
using FixedPoint for uint256;
uint256 internal constant ONE = 1e18;
uint256 internal constant RATIO_PRECISION_SHIFT = 1e4; // RATIO_PRECISION = 1e14
struct Info {
uint96 notional; // initial notional = collateral * leverage
uint96 debt; // debt
uint48 entryToMidRatio; // ratio of entryPrice / _midFromFeed() at build
bool isLong; // whether long or short
bool liquidated; // whether has been liquidated
uint256 oiShares; // shares of aggregate open interest on side
}
/*///////////////////////////////////////////////////////////////
POSITIONS MAPPING FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Retrieves a position from positions mapping
function get(
mapping(bytes32 => Info) storage self,
address owner,
uint256 id
) internal view returns (Info storage position_) {
position_ = self[keccak256(abi.encodePacked(owner, id))];
}
/// @notice Stores a position in positions mapping
function set(
mapping(bytes32 => Info) storage self,
address owner,
uint256 id,
Info memory position
) internal {
self[keccak256(abi.encodePacked(owner, id))] = position;
}
/*///////////////////////////////////////////////////////////////
POSITION CAST GETTER FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Computes the position's initial notional cast to uint256
function _notional(Info memory self) private pure returns (uint256) {
return uint256(self.notional);
}
/// @notice Computes the position's initial open interest cast to uint256
function _oiShares(Info memory self) private pure returns (uint256) {
return uint256(self.oiShares);
}
/// @notice Computes the position's debt cast to uint256
function _debt(Info memory self) private pure returns (uint256) {
return uint256(self.debt);
}
/// @notice Whether the position exists
/// @dev Is false if position has been liquidated or has zero oi
function exists(Info memory self) internal pure returns (bool exists_) {
return (!self.liquidated && self.notional > 0);
}
/*///////////////////////////////////////////////////////////////
POSITION ENTRY PRICE FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Computes the entryToMidRatio cast to uint48 to be set
/// @notice on position build
function calcEntryToMidRatio(uint256 _entryPrice, uint256 _midPrice)
internal
pure
returns (uint48)
{
require(_entryPrice <= 2 * _midPrice, "OVLV1: value == 0 at entry");
return uint48(_entryPrice.divDown(_midPrice) / RATIO_PRECISION_SHIFT);
}
/// @notice Computes the ratio of the entryPrice of position to the midPrice
/// @notice at build cast to uint256
function getEntryToMidRatio(Info memory self) internal pure returns (uint256) {
return (uint256(self.entryToMidRatio) * RATIO_PRECISION_SHIFT);
}
/// @notice Computes the entryPrice of the position cast to uint256
/// @dev entryPrice = entryToMidRatio * midPrice (at build)
function entryPrice(Info memory self) internal pure returns (uint256 entryPrice_) {
uint256 priceRatio = getEntryToMidRatio(self);
uint256 oi = _oiShares(self);
uint256 q = _notional(self);
// will only be zero if all oi shares unwound; handles 0/0 case
// of notion / oi
if (oi == 0) {
return 0;
}
// entry = ratio * mid = ratio * (notional / oi)
entryPrice_ = priceRatio.mulUp(q).divUp(oi);
}
/*///////////////////////////////////////////////////////////////
POSITION FRACTIONAL GETTER FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Computes the initial notional of position when built
/// @dev use mulUp to avoid rounding leftovers on unwind
function notionalInitial(Info memory self, uint256 fraction) internal pure returns (uint256) {
return _notional(self).mulUp(fraction);
}
/// @notice Computes the initial open interest of position when built
/// @dev use mulUp to avoid rounding leftovers on unwind
function oiInitial(Info memory self, uint256 fraction) internal pure returns (uint256) {
return _oiShares(self).mulUp(fraction);
}
/// @notice Computes the current shares of open interest position holds
/// @notice on pos.isLong side of the market
/// @dev use mulUp to avoid rounding leftovers on unwind
function oiSharesCurrent(Info memory self, uint256 fraction) internal pure returns (uint256) {
return _oiShares(self).mulUp(fraction);
}
/// @notice Computes the current debt position holds
/// @dev use mulUp to avoid rounding leftovers on unwind
function debtCurrent(Info memory self, uint256 fraction) internal pure returns (uint256) {
return _debt(self).mulUp(fraction);
}
/// @notice Computes the current open interest of a position accounting for
/// @notice potential funding payments between long/short sides
/// @dev returns zero when oiShares = oiTotalOnSide = oiTotalSharesOnSide = 0 to avoid
/// @dev div by zero errors
/// @dev use mulUp, divUp to avoid rounding leftovers on unwind
function oiCurrent(
Info memory self,
uint256 fraction,
uint256 oiTotalOnSide,
uint256 oiTotalSharesOnSide
) internal pure returns (uint256) {
uint256 posOiShares = oiSharesCurrent(self, fraction);
if (posOiShares == 0 || oiTotalOnSide == 0) return 0;
return posOiShares.mulUp(oiTotalOnSide).divUp(oiTotalSharesOnSide);
}
/*///////////////////////////////////////////////////////////////
POSITION CALC FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Computes the position's cost cast to uint256
/// WARNING: be careful modifying notional and debt on unwind
function cost(Info memory self, uint256 fraction) internal pure returns (uint256) {
uint256 posNotionalInitial = notionalInitial(self, fraction);
uint256 posDebt = debtCurrent(self, fraction);
// should always be > 0 but use subFloor to be safe w reverts
uint256 posCost = posNotionalInitial;
posCost = posCost.subFloor(posDebt);
return posCost;
}
/// @notice Computes the value of a position
/// @dev Floors to zero, so won't properly compute if self is underwater
function value(
Info memory self,
uint256 fraction,
uint256 oiTotalOnSide,
uint256 oiTotalSharesOnSide,
uint256 currentPrice,
uint256 capPayoff
) internal pure returns (uint256 val_) {
uint256 posOiInitial = oiInitial(self, fraction);
uint256 posNotionalInitial = notionalInitial(self, fraction);
uint256 posDebt = debtCurrent(self, fraction);
uint256 posOiCurrent = oiCurrent(self, fraction, oiTotalOnSide, oiTotalSharesOnSide);
uint256 posEntryPrice = entryPrice(self);
// NOTE: PnL = +/- oiCurrent * [currentPrice - entryPrice]; ... (w/o capPayoff)
// NOTE: fundingPayments = notionalInitial * ( oiCurrent / oiInitial - 1 )
// NOTE: value = collateralInitial + PnL + fundingPayments
// NOTE: = notionalInitial - debt + PnL + fundingPayments
if (self.isLong) {
// val = notionalInitial * oiCurrent / oiInitial
// + oiCurrent * min[currentPrice, entryPrice * (1 + capPayoff)]
// - oiCurrent * entryPrice - debt
val_ =
posNotionalInitial.mulUp(posOiCurrent).divUp(posOiInitial) +
Math.min(
posOiCurrent.mulUp(currentPrice),
posOiCurrent.mulUp(posEntryPrice).mulUp(ONE + capPayoff)
);
// floor to 0
val_ = val_.subFloor(posDebt + posOiCurrent.mulUp(posEntryPrice));
} else {
// NOTE: capPayoff >= 1, so no need to include w short
// val = notionalInitial * oiCurrent / oiInitial + oiCurrent * entryPrice
// - oiCurrent * currentPrice - debt
val_ =
posNotionalInitial.mulUp(posOiCurrent).divUp(posOiInitial) +
posOiCurrent.mulUp(posEntryPrice);
// floor to 0
val_ = val_.subFloor(posDebt + posOiCurrent.mulUp(currentPrice));
}
}
/// @notice Computes the current notional of a position including PnL
/// @dev Floors to debt if value <= 0
function notionalWithPnl(
Info memory self,
uint256 fraction,
uint256 oiTotalOnSide,
uint256 oiTotalSharesOnSide,
uint256 currentPrice,
uint256 capPayoff
) internal pure returns (uint256 notionalWithPnl_) {
uint256 posValue = value(
self,
fraction,
oiTotalOnSide,
oiTotalSharesOnSide,
currentPrice,
capPayoff
);
uint256 posDebt = debtCurrent(self, fraction);
notionalWithPnl_ = posValue + posDebt;
}
/// @notice Computes the trading fees to be imposed on a position for build/unwind
function tradingFee(
Info memory self,
uint256 fraction,
uint256 oiTotalOnSide,
uint256 oiTotalSharesOnSide,
uint256 currentPrice,
uint256 capPayoff,
uint256 tradingFeeRate
) internal pure returns (uint256 tradingFee_) {
uint256 posNotional = notionalWithPnl(
self,
fraction,
oiTotalOnSide,
oiTotalSharesOnSide,
currentPrice,
capPayoff
);
tradingFee_ = posNotional.mulUp(tradingFeeRate);
}
/// @notice Whether a position can be liquidated
/// @dev is true when value * (1 - liq fee rate) < maintenance margin
/// @dev liq fees are reward given to liquidator
function liquidatable(
Info memory self,
uint256 oiTotalOnSide,
uint256 oiTotalSharesOnSide,
uint256 currentPrice,
uint256 capPayoff,
uint256 maintenanceMarginFraction,
uint256 liquidationFeeRate
) internal pure returns (bool can_) {
uint256 fraction = ONE;
uint256 posNotionalInitial = notionalInitial(self, fraction);
if (self.liquidated || posNotionalInitial == 0) {
// already been liquidated
return false;
}
uint256 val = value(
self,
fraction,
oiTotalOnSide,
oiTotalSharesOnSide,
currentPrice,
capPayoff
);
uint256 maintenanceMargin = posNotionalInitial.mulUp(maintenanceMarginFraction);
uint256 liquidationFee = val.mulDown(liquidationFeeRate);
can_ = val < maintenanceMargin + liquidationFee;
}
}