Skip to content

Commit

Permalink
PerpV2LeverageModule Audit Fixes (#177)
Browse files Browse the repository at this point in the history
Co-authored-by: bweick <brian.weickmann@gmail.com>
  • Loading branch information
cgewecke and bweick authored Dec 14, 2021
1 parent cc15b59 commit ac8a53e
Show file tree
Hide file tree
Showing 20 changed files with 1,919 additions and 593 deletions.
292 changes: 292 additions & 0 deletions contracts/interfaces/IPerpV2LeverageModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
/*
Copyright 2021 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { ISetToken } from "./ISetToken.sol";
import { IDebtIssuanceModule } from "./IDebtIssuanceModule.sol";
import { IAccountBalance } from "./external/perp-v2/IAccountBalance.sol";
import { IClearingHouse } from "./external/perp-v2/IClearingHouse.sol";
import { IExchange } from "./external/perp-v2/IExchange.sol";
import { IVault } from "./external/perp-v2/IVault.sol";
import { IQuoter } from "./external/perp-v2/IQuoter.sol";
import { IMarketRegistry } from "./external/perp-v2/IMarketRegistry.sol";


/**
* @title IPerpV2LeverageModule
* @author Set Protocol
*
* Interface for the PerpV2LeverageModule. Only specifies Manager permissioned functions, events
* and getters. PerpV2LeverageModule also inherits from ModuleBase and SetTokenAccessible which support
* additional methods.
*/
interface IPerpV2LeverageModule {

/* ============ Structs ============ */

struct PositionNotionalInfo {
address baseToken; // Virtual token minted by the Perp protocol
int256 baseBalance; // Base position notional quantity in 10**18 decimals. When negative, position is short
int256 quoteBalance; // vUSDC "debt" notional quantity minted to open position. When positive, position is short
}

struct PositionUnitInfo {
address baseToken; // Virtual token minted by the Perp protocol
int256 baseUnit; // Base position unit. When negative, position is short
int256 quoteUnit; // vUSDC "debt" position unit. When positive, position is short
}

// Note: when `pendingFundingPayments` is positive it will be credited to account on settlement,
// when negative it's a debt owed that will be repaid on settlement. (PerpProtocol.Exchange returns the value
// with the opposite meaning, e.g positively signed payments are owed by account to system).
struct AccountInfo {
int256 collateralBalance; // Quantity of collateral deposited in Perp vault in 10**18 decimals
int256 owedRealizedPnl; // USDC quantity of profit and loss in 10**18 decimals not yet settled to vault
int256 pendingFundingPayments; // USDC quantity of pending funding payments in 10**18 decimals
int256 netQuoteBalance; // USDC quantity of net quote balance for all open positions in Perp account
}

/* ============ Events ============ */

/**
* @dev Emitted on trade
* @param _setToken Instance of SetToken
* @param _baseToken Virtual token minted by the Perp protocol
* @param _deltaBase Change in baseToken position size resulting from trade
* @param _deltaQuote Change in vUSDC position size resulting from trade
* @param _protocolFee Quantity in collateral decimals sent to fee recipient during lever trade
* @param _isBuy True when baseToken is being bought, false when being sold
*/
event PerpTraded(
ISetToken indexed _setToken,
address indexed _baseToken,
uint256 _deltaBase,
uint256 _deltaQuote,
uint256 _protocolFee,
bool _isBuy
);

/**
* @dev Emitted on deposit (not issue or redeem)
* @param _setToken Instance of SetToken
* @param _collateralToken Token being deposited as collateral (USDC)
* @param _amountDeposited Amount of collateral being deposited into Perp
*/
event CollateralDeposited(
ISetToken indexed _setToken,
IERC20 _collateralToken,
uint256 _amountDeposited
);

/**
* @dev Emitted on withdraw (not issue or redeem)
* @param _setToken Instance of SetToken
* @param _collateralToken Token being withdrawn as collateral (USDC)
* @param _amountWithdrawn Amount of collateral being withdrawn from Perp
*/
event CollateralWithdrawn(
ISetToken indexed _setToken,
IERC20 _collateralToken,
uint256 _amountWithdrawn
);

/* ============ State Variable Getters ============ */

// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
function perpAccountBalance() external view returns(IAccountBalance);

// PerpV2 contract which provides a trading API
function perpClearingHouse() external view returns(IClearingHouse);

// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
function perpExchange() external view returns(IExchange);

// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
function perpVault() external view returns(IVault);

// PerpV2 contract which makes it possible to simulate a trade before it occurs
function perpQuoter() external view returns(IQuoter);

// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
function perpMarketRegistry() external view returns(IMarketRegistry);

// Token (USDC) used as a vault deposit, Perp currently only supports USDC as it's settlement and collateral token
function collateralToken() external view returns(IERC20);

// Decimals of collateral token. We set this in the constructor for later reading
function collateralDecimals() external view returns(uint8);

/* ============ External Functions ============ */

/**
* @dev MANAGER ONLY: Initializes this module to the SetToken. Either the SetToken needs to be on the
* allowed list or anySetAllowed needs to be true.
*
* @param _setToken Instance of the SetToken to initialize
*/
function initialize(ISetToken _setToken) external;

/**
* @dev MANAGER ONLY: Allows manager to buy or sell perps to change exposure to the underlying baseToken.
* Providing a positive value for `_baseQuantityUnits` buys vToken on UniswapV3 via Perp's ClearingHouse,
* Providing a negative value sells the token. `_quoteBoundQuantityUnits` defines a min-receive-like slippage
* bound for the amount of vUSDC quote asset the trade will either pay or receive as a result of the action.
*
* NOTE: This method doesn't update the externalPositionUnit because it is a function of UniswapV3 virtual
* token market prices and needs to be generated on the fly to be meaningful.
*
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
* | ----------------------------------------------------------------------------------------------- |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
* | Long | Buy | pay least amt. of vQuote | upper bound of input quote | positive |
* | Short | Sell | get most amt. of vQuote | lower bound of output quote | negative |
* | ----------------------------------------------------------------------------------------------- |
*
* As a user when delevering, e.g decreasing the magnitude of your position, you'd trade as below
* | ----------------------------------------------------------------------------------------------- |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
* | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative |
* | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive |
* | ----------------------------------------------------------------------------------------------- |
*
* @param _setToken Instance of the SetToken
* @param _baseToken Address virtual token being traded
* @param _baseQuantityUnits Quantity of virtual token to trade in position units
* @param _quoteBoundQuantityUnits Max/min of vQuote asset to pay/receive when buying or selling
*/
function trade(
ISetToken _setToken,
address _baseToken,
int256 _baseQuantityUnits,
uint256 _quoteBoundQuantityUnits
)
external;

/**
* @dev MANAGER ONLY: Deposits default position collateral token into the PerpV2 Vault, increasing
* the size of the Perp account external position. This method is useful for establishing initial
* collateralization ratios, e.g the flow when setting up a 2X external position would be to deposit
* 100 units of USDC and execute a lever trade for ~200 vUSDC worth of vToken with the difference
* between these made up as automatically "issued" margin debt in the PerpV2 system.
*
* @param _setToken Instance of the SetToken
* @param _collateralQuantityUnits Quantity of collateral to deposit in position units
*/
function deposit(ISetToken _setToken, uint256 _collateralQuantityUnits) external;


/**
* @dev MANAGER ONLY: Withdraws collateral token from the PerpV2 Vault to a default position on
* the SetToken. This method is useful when adjusting the overall composition of a Set which has
* a Perp account external position as one of several components.
*
* NOTE: Within PerpV2, `withdraw` settles `owedRealizedPnl` and any pending funding payments
* to the Perp vault prior to transfer.
*
* @param _setToken Instance of the SetToken
* @param _collateralQuantityUnits Quantity of collateral to withdraw in position units
*/
function withdraw(ISetToken _setToken, uint256 _collateralQuantityUnits) external;


/* ============ External Getter Functions ============ */

/**
* @dev Gets the positive equity collateral externalPositionUnit that would be calculated for
* issuing a quantity of SetToken, representing the amount of collateral that would need to
* be transferred in per SetToken. Values in the returned arrays map to the same index in the
* SetToken's components array
*
* @param _setToken Instance of SetToken
* @param _setTokenQuantity Number of sets to issue
*
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
*/
function getIssuanceAdjustments(ISetToken _setToken, uint256 _setTokenQuantity)
external
returns (int256[] memory, int256[] memory);


/**
* @dev Gets the positive equity collateral externalPositionUnit that would be calculated for
* redeeming a quantity of SetToken representing the amount of collateral returned per SetToken.
* Values in the returned arrays map to the same index in the SetToken's components array.
*
* @param _setToken Instance of SetToken
* @param _setTokenQuantity Number of sets to issue
*
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
*/
function getRedemptionAdjustments(ISetToken _setToken, uint256 _setTokenQuantity)
external
returns (int256[] memory, int256[] memory);

/**
* @dev Returns a PositionUnitNotionalInfo array representing all positions open for the SetToken.
*
* @param _setToken Instance of SetToken
*
* @return PositionUnitInfo array, in which each element has properties:
*
* + baseToken: address,
* + baseBalance: baseToken balance as notional quantity (10**18)
* + quoteBalance: USDC quote asset balance as notional quantity (10**18)
*/
function getPositionNotionalInfo(ISetToken _setToken) external view returns (PositionNotionalInfo[] memory);

/**
* @dev Returns a PositionUnitInfo array representing all positions open for the SetToken.
*
* @param _setToken Instance of SetToken
*
* @return PositionUnitInfo array, in which each element has properties:
*
* + baseToken: address,
* + baseUnit: baseToken balance as position unit (10**18)
* + quoteUnit: USDC quote asset balance as position unit (10**18)
*/
function getPositionUnitInfo(ISetToken _setToken) external view returns (PositionUnitInfo[] memory);

/**
* @dev Gets Perp account info for SetToken. Returns an AccountInfo struct containing account wide
* (rather than position specific) balance info
*
* @param _setToken Instance of the SetToken
*
* @return accountInfo struct with properties for:
*
* + collateral balance (10**18, regardless of underlying collateral decimals)
* + owed realized Pnl` (10**18)
* + pending funding payments (10**18)
* + net quote balance (10**18)
*/
function getAccountInfo(ISetToken _setToken) external view returns (AccountInfo memory accountInfo);

/**
* @dev Gets the mid-point price of a virtual asset from UniswapV3 markets maintained by Perp Protocol
*
* @param _baseToken) Address of virtual token to price
* @return price Mid-point price of virtual token in UniswapV3 AMM market
*/
function getAMMSpotPrice(address _baseToken) external view returns (uint256 price);
}
27 changes: 27 additions & 0 deletions contracts/lib/PreciseUnitMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
pragma solidity 0.6.10;
pragma experimental ABIEncoderV2;

import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol";

Expand All @@ -33,10 +34,13 @@ import { SignedSafeMath } from "@openzeppelin/contracts/math/SignedSafeMath.sol"
* CHANGELOG:
* - 9/21/20: Added safePower function
* - 4/21/21: Added approximatelyEquals function
* - 12/13/21: Added preciseDivCeil (int overloads) function
* - 12/13/21: Added abs function
*/
library PreciseUnitMath {
using SafeMath for uint256;
using SignedSafeMath for int256;
using SafeCast for int256;

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

/**
* @dev Divides value a by value b (result is rounded up or away from 0). When `a` is 0, 0 is
* returned. When `b` is 0, method reverts with divide-by-zero error.
*/
function preciseDivCeil(int256 a, int256 b) internal pure returns (int256) {
require(b != 0, "Cant divide by 0");

if (a == 0 ) {
return 0;
} else if ((a > 0 && b > 0) || (a < 0 && b < 0)) {
return a.mul(PRECISE_UNIT_INT).sub(1).div(b).add(1);
} else {
return a.mul(PRECISE_UNIT_INT).add(1).div(b).sub(1);
}
}

/**
* @dev Divides value a by value b (result is rounded down - positive numbers toward 0 and negative away from 0).
*/
Expand Down Expand Up @@ -195,4 +215,11 @@ library PreciseUnitMath {
function approximatelyEquals(uint256 a, uint256 b, uint256 range) internal pure returns (bool) {
return a <= b.add(range) && a >= b.sub(range);
}

/**
* Returns the absolute value of int256 `a` as a uint256
*/
function abs(int256 a) internal pure returns (uint) {
return a >= 0 ? a.toUint256() : a.mul(-1).toUint256();
}
}
Loading

0 comments on commit ac8a53e

Please sign in to comment.