Skip to content

Commit 7320f20

Browse files
bweickcgewecke
authored andcommitted
Refactor issue/redeem hooks
1 parent cc15b59 commit 7320f20

File tree

1 file changed

+87
-157
lines changed

1 file changed

+87
-157
lines changed

contracts/protocol/modules/PerpV2LeverageModule.sol

Lines changed: 87 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)