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