Skip to content

Commit 62def3e

Browse files
authored
Add PerpV2LeverageModuleViewer (#185)
Add PerpV2LeverageModuleViewer for returning max issue and perp position data.
1 parent b98d386 commit 62def3e

21 files changed

+1077
-161
lines changed

contracts/interfaces/IPerpV2LeverageModule.sol

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -108,32 +108,6 @@ interface IPerpV2LeverageModule {
108108
uint256 _amountWithdrawn
109109
);
110110

111-
/* ============ State Variable Getters ============ */
112-
113-
// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
114-
function perpAccountBalance() external view returns(IAccountBalance);
115-
116-
// PerpV2 contract which provides a trading API
117-
function perpClearingHouse() external view returns(IClearingHouse);
118-
119-
// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
120-
function perpExchange() external view returns(IExchange);
121-
122-
// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
123-
function perpVault() external view returns(IVault);
124-
125-
// PerpV2 contract which makes it possible to simulate a trade before it occurs
126-
function perpQuoter() external view returns(IQuoter);
127-
128-
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
129-
function perpMarketRegistry() external view returns(IMarketRegistry);
130-
131-
// Token (USDC) used as a vault deposit, Perp currently only supports USDC as it's settlement and collateral token
132-
function collateralToken() external view returns(IERC20);
133-
134-
// Decimals of collateral token. We set this in the constructor for later reading
135-
function collateralDecimals() external view returns(uint8);
136-
137111
/* ============ External Functions ============ */
138112

139113
/**
@@ -285,8 +259,15 @@ interface IPerpV2LeverageModule {
285259
/**
286260
* @dev Gets the mid-point price of a virtual asset from UniswapV3 markets maintained by Perp Protocol
287261
*
288-
* @param _baseToken) Address of virtual token to price
262+
* @param _baseToken Address of virtual token to price
289263
* @return price Mid-point price of virtual token in UniswapV3 AMM market
290264
*/
291265
function getAMMSpotPrice(address _baseToken) external view returns (uint256 price);
266+
267+
/**
268+
* @dev Returns address of collateral token used by Perpetual Protocol (USDC)
269+
*
270+
* @return Address of Perpetual Protocol collateral token (USDC)
271+
*/
272+
function collateralToken() external view returns (IERC20);
292273
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
Copyright 2022 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+
19+
pragma solidity 0.6.10;
20+
pragma experimental ABIEncoderV2;
21+
22+
interface IClearingHouseConfig {
23+
function getImRatio() external view returns (uint24 imRatio);
24+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Copyright 2022 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+
19+
pragma solidity 0.6.10;
20+
21+
interface IIndexPrice {
22+
function getIndexPrice(uint256 interval) external view returns (uint256 indexPrice);
23+
}

contracts/mocks/protocol/module/DebtModuleMock.sol

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,7 @@ contract DebtModuleMock is ModuleBase {
4747
mapping(address=>int256) public equityIssuanceAdjustment;
4848
mapping(address=>int256) public debtIssuanceAdjustment;
4949

50-
constructor(IController _controller, address _module) public ModuleBase(_controller) {
51-
module = _module;
52-
}
50+
constructor(IController _controller) public ModuleBase(_controller) {}
5351

5452
function addDebt(ISetToken _setToken, address _token, uint256 _amount) external {
5553
_setToken.editExternalPosition(_token, address(this), _amount.toInt256().mul(-1), "");
@@ -131,8 +129,9 @@ contract DebtModuleMock is ModuleBase {
131129
return (equityAdjustments, debtAdjustments);
132130
}
133131

134-
function initialize(ISetToken _setToken) external {
132+
function initialize(ISetToken _setToken, address _module) external {
135133
_setToken.initializeModule();
134+
module = _module;
136135
IDebtIssuanceModule(module).registerToIssuanceModule(_setToken);
137136
}
138137

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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

Comments
 (0)