Skip to content

Commit ac8a53e

Browse files
cgeweckebweick
andauthored
PerpV2LeverageModule Audit Fixes (#177)
Co-authored-by: bweick <[email protected]>
1 parent cc15b59 commit ac8a53e

20 files changed

+1919
-593
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
Copyright 2021 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+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
22+
23+
import { ISetToken } from "./ISetToken.sol";
24+
import { IDebtIssuanceModule } from "./IDebtIssuanceModule.sol";
25+
import { IAccountBalance } from "./external/perp-v2/IAccountBalance.sol";
26+
import { IClearingHouse } from "./external/perp-v2/IClearingHouse.sol";
27+
import { IExchange } from "./external/perp-v2/IExchange.sol";
28+
import { IVault } from "./external/perp-v2/IVault.sol";
29+
import { IQuoter } from "./external/perp-v2/IQuoter.sol";
30+
import { IMarketRegistry } from "./external/perp-v2/IMarketRegistry.sol";
31+
32+
33+
/**
34+
* @title IPerpV2LeverageModule
35+
* @author Set Protocol
36+
*
37+
* Interface for the PerpV2LeverageModule. Only specifies Manager permissioned functions, events
38+
* and getters. PerpV2LeverageModule also inherits from ModuleBase and SetTokenAccessible which support
39+
* additional methods.
40+
*/
41+
interface IPerpV2LeverageModule {
42+
43+
/* ============ Structs ============ */
44+
45+
struct PositionNotionalInfo {
46+
address baseToken; // Virtual token minted by the Perp protocol
47+
int256 baseBalance; // Base position notional quantity in 10**18 decimals. When negative, position is short
48+
int256 quoteBalance; // vUSDC "debt" notional quantity minted to open position. When positive, position is short
49+
}
50+
51+
struct PositionUnitInfo {
52+
address baseToken; // Virtual token minted by the Perp protocol
53+
int256 baseUnit; // Base position unit. When negative, position is short
54+
int256 quoteUnit; // vUSDC "debt" position unit. When positive, position is short
55+
}
56+
57+
// Note: when `pendingFundingPayments` is positive it will be credited to account on settlement,
58+
// when negative it's a debt owed that will be repaid on settlement. (PerpProtocol.Exchange returns the value
59+
// with the opposite meaning, e.g positively signed payments are owed by account to system).
60+
struct AccountInfo {
61+
int256 collateralBalance; // Quantity of collateral deposited in Perp vault in 10**18 decimals
62+
int256 owedRealizedPnl; // USDC quantity of profit and loss in 10**18 decimals not yet settled to vault
63+
int256 pendingFundingPayments; // USDC quantity of pending funding payments in 10**18 decimals
64+
int256 netQuoteBalance; // USDC quantity of net quote balance for all open positions in Perp account
65+
}
66+
67+
/* ============ Events ============ */
68+
69+
/**
70+
* @dev Emitted on trade
71+
* @param _setToken Instance of SetToken
72+
* @param _baseToken Virtual token minted by the Perp protocol
73+
* @param _deltaBase Change in baseToken position size resulting from trade
74+
* @param _deltaQuote Change in vUSDC position size resulting from trade
75+
* @param _protocolFee Quantity in collateral decimals sent to fee recipient during lever trade
76+
* @param _isBuy True when baseToken is being bought, false when being sold
77+
*/
78+
event PerpTraded(
79+
ISetToken indexed _setToken,
80+
address indexed _baseToken,
81+
uint256 _deltaBase,
82+
uint256 _deltaQuote,
83+
uint256 _protocolFee,
84+
bool _isBuy
85+
);
86+
87+
/**
88+
* @dev Emitted on deposit (not issue or redeem)
89+
* @param _setToken Instance of SetToken
90+
* @param _collateralToken Token being deposited as collateral (USDC)
91+
* @param _amountDeposited Amount of collateral being deposited into Perp
92+
*/
93+
event CollateralDeposited(
94+
ISetToken indexed _setToken,
95+
IERC20 _collateralToken,
96+
uint256 _amountDeposited
97+
);
98+
99+
/**
100+
* @dev Emitted on withdraw (not issue or redeem)
101+
* @param _setToken Instance of SetToken
102+
* @param _collateralToken Token being withdrawn as collateral (USDC)
103+
* @param _amountWithdrawn Amount of collateral being withdrawn from Perp
104+
*/
105+
event CollateralWithdrawn(
106+
ISetToken indexed _setToken,
107+
IERC20 _collateralToken,
108+
uint256 _amountWithdrawn
109+
);
110+
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+
137+
/* ============ External Functions ============ */
138+
139+
/**
140+
* @dev MANAGER ONLY: Initializes this module to the SetToken. Either the SetToken needs to be on the
141+
* allowed list or anySetAllowed needs to be true.
142+
*
143+
* @param _setToken Instance of the SetToken to initialize
144+
*/
145+
function initialize(ISetToken _setToken) external;
146+
147+
/**
148+
* @dev MANAGER ONLY: Allows manager to buy or sell perps to change exposure to the underlying baseToken.
149+
* Providing a positive value for `_baseQuantityUnits` buys vToken on UniswapV3 via Perp's ClearingHouse,
150+
* Providing a negative value sells the token. `_quoteBoundQuantityUnits` defines a min-receive-like slippage
151+
* bound for the amount of vUSDC quote asset the trade will either pay or receive as a result of the action.
152+
*
153+
* NOTE: This method doesn't update the externalPositionUnit because it is a function of UniswapV3 virtual
154+
* token market prices and needs to be generated on the fly to be meaningful.
155+
*
156+
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
157+
* | ----------------------------------------------------------------------------------------------- |
158+
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
159+
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
160+
* | Long | Buy | pay least amt. of vQuote | upper bound of input quote | positive |
161+
* | Short | Sell | get most amt. of vQuote | lower bound of output quote | negative |
162+
* | ----------------------------------------------------------------------------------------------- |
163+
*
164+
* As a user when delevering, e.g decreasing the magnitude of your position, you'd trade as below
165+
* | ----------------------------------------------------------------------------------------------- |
166+
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
167+
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
168+
* | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative |
169+
* | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive |
170+
* | ----------------------------------------------------------------------------------------------- |
171+
*
172+
* @param _setToken Instance of the SetToken
173+
* @param _baseToken Address virtual token being traded
174+
* @param _baseQuantityUnits Quantity of virtual token to trade in position units
175+
* @param _quoteBoundQuantityUnits Max/min of vQuote asset to pay/receive when buying or selling
176+
*/
177+
function trade(
178+
ISetToken _setToken,
179+
address _baseToken,
180+
int256 _baseQuantityUnits,
181+
uint256 _quoteBoundQuantityUnits
182+
)
183+
external;
184+
185+
/**
186+
* @dev MANAGER ONLY: Deposits default position collateral token into the PerpV2 Vault, increasing
187+
* the size of the Perp account external position. This method is useful for establishing initial
188+
* collateralization ratios, e.g the flow when setting up a 2X external position would be to deposit
189+
* 100 units of USDC and execute a lever trade for ~200 vUSDC worth of vToken with the difference
190+
* between these made up as automatically "issued" margin debt in the PerpV2 system.
191+
*
192+
* @param _setToken Instance of the SetToken
193+
* @param _collateralQuantityUnits Quantity of collateral to deposit in position units
194+
*/
195+
function deposit(ISetToken _setToken, uint256 _collateralQuantityUnits) external;
196+
197+
198+
/**
199+
* @dev MANAGER ONLY: Withdraws collateral token from the PerpV2 Vault to a default position on
200+
* the SetToken. This method is useful when adjusting the overall composition of a Set which has
201+
* a Perp account external position as one of several components.
202+
*
203+
* NOTE: Within PerpV2, `withdraw` settles `owedRealizedPnl` and any pending funding payments
204+
* to the Perp vault prior to transfer.
205+
*
206+
* @param _setToken Instance of the SetToken
207+
* @param _collateralQuantityUnits Quantity of collateral to withdraw in position units
208+
*/
209+
function withdraw(ISetToken _setToken, uint256 _collateralQuantityUnits) external;
210+
211+
212+
/* ============ External Getter Functions ============ */
213+
214+
/**
215+
* @dev Gets the positive equity collateral externalPositionUnit that would be calculated for
216+
* issuing a quantity of SetToken, representing the amount of collateral that would need to
217+
* be transferred in per SetToken. Values in the returned arrays map to the same index in the
218+
* SetToken's components array
219+
*
220+
* @param _setToken Instance of SetToken
221+
* @param _setTokenQuantity Number of sets to issue
222+
*
223+
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
224+
*/
225+
function getIssuanceAdjustments(ISetToken _setToken, uint256 _setTokenQuantity)
226+
external
227+
returns (int256[] memory, int256[] memory);
228+
229+
230+
/**
231+
* @dev Gets the positive equity collateral externalPositionUnit that would be calculated for
232+
* redeeming a quantity of SetToken representing the amount of collateral returned per SetToken.
233+
* Values in the returned arrays map to the same index in the SetToken's components array.
234+
*
235+
* @param _setToken Instance of SetToken
236+
* @param _setTokenQuantity Number of sets to issue
237+
*
238+
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
239+
*/
240+
function getRedemptionAdjustments(ISetToken _setToken, uint256 _setTokenQuantity)
241+
external
242+
returns (int256[] memory, int256[] memory);
243+
244+
/**
245+
* @dev Returns a PositionUnitNotionalInfo array representing all positions open for the SetToken.
246+
*
247+
* @param _setToken Instance of SetToken
248+
*
249+
* @return PositionUnitInfo array, in which each element has properties:
250+
*
251+
* + baseToken: address,
252+
* + baseBalance: baseToken balance as notional quantity (10**18)
253+
* + quoteBalance: USDC quote asset balance as notional quantity (10**18)
254+
*/
255+
function getPositionNotionalInfo(ISetToken _setToken) external view returns (PositionNotionalInfo[] memory);
256+
257+
/**
258+
* @dev Returns a PositionUnitInfo array representing all positions open for the SetToken.
259+
*
260+
* @param _setToken Instance of SetToken
261+
*
262+
* @return PositionUnitInfo array, in which each element has properties:
263+
*
264+
* + baseToken: address,
265+
* + baseUnit: baseToken balance as position unit (10**18)
266+
* + quoteUnit: USDC quote asset balance as position unit (10**18)
267+
*/
268+
function getPositionUnitInfo(ISetToken _setToken) external view returns (PositionUnitInfo[] memory);
269+
270+
/**
271+
* @dev Gets Perp account info for SetToken. Returns an AccountInfo struct containing account wide
272+
* (rather than position specific) balance info
273+
*
274+
* @param _setToken Instance of the SetToken
275+
*
276+
* @return accountInfo struct with properties for:
277+
*
278+
* + collateral balance (10**18, regardless of underlying collateral decimals)
279+
* + owed realized Pnl` (10**18)
280+
* + pending funding payments (10**18)
281+
* + net quote balance (10**18)
282+
*/
283+
function getAccountInfo(ISetToken _setToken) external view returns (AccountInfo memory accountInfo);
284+
285+
/**
286+
* @dev Gets the mid-point price of a virtual asset from UniswapV3 markets maintained by Perp Protocol
287+
*
288+
* @param _baseToken) Address of virtual token to price
289+
* @return price Mid-point price of virtual token in UniswapV3 AMM market
290+
*/
291+
function getAMMSpotPrice(address _baseToken) external view returns (uint256 price);
292+
}

contracts/lib/PreciseUnitMath.sol

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
pragma solidity 0.6.10;
2020
pragma experimental ABIEncoderV2;
2121

22+
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
2223
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
2324
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";
2425

@@ -33,10 +34,13 @@ import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"
3334
* CHANGELOG:
3435
* - 9/21/20: Added safePower function
3536
* - 4/21/21: Added approximatelyEquals function
37+
* - 12/13/21: Added preciseDivCeil (int overloads) function
38+
* - 12/13/21: Added abs function
3639
*/
3740
library PreciseUnitMath {
3841
using SafeMath for uint256;
3942
using SignedSafeMath for int256;
43+
using SafeCast for int256;
4044

4145
// The number One in precise units.
4246
uint256 constant internal PRECISE_UNIT = 10 ** 18;
@@ -134,6 +138,22 @@ library PreciseUnitMath {
134138
return a > 0 ? a.mul(PRECISE_UNIT).sub(1).div(b).add(1) : 0;
135139
}
136140

141+
/**
142+
* @dev Divides value a by value b (result is rounded up or away from 0). When `a` is 0, 0 is
143+
* returned. When `b` is 0, method reverts with divide-by-zero error.
144+
*/
145+
function preciseDivCeil(int256 a, int256 b) internal pure returns (int256) {
146+
require(b != 0, "Cant divide by 0");
147+
148+
if (a == 0 ) {
149+
return 0;
150+
} else if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
151+
return a.mul(PRECISE_UNIT_INT).sub(1).div(b).add(1);
152+
} else {
153+
return a.mul(PRECISE_UNIT_INT).add(1).div(b).sub(1);
154+
}
155+
}
156+
137157
/**
138158
* @dev Divides value a by value b (result is rounded down - positive numbers toward 0 and negative away from 0).
139159
*/
@@ -195,4 +215,11 @@ library PreciseUnitMath {
195215
function approximatelyEquals(uint256 a, uint256 b, uint256 range) internal pure returns (bool) {
196216
return a <= b.add(range) && a >= b.sub(range);
197217
}
218+
219+
/**
220+
* Returns the absolute value of int256 `a` as a uint256
221+
*/
222+
function abs(int256 a) internal pure returns (uint) {
223+
return a >= 0 ? a.toUint256() : a.mul(-1).toUint256();
224+
}
198225
}

0 commit comments

Comments
 (0)