From 59b6d17fa2549263b4633289a176a1af177011fa Mon Sep 17 00:00:00 2001 From: itofarina Date: Wed, 7 Jun 2023 17:17:41 -0300 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20debt-manager:=20support=20cross-del?= =?UTF-8?q?everage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/tiny-bears-hammer.md | 5 ++ .gas-snapshot | 100 +++++++++++++++------------- contracts/periphery/DebtManager.sol | 54 ++++++++++++++- test/solidity/DebtManager.t.sol | 45 +++++++++++++ 4 files changed, 153 insertions(+), 51 deletions(-) create mode 100644 .changeset/tiny-bears-hammer.md diff --git a/.changeset/tiny-bears-hammer.md b/.changeset/tiny-bears-hammer.md new file mode 100644 index 000000000..6d731b318 --- /dev/null +++ b/.changeset/tiny-bears-hammer.md @@ -0,0 +1,5 @@ +--- +"@exactly/protocol": patch +--- + +✨ debt-manager: support cross-deleverage diff --git a/.gas-snapshot b/.gas-snapshot index 4bd2111f7..a55bedab3 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -10,55 +10,59 @@ AuditorTest:testEnableMarketAuditorMismatch() (gas: 24805) AuditorTest:testEnableMarketShouldRevertWithInvalidPriceFeed() (gas: 149281) AuditorTest:testEnterExitMarket() (gas: 178761) AuditorTest:testExitMarketOwning() (gas: 177445) -DebtManagerTest:testApproveMaliciousMarket() (gas: 29094) -DebtManagerTest:testApproveMarket() (gas: 61541) -DebtManagerTest:testAvailableLiquidity() (gas: 127236) -DebtManagerTest:testBalancerFlashloanCallFromDifferentOrigin() (gas: 65836) -DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 33615) -DebtManagerTest:testCrossLeverageFromUSDCToWETH() (gas: 972581) -DebtManagerTest:testCrossLeverageFromwstETHtoWETH() (gas: 934255) -DebtManagerTest:testCrossLeverageWithDeposit() (gas: 924350) -DebtManagerTest:testCrossLeverageWithInvalidFee() (gas: 154892) -DebtManagerTest:testDeleverage() (gas: 760044) -DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 800712) -DebtManagerTest:testDeleverageWithWithdraw() (gas: 794237) -DebtManagerTest:testFixedDeleverage() (gas: 732125) -DebtManagerTest:testFixedRoll() (gas: 832061) +DebtManagerTest:testApproveMaliciousMarket() (gas: 29116) +DebtManagerTest:testApproveMarket() (gas: 61563) +DebtManagerTest:testAvailableLiquidity() (gas: 127148) +DebtManagerTest:testBalancerFlashloanCallFromDifferentOrigin() (gas: 65858) +DebtManagerTest:testCallReceiveFlashLoanFromAnyAddress() (gas: 33637) +DebtManagerTest:testCrossDeleverageFromWETHToUSDC() (gas: 1129135) +DebtManagerTest:testCrossDeleverageFromwstETHToWETH() (gas: 1052805) +DebtManagerTest:testCrossDeleverageWithInvalidFee() (gas: 785986) +DebtManagerTest:testCrossDeleverageWithInvalidPercentage() (gas: 1104620) +DebtManagerTest:testCrossLeverageFromUSDCToWETH() (gas: 973049) +DebtManagerTest:testCrossLeverageFromwstETHtoWETH() (gas: 934723) +DebtManagerTest:testCrossLeverageWithDeposit() (gas: 924818) +DebtManagerTest:testCrossLeverageWithInvalidFee() (gas: 155019) +DebtManagerTest:testDeleverage() (gas: 760132) +DebtManagerTest:testDeleverageHalfBorrowPosition() (gas: 800866) +DebtManagerTest:testDeleverageWithWithdraw() (gas: 794325) +DebtManagerTest:testFixedDeleverage() (gas: 732169) +DebtManagerTest:testFixedRoll() (gas: 832083) DebtManagerTest:testFixedRollSameMaturityWithThreeLoops() (gas: 690162) -DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 1156577) -DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 2080139) -DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 1156537) -DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 2074877) -DebtManagerTest:testFixedToFloatingRoll() (gas: 787156) -DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 917251) -DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 1349570) -DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 1948971) -DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 1053097) -DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 692539) -DebtManagerTest:testFloatingToFixedRoll() (gas: 808584) -DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 956240) -DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 1452843) -DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 2218100) -DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 1133688) -DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 1141687) -DebtManagerTest:testLateFixedDeleverage() (gas: 857112) -DebtManagerTest:testLateFixedRoll() (gas: 915668) -DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 1432856) -DebtManagerTest:testLateFixedToFloatingRoll() (gas: 869320) -DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 1359083) -DebtManagerTest:testLeverage() (gas: 634676) -DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 1270622) -DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 668330) -DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 3757470) -DebtManagerTest:testMockBalancerVault() (gas: 5443352) -DebtManagerTest:testPartialFixedDeleverage() (gas: 798591) -DebtManagerTest:testPartialFixedRoll() (gas: 875325) -DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 845410) -DebtManagerTest:testPartialLateFixedRoll() (gas: 960501) -DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 936905) -DebtManagerTest:testPermit2AndLeverage() (gas: 781144) -DebtManagerTest:testPermitAndDeleverage() (gas: 835432) -DebtManagerTest:testPermitAndRollFloatingToFixed() (gas: 893978) +DebtManagerTest:testFixedRollWithAccurateBorrowSlippage() (gas: 1156621) +DebtManagerTest:testFixedRollWithAccurateBorrowSlippageWithThreeLoops() (gas: 2080183) +DebtManagerTest:testFixedRollWithAccurateRepaySlippage() (gas: 1156603) +DebtManagerTest:testFixedRollWithAccurateRepaySlippageWithThreeLoops() (gas: 2074921) +DebtManagerTest:testFixedToFloatingRoll() (gas: 787178) +DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidity() (gas: 917273) +DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippage() (gas: 1349614) +DebtManagerTest:testFixedToFloatingRollHigherThanAvailableLiquidityWithSlippageWithThreeLoops() (gas: 1949037) +DebtManagerTest:testFixedToFloatingRollWithAccurateSlippage() (gas: 1053163) +DebtManagerTest:testFlashloanFeeGreaterThanZero() (gas: 692583) +DebtManagerTest:testFloatingToFixedRoll() (gas: 808606) +DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidity() (gas: 956262) +DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippage() (gas: 1452887) +DebtManagerTest:testFloatingToFixedRollHigherThanAvailableLiquidityWithSlippageWithThreePools() (gas: 2218166) +DebtManagerTest:testFloatingToFixedRollWithAccurateSlippage() (gas: 1133754) +DebtManagerTest:testFloatingToFixedRollWithAccurateSlippageWithPreviousPosition() (gas: 1141753) +DebtManagerTest:testLateFixedDeleverage() (gas: 857156) +DebtManagerTest:testLateFixedRoll() (gas: 915690) +DebtManagerTest:testLateFixedRollWithThreeLoops() (gas: 1432878) +DebtManagerTest:testLateFixedToFloatingRoll() (gas: 869342) +DebtManagerTest:testLateFixedToFloatingRollWithThreeLoops() (gas: 1359105) +DebtManagerTest:testLeverage() (gas: 634720) +DebtManagerTest:testLeverageShouldFailWhenHealthFactorNearOne() (gas: 1270732) +DebtManagerTest:testLeverageWithAlreadyDepositedAmount() (gas: 668352) +DebtManagerTest:testLeverageWithInvalidBalancerVault() (gas: 4043629) +DebtManagerTest:testMockBalancerVault() (gas: 5729555) +DebtManagerTest:testPartialFixedDeleverage() (gas: 798613) +DebtManagerTest:testPartialFixedRoll() (gas: 875347) +DebtManagerTest:testPartialFixedToFloatingRoll() (gas: 845454) +DebtManagerTest:testPartialLateFixedRoll() (gas: 960590) +DebtManagerTest:testPartialLateFixedToFloatingRoll() (gas: 936927) +DebtManagerTest:testPermit2AndLeverage() (gas: 781166) +DebtManagerTest:testPermitAndDeleverage() (gas: 835476) +DebtManagerTest:testPermitAndRollFloatingToFixed() (gas: 893911) InterestRateModelTest:testFixedBorrowRate() (gas: 8089) InterestRateModelTest:testFloatingBorrowRate() (gas: 6236) InterestRateModelTest:testMinFixedRate() (gas: 7610) diff --git a/contracts/periphery/DebtManager.sol b/contracts/periphery/DebtManager.sol index 3aa7f0243..5821a2209 100644 --- a/contracts/periphery/DebtManager.sol +++ b/contracts/periphery/DebtManager.sol @@ -129,7 +129,42 @@ contract DebtManager is Initializable { outAsset: v.outAsset, principal: deposit ? principal : 0, account: msg.sender, - fee: fee + fee: fee, + leverage: true + }) + ) + ); + } + + /// @notice Cross-deleverages the floating position of `msg.sender` + /// @param inMarket The Market to withdraw the leveraged position. + /// @param outMarket The Market to repay the leveraged position. + /// @param fee The fee of the pool that will be used to swap the assets. + /// @param percentage The percentage that the position will be deleveraged. + function crossDeleverage(Market inMarket, Market outMarket, uint24 fee, uint256 percentage) external { + LeverageVars memory v; + v.inAsset = address(inMarket.asset()); + v.outAsset = address(outMarket.asset()); + + (, , uint256 floatingBorrowShares) = outMarket.accounts(msg.sender); + v.amount = outMarket.previewRefund(floatingBorrowShares.mulWadDown(percentage)); + + PoolKey memory poolKey = PoolAddress.getPoolKey(v.inAsset, v.outAsset, fee); + IUniswapV3Pool(PoolAddress.computeAddress(uniswapV3Factory, poolKey)).swap( + address(this), + v.inAsset == poolKey.token0, + -int256(v.amount), + v.inAsset == poolKey.token0 ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1, + abi.encode( + SwapCallbackData({ + inMarket: inMarket, + outMarket: outMarket, + inAsset: v.inAsset, + outAsset: v.outAsset, + principal: v.amount, + account: msg.sender, + fee: fee, + leverage: false }) ) ); @@ -406,8 +441,20 @@ contract DebtManager is Initializable { PoolKey memory poolKey = PoolAddress.getPoolKey(s.inAsset, s.outAsset, s.fee); assert(msg.sender == PoolAddress.computeAddress(uniswapV3Factory, poolKey)); - s.inMarket.deposit(s.principal + uint256(-(s.inAsset == poolKey.token0 ? amount0Delta : amount1Delta)), s.account); - s.outMarket.borrow(uint256(s.inAsset == poolKey.token1 ? amount0Delta : amount1Delta), msg.sender, s.account); + if (s.leverage) { + s.inMarket.deposit( + s.principal + uint256(-(s.inAsset == poolKey.token0 ? amount0Delta : amount1Delta)), + s.account + ); + s.outMarket.borrow(uint256(s.inAsset == poolKey.token1 ? amount0Delta : amount1Delta), msg.sender, s.account); + } else { + s.outMarket.repay(s.principal, s.account); + s.inMarket.withdraw( + s.inAsset == poolKey.token1 ? uint256(amount1Delta) : uint256(amount0Delta), + msg.sender, + s.account + ); + } } /// @notice Calls `token.permit` on behalf of `permit.account`. @@ -574,6 +621,7 @@ contract DebtManager is Initializable { address account; uint256 principal; uint24 fee; + bool leverage; } } diff --git a/test/solidity/DebtManager.t.sol b/test/solidity/DebtManager.t.sol index 398fe7c99..4eb6df463 100644 --- a/test/solidity/DebtManager.t.sol +++ b/test/solidity/DebtManager.t.sol @@ -752,6 +752,51 @@ contract DebtManagerTest is Test { debtManager.crossLeverage(marketUSDC, marketWETH, 200, 100_000e6, 1.1e18, true); } + function testCrossDeleverageFromwstETHToWETH() external _checkBalances { + marketwstETH.asset().approve(address(debtManager), type(uint256).max); + debtManager.auditor().enterMarket(marketwstETH); + debtManager.crossLeverage(marketwstETH, marketWETH, 500, 10e18, 1.02e18, true); + (, , uint256 floatingBorrowShares) = marketWETH.accounts(address(this)); + uint256 percentage = 1e18; + debtManager.crossDeleverage(marketwstETH, marketWETH, 500, percentage); + + assertEq(marketwstETH.maxWithdraw(address(this)), 9_980_645_608_672_055_653); + (, , uint256 newFloatingBorrowShares) = marketWETH.accounts(address(this)); + assertEq(newFloatingBorrowShares, floatingBorrowShares - floatingBorrowShares.mulWadDown(percentage)); + } + + function testCrossDeleverageFromWETHToUSDC() external _checkBalances { + marketWETH.asset().approve(address(debtManager), type(uint256).max); + debtManager.auditor().enterMarket(marketWETH); + debtManager.crossLeverage(marketWETH, marketUSDC, 500, 10e18, 1.02e18, true); + (, , uint256 floatingBorrowShares) = marketUSDC.accounts(address(this)); + uint256 percentage = 1e18; + debtManager.crossDeleverage(marketWETH, marketUSDC, 500, percentage); + + uint256 maxWithdraw = marketWETH.maxWithdraw(address(this)); + assertEq(maxWithdraw, 9_970_057_426_084_898_139); + (, , uint256 newFloatingBorrowShares) = marketUSDC.accounts(address(this)); + assertEq(newFloatingBorrowShares, floatingBorrowShares - floatingBorrowShares.mulWadDown(percentage)); + } + + function testCrossDeleverageWithInvalidFee() external _checkBalances { + marketwstETH.asset().approve(address(debtManager), type(uint256).max); + debtManager.auditor().enterMarket(marketwstETH); + debtManager.crossLeverage(marketwstETH, marketWETH, 500, 10_000e6, 1.02e18, true); + + vm.expectRevert(bytes("")); + debtManager.crossDeleverage(marketwstETH, marketWETH, 200, 1e18); + } + + function testCrossDeleverageWithInvalidPercentage() external _checkBalances { + marketwstETH.asset().approve(address(debtManager), type(uint256).max); + debtManager.auditor().enterMarket(marketwstETH); + debtManager.crossLeverage(marketwstETH, marketWETH, 500, 10e18, 1.02e18, true); + uint256 percentage = 2e18; + vm.expectRevert(abi.encodeWithSelector(InsufficientAccountLiquidity.selector)); + debtManager.crossDeleverage(marketwstETH, marketWETH, 500, percentage); + } + function testPermitAndRollFloatingToFixed() external { marketUSDC.deposit(100_000e6, bob); vm.prank(bob);