diff --git a/contracts/protocol/modules/v2/PerpV2LeverageModuleV2.sol b/contracts/protocol/modules/v2/PerpV2LeverageModuleV2.sol index f36a4b07d..11377012d 100644 --- a/contracts/protocol/modules/v2/PerpV2LeverageModuleV2.sol +++ b/contracts/protocol/modules/v2/PerpV2LeverageModuleV2.sol @@ -47,6 +47,7 @@ import { PreciseUnitMath } from "../../../lib/PreciseUnitMath.sol"; import { AddressArrayUtils } from "../../../lib/AddressArrayUtils.sol"; import { UnitConversionUtils } from "../../../lib/UnitConversionUtils.sol"; + /** * @title PerpV2LeverageModuleV2 * @author Set Protocol @@ -1053,25 +1054,28 @@ contract PerpV2LeverageModuleV2 is ModuleBaseV2, ReentrancyGuard, Ownable, SetTo } /** - * @dev Sets the external position unit based on PerpV2 account value. + * @dev Sets the external position unit based on PerpV2 Vault collateral balance. If there more than 1 + * position unit of USDC of account value (allowing for PRECISE UNIT rounding errors) adds the component + * and/or the external position to the SetToken. Else, untracks the component and/or removes external + * position from the SetToken. Refer to PositionV2#editExternalPosition for detailed flow. + * + * NOTE: Setting external position unit to the collateral balance is acceptable because, as noted above, the + * external position unit is only updated on an as-needed basis during issuance, redemption, deposit and + * withdraw. It does not reflect the current value of the Set's perpetual position. The true current value can + * be calculated from getPositionNotionalInfo. * * @param _setToken Instance of SetToken */ function _updateExternalPositionUnit(ISetToken _setToken) internal { - int256 accountValueUnit = perpClearingHouse.getAccountValue(address(_setToken)) + int256 collateralValueUnit = perpVault.getBalance(address(_setToken)) + .toPreciseUnitsFromDecimals(collateralDecimals) .preciseDiv(_setToken.totalSupply().toInt256()) .fromPreciseUnitToDecimals(collateralDecimals); - // Set the external position unit based on PerpV2 Account value; If there is more than 1 position unit of USDC of account - // value (to tolerate PRECISE_UNIT math rounding errors), add the component and/or the external position to the SetToken. - // Else, untrack the component and/or remove external position from the SetToken. Refer to PositionV2#editExternalPosition for detailed flow. - // Setting external posiiton unit to 1 or 0 is acceptable because, As noted above, the external position unit is only updated on an as-needed - // basis during issuance, redemption, deposit and withdraw. It does not reflect the current value of the Set's perpetual position. - // The current value can be calculated from getPositionNotionalInfo. _setToken.editExternalPosition( address(collateralToken), address(this), - accountValueUnit > 1 ? 1 : 0, + collateralValueUnit, "" ); } diff --git a/test/integration/perpV2BasisTradingSlippageIssuance.spec.ts b/test/integration/perpV2BasisTradingSlippageIssuance.spec.ts index a3338c828..eef3bd7fb 100644 --- a/test/integration/perpV2BasisTradingSlippageIssuance.spec.ts +++ b/test/integration/perpV2BasisTradingSlippageIssuance.spec.ts @@ -254,7 +254,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { setToken.address, { feeRecipient: owner.address, - performanceFeePercentage: ether(.0), + performanceFeePercentage: ether(.1), maxPerformanceFeePercentage: ether(.2) } ); @@ -294,6 +294,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { baseToken, 2, ether(.02), + true, true ); @@ -508,6 +509,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { baseToken, 2, ether(.02), + true, true ); @@ -855,6 +857,7 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { baseToken, 6, ether(.02), + true, true ); @@ -909,7 +912,12 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { describe("when liquidation results in negative account value", () => { beforeEach(async () => { + // Move oracle price down, wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.5)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + // Calculated leverage = ~8.5X = 8_654_438_822_995_683_587 + // Lever again to track funding as `settled` await leverUp( setToken, perpBasisTradingModule, @@ -918,9 +926,13 @@ describe("PerpV2BasisTradingSlippageIssuance", () => { baseToken, 6, ether(.02), + true, true ); + // Freeze funding changes + await perpSetup.clearingHouseConfig.setMaxFundingRate(ZERO); + // Move oracle price down to 5 USDC to enable liquidation await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(5.0)); diff --git a/test/integration/perpV2LeverageV2SlippageIssuance.spec.ts b/test/integration/perpV2LeverageV2SlippageIssuance.spec.ts index 2c900d658..4060e6435 100644 --- a/test/integration/perpV2LeverageV2SlippageIssuance.spec.ts +++ b/test/integration/perpV2LeverageV2SlippageIssuance.spec.ts @@ -34,7 +34,7 @@ import { } from "@utils/test/index"; import { PerpV2Fixture, SystemFixture } from "@utils/fixtures"; import { BigNumber } from "ethers"; -import { ONE, ADDRESS_ZERO, ZERO, MAX_UINT_256, ZERO_BYTES } from "@utils/constants"; +import { ADDRESS_ZERO, ZERO, MAX_UINT_256, ZERO_BYTES } from "@utils/constants"; const expect = getWaffleExpect(); @@ -373,7 +373,7 @@ describe("PerpV2LeverageSlippageIssuance", () => { ); expect(defaultPositionUnit).eq(ZERO); - expect(externalPositionUnit).eq(ONE); + expect(externalPositionUnit).eq(depositQuantityUnit); }); }); diff --git a/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts b/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts index f1b07e086..18532795f 100644 --- a/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts +++ b/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts @@ -779,7 +779,8 @@ describe("PerpV2BasisTradingModule", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpBasisTradingModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpBasisTradingModule ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); @@ -870,7 +871,8 @@ describe("PerpV2BasisTradingModule", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpBasisTradingModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpBasisTradingModule ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); @@ -1020,7 +1022,8 @@ describe("PerpV2BasisTradingModule", () => { const externalPositionUnit = await setToken.getExternalPositionRealUnit(usdc.address, perpBasisTradingModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( setToken, - perpSetup + perpSetup, + perpBasisTradingModule ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); diff --git a/test/protocol/modules/v2/perpV2LeverageModuleV2.spec.ts b/test/protocol/modules/v2/perpV2LeverageModuleV2.spec.ts index f3a1bb5e6..bb491ed4f 100644 --- a/test/protocol/modules/v2/perpV2LeverageModuleV2.spec.ts +++ b/test/protocol/modules/v2/perpV2LeverageModuleV2.spec.ts @@ -1205,7 +1205,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule, ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); @@ -1261,7 +1262,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule ); // Deposit notional amount = specified position unit * totalSupply = 1 * 2 = $2 @@ -1344,7 +1346,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule ); // Deposit amount = $1 * 2 (two deposits) @@ -1438,7 +1441,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule ); // Deposit amount = $1 * 2 (two deposits) @@ -1579,7 +1583,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); @@ -1657,7 +1662,8 @@ describe("PerpV2LeverageModuleV2", () => { const externalPositionUnit = await subjectSetToken.getExternalPositionRealUnit(usdc.address, perpLeverageModule.address); const expectedExternalPositionUnit = await calculateExternalPositionUnit( subjectSetToken, - perpSetup + perpSetup, + perpLeverageModule ); expect(externalPositionUnit).eq(expectedExternalPositionUnit); }); diff --git a/utils/common/perpV2Utils.ts b/utils/common/perpV2Utils.ts index 8f4119d9d..e73f6f720 100644 --- a/utils/common/perpV2Utils.ts +++ b/utils/common/perpV2Utils.ts @@ -8,7 +8,7 @@ import { preciseMul } from "../index"; -import { ONE, TWO, ZERO, ONE_DAY_IN_SECONDS } from "../constants"; +import { TWO, ZERO, ONE_DAY_IN_SECONDS } from "../constants"; import { PerpV2BasisTradingModule, PerpV2LeverageModuleV2, SetToken } from "../contracts"; import { PerpV2Fixture } from "../fixtures"; @@ -27,7 +27,8 @@ export async function leverUp( baseToken: Address, leverageRatio: number, slippagePercentage: BigNumber, - isLong: boolean + isLong: boolean, + trackFunding?: boolean, ): Promise{ const spotPrice = await fixture.getSpotPrice(baseToken); const totalSupply = await setToken.totalSupply(); @@ -50,12 +51,21 @@ export async function leverUp( totalSupply ); - await module.connect(owner.wallet).trade( - setToken.address, - baseToken, - baseTradeQuantityUnit, - receiveQuoteQuantityUnit - ); + if (trackFunding) { + await (module as PerpV2BasisTradingModule).connect(owner.wallet).tradeAndTrackFunding( + setToken.address, + baseToken, + baseTradeQuantityUnit, + receiveQuoteQuantityUnit + ); + } else { + await module.connect(owner.wallet).trade( + setToken.address, + baseToken, + baseTradeQuantityUnit, + receiveQuoteQuantityUnit + ); + } return baseTradeQuantityUnit; } @@ -180,10 +190,10 @@ export async function calculateUSDCTransferOutPreciseUnits( export async function calculateExternalPositionUnit( setToken: SetToken, fixture: PerpV2Fixture, + module: PerpV2LeverageModuleV2 | PerpV2BasisTradingModule ): Promise { - const totalPositionValue = await fixture.clearingHouse.getAccountValue(setToken.address); - // return toUSDCDecimals(preciseDiv(totalPositionValue, await setToken.totalSupply())); - return totalPositionValue.gt(ZERO) ? ONE : ZERO; + const accountInfo = await module.getAccountInfo(setToken.address); + return toUSDCDecimals(preciseDiv(accountInfo.collateralBalance, await setToken.totalSupply())); } // On every interaction with perpV2, it settles funding for a trader into owed realized pnl