@@ -12,7 +12,7 @@ import {MockMorphoAllocator} from "test/mock/request/MockMorphoAllocator.sol";
1212import {Offer} from "src/interfaces/request/IOfferReceiver.sol " ;
1313import {LibCommonErrors} from "src/libs/common/LibCommonErrors.sol " ;
1414import {MockERC20} from "test/mock/MockERC20.sol " ;
15- import {Mode, State} from "src/libs/funds/Order.sol " ;
15+ import {Order, Mode, State} from "src/libs/funds/Order.sol " ;
1616import {IntentProperties, Asset} from "src/libs/facility/LibIntent.sol " ;
1717import {WithdrawalStrategy} from "src/interfaces/manager/base/IPositionManagerAdmin.sol " ;
1818import {MarketParams} from "lib/morpho-blue/src/interfaces/IMorpho.sol " ;
@@ -937,7 +937,111 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest {
937937 assertEq (collateralToken.balanceOf (address (facility)), 0 );
938938 }
939939
940- /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
940+ /// @dev Midas flow: the redeem order is created ahead of time, so SyncWithdrawal must skip create().
941+ function testFuzz_execute_syncWithdrawalSkipsCreateWhenOrderExists (bool proportional ) public {
942+ uint256 morphoBalBefore = debtToken.balanceOf (address (morpho));
943+ vm.prank (facilitator);
944+ facility.depositManager (intentId, 0 , 5_000e18 , false );
945+
946+ uint256 withdrawAmount = 1_000e18 ;
947+ uint256 repayAmount = 500e18 ;
948+ WithdrawalStrategy strategy = proportional ? WithdrawalStrategy.PROPORTIONAL : WithdrawalStrategy.SEQUENTIAL;
949+ vm.prank (facilitator);
950+ _setFund (intentId, address (mockFund));
951+ mockFund.setNextCommitState (State.UNLOCKING);
952+ mockFund.setUnlockAmount (withdrawAmount);
953+ debtToken.setBalance (address (mockFund), withdrawAmount);
954+ vm.prank (facilitator);
955+ facility.create (intentId, withdrawAmount, withdrawAmount, Mode.REDEEM);
956+
957+ vm.prank (executor);
958+ flashLoanRequest.execute (
959+ repayAmount,
960+ _buildSetRequestParams (intentId, block .timestamp + 1 hours),
961+ address (syncWithdrawal),
962+ abi.encodeCall (SyncWithdrawal.run, (address (facility), intentId, withdrawAmount, repayAmount, false , strategy, 0 ))
963+ );
964+
965+ assertEq (positionManager.collateralAmount (), 9_000e18 );
966+ assertEq (positionManager.debtAmount (), 4_500e18 );
967+ assertEq (collateralToken.balanceOf (address (mockFund)), withdrawAmount);
968+ assertEq (collateralToken.balanceOf (address (facility)), 0 );
969+ assertEq (debtToken.balanceOf (address (facility)), 5_500e18 );
970+ assertEq (debtToken.balanceOf (address (morpho)), morphoBalBefore - 4_500e18 );
971+ assertEq (debtToken.balanceOf (address (flashLoanRequest)), 0 );
972+ (, address fund , address request ,) = facility.getIntent (intentId);
973+ assertEq (fund, address (0 ));
974+ assertEq (request, address (0 ));
975+ }
976+
977+ /// @dev A canceled order is deleted from the intent, so SyncWithdrawal creates a fresh one.
978+ function test_execute_syncWithdrawalCreatesAfterCanceledOrder () public {
979+ vm.prank (facilitator);
980+ facility.depositManager (intentId, 0 , 5_000e18 , false );
981+
982+ uint256 withdrawAmount = 1_000e18 ;
983+ uint256 repayAmount = 500e18 ;
984+ vm.prank (facilitator);
985+ _setFund (intentId, address (mockFund));
986+ vm.prank (facilitator);
987+ facility.create (intentId, 1 , 1 , Mode.REDEEM);
988+ vm.prank (facilitator);
989+ facility.cancel (intentId);
990+ (Order memory canceled ,) = facility.getOrder (intentId);
991+ assertEq (canceled.owner, address (0 ));
992+
993+ // Cancel also cleared the fund: re-set it (fresh deadline, the first signature digest is used)
994+ address [] memory fundSigners = new address [](1 );
995+ bytes [] memory fundSigs = new bytes [](1 );
996+ uint256 fundDeadline = block .timestamp + 3 hours ;
997+ fundSigners[0 ] = guardian;
998+ fundSigs[0 ] = _signSetFund (intentId, address (mockFund), fundDeadline, GUARDIAN_PK);
999+ vm.prank (facilitator);
1000+ _setFund (intentId, address (mockFund), fundDeadline, fundSigners, fundSigs);
1001+ mockFund.setNextCommitState (State.UNLOCKING);
1002+ mockFund.setUnlockAmount (withdrawAmount);
1003+ debtToken.setBalance (address (mockFund), withdrawAmount);
1004+
1005+ vm.prank (executor);
1006+ flashLoanRequest.execute (
1007+ repayAmount,
1008+ _buildSetRequestParams (intentId, block .timestamp + 1 hours),
1009+ address (syncWithdrawal),
1010+ abi.encodeCall (
1011+ SyncWithdrawal.run,
1012+ (address (facility), intentId, withdrawAmount, repayAmount, false , WithdrawalStrategy.SEQUENTIAL, 0 )
1013+ )
1014+ );
1015+
1016+ // The fresh order consumed the full withdrawal (the canceled order's input was 1)
1017+ assertEq (collateralToken.balanceOf (address (mockFund)), withdrawAmount);
1018+ assertEq (collateralToken.balanceOf (address (facility)), 0 );
1019+ assertEq (debtToken.balanceOf (address (facility)), 5_500e18 );
1020+ }
1021+
1022+ /// @dev An existing DEPOSIT order must not be committed as part of a withdrawal.
1023+ function test_execute_syncWithdrawalRevertsWhenExistingOrderIsDeposit () public {
1024+ vm.prank (facilitator);
1025+ facility.depositManager (intentId, 0 , 5_000e18 , false );
1026+
1027+ vm.prank (facilitator);
1028+ _setFund (intentId, address (mockFund));
1029+ vm.prank (facilitator);
1030+ facility.create (intentId, 1_000e18 , 1_000e18 , Mode.DEPOSIT);
1031+
1032+ vm.prank (executor);
1033+ vm.expectRevert (abi.encodeWithSelector (SyncWithdrawal.NotRedeemOrder.selector , intentId));
1034+ flashLoanRequest.execute (
1035+ 500e18 ,
1036+ _buildSetRequestParams (intentId, block .timestamp + 1 hours),
1037+ address (syncWithdrawal),
1038+ abi.encodeCall (
1039+ SyncWithdrawal.run, (address (facility), intentId, 1_000e18 , 500e18 , false , WithdrawalStrategy.SEQUENTIAL, 0 )
1040+ )
1041+ );
1042+ }
1043+
1044+ /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9411045 /* I. pullFunds() TESTS */
9421046 /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
9431047
0 commit comments