diff --git a/src/request/scripts/SyncMidasWithdrawal.sol b/src/request/scripts/SyncMidasWithdrawal.sol new file mode 100644 index 00000000..59ce185e --- /dev/null +++ b/src/request/scripts/SyncMidasWithdrawal.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.24; + +import {IFacilityFunds} from "../../interfaces/facility/base/IFacilityFunds.sol"; +import {IFacilityPositionManager} from "../../interfaces/facility/base/IFacilityPositionManager.sol"; +import {WithdrawalStrategy} from "../../interfaces/manager/base/IPositionManagerAdmin.sol"; + +/// @title SyncMidasWithdrawal +/// @notice Stateless script designed to be delegatecalled from MorphoFlashLoanRequest. +/// Executes the remaining Midas redemption flow: withdrawManager → commit → unlock. +/// @dev Must NOT have any storage variables. The redeem order must already exist, its bond +/// leg must be complete, and instant redemption must be unlocked before execute(). +/// @author 3F Protocol +contract SyncMidasWithdrawal { + /// @notice Settles the remaining Midas redemption for a facility intent. + /// @param facility The facility contract address. + /// @param intentId The intent ID on the facility. + /// @param withdrawAmount The remaining collateral required by the Midas redemption. + /// @param repayAmount The amount of debt to repay to the position manager. + /// @param useTarget Whether to use the target asset for the position manager withdrawal. + /// @param strategy The withdrawal strategy (SEQUENTIAL or PROPORTIONAL). + function run( + address facility, + uint256 intentId, + uint256 withdrawAmount, + uint256 repayAmount, + bool useTarget, + WithdrawalStrategy strategy + ) external { + // 1. Withdraw collateral from position manager (repay debt, get collateral back) + IFacilityPositionManager(facility).withdrawManager(intentId, withdrawAmount, repayAmount, useTarget, strategy); + + // 2. Commit the remaining redemption collateral + IFacilityFunds(facility).commit(intentId); + + // 3. Unlock the redemption proceeds + IFacilityFunds(facility).unlock(intentId); + } +} diff --git a/test/request/MorphoFlashLoanRequest.t.sol b/test/request/MorphoFlashLoanRequest.t.sol index 862b0443..c6b67e5b 100644 --- a/test/request/MorphoFlashLoanRequest.t.sol +++ b/test/request/MorphoFlashLoanRequest.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {FacilityBaseTest} from "test/facility/FacilityBase.t.sol"; import {MorphoFlashLoanRequest} from "src/request/MorphoFlashLoanRequest.sol"; import {MorphoFlashLoanRequestFactory} from "src/request/MorphoFlashLoanRequestFactory.sol"; +import {SyncMidasWithdrawal} from "src/request/scripts/SyncMidasWithdrawal.sol"; import {SyncDeposit} from "src/request/scripts/SyncDeposit.sol"; import {SyncWithdrawal} from "src/request/scripts/SyncWithdrawal.sol"; import {SyncAllocatorDeposit} from "src/request/scripts/SyncAllocatorDeposit.sol"; @@ -105,6 +106,7 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest { MorphoFlashLoanRequest public flashLoanRequest; MockScript public mockScript; SyncDeposit public syncDeposit; + SyncMidasWithdrawal public syncMidasWithdrawal; SyncWithdrawal public syncWithdrawal; SyncAllocatorDeposit public syncAllocatorDeposit; MockMorphoAllocator public mockAllocator; @@ -141,6 +143,12 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest { vm.prank(owner); flashLoanRequest.setScript(address(syncDeposit), true); + // Deploy and whitelist SyncMidasWithdrawal + syncMidasWithdrawal = new SyncMidasWithdrawal(); + vm.label(address(syncMidasWithdrawal), "SyncMidasWithdrawal"); + vm.prank(owner); + flashLoanRequest.setScript(address(syncMidasWithdrawal), true); + // Deploy and whitelist SyncWithdrawal syncWithdrawal = new SyncWithdrawal(); vm.label(address(syncWithdrawal), "SyncWithdrawal"); @@ -937,6 +945,45 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest { assertEq(collateralToken.balanceOf(address(facility)), 0); } + function testFuzz_execute_syncMidasWithdrawalCommitUnlock(bool proportional) public { + uint256 morphoBalBefore = debtToken.balanceOf(address(morpho)); + vm.prank(facilitator); + facility.depositManager(intentId, 0, 5_000e18, false); + + uint256 withdrawAmount = 1_000e18; + uint256 repayAmount = 500e18; + WithdrawalStrategy strategy = proportional ? WithdrawalStrategy.PROPORTIONAL : WithdrawalStrategy.SEQUENTIAL; + vm.prank(facilitator); + _setFund(intentId, address(mockFund)); + // MockFund represents the remaining redemption at the script's facility boundary. + mockFund.setNextCommitState(State.UNLOCKING); + mockFund.setUnlockAmount(withdrawAmount); + debtToken.setBalance(address(mockFund), withdrawAmount); + vm.prank(facilitator); + facility.create(intentId, withdrawAmount, withdrawAmount, Mode.REDEEM); + + vm.prank(executor); + flashLoanRequest.execute( + repayAmount, + _buildSetRequestParams(intentId, block.timestamp + 1 hours), + address(syncMidasWithdrawal), + abi.encodeCall( + SyncMidasWithdrawal.run, (address(facility), intentId, withdrawAmount, repayAmount, false, strategy) + ) + ); + + assertEq(positionManager.collateralAmount(), 9_000e18); + assertEq(positionManager.debtAmount(), 4_500e18); + assertEq(collateralToken.balanceOf(address(mockFund)), withdrawAmount); + assertEq(collateralToken.balanceOf(address(facility)), 0); + assertEq(debtToken.balanceOf(address(facility)), 5_500e18); + assertEq(debtToken.balanceOf(address(morpho)), morphoBalBefore - 4_500e18); + assertEq(debtToken.balanceOf(address(flashLoanRequest)), 0); + (, address fund, address request,) = facility.getIntent(intentId); + assertEq(fund, address(0)); + assertEq(request, address(0)); + } + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* I. pullFunds() TESTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/