PerpV2LeverageModule <> PerpV2LeverageModuleV2
474 lines
/*
/*
Copyright 2021 Set Labs Inc.
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
See the License for the specific language governing permissions and
limitations under the License.
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
SPDX-License-Identifier: Apache License, Version 2.0
*/
*/
pragma solidity 0.6.10;
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";
pragma experimental "ABIEncoderV2";
import "@openzeppelin/contracts/math/SignedSafeMath.sol";
import "@openzeppelin/contracts/math/SignedSafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import { PerpV2 } from "../integration/lib/PerpV2.sol";
import { PerpV2LibraryV2 } from "../integration/lib/PerpV2LibraryV2.sol";
import { PerpV2Positions } from "../integration/lib/PerpV2Positions.sol";
import { UniswapV3Math } from "../integration/lib/UniswapV3Math.sol";
import { UniswapV3Math } from "../integration/lib/UniswapV3Math.sol";
import { IAccountBalance } from "../../interfaces/external/perp-v2/IAccountBalance.sol";
import { IAccountBalance } from "../../interfaces/external/perp-v2/IAccountBalance.sol";
import { IClearingHouse } from "../../interfaces/external/perp-v2/IClearingHouse.sol";
import { IClearingHouse } from "../../interfaces/external/perp-v2/IClearingHouse.sol";
import { IClearingHouseConfig } from "../../interfaces/external/perp-v2/IClearingHouseConfig.sol";
import { IClearingHouseConfig } from "../../interfaces/external/perp-v2/IClearingHouseConfig.sol";
import { IExchange } from "../../interfaces/external/perp-v2/IExchange.sol";
import { IExchange } from "../../interfaces/external/perp-v2/IExchange.sol";
import { IIndexPrice } from "../../interfaces/external/perp-v2/IIndexPrice.sol";
import { IIndexPrice } from "../../interfaces/external/perp-v2/IIndexPrice.sol";
import { IVault } from "../../interfaces/external/perp-v2/IVault.sol";
import { IVault } from "../../interfaces/external/perp-v2/IVault.sol";
import { IQuoter } from "../../interfaces/external/perp-v2/IQuoter.sol";
import { IQuoter } from "../../interfaces/external/perp-v2/IQuoter.sol";
import { IMarketRegistry } from "../../interfaces/external/perp-v2/IMarketRegistry.sol";
import { IMarketRegistry } from "../../interfaces/external/perp-v2/IMarketRegistry.sol";
import { IController } from "../../interfaces/IController.sol";
import { IController } from "../../interfaces/IController.sol";
import { IDebtIssuanceModule } from "../../interfaces/IDebtIssuanceModule.sol";
import { IDebtIssuanceModule } from "../../interfaces/IDebtIssuanceModule.sol";
import { IModuleIssuanceHookV2 } from "../../interfaces/IModuleIssuanceHookV2.sol";
import { IModuleIssuanceHookV2 } from "../../interfaces/IModuleIssuanceHookV2.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { ModuleBaseV2 } from "../lib/ModuleBaseV2.sol";
import { SetTokenAccessible } from "../lib/SetTokenAccessible.sol";
import { SetTokenAccessible } from "../lib/SetTokenAccessible.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
import { AddressArrayUtils } from "../../lib/AddressArrayUtils.sol";
import { AddressArrayUtils } from "../../lib/AddressArrayUtils.sol";
import { UnitConversionUtils } from "../../lib/UnitConversionUtils.sol";
import { UnitConversionUtils } from "../../lib/UnitConversionUtils.sol";
/**
/**
* @title PerpV2LeverageModule
* @title PerpV2LeverageModuleV2
* @author Set Protocol
* @author Set Protocol
* @notice Smart contract that enables leveraged trading using the PerpV2 protocol. Each SetToken can only manage a single Perp account
* @notice Smart contract that enables leveraged trading using the PerpV2 protocol. Each SetToken can only manage a single Perp account
* represented as a positive equity external position whose value is the net Perp account value denominated in the collateral token
* represented as a positive equity external position whose value is the net Perp account value denominated in the collateral token
* deposited into the Perp Protocol. This module only allows Perp positions to be collateralized by one asset, USDC, set on deployment of
* deposited into the Perp Protocol. This module only allows Perp positions to be collateralized by one asset, USDC, set on deployment of
* this contract (see collateralToken) however it can take positions simultaneously in multiple base assets.
* this contract (see collateralToken) however it can take positions simultaneously in multiple base assets.
*
*
* Upon issuance and redemption positions are not EXACTLY replicated like for other position types since a trade is necessary to enter/exit
* Upon issuance and redemption positions are not EXACTLY replicated like for other position types since a trade is necessary to enter/exit
* the position on behalf of the issuer/redeemer. Any cost of entering/exiting the position (slippage) is carried by the issuer/redeemer.
* the position on behalf of the issuer/redeemer. Any cost of entering/exiting the position (slippage) is carried by the issuer/redeemer.
* Any pending funding costs or PnL is carried by the current token holders. To be used safely this module MUST issue using the
* Any pending funding costs or PnL is carried by the current token holders. To be used safely this module MUST issue using the
* SlippageIssuanceModule or else issue and redeem transaction could be sandwich attacked.
* SlippageIssuanceModule or else issue and redeem transaction could be sandwich attacked.
*
*
* NOTE: The external position unit is only updated on an as-needed basis during issuance/redemption. It does not reflect the current
* NOTE: The external position unit is only updated on an as-needed basis during issuance/redemption. It does not reflect the current
* value of the Set's perpetual position. The current value can be calculated from getPositionNotionalInfo.
* value of the Set's perpetual position. The current value can be calculated from getPositionNotionalInfo.
*
* CHANGELOG:
* - This contract has the same functionality as `PerpV2LeverageModule` but smaller bytecode size. It extends ModuleBaseV2 (which uses
* linked PositionV2 library) and uses linked PerpV2LibraryV2 and PerpV2Positions library. This separation of logic across linked library
* contracts helps us to significantly decrease the bytecode size of this contract.
*/
*/
contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenAccessible, IModuleIssuanceHookV2 {
contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTokenAccessible, IModuleIssuanceHookV2 {
using PerpV2 for ISetToken;
using PerpV2LibraryV2 for ISetToken;
using PreciseUnitMath for int256;
using PreciseUnitMath for int256;
using SignedSafeMath for int256;
using SignedSafeMath for int256;
using UnitConversionUtils for int256;
using UnitConversionUtils for int256;
using UniswapV3Math for uint160;
using UniswapV3Math for uint160;
using UniswapV3Math for uint256;
using UniswapV3Math for uint256;
using UnitConversionUtils for uint256;
using UnitConversionUtils for uint256;
using AddressArrayUtils for address[];
using AddressArrayUtils for address[];
/* ============ Structs ============ */
/* ============ Structs ============ */
struct ActionInfo {
ISetToken setToken;
address baseToken; // Virtual token minted by the Perp protocol
bool isBuy; // When true, `baseToken` is being bought, when false, sold
uint256 baseTokenAmount; // Base token quantity in 10**18 decimals
uint256 oppositeAmountBound; // vUSDC pay or receive quantity bound (see `_createActionInfoNotional` for details)
}
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,
// 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
// 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).
// with the opposite meaning, e.g positively signed payments are owed by account to system).
struct AccountInfo {
struct AccountInfo {
int256 collateralBalance; // Quantity of collateral deposited in Perp vault in 10**18 decimals
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 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 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
int256 netQuoteBalance; // USDC quantity of net quote balance for all open positions in Perp account
}
}
/* ============ Events ============ */
/* ============ Events ============ */
/**
/**
* @dev Emitted on trade
* @dev Emitted on trade
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _baseToken Virtual token minted by the Perp protocol
* @param _baseToken Virtual token minted by the Perp protocol
* @param _deltaBase Change in baseToken position size resulting from trade
* @param _deltaBase Change in baseToken position size resulting from trade
* @param _deltaQuote Change in vUSDC 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 _protocolFee Quantity in collateral decimals sent to fee recipient during lever trade
* @param _isBuy True when baseToken is being bought, false when being sold
* @param _isBuy True when baseToken is being bought, false when being sold
*/
*/
event PerpTraded(
event PerpTraded(
ISetToken indexed _setToken,
ISetToken indexed _setToken,
address indexed _baseToken,
address indexed _baseToken,
uint256 indexed _deltaBase,
uint256 indexed _deltaBase,
uint256 _deltaQuote,
uint256 _deltaQuote,
uint256 _protocolFee,
uint256 _protocolFee,
bool _isBuy
bool _isBuy
);
);
/**
/**
* @dev Emitted on deposit (not issue or redeem)
* @dev Emitted on deposit (not issue or redeem)
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _collateralToken Token being deposited as collateral (USDC)
* @param _collateralToken Token being deposited as collateral (USDC)
* @param _amountDeposited Amount of collateral being deposited into Perp
* @param _amountDeposited Amount of collateral being deposited into Perp
*/
*/
event CollateralDeposited(
event CollateralDeposited(
ISetToken indexed _setToken,
ISetToken indexed _setToken,
IERC20 indexed _collateralToken,
IERC20 indexed _collateralToken,
uint256 indexed _amountDeposited
uint256 indexed _amountDeposited
);
);
/**
/**
* @dev Emitted on withdraw (not issue or redeem)
* @dev Emitted on withdraw (not issue or redeem)
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _collateralToken Token being withdrawn as collateral (USDC)
* @param _collateralToken Token being withdrawn as collateral (USDC)
* @param _amountWithdrawn Amount of collateral being withdrawn from Perp
* @param _amountWithdrawn Amount of collateral being withdrawn from Perp
*/
*/
event CollateralWithdrawn(
event CollateralWithdrawn(
ISetToken indexed _setToken,
ISetToken indexed _setToken,
IERC20 indexed _collateralToken,
IERC20 indexed _collateralToken,
uint256 indexed _amountWithdrawn
uint256 indexed _amountWithdrawn
);
);
/* ============ Constants ============ */
/* ============ Constants ============ */
// String identifying the DebtIssuanceModule in the IntegrationRegistry. Note: Governance must add DefaultIssuanceModule as
// String identifying the DebtIssuanceModule in the IntegrationRegistry. Note: Governance must add DefaultIssuanceModule as
// the string as the integration name
// the string as the integration name
string constant internal DEFAULT_ISSUANCE_MODULE_NAME = "DefaultIssuanceModule";
string constant internal DEFAULT_ISSUANCE_MODULE_NAME = "DefaultIssuanceModule";
// 0 index stores protocol fee % on the controller, charged in the _executeTrade function
// 0 index stores protocol fee % on the controller, charged in the _executeTrade function
uint256 constant internal PROTOCOL_TRADE_FEE_INDEX = 0;
uint256 constant internal PROTOCOL_TRADE_FEE_INDEX = 0;
/* ============ State Variables ============ */
/* ============ State Variables ============ */
// Token (USDC) used as a vault deposit, Perp currently only supports USDC as it's settlement and collateral token
// Token (USDC) used as a vault deposit, Perp currently only supports USDC as it's settlement and collateral token
IERC20 public immutable collateralToken;
IERC20 public immutable collateralToken;
// Decimals of collateral token. We set this in the constructor for later reading
// Decimals of collateral token. We set this in the constructor for later reading
uint8 internal immutable collateralDecimals;
uint8 internal immutable collateralDecimals;
// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
// PerpV2 contract which provides getters for base, quote, and owedRealizedPnl balances
IAccountBalance public immutable perpAccountBalance;
IAccountBalance public immutable perpAccountBalance;
// PerpV2 contract which provides a trading API
// PerpV2 contract which provides a trading API
IClearingHouse public immutable perpClearingHouse;
IClearingHouse public immutable perpClearingHouse;
// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
// PerpV2 contract which manages trading logic. Provides getters for UniswapV3 pools and pending funding balances
IExchange public immutable perpExchange;
IExchange public immutable perpExchange;
// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
// PerpV2 contract which handles deposits and withdrawals. Provides getter for collateral balances
IVault public immutable perpVault;
IVault public immutable perpVault;
// PerpV2 contract which makes it possible to simulate a trade before it occurs
// PerpV2 contract which makes it possible to simulate a trade before it occurs
IQuoter public immutable perpQuoter;
IQuoter public immutable perpQuoter;
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
// PerpV2 contract which provides a getter for baseToken UniswapV3 pools
IMarketRegistry public immutable perpMarketRegistry;
IMarketRegistry public immutable perpMarketRegistry;
// PerpV2 operations are very gas intensive and there is a limit on the number of positions that can be opened in a single transaction
// PerpV2 operations are very gas intensive and there is a limit on the number of positions that can be opened in a single transaction
// during issuance/redemption. `maxPerpPositionsPerSet` is a safe limit set by governance taking Optimism's block gas limit into account.
// during issuance/redemption. `maxPerpPositionsPerSet` is a safe limit set by governance taking Optimism's block gas limit into account.
uint256 public maxPerpPositionsPerSet;
uint256 public maxPerpPositionsPerSet;
// Mapping of SetTokens to an array of virtual token addresses the Set has open positions for.
// Mapping of SetTokens to an array of virtual token addresses the Set has open positions for.
// Array is updated when new positions are opened or old positions are zeroed out.
// Array is updated when new positions are opened or old positions are zeroed out.
mapping(ISetToken => address[]) internal positions;
mapping(ISetToken => address[]) internal positions;
/* ============ Constructor ============ */
/* ============ Constructor ============ */
/**
/**
* @dev Sets external PerpV2 Protocol contract addresses. Sets `collateralToken` and `collateralDecimals`
* @dev Sets external PerpV2 Protocol contract addresses. Sets `collateralToken` and `collateralDecimals`
* to the Perp vault's settlement token (USDC) and its decimals, respectively.
* to the Perp vault's settlement token (USDC) and its decimals, respectively.
*
*
* @param _controller Address of controller contract
* @param _controller Address of controller contract
* @param _perpVault Address of Perp Vault contract
* @param _perpVault Address of Perp Vault contract
* @param _perpQuoter Address of Perp Quoter contract
* @param _perpQuoter Address of Perp Quoter contract
* @param _perpMarketRegistry Address of Perp MarketRegistry contract
* @param _perpMarketRegistry Address of Perp MarketRegistry contract
*/
*/
constructor(
constructor(
IController _controller,
IController _controller,
IVault _perpVault,
IVault _perpVault,
IQuoter _perpQuoter,
IQuoter _perpQuoter,
IMarketRegistry _perpMarketRegistry,
IMarketRegistry _perpMarketRegistry,
uint256 _maxPerpPositionsPerSet
uint256 _maxPerpPositionsPerSet
)
)
public
public
ModuleBase(_controller)
ModuleBaseV2(_controller)
SetTokenAccessible(_controller)
SetTokenAccessible(_controller)
{
{
// Use temp variables to initialize immutables
// Use temp variables to initialize immutables
address tempCollateralToken = _perpVault.getSettlementToken();
address tempCollateralToken = _perpVault.getSettlementToken();
collateralToken = IERC20(tempCollateralToken);
collateralToken = IERC20(tempCollateralToken);
collateralDecimals = ERC20(tempCollateralToken).decimals();
collateralDecimals = ERC20(tempCollateralToken).decimals();
perpAccountBalance = IAccountBalance(_perpVault.getAccountBalance());
perpAccountBalance = IAccountBalance(_perpVault.getAccountBalance());
perpClearingHouse = IClearingHouse(_perpVault.getClearingHouse());
perpClearingHouse = IClearingHouse(_perpVault.getClearingHouse());
perpExchange = IExchange(_perpVault.getExchange());
perpExchange = IExchange(_perpVault.getExchange());
perpVault = _perpVault;
perpVault = _perpVault;
perpQuoter = _perpQuoter;
perpQuoter = _perpQuoter;
perpMarketRegistry = _perpMarketRegistry;
perpMarketRegistry = _perpMarketRegistry;
maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
maxPerpPositionsPerSet = _maxPerpPositionsPerSet;
}
}
/* ============ External Functions ============ */
/* ============ External Functions ============ */
/**
/**
* @dev MANAGER ONLY: Initializes this module to the SetToken. Either the SetToken needs to be on the
* @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.
* allowed list or anySetAllowed needs to be true.
*
*
* @param _setToken Instance of the SetToken to initialize
* @param _setToken Instance of the SetToken to initialize
*/
*/
function initialize(
function initialize(
ISetToken _setToken
ISetToken _setToken
)
)
public
public
onlySetManager(_setToken, msg.sender)
onlySetManager(_setToken, msg.sender)
onlyValidAndPendingSet(_setToken)
onlyValidAndPendingSet(_setToken)
onlyAllowedSet(_setToken)
onlyAllowedSet(_setToken)
{
{
// Initialize module before trying register
// Initialize module before trying register
_setToken.initializeModule();
_setToken.initializeModule();
// Get debt issuance module registered to this module and require that it is initialized
// Get debt issuance module registered to this module and require that it is initialized
require(_setToken.isInitializedModule(
require(_setToken.isInitializedModule(
getAndValidateAdapter(DEFAULT_ISSUANCE_MODULE_NAME)),
getAndValidateAdapter(DEFAULT_ISSUANCE_MODULE_NAME)),
"Issuance not initialized"
"Issuance not initialized"
);
);
// Try if register exists on any of the modules including the debt issuance module
// Try if register exists on any of the modules including the debt issuance module
address[] memory modules = _setToken.getModules();
address[] memory modules = _setToken.getModules();
for(uint256 i = 0; i < modules.length; i++) {
for(uint256 i = 0; i < modules.length; i++) {
try IDebtIssuanceModule(modules[i]).registerToIssuanceModule(_setToken) {
try IDebtIssuanceModule(modules[i]).registerToIssuanceModule(_setToken) {
// This module registered itself on `modules[i]` issuance module.
// This module registered itself on `modules[i]` issuance module.
} catch {
} catch {
// Try will fail if `modules[i]` is not an instance of IDebtIssuanceModule and does not
// Try will fail if `modules[i]` is not an instance of IDebtIssuanceModule and does not
// implement the `registerToIssuanceModule` function, or if the `registerToIssuanceModule`
// implement the `registerToIssuanceModule` function, or if the `registerToIssuanceModule`
// function call reverted. Irrespective of the reason for failure, continue to the next module.
// function call reverted. Irrespective of the reason for failure, continue to the next module.
}
}
}
}
}
}
/**
/**
* @dev MANAGER ONLY: Allows manager to buy or sell perps to change exposure to the underlying baseToken.
* @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 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
* 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.
* 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
* 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.
* token market prices and needs to be generated on the fly to be meaningful.
*
*
* In the tables below, basePositionUnit = baseTokenBalance / setTotalSupply.
* In the tables below, basePositionUnit = baseTokenBalance / setTotalSupply.
*
*
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
* As a user when levering, e.g increasing the magnitude of your position, you'd trade as below
* | ----------------------------------------------------------------------------------------------- |
* | ----------------------------------------------------------------------------------------------- |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
* | ----- |-------- | ------------------------- | --------------------------- | ------------------- |
* | Long | Buy | pay least amt. of vQuote | upper bound of input quote | positive |
* | 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 |
* | Short | Sell | get most amt. of vQuote | lower bound of output quote | negative |
* | ----------------------------------------------------------------------------------------------- |
* | ----------------------------------------------------------------------------------------------- |
*
*
* As a user when delevering by partially closing your position, you'd trade as below
* As a user when delevering by partially closing your position, you'd trade as below
* -----------------------------------------------------------------------------------------------------------------------------------
* -----------------------------------------------------------------------------------------------------------------------------------
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-------- | ------------------------- | --------------------------- | ----------------------------------------------------|
* | ----- |-------- | ------------------------- | --------------------------- | ----------------------------------------------------|
* | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| < |basePositionUnit| |
* | Long | Sell | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| < |basePositionUnit| |
* | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| < |basePositionUnit| |
* | Short | Buy | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| < |basePositionUnit| |
* -----------------------------------------------------------------------------------------------------------------------------------
* -----------------------------------------------------------------------------------------------------------------------------------
*
*
* As a user when completely closing a position, you'd trade as below
* As a user when completely closing a position, you'd trade as below
* -------------------------------------------------------------------------------------------------------------------------------------------
* -------------------------------------------------------------------------------------------------------------------------------------------
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-----------------| ------------------------- | --------------------------- | ----------------------------------------------------|
* | ----- |-----------------| ------------------------- | --------------------------- | ----------------------------------------------------|
* | Long | Sell to close | get most amt. of vQuote | upper bound of input quote | negative, baseQuantityUnits = -1 * basePositionUnit |
* | Long | Sell to close | get most amt. of vQuote | upper bound of input quote | negative, baseQuantityUnits = -1 * basePositionUnit |
* | Short | Buy to close | pay least amt. of vQuote | lower bound of output quote | positive, baseQuantityUnits = -1 * basePositionUnit |
* | Short | Buy to close | pay least amt. of vQuote | lower bound of output quote | positive, baseQuantityUnits = -1 * basePositionUnit |
* -------------------------------------------------------------------------------------------------------------------------------------------
* -------------------------------------------------------------------------------------------------------------------------------------------
*
*
* As a user when reversing a position, e.g going from a long position to a short position in a single trade, you'd trade as below
* As a user when reversing a position, e.g going from a long position to a short position in a single trade, you'd trade as below
* -------------------------------------------------------------------------------------------------------------------------------------------
* -------------------------------------------------------------------------------------------------------------------------------------------
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | Type | Action | Goal | `quoteBoundQuantity` | `baseQuantityUnits` |
* | ----- |-----------------|---------------------------| --------------------------- | ----------------------------------------------------|
* | ----- |-----------------|---------------------------| --------------------------- | ----------------------------------------------------|
* | Long | Sell to reverse | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| > |basePositionUnit| |
* | Long | Sell to reverse | get most amt. of vQuote | upper bound of input quote | negative, |baseQuantityUnits| > |basePositionUnit| |
* | Short | Buy to reverse | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| > |basePositionUnit| |
* | Short | Buy to reverse | pay least amt. of vQuote | lower bound of output quote | positive, |baseQuantityUnits| > |basePositionUnit| |
* -------------------------------------------------------------------------------------------------------------------------------------------
* -------------------------------------------------------------------------------------------------------------------------------------------
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _baseToken Address virtual token being traded
* @param _baseToken Address virtual token being traded
* @param _baseQuantityUnits Quantity of virtual token to trade in position units
* @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
* @param _quoteBoundQuantityUnits Max/min of vQuote asset to pay/receive when buying or selling
*/
*/
function trade(
function trade(
ISetToken _setToken,
ISetToken _setToken,
address _baseToken,
address _baseToken,
int256 _baseQuantityUnits,
int256 _baseQuantityUnits,
uint256 _quoteBoundQuantityUnits
uint256 _quoteBoundQuantityUnits
)
)
public
public
nonReentrant
nonReentrant
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
ActionInfo memory actionInfo = _createAndValidateActionInfo(
PerpV2LibraryV2.ActionInfo memory actionInfo = _createAndValidateActionInfo(
_setToken,
_setToken,
_baseToken,
_baseToken,
_baseQuantityUnits,
_baseQuantityUnits,
_quoteBoundQuantityUnits
_quoteBoundQuantityUnits
);
);
(uint256 deltaBase, uint256 deltaQuote) = _executeTrade(actionInfo);
(uint256 deltaBase, uint256 deltaQuote) = PerpV2LibraryV2.executeTrade(actionInfo, perpClearingHouse);
uint256 protocolFee = _accrueProtocolFee(_setToken, deltaQuote);
uint256 protocolFee = _accrueProtocolFee(_setToken, deltaQuote);
_updatePositionList(_setToken, _baseToken);
_updatePositionList(_setToken, _baseToken);
emit PerpTraded(
emit PerpTraded(
_setToken,
_setToken,
_baseToken,
_baseToken,
deltaBase,
deltaBase,
deltaQuote,
deltaQuote,
protocolFee,
protocolFee,
actionInfo.isBuy
actionInfo.isBuy
);
);
}
}
/**
/**
* @dev MANAGER ONLY: Deposits default position collateral token into the PerpV2 Vault, increasing
* @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
* 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
* 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
* 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.
* between these made up as automatically "issued" margin debt in the PerpV2 system.
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _collateralQuantityUnits Quantity of collateral to deposit in position units
* @param _collateralQuantityUnits Quantity of collateral to deposit in position units
*/
*/
function deposit(
function deposit(
ISetToken _setToken,
ISetToken _setToken,
uint256 _collateralQuantityUnits
uint256 _collateralQuantityUnits
)
)
public
public
nonReentrant
nonReentrant
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
require(_collateralQuantityUnits > 0, "Deposit amount is 0");
require(_collateralQuantityUnits > 0, "Deposit amount is 0");
uint256 notionalDepositedQuantity = _depositAndUpdatePositions(_setToken, _collateralQuantityUnits);
uint256 notionalDepositedQuantity = _depositAndUpdatePositions(_setToken, _collateralQuantityUnits);
emit CollateralDeposited(_setToken, collateralToken, notionalDepositedQuantity);
emit CollateralDeposited(_setToken, collateralToken, notionalDepositedQuantity);
}
}
/**
/**
* @dev MANAGER ONLY: Withdraws collateral token from the PerpV2 Vault to a default position on
* @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
* 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.
* a Perp account external position as one of several components.
*
*
* NOTE: Within PerpV2, `withdraw` settles `owedRealizedPnl` and any pending funding payments
* NOTE: Within PerpV2, `withdraw` settles `owedRealizedPnl` and any pending funding payments
* to the Perp vault prior to transfer.
* to the Perp vault prior to transfer.
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _collateralQuantityUnits Quantity of collateral to withdraw in position units
* @param _collateralQuantityUnits Quantity of collateral to withdraw in position units
*/
*/
function withdraw(
function withdraw(
ISetToken _setToken,
ISetToken _setToken,
uint256 _collateralQuantityUnits
uint256 _collateralQuantityUnits
)
)
public
public
nonReentrant
nonReentrant
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
require(_collateralQuantityUnits > 0, "Withdraw amount is 0");
require(_collateralQuantityUnits > 0, "Withdraw amount is 0");
uint256 notionalWithdrawnQuantity = _withdrawAndUpdatePositions(_setToken, _collateralQuantityUnits);
uint256 notionalWithdrawnQuantity = _withdrawAndUpdatePositions(_setToken, _collateralQuantityUnits);
emit CollateralWithdrawn(_setToken, collateralToken, notionalWithdrawnQuantity);
emit CollateralWithdrawn(_setToken, collateralToken, notionalWithdrawnQuantity);
}
}
/**
/**
* @dev MANAGER ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* @dev MANAGER ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* position mappings associated with SetToken.
* position mappings associated with SetToken.
*
*
* NOTE: Function will revert if there is greater than a position unit amount of USDC of account value.
* NOTE: Function will revert if there is greater than a position unit amount of USDC of account value.
*/
*/
function removeModule() public virtual override onlyValidAndInitializedSet(ISetToken(msg.sender)) {
function removeModule() public virtual override onlyValidAndInitializedSet(ISetToken(msg.sender)) {
ISetToken setToken = ISetToken(msg.sender);
ISetToken setToken = ISetToken(msg.sender);
// Check that there is less than 1 position unit of USDC of account value (to tolerate PRECISE_UNIT math rounding errors).
// Check that there is less than 1 position unit of USDC of account value (to tolerate PRECISE_UNIT math rounding errors).
// Account value is checked here because liquidation may result in a positive vault balance while net value is below zero.
// Account value is checked here because liquidation may result in a positive vault balance while net value is below zero.
int256 accountValueUnit = perpClearingHouse.getAccountValue(address(setToken)).preciseDiv(setToken.totalSupply().toInt256());
int256 accountValueUnit = perpClearingHouse.getAccountValue(address(setToken)).preciseDiv(setToken.totalSupply().toInt256());
require(
require(
accountValueUnit.fromPreciseUnitToDecimals(collateralDecimals) <= 1,
accountValueUnit.fromPreciseUnitToDecimals(collateralDecimals) <= 1,
"Account balance exists"
"Account balance exists"
);
);
// `positions[setToken]` mapping stores an array of addresses. The base token addresses are removed from the array when the
// `positions[setToken]` mapping stores an array of addresses. The base token addresses are removed from the array when the
// corresponding base token positions are zeroed out. Since no positions exist when removing the module, the stored array should
// corresponding base token positions are zeroed out. Since no positions exist when removing the module, the stored array should
// already be empty, and the mapping can be deleted directly.
// already be empty, and the mapping can be deleted directly.
delete positions[setToken];
delete positions[setToken];
// Try if unregister exists on any of the modules
// Try if unregister exists on any of the modules
address[] memory modules = setToken.getModules();
address[] memory modules = setToken.getModules();
for(uint256 i = 0; i < modules.length; i++) {
for(uint256 i = 0; i < modules.length; i++) {
try IDebtIssuanceModule(modules[i]).unregisterFromIssuanceModule(setToken) {} catch {}
try IDebtIssuanceModule(modules[i]).unregisterFromIssuanceModule(setToken) {} catch {}
}
}
}
}
/**
/**
* @dev MANAGER ONLY: Add registration of this module on the debt issuance module for the SetToken.
* @dev MANAGER ONLY: Add registration of this module on the debt issuance module for the SetToken.
*
*
* Note: if the debt issuance module is not added to SetToken before this module is initialized, then
* Note: if the debt issuance module is not added to SetToken before this module is initialized, then
* this function needs to be called if the debt issuance module is later added and initialized to prevent state
* this function needs to be called if the debt issuance module is later added and initialized to prevent state
* inconsistencies
* inconsistencies
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _debtIssuanceModule Debt issuance module address to register
* @param _debtIssuanceModule Debt issuance module address to register
*/
*/
function registerToModule(ISetToken _setToken, IDebtIssuanceModule _debtIssuanceModule) external onlyManagerAndValidSet(_setToken) {
function registerToModule(ISetToken _setToken, IDebtIssuanceModule _debtIssuanceModule) external onlyManagerAndValidSet(_setToken) {
require(_setToken.isInitializedModule(address(_debtIssuanceModule)), "Issuance not initialized");
require(_setToken.isInitializedModule(address(_debtIssuanceModule)), "Issuance not initialized");
_debtIssuanceModule.registerToIssuanceModule(_setToken);
_debtIssuanceModule.registerToIssuanceModule(_setToken);
}
}
/**
/**
* @dev MODULE ONLY: Hook called prior to issuance. Only callable by valid module. Should only be called ONCE
* @dev MODULE ONLY: Hook called prior to issuance. Only callable by valid module. Should only be called ONCE
* during issue. Trades into current positions and sets the collateralToken's externalPositionUnit so that
* during issue. Trades into current positions and sets the collateralToken's externalPositionUnit so that
* issuance module can transfer in the right amount of collateral accounting for accrued fees/pnl and slippage
* issuance module can transfer in the right amount of collateral accounting for accrued fees/pnl and slippage
* incurred during issuance. Any pending funding payments and accrued owedRealizedPnl are attributed to current
* incurred during issuance. Any pending funding payments and accrued owedRealizedPnl are attributed to current
* Set holders.
* Set holders.
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _setTokenQuantity Quantity of Set to issue
* @param _setTokenQuantity Quantity of Set to issue
*/
*/
function moduleIssueHook(
function moduleIssueHook(
ISetToken _setToken,
ISetToken _setToken,
uint256 _setTokenQuantity
uint256 _setTokenQuantity
)
)
public
public
virtual
virtual
override
override
onlyModule(_setToken)
onlyModule(_setToken)
{
{
if (_setToken.totalSupply() == 0) return;
if (_setToken.totalSupply() == 0) return;
if (!_setToken.hasExternalPosition(address(collateralToken))) return;
if (!_setToken.hasExternalPosition(address(collateralToken))) return;
int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, true, false);
int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, true, false);
// Set collateralToken externalPositionUnit such that DIM can use it for transfer calculation
// Set collateralToken externalPositionUnit such that DIM can use it for transfer calculation
_setToken.editExternalPositionUnit(
_setToken.editExternalPositionUnit(
address(collateralToken),
address(collateralToken),
address(this),
address(this),
newExternalPositionUnit
newExternalPositionUnit
);
);
}
}
/**
/**
* @dev MODULE ONLY: Hook called prior to redemption in the
* @dev MODULE ONLY: Hook called prior to redemption in the issuance module. Trades out of existing
* positions to make redemption capital withdrawable from PerpV2 vault. Sets the `externalPositionUnit`
* equal to the realizable value of account in position units (as measured by the trade outcomes for
* this redemption). Any `owedRealizedPnl` and pending funding payments are socialized in this step so
* that redeemer pays/receives their share of them. Should only be called ONCE during redeem.
*
* @param _setToken Instance of the SetToken
* @param _setTokenQuantity Quantity of SetToken to redeem
*/
functi