@@ -469,7 +469,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
469469 {
470470 if (_setToken.totalSupply () == 0 ) return ;
471471
472- int256 newExternalPositionUnit = _executeModuleIssuanceHook (_setToken, _setTokenQuantity, false );
472+ int256 newExternalPositionUnit = _executePositionTrades (_setToken, _setTokenQuantity, true , false );
473473
474474 // Set collateralToken externalPositionUnit such that DIM can use it for transfer calculation
475475 _setToken.editExternalPositionUnit (
@@ -499,7 +499,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
499499 {
500500 if (_setToken.totalSupply () == 0 ) return ;
501501
502- int256 newExternalPositionUnit = _executeModuleRedemptionHook (_setToken, _setTokenQuantity, false );
502+ int256 newExternalPositionUnit = _executePositionTrades (_setToken, _setTokenQuantity, false , false );
503503
504504 // Set USDC externalPositionUnit such that DIM can use it for transfer calculation
505505 _setToken.editExternalPositionUnit (
@@ -586,7 +586,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
586586 address [] memory components = _setToken.getComponents ();
587587
588588 if (positions[_setToken].length > 0 ) {
589- int256 newExternalPositionUnit = _executeModuleIssuanceHook (_setToken, _setTokenQuantity, true );
589+ int256 newExternalPositionUnit = _executePositionTrades (_setToken, _setTokenQuantity, true , true );
590590 return _formatAdjustments (_setToken, components, newExternalPositionUnit);
591591 } else {
592592 return _formatAdjustments (_setToken, components, 0 );
@@ -612,7 +612,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
612612 address [] memory components = _setToken.getComponents ();
613613
614614 if (positions[_setToken].length > 0 ) {
615- int256 newExternalPositionUnit = _executeModuleRedemptionHook (_setToken, _setTokenQuantity, true );
615+ int256 newExternalPositionUnit = _executePositionTrades (_setToken, _setTokenQuantity, false , true );
616616 return _formatAdjustments (_setToken, components, newExternalPositionUnit);
617617 } else {
618618 return _formatAdjustments (_setToken, components, 0 );
@@ -725,173 +725,127 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
725725 /* ============ Internal Functions ============ */
726726
727727 /**
728- * @dev MODULE ONLY: Hook called prior to issuance. Only callable by valid module.
729- *
730- * NOTE: OwedRealizedPnl and PendingFunding values can be either positive or negative
731- *
732- * OwedRealizedPnl
733- * ---------------
734- * Accrues when trades execute and result in a profit or loss per the table
735- * below. Each withdrawal zeros out `owedRealizedPnl`, settling it to the vault.
736- *
737- * | -------------------------------------------------- |
738- * | Position Type | AMM Spot Price | Action | Value |
739- * | ------------- | -------------- | ------ | ------- |
740- * | Long | Rises | Sell | Positive |
741- * | Long | Falls | Sell | Negative |
742- * | Short | Rises | Buy | Negative |
743- * | Short | Falls | Buy | Positive |
744- * | -------------------------------------------------- |
745- *
746- *
747- * PendingFunding
748- * --------------
749- * The direction of this flow is determined by the difference between virtual asset UniV3 spot prices and
750- * their parent asset's broader market price (as represented by a Chainlink oracle), per the table below.
751- * Each trade zeroes out `pendingFunding`, settling it to owedRealizedPnl.
752- *
753- * | --------------------------------------- |
754- * | Position Type | Oracle Price | Value |
755- * | ------------- | ------------ | -------- |
756- * | Long | Below AMM | Negative |
757- * | Long | Above AMM | Positive |
758- * | Short | Below AMM | Positive |
759- * | Short | Above AMM | Negative |
760- * | --------------------------------------- |
728+ * @dev MODULE ONLY: Hook called prior to issuance or redemption. Only callable by valid module.
761729 *
762730 * @param _setToken Instance of the SetToken
763731 * @param _setTokenQuantity Quantity of Set to issue
732+ * @param _isIssue If true, invocation is for issuance, redemption otherwise
764733 * @param _isSimulation If true, trading is only simulated (to return issuance adjustments)
765734 */
766- function _executeModuleIssuanceHook (
735+ function _executePositionTrades (
767736 ISetToken _setToken ,
768737 uint256 _setTokenQuantity ,
738+ bool _isIssue ,
769739 bool _isSimulation
770740 )
771741 internal
772742 returns (int256 )
773743 {
774- // From perp:
775- // accountValue = collateral <---
776- // + owedRealizedPnl } totalCollateralValue
777- // + pendingFundingPayment <---
778- // + sum_over_market(positionValue_market)
779- // + netQuoteBalance
780-
781- AccountInfo memory accountInfo = getAccountInfo (_setToken);
744+ int256 setTokenQuantityInt = _setTokenQuantity.toInt256 ();
782745
783- int256 usdcAmountIn = accountInfo.collateralBalance
784- .add (accountInfo.owedRealizedPnl)
785- .add (accountInfo.pendingFundingPayments)
786- .add (accountInfo.netQuoteBalance)
787- .preciseDiv (_setToken.totalSupply ().toInt256 ())
788- .preciseMul (_setTokenQuantity.toInt256 ());
746+ // Note: `issued` naming convention used here for brevity. This logic is also run on redemption
747+ // and variable may refer to the value which will be redeemed.
748+ int256 accountValueIssued = _calculatePartialAccountValuePositionUnit (_setToken).preciseMul (setTokenQuantityInt);
789749
790750 PositionUnitInfo[] memory positionInfo = getPositionUnitInfo (_setToken);
791751
792752 for (uint i = 0 ; i < positionInfo.length ; i++ ) {
793- // baseUnit, +ve existing long position, -ve for existing short position
794- int256 baseTradeNotionalQuantity = positionInfo[i].baseUnit.preciseMul (_setTokenQuantity.toInt256 ());
795-
753+ int256 baseTradeNotionalQuantity = positionInfo[i].baseUnit.preciseMul (setTokenQuantityInt);
754+
755+ // When redeeming, we flip the sign of baseTradeNotionalQuantity
756+ // because we are reducing the size of the position,
757+ // e.g selling base when long, buying base when short
758+ // | ------------------------------------------------------------ |
759+ // | Action | Position Type | Sign of baseTradeNotionalQuantity |
760+ // | -------- | ------------ | --------------------------------- |
761+ // | Issue | Long | Positive |
762+ // | Issue | Short | Negative |
763+ // | Redeem | Long | Negative |
764+ // | Redeem | Short | Positive |
765+ // | ------------------------------------------------------------ |
796766 ActionInfo memory actionInfo = _createAndValidateActionInfoNotional (
797767 _setToken,
798768 positionInfo[i].baseToken,
799- baseTradeNotionalQuantity,
769+ _isIssue ? baseTradeNotionalQuantity : baseTradeNotionalQuantity. mul ( - 1 ) ,
800770 0
801771 );
802772
803- int256 spotPrice = getAMMSpotPrice (positionInfo[i].baseToken).toInt256 ();
804-
805- // idealDeltaQuote, +ve for existing long position, -ve for existing short position
806- int256 idealDeltaQuote = baseTradeNotionalQuantity.preciseMul (spotPrice);
807-
808773 // Execute or simulate trade.
809774 // `deltaQuote` is always a positive number
810775 (, uint256 deltaQuote ) = _isSimulation ? _simulateTrade (actionInfo) : _executeTrade (actionInfo);
811776
812- // Calculate slippage quantity as a positive value
813- // When long, trade slippage results in more quote required, deltaQuote > idealDeltaQuote
814- // When short, trade slippage results in less quote receivied, abs(idealDeltaQuote) > abs(deltaQuote)
815- int256 slippageQuantity = baseTradeNotionalQuantity >= 0
816- ? deltaQuote.toInt256 ().sub (idealDeltaQuote)
817- : _abs (idealDeltaQuote).sub (deltaQuote.toInt256 ());
818-
819777 // slippage is borne by the issuer
820- usdcAmountIn = usdcAmountIn.add (idealDeltaQuote).add (slippageQuantity);
778+ accountValueIssued = baseTradeNotionalQuantity >= 0 ? accountValueIssued.add (deltaQuote.toInt256 ()) :
779+ accountValueIssued.sub (deltaQuote.toInt256 ());
821780 }
822781
823782 // Return value in collateral decimals (e.g USDC = 6)
783+ //
784+ // TODO - we need to add a method to preciseMath to handle the int case for this....
785+ //
786+ // Use preciseDivCeil when issuing to ensure we don't under-collateralize due to rounding error
787+ // _isIssue ? accountValueIssued.preciseDivCeil(setTokenQuantityInt) : accountValueIssued.preciseDiv(setTokenQuantityInt),
824788 return _fromPreciseUnitToDecimals (
825- usdcAmountIn .preciseDiv (_setTokenQuantity. toInt256 () ),
789+ accountValueIssued .preciseDiv (setTokenQuantityInt ),
826790 collateralDecimals
827791 );
828792 }
829793
830794 /**
831- * @dev Hook called prior to redemption. Only callable by valid module.
795+ * Calculates the "partial account value" position unit. This is the sum of the vault collateral balance,
796+ * the net quote balance for all positions, and any pending funding or owed realized Pnl balances,
797+ * as a position unit. It forms the base to which traded position values are added during issuance or redemption,
798+ * and to which existing position values are added when calculating the externalPositionUnit.
799+ *
800+ * From perp:
801+ * `accountValue = collateral <---
802+ * + owedRealizedPnl } totalCollateralValue
803+ * + pendingFundingPayment <---
804+ * + sum_over_market(positionValue_market)
805+ * + netQuoteBalance
806+ *
807+ * NOTE: OwedRealizedPnl and PendingFunding values can be either positive or negative
808+ *
809+ * OwedRealizedPnl
810+ * ---------------
811+ * Accrues when trades execute and result in a profit or loss per the table
812+ * below. Each withdrawal zeros out `owedRealizedPnl`, settling it to the vault.
813+ *
814+ * | -------------------------------------------------- |
815+ * | Position Type | AMM Spot Price | Action | Value |
816+ * | ------------- | -------------- | ------ | ------- |
817+ * | Long | Rises | Sell | Positive |
818+ * | Long | Falls | Sell | Negative |
819+ * | Short | Rises | Buy | Negative |
820+ * | Short | Falls | Buy | Positive |
821+ * | -------------------------------------------------- |
822+ *
823+ *
824+ * PendingFunding
825+ * --------------
826+ * The direction of this flow is determined by the difference between virtual asset UniV3 spot prices and
827+ * their parent asset's broader market price (as represented by a Chainlink oracle), per the table below.
828+ * Each trade zeroes out `pendingFunding`, settling it to owedRealizedPnl.
829+ *
830+ * | --------------------------------------- |
831+ * | Position Type | Oracle Price | Value |
832+ * | ------------- | ------------ | -------- |
833+ * | Long | Below AMM | Negative |
834+ * | Long | Above AMM | Positive |
835+ * | Short | Below AMM | Positive |
836+ * | Short | Above AMM | Negative |
837+ * | --------------------------------------- |
838+ *
832839 * @param _setToken Instance of the SetToken
833- * @param _setTokenQuantity Quantity of Set to redeem
834- * @param _isSimulation If true, trading is only simulated (to return issuance adjustments)
835840 */
836- function _executeModuleRedemptionHook (
837- ISetToken _setToken ,
838- uint256 _setTokenQuantity ,
839- bool _isSimulation
840- )
841- internal
842- returns (int256 )
843- {
844- int256 realizedPnl = 0 ;
845-
846- PositionNotionalInfo[] memory positionInfo = getPositionNotionalInfo (_setToken);
841+ function _calculatePartialAccountValuePositionUnit (ISetToken _setToken ) internal view returns (int256 accountValue ) {
847842 AccountInfo memory accountInfo = getAccountInfo (_setToken);
848843
849- // Calculate already accrued PnL from non-issuance/redemption sources (ex: levering)
850- int256 totalFundingAndCarriedPnL = accountInfo.pendingFundingPayments.add (accountInfo.owedRealizedPnl);
851- int256 owedRealizedPnlPositionUnit = totalFundingAndCarriedPnL.preciseDiv (_setToken.totalSupply ().toInt256 ());
852-
853- for (uint256 i = 0 ; i < positionInfo.length ; i++ ) {
854- // Calculate amount to trade
855- int256 basePositionUnit = positionInfo[i].baseBalance.preciseDiv (_setToken.totalSupply ().toInt256 ());
856- int256 baseTradeNotionalQuantity = basePositionUnit.preciseMul (_setTokenQuantity.toInt256 ());
857-
858- // Calculate amount quote debt will be reduced by
859- int256 reducedOpenNotional = _getReducedOpenNotional (
860- _setTokenQuantity.toInt256 (),
861- basePositionUnit,
862- positionInfo[i]
863- );
864-
865- // Trade, inverting notional quantity sign because we are reducing position
866- ActionInfo memory actionInfo = _createAndValidateActionInfoNotional (
867- _setToken,
868- positionInfo[i].baseToken,
869- baseTradeNotionalQuantity.mul (- 1 ),
870- 0
871- );
872-
873- // Execute or simulate trade.
874- // `deltaQuote` is always a positive number
875- (,uint256 deltaQuote ) = _isSimulation ? _simulateTrade (actionInfo) : _executeTrade (actionInfo);
876-
877- // Calculate realized PnL for and add to running total.
878- // When basePositionUnit is positive, position is long.
879- realizedPnl = basePositionUnit >= 0 ? realizedPnl.add (reducedOpenNotional.add (deltaQuote.toInt256 ())) :
880- realizedPnl.add (reducedOpenNotional.sub (deltaQuote.toInt256 ()));
881- }
882-
883- // Calculate amount of collateral to withdraw
884- int256 collateralPositionUnit = _getCollateralBalance (_setToken).preciseDiv (_setToken.totalSupply ().toInt256 ());
885-
886- int256 usdcToWithdraw =
887- collateralPositionUnit.preciseMul (_setTokenQuantity.toInt256 ())
888- .add (owedRealizedPnlPositionUnit.preciseMul (_setTokenQuantity.toInt256 ()))
889- .add (realizedPnl);
890-
891- return _fromPreciseUnitToDecimals (
892- usdcToWithdraw.preciseDiv (_setTokenQuantity.toInt256 ()),
893- collateralDecimals
894- );
844+ accountValue = accountInfo.collateralBalance
845+ .add (accountInfo.owedRealizedPnl)
846+ .add (accountInfo.pendingFundingPayments)
847+ .add (accountInfo.netQuoteBalance)
848+ .preciseDiv (_setToken.totalSupply ().toInt256 ());
895849 }
896850
897851 /**
@@ -1120,7 +1074,7 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
11201074 returns (ActionInfo memory )
11211075 {
11221076 // NOT checking that _baseTokenQuantity != 0 here because for places this is directly called
1123- // (issue/redeem hooks) we know they position cannot be 0. We check in _createAndValidateActionInfo
1077+ // (issue/redeem hooks) we know the position cannot be 0. We check in _createAndValidateActionInfo
11241078 // that quantity is 0 for inputs to trade.
11251079 bool isShort = _baseTokenQuantity < 0 ;
11261080
@@ -1149,32 +1103,12 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
11491103 }
11501104 }
11511105
1152- /**
1153- * @dev Gets the ratio by which redemption will reduce open notional quote balance. This value
1154- * is used to calculate realizedPnl of the asset sale in _executeModuleRedeemHook
1155- */
1156- function _getReducedOpenNotional (
1157- int256 _setTokenQuantity ,
1158- int256 _basePositionUnit ,
1159- PositionNotionalInfo memory _positionInfo
1160- )
1161- internal
1162- pure
1163- returns (int256 )
1164- {
1165- // From perp formulas: closeRatio = abs(baseTradeNotional) / abs(baseBalance)
1166- int256 baseTradeNotionalQuantity = _setTokenQuantity.preciseMul (_basePositionUnit);
1167- int256 closeRatio = _abs (baseTradeNotionalQuantity).preciseDiv (_abs (_positionInfo.baseBalance));
1168- return _positionInfo.quoteBalance.preciseMul (closeRatio);
1169- }
1170-
11711106 /**
11721107 * @dev Calculates the sum of collateralToken denominated market-prices of assets and debt for the Perp account per
11731108 * SetToken
11741109 */
11751110 function _calculateExternalPositionUnit (ISetToken _setToken ) internal view returns (int256 ) {
11761111 PositionNotionalInfo[] memory positionInfo = getPositionNotionalInfo (_setToken);
1177- AccountInfo memory accountInfo = getAccountInfo (_setToken);
11781112 int256 totalPositionValue = 0 ;
11791113
11801114 for (uint i = 0 ; i < positionInfo.length ; i++ ) {
@@ -1184,12 +1118,8 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, IModuleIs
11841118 );
11851119 }
11861120
1187- int256 externalPositionUnitInPrecisionDecimals = totalPositionValue
1188- .add (accountInfo.collateralBalance)
1189- .add (accountInfo.netQuoteBalance)
1190- .add (accountInfo.owedRealizedPnl)
1191- .add (accountInfo.pendingFundingPayments)
1192- .preciseDiv (_setToken.totalSupply ().toInt256 ());
1121+ int256 externalPositionUnitInPrecisionDecimals = _calculatePartialAccountValuePositionUnit (_setToken)
1122+ .add (totalPositionValue.preciseDiv (_setToken.totalSupply ().toInt256 ()));
11931123
11941124 return _fromPreciseUnitToDecimals (
11951125 externalPositionUnitInPrecisionDecimals,
0 commit comments