PerpV2BasistTradingModule master branch <> PerpV2BasisTradingModule latest

Created Diff never expires
34 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
532 lines
32 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
530 lines
/*
/*
Copyright 2022 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 { IController } from "../../interfaces/IController.sol";
import { IController } from "../../interfaces/IController.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IMarketRegistry } from "../../interfaces/external/perp-v2/IMarketRegistry.sol";
import { IMarketRegistry } from "../../interfaces/external/perp-v2/IMarketRegistry.sol";
import { Invoke } from "../lib/Invoke.sol";
import { Invoke } from "../lib/Invoke.sol";
import { IQuoter } from "../../interfaces/external/perp-v2/IQuoter.sol";
import { IQuoter } from "../../interfaces/external/perp-v2/IQuoter.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { ISetToken } from "../../interfaces/ISetToken.sol";
import { IVault } from "../../interfaces/external/perp-v2/IVault.sol";
import { IVault } from "../../interfaces/external/perp-v2/IVault.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { ModuleBase } from "../lib/ModuleBase.sol";
import { PerpV2 } from "../integration/lib/PerpV2.sol";
import { PerpV2LeverageModuleV2 } from "./PerpV2LeverageModuleV2.sol";
import { PerpV2LeverageModule } from "./PerpV2LeverageModule.sol";
import { Position } from "../lib/Position.sol";
import { Position } from "../lib/Position.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";


/**
/**
* @title PerpV2BasisTradingModule
* @title PerpV2BasisTradingModule
* @author Set Protocol
* @author Set Protocol
*
*
* @notice Smart contract that extends functionality offered by PerpV2LeverageModule. It tracks funding that is settled due to
* @notice Smart contract that extends functionality offered by PerpV2LeverageModuleV2. It tracks funding that is settled due to
* actions on Perpetual protocol and allows it to be withdrawn by the manager. The withdrawn funding can be reinvested in the Set
* actions on Perpetual protocol and allows it to be withdrawn by the manager. The withdrawn funding can be reinvested in the Set
* to create a yield generating basis trading product. The manager can also collect performance fees on the withdrawn funding.
* to create a yield generating basis trading product. The manager can also collect performance fees on the withdrawn funding.
*
*
* 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.
*/
*/
contract PerpV2BasisTradingModule is PerpV2LeverageModule {
contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 {


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


struct FeeState {
struct FeeState {
address feeRecipient; // Address to accrue fees to
address feeRecipient; // Address to accrue fees to
uint256 maxPerformanceFeePercentage; // Max performance fee manager commits to using (1% = 1e16, 100% = 1e18)
uint256 maxPerformanceFeePercentage; // Max performance fee manager commits to using (1% = 1e16, 100% = 1e18)
uint256 performanceFeePercentage; // Performance fees accrued to manager (1% = 1e16, 100% = 1e18)
uint256 performanceFeePercentage; // Performance fees accrued to manager (1% = 1e16, 100% = 1e18)
}
}


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


/**
/**
* @dev Emitted on performance fee update
* @dev Emitted on performance fee update
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _newPerformanceFee New performance fee percentage (1% = 1e16)
* @param _newPerformanceFee New performance fee percentage (1% = 1e16)
*/
*/
event PerformanceFeeUpdated(ISetToken indexed _setToken, uint256 _newPerformanceFee);
event PerformanceFeeUpdated(ISetToken indexed _setToken, uint256 _newPerformanceFee);


/**
/**
* @dev Emitted on fee recipient update
* @dev Emitted on fee recipient update
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _newFeeRecipient New performance fee recipient
* @param _newFeeRecipient New performance fee recipient
*/
*/
event FeeRecipientUpdated(ISetToken indexed _setToken, address _newFeeRecipient);
event FeeRecipientUpdated(ISetToken indexed _setToken, address _newFeeRecipient);


/**
/**
* @dev Emitted on funding withdraw
* @dev Emitted on funding withdraw
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _collateralToken Token being withdrawn as funding (USDC)
* @param _collateralToken Token being withdrawn as funding (USDC)
* @param _amountWithdrawn Amount of funding being withdrawn from Perp (USDC)
* @param _amountWithdrawn Amount of funding being withdrawn from Perp (USDC)
* @param _managerFee Amount of performance fee accrued to manager (USDC)
* @param _managerFee Amount of performance fee accrued to manager (USDC)
* @param _protocolFee Amount of performance fee accrued to protocol (USDC)
* @param _protocolFee Amount of performance fee accrued to protocol (USDC)
*/
*/
event FundingWithdrawn(
event FundingWithdrawn(
ISetToken indexed _setToken,
ISetToken indexed _setToken,
IERC20 _collateralToken,
IERC20 _collateralToken,
uint256 _amountWithdrawn,
uint256 _amountWithdrawn,
uint256 _managerFee,
uint256 _managerFee,
uint256 _protocolFee
uint256 _protocolFee
);
);


/* ============ Constants ============ */
/* ============ Constants ============ */


// 1 index stores protocol performance fee % on the controller, charged in the _handleFees function
// 1 index stores protocol performance fee % on the controller, charged in the _handleFees function
uint256 private constant PROTOCOL_PERFORMANCE_FEE_INDEX = 1;
uint256 private constant PROTOCOL_PERFORMANCE_FEE_INDEX = 1;


/* ============ State Variables ============ */
/* ============ State Variables ============ */


// Mapping to store fee settings for each SetToken
// Mapping to store fee settings for each SetToken
mapping(ISetToken => FeeState) public feeSettings;
mapping(ISetToken => FeeState) public feeSettings;


// Mapping to store funding that has been settled on Perpetual Protocol due to actions via this module
// Mapping to store funding that has been settled on Perpetual Protocol due to actions via this module
// and hasn't been withdrawn for reinvesting yet. Values are stored in precise units (10e18).
// and hasn't been withdrawn for reinvesting yet. Values are stored in precise units (10e18).
mapping(ISetToken => uint256) public settledFunding;
mapping(ISetToken => uint256) public settledFunding;


/* ============ 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
* @param _maxPerpPositionsPerSet Max perpetual positions in one SetToken
* @param _maxPerpPositionsPerSet Max perpetual positions in one SetToken
*/
*/
constructor(
constructor(
IController _controller,
IController _controller,
IVault _perpVault,
IVault _perpVault,
IQuoter _perpQuoter,
IQuoter _perpQuoter,
IMarketRegistry _perpMarketRegistry,
IMarketRegistry _perpMarketRegistry,
uint256 _maxPerpPositionsPerSet
uint256 _maxPerpPositionsPerSet
)
)
public
public
PerpV2LeverageModule(
PerpV2LeverageModuleV2(
_controller,
_controller,
_perpVault,
_perpVault,
_perpQuoter,
_perpQuoter,
_perpMarketRegistry,
_perpMarketRegistry,
_maxPerpPositionsPerSet
_maxPerpPositionsPerSet
)
)
{}
{}


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


/**
/**
* @dev MANAGER ONLY: Initializes this module to the SetToken and sets fee settings. Either the SetToken needs to
* @dev MANAGER ONLY: Initializes this module to the SetToken and sets fee settings. Either the SetToken needs to
* be on the allowed list or anySetAllowed needs to be true.
* be on the 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,
FeeState memory _settings
FeeState memory _settings
)
)
external
external
{
{
_validateFeeState(_settings);
_validateFeeState(_settings);


// Initialize by calling PerpV2LeverageModule#initialize.
// Initialize by calling PerpV2LeverageModuleV2#initialize.
// Verifies caller is manager. Verifies Set is valid, allowed and in pending state.
// Verifies caller is manager. Verifies Set is valid, allowed and in pending state.
PerpV2LeverageModule.initialize(_setToken);
PerpV2LeverageModuleV2.initialize(_setToken);


feeSettings[_setToken] = _settings;
feeSettings[_setToken] = _settings;
}
}


/**
/**
* @dev MANAGER ONLY: Similar to PerpV2LeverageModule#trade. Allows manager to buy or sell perps to change exposure
* @dev MANAGER ONLY: Similar to PerpV2LeverageModuleV2#trade. Allows manager to buy or sell perps to change exposure
* to the underlying baseToken. Any pending funding that would be settled during opening a position on Perpetual
* to the underlying baseToken. Any pending funding that would be settled during opening a position on Perpetual
* protocol is added to (or subtracted from) `settledFunding[_setToken]` and can be withdrawn later by the
* protocol is added to (or subtracted from) `settledFunding[_setToken]` and can be withdrawn later by the
* SetToken manager.
* SetToken manager.
* NOTE: Calling a `nonReentrant` function from another `nonReentrant` function is not supported. Hence, we can't
* NOTE: Calling a `nonReentrant` function from another `nonReentrant` function is not supported. Hence, we can't
* add the `nonReentrant` modifier here because `PerpV2LeverageModule#trade` function has a reentrancy check.
* add the `nonReentrant` modifier here because `PerpV2LeverageModuleV2#trade` function has a reentrancy check.
* 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.
*
*
* @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 tradeAndTrackFunding(
function tradeAndTrackFunding(
ISetToken _setToken,
ISetToken _setToken,
address _baseToken,
address _baseToken,
int256 _baseQuantityUnits,
int256 _baseQuantityUnits,
uint256 _quoteBoundQuantityUnits
uint256 _quoteBoundQuantityUnits
)
)
external
external
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
// Track funding before it is settled
// Track funding before it is settled
_updateSettledFunding(_setToken);
_updateSettledFunding(_setToken);


// Trade using PerpV2LeverageModule#trade.
// Trade using PerpV2LeverageModuleV2#trade.
PerpV2LeverageModule.trade(
PerpV2LeverageModuleV2.trade(
_setToken,
_setToken,
_baseToken,
_baseToken,
_baseQuantityUnits,
_baseQuantityUnits,
_quoteBoundQuantityUnits
_quoteBoundQuantityUnits
);
);
}
}


/**
/**
* @dev MANAGER ONLY: Withdraws tracked settled funding (in USDC) from the PerpV2 Vault to a default position
* @dev MANAGER ONLY: Withdraws tracked settled funding (in USDC) from the PerpV2 Vault to a default position
* on the SetToken. Collects manager and protocol performance fees on the withdrawn amount.
* on the SetToken. Collects manager and protocol performance fees on the withdrawn amount.
* This method is useful when withdrawing funding to be reinvested into the Basis Trading product.
* This method is useful when withdrawing funding to be reinvested into the Basis Trading product.
*
*
* 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 _notionalFunding Notional amount of funding to withdraw (in USDC decimals)
* @param _notionalFunding Notional amount of funding to withdraw (in USDC decimals)
*/
*/
function withdrawFundingAndAccrueFees(
function withdrawFundingAndAccrueFees(
ISetToken _setToken,
ISetToken _setToken,
uint256 _notionalFunding
uint256 _notionalFunding
)
)
external
external
nonReentrant
nonReentrant
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
_updateSettledFunding(_setToken);
_updateSettledFunding(_setToken);


uint256 settledFunding = settledFunding[_setToken].fromPreciseUnitToDecimals(collateralDecimals);
uint256 settledFundingInCollateralDecimals = settledFunding[_setToken].fromPreciseUnitToDecimals(collateralDecimals);


if (_notionalFunding > settledFunding) { _notionalFunding = settledFunding; }
if (_notionalFunding > settledFundingInCollateralDecimals) { _notionalFunding = settledFundingInCollateralDecimals; }


uint256 collateralBalanceBeforeWithdraw = collateralToken.balanceOf(address(_setToken));
uint256 collateralBalanceBeforeWithdraw = collateralToken.balanceOf(address(_setToken));


_withdraw(_setToken, _notionalFunding);
_withdraw(_setToken, _notionalFunding);


(uint256 managerFee, uint256 protocolFee) = _handleFees(_setToken, _notionalFunding);
(uint256 managerFee, uint256 protocolFee) = _handleFees(_setToken, _notionalFunding);
_updateWithdrawFundingState(_setToken, _notionalFunding, collateralBalanceBeforeWithdraw);
_updateWithdrawFundingState(_setToken, _notionalFunding, collateralBalanceBeforeWithdraw);


emit FundingWithdrawn(_setToken, collateralToken, _notionalFunding, managerFee, protocolFee);
emit FundingWithdrawn(_setToken, collateralToken, _notionalFunding, managerFee, protocolFee);
}
}


/**
/**
* @dev SETTOKEN ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* @dev SETTOKEN ONLY: Removes this module from the SetToken, via call by the SetToken. Deletes
* position mappings and fee states associated with SetToken. Resets settled funding to zero.
* position mappings and fee states associated with SetToken. Resets settled funding to zero.
* Fees are not accrued in case reason for removing module is related to fee accrual.
* Fees are not accrued in case reason for removing module is related to fee accrual.
*
*
* 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 override(PerpV2LeverageModule) {
function removeModule() public override(PerpV2LeverageModuleV2) {
// Call PerpV2LeverageModule#removeModule to delete positions mapping and unregister on other modules.
// Call PerpV2LeverageModuleV2#removeModule to delete positions mapping and unregister on other modules.
// Verifies Set is valid and initialized.
// Verifies Set is valid and initialized.
PerpV2LeverageModule.removeModule();
PerpV2LeverageModuleV2.removeModule();


ISetToken setToken = ISetToken(msg.sender);
ISetToken setToken = ISetToken(msg.sender);
// Not charging any fees
// Not charging any fees
delete feeSettings[setToken];
delete feeSettings[setToken];
delete settledFunding[setToken];
delete settledFunding[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. Any pending funding payment that would be settled during trading into positions on Perpetual
* Set holders. Any pending funding payment that would be settled during trading into positions on Perpetual
* protocol is added to (or subtracted from) `settledFunding[_setToken]` and can be withdrawn later by the manager.
* protocol is added to (or subtracted from) `settledFunding[_setToken]` and can be withdrawn later by the manager.
*
*
* @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
override(PerpV2LeverageModule)
override(PerpV2LeverageModuleV2)
{
{
// Track funding before it is settled
// Track funding before it is settled
_updateSettledFunding(_setToken);
_updateSettledFunding(_setToken);
// Call PerpV2LeverageModule#moduleIssueHook to set external position unit.
// Call PerpV2LeverageModuleV2#moduleIssueHook to set external position unit.
// Validates caller is module.
// Validates caller is module.
PerpV2LeverageModule.moduleIssueHook(_setToken, _setTokenQuantity);
PerpV2LeverageModuleV2.moduleIssueHook(_setToken, _setTokenQuantity);
}
}


/**
/**
* @dev MODULE ONLY: Hook called prior to redemption in the issuance module. Trades out of existing
* @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`
* 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
* equal to the realizable value of account in position units (as measured by the trade outcomes for
* this redemption) net performance fees to be paid by the redeemer for his share of positive funding yield.
* this redemption) net performance fees to be paid by the redeemer for his share of positive funding yield.
* Any `owedRealizedPnl` and pending funding payments are socialized in this step so that redeemer
* 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. Any pending funding payment
* pays/receives their share of them. Should only be called ONCE during redeem. Any pending funding payment
* that would be settled during trading out of positions on Perpetual protocol is added to (or subtracted from)
* that would be settled during trading out of positions on Perpetual protocol is added to (or subtracted from)
* `settledFunding[_setToken]` and can be withdrawn later by the manager.
* `settledFunding[_setToken]` and can be withdrawn later by the manager.
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _setTokenQuantity Quantity of SetToken to redeem
* @param _setTokenQuantity Quantity of SetToken to redeem
*/
*/
function moduleRedeemHook(
function moduleRedeemHook(
ISetToken _setToken,
ISetToken _setToken,
uint256 _setTokenQuantity
uint256 _setTokenQuantity
)
)
external
external
override(PerpV2LeverageModule)
override(PerpV2LeverageModuleV2)
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;


// Track funding before it is settled
// Track funding before it is settled
_updateSettledFunding(_setToken);
_updateSettledFunding(_setToken);


int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, false);
int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, false);
if (settledFunding[_setToken] > 0) {
if (settledFunding[_setToken] > 0) {
// Calculate performance fee unit
// Calculate performance fee unit
// Performance fee unit = (Tracked settled funding * Performance fee) / Set total supply
// Performance fee unit = (Tracked settled funding * Performance fee) / Set total supply
uint256 performanceFeeUnit = settledFunding[_setToken]
uint256 performanceFeeUnit = settledFunding[_setToken]
.preciseDiv(_setToken.totalSupply())
.preciseDiv(_setToken.totalSupply())
.preciseMulCeil(_performanceFeePercentage(_setToken))
.preciseMulCeil(_performanceFeePercentage(_setToken))
.fromPreciseUnitToDecimals(collateralDecimals);
.fromPreciseUnitToDecimals(collateralDecimals);
// Subtract performance fee unit from calculated external position unit
// Subtract performance fee unit from calculated external position unit
// Issuance module calculates equity amount to be transferred out using,
// Issuance module calculates equity amount to be transferred out using,
// equity amount = (newExternalPositionUnit - performanceFeeUnit) * _setTokenQuantity
// equity amount = (newExternalPositionUnit - performanceFeeUnit) * _setTokenQuantity
// where, `performanceFeeUnit * _setTokenQuantity` is share of the total performance fee to
// where, `performanceFeeUnit * _setTokenQuantity` is share of the total performance fee to
// be paid by the redeemer
// be paid by the redeemer
newExternalPositionUnit = newExternalPositionUnit.sub(performanceFeeUnit.toInt256());
newExternalPositionUnit = newExternalPositionUnit.sub(performanceFeeUnit.toInt256());
}
}


// Set USDC externalPositionUnit such that DIM can use it for transfer calculation
// Set USDC externalPositionUnit such that DIM can use it for transfer calculation
_setToken.editExternalPositionUnit(
_setToken.editExternalPositionUnit(
address(collateralToken),
address(collateralToken),
address(this),
address(this),
newExternalPositionUnit
newExternalPositionUnit
);
);
}
}


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


/**
/**
* @dev MANAGER ONLY. Update performance fee percentage.
* @dev MANAGER ONLY. Update performance fee percentage.
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _newFee New performance fee percentage in precise units (1e16 = 1%)
* @param _newFee New performance fee percentage in precise units (1e16 = 1%)
*/
*/
function updatePerformanceFee(
function updatePerformanceFee(
ISetToken _setToken,
ISetToken _setToken,
uint256 _newFee
uint256 _newFee
)
)
external
external
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
require(_newFee < feeSettings[_setToken].maxPerformanceFeePercentage, "Fee must be less than max");
require(_newFee < feeSettings[_setToken].maxPerformanceFeePercentage, "Fee must be less than max");
// We require `settledFunding[_setToken]` to be zero. Hence, we do not call `_updateSettledFunding` here, which
// We require `settledFunding[_setToken]` to be zero. Hence, we do not call `_updateSettledFunding` here, which
// eases the UX of updating performance fees for the manager. Although, manager loses the ability to collect fees
// eases the UX of updating performance fees for the manager. Although, manager loses the ability to collect fees
// on pending funding that has been accrued on PerpV2 but not tracked on this module.
// on pending funding that has been accrued on PerpV2 but not tracked on this module.
// Assert all settled funding (in USD) has been withdrawn. Comparing USD amount allows us to neglect small
// Assert all settled funding (in USD) has been withdrawn. Comparing USD amount allows us to neglect small
// dust amounts that aren't withdrawable.
// dust amounts that aren't withdrawable.
require(
require(
settledFunding[_setToken].fromPreciseUnitToDecimals(collateralDecimals) == 0,
settledFunding[_setToken].fromPreciseUnitToDecimals(collateralDecimals) == 0,
"Non-zero settled funding remains"
"Non-zero settled funding remains"
);
);


feeSettings[_setToken].performanceFeePercentage = _newFee;
feeSettings[_setToken].performanceFeePercentage = _newFee;


emit PerformanceFeeUpdated(_setToken, _newFee);
emit PerformanceFeeUpdated(_setToken, _newFee);
}
}


/**
/**
* @dev MANAGER ONLY. Update performance fee recipient (address to which performance fees are sent).
* @dev MANAGER ONLY. Update performance fee recipient (address to which performance fees are sent).
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _newFeeRecipient Address of new fee recipient
* @param _newFeeRecipient Address of new fee recipient
*/
*/
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient)
function updateFeeRecipient(ISetToken _setToken, address _newFeeRecipient)
external
external
onlyManagerAndValidSet(_setToken)
onlyManagerAndValidSet(_setToken)
{
{
require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address");
require(_newFeeRecipient != address(0), "Fee Recipient must be non-zero address");


feeSettings[_setToken].feeRecipient = _newFeeRecipient;
feeSettings[_setToken].feeRecipient = _newFeeRecipient;


emit FeeRecipientUpdated(_setToken, _newFeeRecipient);
emit FeeRecipientUpdated(_setToken, _newFeeRecipient);
}
}




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


/**
/**
* @dev Gets the positive equity collateral externalPositionUnit that would be calculated for
* @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.
* 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.
* Values in the returned arrays map to the same index in the SetToken's components array.
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _setTokenQuantity Number of sets to redeem
* @param _setTokenQuantity Number of sets to redeem
*
*
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
* @return equityAdjustments array containing a single element and an empty debtAdjustments array
*/
*/
function getRedemptionAdjustments(
function getRedemptionAdjustments(
ISetToken _setToken,
ISetToken _setToken,
uint256 _setTokenQuantity
uint256 _setTokenQuantity
)
)
external
external
override(PerpV2LeverageModule)
override(PerpV2LeverageModuleV2)
returns (int256[] memory, int256[] memory _)
returns (int256[] memory, int256[] memory _)
{
{
address[] memory components = _setToken.getComponents();


int256 newExternalPositionUnit = 0;
if (positions[_setToken].length > 0) {
if (positions[_setToken].length > 0) {
int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, true);

// Calculate performance fee unit
// Calculate performance fee unit
// Performance fee unit = (Tracked settled funding * Performance fee) / Set total supply
// Performance fee unit = (Tracked settled funding * Performance fee) / Set total supply
uint256 performanceFeeUnit = _getUpdatedSettledFunding(_setToken)
uint256 performanceFeeUnit = _getUpdatedSettledFunding(_setToken)
.preciseDiv(_setToken.totalSupply())
.preciseDiv(_setToken.totalSupply())
.preciseMulCeil(_performanceFeePercentage(_setToken))
.preciseMulCeil(_performanceFeePercentage(_setToken))
.fromPreciseUnitToDecimals(collateralDecimals);
.fromPreciseUnitToDecimals(collateralDecimals);


int256 partialNewExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, true);
// Subtract performance fee unit from calculated external position unit
// Subtract performance fee unit from calculated external position unit
// Issuance module calculates equity amount to be transferred out using,
// Issuance module calculates equity amount to be transferred out using,
// equity amount = (newExternalPositionUnit - performanceFeeUnit) * _setTokenQuantity
// equity amount = (partialNewExternalPositionUnit - performanceFeeUnit) * _setTokenQuantity
// where, `performanceFeeUnit * _setTokenQuantity` is share of the total performance fee to
// where, `performanceFeeUnit * _setTokenQuantity` is share of the total performance fee to
// be paid by the redeemer
// be paid by the redeemer
int256 netNewExternalPositionUnit = newExternalPositionUnit.sub(performanceFeeUnit.toInt256());
newExternalPositionUnit = partialNewExternalPositionUnit.sub(performanceFeeUnit.toInt256());

return _formatAdjustments(_setToken, components, netNewExternalPositionUnit);
} else {
return _formatAdjustments(_setToken, components, 0);
}
}

return _formatAdjustments(_setToken, newExternalPositionUnit);
}
}


/* ============ Internal Functions ============ */
/* ============ Internal Functions ============ */


/**
/**
* @dev Updates tracked settled funding. Once funding is settled to `owedRealizedPnl` on Perpetual protocol, it is difficult to
* @dev Updates tracked settled funding. Once funding is settled to `owedRealizedPnl` on Perpetual protocol, it is difficult to
* extract out the funding value again on-chain. This function is called in an external function and is used to track and store
* extract out the funding value again on-chain. This function is called in an external function and is used to track and store
* pending funding payment that is about to be settled due to subsequent logic in the external function.
* pending funding payment that is about to be settled due to subsequent logic in the external function.
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
*/
*/
function _updateSettledFunding(ISetToken _setToken) internal {
function _updateSettledFunding(ISetToken _setToken) internal {
settledFunding[_setToken] = _getUpdatedSettledFunding(_setToken);
settledFunding[_setToken] = _getUpdatedSettledFunding(_setToken);
}
}


/**
/**
* @dev Adds pending funding payment to tracked settled funding. Returns updated settled funding value.
* @dev Adds pending funding payment to tracked settled funding. Returns updated settled funding value.
*
*
* NOTE: Tracked settled funding value can not be less than zero, hence it is reset to zero if pending funding
* NOTE: Tracked settled funding value can not be less than zero, hence it is reset to zero if pending funding
* payment is negative and |pending funding payment| >= |settledFunding[_setToken]|.
* payment is negative and |pending funding payment| >= |settledFunding[_setToken]|.
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
*/
*/
function _getUpdatedSettledFunding(ISetToken _setToken) internal returns (uint256) {
function _getUpdatedSettledFunding(ISetToken _setToken) internal view returns (uint256) {
// NOTE: pendingFundingPayments are represented as in the Perp system as "funding owed"
// NOTE: pendingFundingPayments are represented as in the Perp system as "funding owed"
// e.g a positive number is a debt which gets subtracted from owedRealizedPnl on settlement.
// e.g a positive number is a debt which gets subtracted from owedRealizedPnl on settlement.
// We are flipping its sign here to reflect its settlement value.
// We are flipping its sign here to reflect its settlement value.
int256 pendingFundingToBeSettled = perpExchange.getAllPendingFundingPayment(address(_setToken)).neg();
int256 pendingFundingToBeSettled = perpExchange.getAllPendingFundingPayment(address(_setToken)).neg();
if (pendingFundingToBeSettled >= 0) {
if (pendingFundingToBeSettled >= 0) {
return settledFunding[_setToken].add(pendingFundingToBeSettled.toUint256());
return settledFunding[_setToken].add(pendingFundingToBeSettled.toUint256());
}
}


if (settledFunding[_setToken] > pendingFundingToBeSettled.abs()) {
if (settledFunding[_setToken] > pendingFundingToBeSettled.abs()) {
return settledFunding[_setToken].sub(pendingFundingToBeSettled.abs());
return settledFunding[_setToken].sub(pendingFundingToBeSettled.abs());
}
}


return 0;
return 0;
}
}


/**
/**
* @dev Calculates manager and protocol fees on withdranwn funding amount and transfers them to
* @dev Calculates manager and protocol fees on withdranwn funding amount and transfers them to
* their respective recipients (in USDC).
* their respective recipients (in USDC).
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
* @param _amount Notional funding amount on which fees is charged
* @param _amount Notional funding amount on which fees is charged
*
*
* @return managerFee Manager performance fees
* @return managerFee Manager performance fees
* @return protocolFee Protocol performance fees
* @return protocolFee Protocol performance fees
*/
*/
function _handleFees(
function _handleFees(
ISetToken _setToken,
ISetToken _setToken,
uint256 _amount
uint256 _amount
)
)
internal
internal
returns (uint256 managerFee, uint256 protocolFee)
returns (uint256 managerFee, uint256 protocolFee)
{
{
uint256 performanceFee = feeSettings[_setToken].performanceFeePercentage;
uint256 performanceFee = feeSettings[_setToken].performanceFeePercentage;


if (performanceFee > 0) {
if (performanceFee > 0) {
uint256 protocolFeeSplit = controller.getModuleFee(address(this), PROTOCOL_PERFORMANCE_FEE_INDEX);
uint256 protocolFeeSplit = controller.getModuleFee(address(this), PROTOCOL_PERFORMANCE_FEE_INDEX);


uint256 totalFee = performanceFee.preciseMul(_amount);
uint256 totalFee = performanceFee.preciseMul(_amount);
protocolFee = totalFee.preciseMul(protocolFeeSplit);
protocolFee = totalFee.preciseMul(protocolFeeSplit);
managerFee = totalFee.sub(protocolFee);
managerFee = totalFee.sub(protocolFee);


_setToken.strictInvokeTransfer(address(collateralToken), feeSettings[_setToken].feeRecipient, managerFee);
_setToken.strictInvokeTransfer(address(collateralToken), feeSettings[_setToken].feeRecipient, managerFee);
payProtocolFeeFromSetToken(_setToken, address(collateralToken), protocolFee);
payProtocolFeeFromSetToken(_setToken, address(collateralToken), protocolFee);
}
}


return (managerFee, protocolFee);
return (managerFee, protocolFee);
}
}


/**
/**
* @dev Updates collateral token default position unit and tracked settled funding. Used in `withdrawFundingAndAcrrueFees()`.
* @dev Updates collateral token default position unit and tracked settled funding. Used in `withdrawFundingAndAcrrueFees()`.
*
*
* @param _setToken Instance of the SetToken
* @param _setToken Instance of the SetToken
* @param _notionalFunding Amount of funding withdrawn (in USDC decimals)
* @param _notionalFunding Amount of funding withdrawn (in USDC decimals)
* @param _collateralBalanceBeforeWithdraw Balance of collateral token in the Set before withdrawing more USDC from Perp
* @param _collateralBalanceBeforeWithdraw Balance of collateral token in the Set before withdrawing more USDC from Perp
*/
*/
function _updateWithdrawFundingState(ISetToken _setToken, uint256 _notionalFunding, uint256 _collateralBalanceBeforeWithdraw) internal {
function _updateWithdrawFundingState(ISetToken _setToken, uint256 _notionalFunding, uint256 _collateralBalanceBeforeWithdraw) internal {
// Update default position unit to add the withdrawn funding (in USDC)
// Update default position unit to add the withdrawn funding (in USDC)
_setToken.calculateAndEditDefaultPosition(
_setToken.calculateAndEditDefaultPosition(
address(collateralToken),
address(collateralToken),
_setToken.totalSupply(),
_setToken.totalSupply(),
_collateralBalanceBeforeWithdraw
_collateralBalanceBeforeWithdraw
);
);


// Subtract withdrawn funding from tracked settled funding
// Subtract withdrawn funding from tracked settled funding
settledFunding[_setToken] = settledFunding[_setToken].sub(
settledFunding[_setToken] = settledFunding[_setToken].sub(
_notionalFunding.toPreciseUnitsFromDecimals(collateralDecimals)
_notionalFunding.toPreciseUnitsFromDecimals(collateralDecimals)
);
);
}
}


/**
/**
* @dev Validates fee settings.
* @dev Validates fee settings.
*
*
* @param _settings FeeState struct containing performance fee settings
* @param _settings FeeState struct containing performance fee settings
*/
*/
function _validateFeeState(FeeState memory _settings) internal view {
function _validateFeeState(FeeState memory _settings) internal pure {
require(_settings.feeRecipient != address(0), "Fee Recipient must be non-zero address");
require(_settings.feeRecipient != address(0), "Fee Recipient must be non-zero address");
require(_settings.maxPerformanceFeePercentage <= PreciseUnitMath.preciseUnit(), "Max fee must be <= 100%");
require(_settings.maxPerformanceFeePercentage <= PreciseUnitMath.preciseUnit(), "Max fee must be <= 100%");
require(_settings.performanceFeePercentage <= _settings.maxPerformanceFeePercentage, "Fee must be <= max");
require(_settings.performanceFeePercentage <= _settings.maxPerformanceFeePercentage, "Fee must be <= max");
}
}


/**
/**
* @dev Helper function that returns performance fee percentage.
* @dev Helper function that returns performance fee percentage.
*
*
* @param _setToken Instance of SetToken
* @param _setToken Instance of SetToken
*/
*/
function _performanceFeePercentage(ISetToken _setToken) internal view returns (uint256) {
function _performanceFeePercentage(ISetToken _setToken) internal view returns (uint256) {
return feeSettings[_setToken].performanceFeePercentage;
return feeSettings[_setToken].performanceFeePercentage;
}
}


}
}