/
VaultLib.sol
296 lines (274 loc) · 11.1 KB
/
VaultLib.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
//SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
//interface
import {INonfungiblePositionManager} from "@uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol";
//lib
import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol";
import {TickMathExternal} from "./TickMathExternal.sol";
import {SqrtPriceMathPartial} from "./SqrtPriceMathPartial.sol";
import {Uint256Casting} from "./Uint256Casting.sol";
/**
* Error code:
* V1: Vault already had nft
* V2: Vault has no NFT
*/
library VaultLib {
using SafeMath for uint256;
using Uint256Casting for uint256;
uint256 constant ONE_ONE = 1e36;
// the collateralization ratio (CR) is checked with the numerator and denominator separately
// a user is safe if - collateral value >= (COLLAT_RATIO_NUMER/COLLAT_RATIO_DENOM)* debt value
uint256 public constant CR_NUMERATOR = 3;
uint256 public constant CR_DENOMINATOR = 2;
struct Vault {
// the address that can update the vault
address operator;
// uniswap position token id deposited into the vault as collateral
// 2^32 is 4,294,967,296, which means the vault structure will work with up to 4 billion positions
uint32 NftCollateralId;
// amount of eth (wei) used in the vault as collateral
// 2^96 / 1e18 = 79,228,162,514, which means a vault can store up to 79 billion eth
// when we need to do calculations, we always cast this number to uint256 to avoid overflow
uint96 collateralAmount;
// amount of wPowerPerp minted from the vault
uint128 shortAmount;
}
/**
* @notice add eth collateral to a vault
* @param _vault in-memory vault
* @param _amount amount of eth to add
*/
function addEthCollateral(Vault memory _vault, uint256 _amount) internal pure {
_vault.collateralAmount = uint256(_vault.collateralAmount).add(_amount).toUint96();
}
/**
* @notice add uniswap position token collateral to a vault
* @param _vault in-memory vault
* @param _tokenId uniswap position token id
*/
function addUniNftCollateral(Vault memory _vault, uint256 _tokenId) internal pure {
require(_vault.NftCollateralId == 0, "V1");
require(_tokenId != 0, "C23");
_vault.NftCollateralId = _tokenId.toUint32();
}
/**
* @notice remove eth collateral from a vault
* @param _vault in-memory vault
* @param _amount amount of eth to remove
*/
function removeEthCollateral(Vault memory _vault, uint256 _amount) internal pure {
_vault.collateralAmount = uint256(_vault.collateralAmount).sub(_amount).toUint96();
}
/**
* @notice remove uniswap position token collateral from a vault
* @param _vault in-memory vault
*/
function removeUniNftCollateral(Vault memory _vault) internal pure {
require(_vault.NftCollateralId != 0, "V2");
_vault.NftCollateralId = 0;
}
/**
* @notice add debt to vault
* @param _vault in-memory vault
* @param _amount amount of debt to add
*/
function addShort(Vault memory _vault, uint256 _amount) internal pure {
_vault.shortAmount = uint256(_vault.shortAmount).add(_amount).toUint128();
}
/**
* @notice remove debt from vault
* @param _vault in-memory vault
* @param _amount amount of debt to remove
*/
function removeShort(Vault memory _vault, uint256 _amount) internal pure {
_vault.shortAmount = uint256(_vault.shortAmount).sub(_amount).toUint128();
}
/**
* @notice check if a vault is properly collateralized
* @param _vault the vault we want to check
* @param _positionManager address of the uniswap position manager
* @param _normalizationFactor current _normalizationFactor
* @param _ethQuoteCurrencyPrice current eth price scaled by 1e18
* @param _minCollateral minimum collateral that needs to be in a vault
* @param _wsqueethPoolTick current price tick for wsqueeth pool
* @param _isWethToken0 whether weth is token0 in the wsqueeth pool
* @return true if the vault is sufficiently collateralized
* @return true if the vault is considered as a dust vault
*/
function getVaultStatus(
Vault memory _vault,
address _positionManager,
uint256 _normalizationFactor,
uint256 _ethQuoteCurrencyPrice,
uint256 _minCollateral,
int24 _wsqueethPoolTick,
bool _isWethToken0
) internal view returns (bool, bool) {
if (_vault.shortAmount == 0) return (true, false);
uint256 debtValueInETH = uint256(_vault.shortAmount).mul(_normalizationFactor).mul(_ethQuoteCurrencyPrice).div(
ONE_ONE
);
uint256 totalCollateral = _getEffectiveCollateral(
_vault,
_positionManager,
_normalizationFactor,
_ethQuoteCurrencyPrice,
_wsqueethPoolTick,
_isWethToken0
);
bool isDust = totalCollateral < _minCollateral;
bool isAboveWater = totalCollateral.mul(CR_DENOMINATOR) >= debtValueInETH.mul(CR_NUMERATOR);
return (isAboveWater, isDust);
}
/**
* @notice get the total effective collateral of a vault, which is:
* collateral amount + uniswap position token equivelent amount in eth
* @param _vault the vault we want to check
* @param _positionManager address of the uniswap position manager
* @param _normalizationFactor current _normalizationFactor
* @param _ethQuoteCurrencyPrice current eth price scaled by 1e18
* @param _wsqueethPoolTick current price tick for wsqueeth pool
* @param _isWethToken0 whether weth is token0 in the wsqueeth pool
* @return the total worth of collateral in the vault
*/
function _getEffectiveCollateral(
Vault memory _vault,
address _positionManager,
uint256 _normalizationFactor,
uint256 _ethQuoteCurrencyPrice,
int24 _wsqueethPoolTick,
bool _isWethToken0
) internal view returns (uint256) {
if (_vault.NftCollateralId == 0) return _vault.collateralAmount;
// the user has deposited uniswap position token as collateral, see how much eth / wSqueeth the uniswap position token has
(uint256 nftEthAmount, uint256 nftWsqueethAmount) = _getUniPositionBalances(
_positionManager,
_vault.NftCollateralId,
_wsqueethPoolTick,
_isWethToken0
);
// convert squeeth amount from uniswap position token as equivalent amount of collateral
uint256 wSqueethIndexValueInEth = nftWsqueethAmount.mul(_normalizationFactor).mul(_ethQuoteCurrencyPrice).div(
ONE_ONE
);
// add eth value from uniswap position token as collateral
return nftEthAmount.add(wSqueethIndexValueInEth).add(_vault.collateralAmount);
}
/**
* @notice determine how much eth / wPowerPerp the uniswap position contains
* @param _positionManager address of the uniswap position manager
* @param _tokenId uniswap position token id
* @param _wPowerPerpPoolTick current price tick
* @param _isWethToken0 whether weth is token0 in the pool
* @return ethAmount the eth amount this LP token contains
* @return wPowerPerpAmount the wPowerPerp amount this LP token contains
*/
function _getUniPositionBalances(
address _positionManager,
uint256 _tokenId,
int24 _wPowerPerpPoolTick,
bool _isWethToken0
) internal view returns (uint256 ethAmount, uint256 wPowerPerpAmount) {
(
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint128 tokensOwed0,
uint128 tokensOwed1
) = _getUniswapPositionInfo(_positionManager, _tokenId);
(uint256 amount0, uint256 amount1) = _getToken0Token1Balances(
tickLower,
tickUpper,
_wPowerPerpPoolTick,
liquidity
);
return
_isWethToken0
? (amount0 + tokensOwed0, amount1 + tokensOwed1)
: (amount1 + tokensOwed1, amount0 + tokensOwed0);
}
/**
* @notice get uniswap position token info
* @param _positionManager address of the uniswap position position manager
* @param _tokenId uniswap position token id
* @return tickLower lower tick of the position
* @return tickUpper upper tick of the position
* @return liquidity raw liquidity amount of the position
* @return tokensOwed0 amount of token 0 can be collected as fee
* @return tokensOwed1 amount of token 1 can be collected as fee
*/
function _getUniswapPositionInfo(address _positionManager, uint256 _tokenId)
internal
view
returns (
int24,
int24,
uint128,
uint128,
uint128
)
{
INonfungiblePositionManager positionManager = INonfungiblePositionManager(_positionManager);
(
,
,
,
,
,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
,
,
uint128 tokensOwed0,
uint128 tokensOwed1
) = positionManager.positions(_tokenId);
return (tickLower, tickUpper, liquidity, tokensOwed0, tokensOwed1);
}
/**
* @notice get balances of token0 / token1 in a uniswap position
* @dev knowing liquidity, tick range, and current tick gives balances
* @param _tickLower address of the uniswap position manager
* @param _tickUpper uniswap position token id
* @param _tick current price tick used for calculation
* @return amount0 the amount of token0 in the uniswap position token
* @return amount1 the amount of token1 in the uniswap position token
*/
function _getToken0Token1Balances(
int24 _tickLower,
int24 _tickUpper,
int24 _tick,
uint128 _liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
// get the current price and tick from wPowerPerp pool
uint160 sqrtPriceX96 = TickMathExternal.getSqrtRatioAtTick(_tick);
if (_tick < _tickLower) {
amount0 = SqrtPriceMathPartial.getAmount0Delta(
TickMathExternal.getSqrtRatioAtTick(_tickLower),
TickMathExternal.getSqrtRatioAtTick(_tickUpper),
_liquidity,
true
);
} else if (_tick < _tickUpper) {
amount0 = SqrtPriceMathPartial.getAmount0Delta(
sqrtPriceX96,
TickMathExternal.getSqrtRatioAtTick(_tickUpper),
_liquidity,
true
);
amount1 = SqrtPriceMathPartial.getAmount1Delta(
TickMathExternal.getSqrtRatioAtTick(_tickLower),
sqrtPriceX96,
_liquidity,
true
);
} else {
amount1 = SqrtPriceMathPartial.getAmount1Delta(
TickMathExternal.getSqrtRatioAtTick(_tickLower),
TickMathExternal.getSqrtRatioAtTick(_tickUpper),
_liquidity,
true
);
}
}
}