1
+ /*
2
+ Copyright 2020 Set Labs Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+
16
+ SPDX-License-Identifier: Apache License, Version 2.0
17
+ */
18
+ pragma solidity 0.6.10 ;
19
+ pragma experimental "ABIEncoderV2 " ;
20
+
21
+
22
+ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
23
+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol " ;
24
+ import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol " ;
25
+ import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol " ;
26
+ import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol " ;
27
+
28
+ import { IAccountBalance } from "../interfaces/external/perp-v2/IAccountBalance.sol " ;
29
+ import { IClearingHouseConfig } from "../interfaces/external/perp-v2/IClearingHouseConfig.sol " ;
30
+ import { IIndexPrice } from "../interfaces/external/perp-v2/IIndexPrice.sol " ;
31
+ import { IPerpV2LeverageModule } from "../interfaces/IPerpV2LeverageModule.sol " ;
32
+ import { ISetToken } from "../interfaces/ISetToken.sol " ;
33
+ import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol " ;
34
+
35
+
36
+ /**
37
+ * @title PerpV2LeverageModuleViewer
38
+ * @author Set Protocol
39
+ *
40
+ * PerpV2LeverageModuleViewer enables queries of information regarding open PerpV2 positions
41
+ * specifically for leverage ratios and issuance maximums.
42
+ */
43
+ contract PerpV2LeverageModuleViewer {
44
+ using SafeCast for int256 ;
45
+ using SafeCast for uint256 ;
46
+ using SafeMath for uint256 ;
47
+ using PreciseUnitMath for uint256 ;
48
+ using PreciseUnitMath for int256 ;
49
+ using SignedSafeMath for int256 ;
50
+
51
+ /* ============ Structs ============ */
52
+
53
+ struct VAssetDisplayInfo {
54
+ string symbol; // Symbol of vAsset
55
+ address vAssetAddress; // Address of vAsset
56
+ int256 positionUnit; // Position unit of vAsset
57
+ uint256 indexPrice; // Current index price of vAsset
58
+ int256 currentLeverageRatio; // Current leverage ratio of vAsset (using total collateral value)
59
+ }
60
+
61
+ /* ============ State Variables ============ */
62
+
63
+ IPerpV2LeverageModule public immutable perpModule; // PerpV2LeverageModule instance
64
+ IAccountBalance public immutable perpAccountBalance; // Perp's Account Balance contract
65
+ IClearingHouseConfig public immutable perpClearingHouseConfig; // PerpV2's ClearingHouseConfig contract
66
+ ERC20 public immutable vQuoteToken; // Virtual Quote asset for PerpV2 (vUSDC)
67
+ IERC20 public immutable collateralToken; // Address of collateral token used by Perp (USDC)
68
+
69
+ /* ============ Constructor ============ */
70
+
71
+ /**
72
+ * @dev Sets passed state variable and grabs collateral asset from perpModule.
73
+ *
74
+ * @param _perpModule Address of PerpV2LeverageModule contract
75
+ * @param _perpAccountBalance Address of PerpV2's AccountBalance contract
76
+ * @param _perpClearingHouseConfig Address of PerpV2's ClearingHouseConfig contract
77
+ * @param _vQuoteToken Address of virtual Quote asset for PerpV2 (vUSDC)
78
+ */
79
+ constructor (
80
+ IPerpV2LeverageModule _perpModule ,
81
+ IAccountBalance _perpAccountBalance ,
82
+ IClearingHouseConfig _perpClearingHouseConfig ,
83
+ ERC20 _vQuoteToken
84
+ ) public {
85
+ perpModule = _perpModule;
86
+ perpAccountBalance = _perpAccountBalance;
87
+ perpClearingHouseConfig = _perpClearingHouseConfig;
88
+ vQuoteToken = _vQuoteToken;
89
+ collateralToken = _perpModule.collateralToken ();
90
+ }
91
+
92
+ /* ============ External View Functions ============ */
93
+
94
+ /**
95
+ * @dev Returns the maximum amount of Sets that can be issued. Because upon issuance we lever up the Set
96
+ * before depositing collateral there is a ceiling on the amount of Sets that can be issued before the max
97
+ * leverage ratio is met. In order to accurately predict this amount the user must pass in an expected
98
+ * slippage amount, this amount should be calculated relative to Index price(s) of vAssets held by the Set,
99
+ * not the mid-market prices. The formulas used here are based on the "conservative" definition of free
100
+ * collateral as defined in PerpV2's docs: freeCollateral = min(totalCollateral, accountValue) - totalDebt * initialMarginRatio
101
+ *
102
+ * We want to find the point where freeCollateral = 0 after all trades have been executed.
103
+ * freeCollateral = 0 => totalDebt = min(totalCollateral, accountValue) / initialMarginRatio
104
+ * and, availableDebt = totalDebt - currentDebt
105
+ *
106
+ * Now, accountValue = totalCollateral + unrealizedPnl
107
+ * if unrealizedPnl >=0:
108
+ * min(totalCollateral, accountValue) = totalCollateral
109
+ * availableDebt = (totalCollateral / imRatio) - currentDebt
110
+ * if unrealizedPnl < 0:
111
+ * min(totalCollateral, accountValue) = accountValue
112
+ * availableDebt = ((totalCollateral + unrealizedPnl) / imRatio) - currentDebt
113
+ *
114
+ * We also know that any slippage gets accrued to unrealizedPnl BEFORE any new collateral is being deposited so
115
+ * we need to account for our expected slippage accrual impact on accountValue by subtracting our expected amount
116
+ * of slippage divided by the imRatio from the availableDebt. We can then divide the availableDebtWithSlippage by
117
+ * the absolute value of our current position and multiply by our totalSupply to get the max issue amount.
118
+ *
119
+ * @param _setToken Instance of SetToken
120
+ * @param _slippage Expected slippage from entering position in precise units (1% = 10^16)
121
+ *
122
+ * @return Maximum amount of Sets that can be issued
123
+ */
124
+ function getMaximumSetTokenIssueAmount (ISetToken _setToken , int256 _slippage ) external view returns (uint256 ) {
125
+ uint256 totalAbsPositionValue = perpAccountBalance.getTotalAbsPositionValue (address (_setToken));
126
+
127
+ if (totalAbsPositionValue == 0 ) { return PreciseUnitMath.maxUint256 (); }
128
+
129
+ // Scale imRatio to 10 ** 18 (preciseUnits)
130
+ int256 imRatio = uint256 (perpClearingHouseConfig.getImRatio ()).mul (1e12 ).toInt256 ();
131
+
132
+ (, int256 unrealizedPnl , ) = perpAccountBalance.getPnlAndPendingFee (address (_setToken));
133
+ int256 totalDebtValue = perpAccountBalance.getTotalDebtValue (address (_setToken)).toInt256 ();
134
+
135
+ int256 totalCollateralValue = _calculateTotalCollateralValue (_setToken);
136
+
137
+ int256 availableDebt = unrealizedPnl >= 0
138
+ ? totalCollateralValue.preciseDiv (imRatio).sub (totalDebtValue)
139
+ : totalCollateralValue.add (unrealizedPnl).preciseDiv (imRatio).sub (totalDebtValue);
140
+
141
+ int256 availableDebtWithSlippage = availableDebt.sub (availableDebt.preciseMul (_slippage).preciseDiv (imRatio));
142
+
143
+ // max issue amount = available debt in USD (with slippage) / increase in totalDebtValue per Set issued
144
+ // = (availableDebtWithSlippage / totalAbsPositionValue) * setTotalSupply
145
+ return availableDebtWithSlippage.toUint256 ().preciseDiv (totalAbsPositionValue).preciseMul (_setToken.totalSupply ());
146
+ }
147
+
148
+ /**
149
+ * @dev Returns the position unit for total collateral value as defined by Perpetual Protocol. TCV = collateral + owedRealizedPnl + pendingFunding.
150
+ *
151
+ * @param _setToken Instance of SetToken
152
+ *
153
+ * @return Collateral token address
154
+ * @return Total collateral value position unit
155
+ */
156
+ function getTotalCollateralUnit (ISetToken _setToken ) external view returns (IERC20 , int256 ) {
157
+ int256 setTotalSupply = _setToken.totalSupply ().toInt256 ();
158
+ return (collateralToken, _calculateTotalCollateralValue (_setToken).preciseDiv (setTotalSupply));
159
+ }
160
+
161
+ /**
162
+ * @dev Returns relevant data for displaying current positions. Identifying info for each position plus current
163
+ * size, index price, and leverage of each vAsset with an open position is returned. The sum quantity of vUSDC
164
+ * is returned along with identifying info in last index of array.
165
+ *
166
+ * @param _setToken Instance of SetToken
167
+ *
168
+ * @return assetInfo Array of info concerning size and leverage of current vAsset positions
169
+ */
170
+ function getVirtualAssetsDisplayInfo (
171
+ ISetToken _setToken
172
+ )
173
+ external
174
+ view
175
+ returns (VAssetDisplayInfo[] memory assetInfo )
176
+ {
177
+ uint256 setTotalSupply = _setToken.totalSupply ();
178
+ IPerpV2LeverageModule.PositionNotionalInfo[] memory positionInfo = perpModule.getPositionNotionalInfo (_setToken);
179
+
180
+ int256 totalCollateralValue = _calculateTotalCollateralValue (_setToken);
181
+
182
+ uint256 positionsLength = positionInfo.length ;
183
+ assetInfo = new VAssetDisplayInfo [](positionsLength.add (1 ));
184
+
185
+ int256 vQuoteBalance;
186
+ for (uint256 i = 0 ; i < positionsLength; i++ ) {
187
+ IPerpV2LeverageModule.PositionNotionalInfo memory position = positionInfo[i];
188
+ uint256 indexPrice = IIndexPrice (position.baseToken).getIndexPrice (0 );
189
+ assetInfo[i] = VAssetDisplayInfo ({
190
+ symbol: ERC20 (position.baseToken).symbol (),
191
+ vAssetAddress: position.baseToken,
192
+ positionUnit: position.baseBalance.preciseDiv (setTotalSupply.toInt256 ()),
193
+ indexPrice: indexPrice,
194
+ currentLeverageRatio: _calculateCurrentLeverageRatio (position, indexPrice, totalCollateralValue)
195
+ });
196
+
197
+ vQuoteBalance = vQuoteBalance.add (position.quoteBalance);
198
+ }
199
+
200
+ assetInfo[positionsLength] = VAssetDisplayInfo ({
201
+ symbol: vQuoteToken.symbol (),
202
+ vAssetAddress: address (vQuoteToken),
203
+ positionUnit: vQuoteBalance.preciseDiv (setTotalSupply.toInt256 ()),
204
+ indexPrice: PreciseUnitMath.preciseUnit (),
205
+ currentLeverageRatio: 0
206
+ });
207
+ }
208
+
209
+ /* ============ Internal Functions ============ */
210
+
211
+ /**
212
+ * @dev Returns total collateral value attributed to SetToken. TCV = collateral + owedRealizedPnl + funding.
213
+ *
214
+ * @param _setToken Instance of SetToken
215
+ *
216
+ * @return Total collateral value attributed to SetToken
217
+ */
218
+ function _calculateTotalCollateralValue (ISetToken _setToken ) internal view returns (int256 ) {
219
+ IPerpV2LeverageModule.AccountInfo memory accountInfo = perpModule.getAccountInfo (_setToken);
220
+
221
+ return accountInfo.collateralBalance
222
+ .add (accountInfo.owedRealizedPnl)
223
+ .add (accountInfo.pendingFundingPayments);
224
+ }
225
+
226
+ /**
227
+ * @dev Returns leverage of passed position relative total collateral value of Set. Leverage ratio is defined as follows:
228
+ * lr_asset = positionValue / accountValue where,
229
+ * positionValue = indexPrice_asset * notionalBaseTokenAmount_asset and
230
+ * accountValue = collateral + owedRealizedPnl + funding + positionValue_asset + quoteBalance_asset
231
+ *
232
+ * @param _position Struct containing position info for the vAsset
233
+ * @param _indexPrice Index price of vAsset
234
+ * @param _totalCollateralValue Value of total collateral attributed to SetToken
235
+ *
236
+ * @return Leverage ratio of vAsset relative to current total collateral
237
+ */
238
+ function _calculateCurrentLeverageRatio (
239
+ IPerpV2LeverageModule.PositionNotionalInfo memory _position ,
240
+ uint256 _indexPrice ,
241
+ int256 _totalCollateralValue
242
+ )
243
+ internal
244
+ pure
245
+ returns (int256 )
246
+ {
247
+ int256 positionValue = _indexPrice.toInt256 ().preciseMul (_position.baseBalance);
248
+ int256 accountValue = positionValue.add (_totalCollateralValue).add (_position.quoteBalance);
249
+ return positionValue.preciseDiv (accountValue);
250
+ }
251
+ }
0 commit comments