diff --git a/contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol b/contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol index a6dfcbc4a..a426707e7 100644 --- a/contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol +++ b/contracts/protocol/modules/v2/PerpV2BasisTradingModule.sol @@ -429,7 +429,7 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 { if (positions[_setToken].length > 0) { - uint256 updatedSettledFunding = _getUpdatedSettledFunding(_setToken); + uint256 updatedSettledFunding = getUpdatedSettledFunding(_setToken); int256 newExternalPositionUnit = _executePositionTrades(_setToken, _setTokenQuantity, false, true); @@ -439,31 +439,18 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 { return _formatAdjustments(_setToken, newExternalPositionUnitNetFees); } - /* ============ Internal Functions ============ */ - - /** - * @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 - * pending funding payment that is about to be settled due to subsequent logic in the external function. - * - * @param _setToken Instance of SetToken - * @return uint256 Returns the updated settled funding value - */ - function _updateSettledFunding(ISetToken _setToken) internal returns (uint256) { - uint256 newSettledFunding = _getUpdatedSettledFunding(_setToken); - settledFunding[_setToken] = newSettledFunding; - return newSettledFunding; - } - /** * @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 * payment is negative and |pending funding payment| >= |settledFunding[_setToken]|. * + * NOTE: Returned updated settled funding value is correct only for the current block since pending funding payment + * updates every block. + * * @param _setToken Instance of SetToken */ - function _getUpdatedSettledFunding(ISetToken _setToken) internal view returns (uint256) { + function getUpdatedSettledFunding(ISetToken _setToken) public view returns (uint256) { // 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. // We are flipping its sign here to reflect its settlement value. @@ -480,6 +467,22 @@ contract PerpV2BasisTradingModule is PerpV2LeverageModuleV2 { return 0; } + /* ============ Internal Functions ============ */ + + /** + * @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 + * pending funding payment that is about to be settled due to subsequent logic in the external function. + * + * @param _setToken Instance of SetToken + * @return uint256 Returns the updated settled funding value + */ + function _updateSettledFunding(ISetToken _setToken) internal returns (uint256) { + uint256 newSettledFunding = getUpdatedSettledFunding(_setToken); + settledFunding[_setToken] = newSettledFunding; + return newSettledFunding; + } + /** * @dev Calculates manager and protocol fees on withdrawn funding amount and transfers them to * their respective recipients (in USDC). diff --git a/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts b/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts index 36d74ace8..d62ed70d3 100644 --- a/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts +++ b/test/protocol/modules/v2/perpV2BasisTradingModule.spec.ts @@ -1916,6 +1916,137 @@ describe("PerpV2BasisTradingModule", () => { }); }); + describe("#getUpdatedSettledFunding", async () => { + let setToken: SetToken; + let subjectSetToken: Address; + + const initializeContracts = async () => { + const depositQuantity = usdcUnits(10); + + setToken = await issueSetsAndDepositToPerp(depositQuantity, true); + + await perpBasisTradingModule.connect(owner.wallet).trade( + setToken.address, + vETH.address, + ether(0.5), + ether(5.15) + ); + + // Move oracle price up and wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.2)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + + await perpBasisTradingModule.connect(owner.wallet).tradeAndTrackFunding( + setToken.address, + vETH.address, + ether(0.5), + ether(5.15), + ); + }; + + const initializeSubjectVariables = async () => { + subjectSetToken = setToken.address; + }; + + cacheBeforeEach(initializeContracts); + beforeEach(initializeSubjectVariables); + + async function subject(): Promise { + return await perpBasisTradingModule.getUpdatedSettledFunding(subjectSetToken); + } + + describe("when pending funding payment is zero", async () => { + it("should return current settled funding", async () => { + const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken); + const updatedSettledFunding = await subject(); + + expect(settledFundingBefore).to.be.gt(ZERO); + expect(updatedSettledFunding).to.eq(settledFundingBefore); + }); + }); + + describe("when pending funding payment is positive", async () => { + beforeEach(async () => { + // Move oracle price up and wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(10.5)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + }); + + it("should return correct updated settled funding", async () => { + const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken); + const pendingFundingPayment = (await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken)).mul(-1); + const expectedSettledFunding = settledFundingBefore.add(pendingFundingPayment); + + const updatedSettledFunding = await subject(); + + expect(pendingFundingPayment).to.be.gt(ZERO); + expect(updatedSettledFunding).to.be.gt(settledFundingBefore); + expect(updatedSettledFunding).to.eq(expectedSettledFunding); + }); + }); + + describe("when pending funding payment is negative", async () => { + describe("when absolute settled funding is less than absolute negative funding", async () => { + beforeEach(async () => { + // Move oracle price down and wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(9.5)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + }); + + it("verify testing conditions", async () => { + const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken); + const pendingFundingBefore = await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken); + + expect(pendingFundingBefore.abs()).to.be.gt(settledFundingBefore); + }); + + it("should return correct updated settled funding (zero)", async () => { + const updatedSettledFunding = await subject(); + expect(updatedSettledFunding).to.eq(ZERO); + }); + }); + + describe("when absolute settled funding is greater then absolute negative pending funding", async () => { + beforeEach(async () => { + // Move oracle price up and wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(11)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + + // Trade to accrue pending funding to tracked settled funding + await perpBasisTradingModule.connect(owner.wallet).tradeAndTrackFunding( + setToken.address, + vETH.address, + ether(1), + ether(10.15) + ); + + // Move oracle price down and wait one day + await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(9.8)); + await increaseTimeAsync(ONE_DAY_IN_SECONDS); + }); + + it("verify testing conditions", async () => { + const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken); + const pendingFundingBefore = await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken); + + expect(settledFundingBefore.abs()).to.be.gt(pendingFundingBefore.abs()); + }); + + it("should return correct updated settled funding", async () => { + const settledFundingBefore = await perpBasisTradingModule.settledFunding(subjectSetToken); + const pendingFundingPayment = (await perpSetup.exchange.getAllPendingFundingPayment(subjectSetToken)).mul(-1); + const expectedSettledFunding = settledFundingBefore.add(pendingFundingPayment); + + const updatedSettledFunding = await subject(); + + expect(pendingFundingPayment).to.be.lt(ZERO); + expect(updatedSettledFunding).to.be.lt(settledFundingBefore); + expect(updatedSettledFunding).to.eq(expectedSettledFunding); + }); + }); + }); + }); + describe("#updateFeeRecipient", async () => { let setToken: SetToken; let isInitialized: boolean;