@@ -4,6 +4,7 @@ pragma solidity ^0.8.24;
44import {FacilityBaseTest} from "test/facility/FacilityBase.t.sol " ;
55import {MorphoFlashLoanRequest} from "src/request/MorphoFlashLoanRequest.sol " ;
66import {MorphoFlashLoanRequestFactory} from "src/request/MorphoFlashLoanRequestFactory.sol " ;
7+ import {SyncMidasWithdrawal} from "src/request/scripts/SyncMidasWithdrawal.sol " ;
78import {SyncDeposit} from "src/request/scripts/SyncDeposit.sol " ;
89import {SyncWithdrawal} from "src/request/scripts/SyncWithdrawal.sol " ;
910import {SyncAllocatorDeposit} from "src/request/scripts/SyncAllocatorDeposit.sol " ;
@@ -12,6 +13,7 @@ import {MockMorphoAllocator} from "test/mock/request/MockMorphoAllocator.sol";
1213import {Offer} from "src/interfaces/request/IOfferReceiver.sol " ;
1314import {LibCommonErrors} from "src/libs/common/LibCommonErrors.sol " ;
1415import {MockERC20} from "test/mock/MockERC20.sol " ;
16+ import {MockBorrowPosition} from "test/mock/borrow/MockBorrowPosition.sol " ;
1517import {Mode, State} from "src/libs/funds/Order.sol " ;
1618import {IntentProperties, Asset} from "src/libs/facility/LibIntent.sol " ;
1719import {WithdrawalStrategy} from "src/interfaces/manager/base/IPositionManagerAdmin.sol " ;
@@ -105,6 +107,7 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest {
105107 MorphoFlashLoanRequest public flashLoanRequest;
106108 MockScript public mockScript;
107109 SyncDeposit public syncDeposit;
110+ SyncMidasWithdrawal public syncMidasWithdrawal;
108111 SyncWithdrawal public syncWithdrawal;
109112 SyncAllocatorDeposit public syncAllocatorDeposit;
110113 MockMorphoAllocator public mockAllocator;
@@ -141,6 +144,12 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest {
141144 vm.prank (owner);
142145 flashLoanRequest.setScript (address (syncDeposit), true );
143146
147+ // Deploy and whitelist SyncMidasWithdrawal
148+ syncMidasWithdrawal = new SyncMidasWithdrawal ();
149+ vm.label (address (syncMidasWithdrawal), "SyncMidasWithdrawal " );
150+ vm.prank (owner);
151+ flashLoanRequest.setScript (address (syncMidasWithdrawal), true );
152+
144153 // Deploy and whitelist SyncWithdrawal
145154 syncWithdrawal = new SyncWithdrawal ();
146155 vm.label (address (syncWithdrawal), "SyncWithdrawal " );
@@ -908,8 +917,8 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest {
908917 .execute (
909918 actualDebtBorrowed,
910919 MorphoFlashLoanRequest.SetRequestParams ({
911- intentId: syncIntentId, deadline: block .timestamp + 2 hours, signers: signers, signatures: sigs
912- }),
920+ intentId: syncIntentId, deadline: block .timestamp + 2 hours, signers: signers, signatures: sigs
921+ }),
913922 address (syncWithdrawal),
914923 withdrawPayload
915924 );
@@ -937,6 +946,90 @@ contract MorphoFlashLoanRequestTest is FacilityBaseTest {
937946 assertEq (collateralToken.balanceOf (address (facility)), 0 );
938947 }
939948
949+ function test_execute_syncMidasWithdrawalCommitUnlock () public {
950+ uint256 morphoBalBefore = debtToken.balanceOf (address (morpho));
951+ vm.prank (facilitator);
952+ facility.depositManager (intentId, 0 , 5_000e18 , false );
953+
954+ // A price drop makes the live LTV exceed the requested 50% target.
955+ oracle.setPrice (0.9e36 );
956+ uint256 withdrawAmount = 1_000e18 ;
957+ uint256 repayAmount = 950e18 ; // 5,000 - (10,000 - 1,000) * 0.9 * 0.5
958+ vm.prank (facilitator);
959+ _setFund (intentId, address (mockFund));
960+ // MockFund represents the remaining redemption at the script's facility boundary.
961+ mockFund.setNextCommitState (State.UNLOCKING);
962+ mockFund.setUnlockAmount (withdrawAmount);
963+ debtToken.setBalance (address (mockFund), withdrawAmount);
964+ vm.prank (facilitator);
965+ facility.create (intentId, withdrawAmount, withdrawAmount, Mode.REDEEM);
966+
967+ vm.prank (executor);
968+ flashLoanRequest.execute (
969+ repayAmount,
970+ _buildSetRequestParams (intentId, block .timestamp + 1 hours),
971+ address (syncMidasWithdrawal),
972+ abi.encodeCall (SyncMidasWithdrawal.run, (address (facility), intentId, withdrawAmount, 0.5e18 , false ))
973+ );
974+
975+ assertEq (positionManager.collateralAmount (), 9_000e18 );
976+ assertEq (positionManager.debtAmount (), 4_050e18 );
977+ assertEq (positionManager.debtAmount () * 1e18 / positionManager.collateralAmountQuoted (), 0.5e18 );
978+ assertEq (collateralToken.balanceOf (address (mockFund)), withdrawAmount);
979+ assertEq (collateralToken.balanceOf (address (facility)), 0 );
980+ assertEq (debtToken.balanceOf (address (facility)), 5_050e18 );
981+ assertEq (debtToken.balanceOf (address (morpho)), morphoBalBefore - 4_050e18 );
982+ assertEq (debtToken.balanceOf (address (flashLoanRequest)), 0 );
983+ (, address fund , address request ,) = facility.getIntent (intentId);
984+ assertEq (fund, address (0 ));
985+ assertEq (request, address (0 ));
986+ }
987+
988+ function test_syncMidasWithdrawal_rejectsInvalidParameters () public {
989+ vm.expectRevert (SyncMidasWithdrawal.InvalidTargetLtv.selector );
990+ syncMidasWithdrawal.run (address (facility), intentId, 1 , 1e18 , false );
991+ vm.expectRevert (SyncMidasWithdrawal.InvalidPositionManager.selector );
992+ syncMidasWithdrawal.run (address (facility), intentId, 1 , 0.5e18 , true );
993+ vm.expectRevert (SyncMidasWithdrawal.InvalidWithdrawAmount.selector );
994+ syncMidasWithdrawal.run (address (facility), intentId, 0 , 0.5e18 , false );
995+ uint256 tooMuch = positionManager.collateralAmount () + 1 ;
996+ vm.expectRevert (SyncMidasWithdrawal.InvalidWithdrawAmount.selector );
997+ syncMidasWithdrawal.run (address (facility), intentId, tooMuch, 0.5e18 , false );
998+ }
999+
1000+ function test_syncMidasWithdrawal_revertsWhenPartialQueueRemovesMoreQuotedCollateral () public {
1001+ vm.prank (facilitator);
1002+ facility.depositManager (intentId, 0 , 4_500e18 , false );
1003+
1004+ // This lower-priced module contributes to the aggregate quote but is excluded from withdrawals.
1005+ MockBorrowPosition excluded = new MockBorrowPosition (address (collateralToken), address (debtToken));
1006+ excluded.setTotalCollateral (10_000e18 );
1007+ excluded.setTotalCollateralQuoted (5_000e18 );
1008+ excluded.setSafeLtv (uint128 (PM_LTV));
1009+ vm.mockCall (address (excluded), abi.encodeWithSignature ("owner() " ), abi.encode (address (positionManager)));
1010+ vm.prank (owner);
1011+ positionManager.addBorrowModule (address (excluded));
1012+
1013+ uint256 withdrawAmount = 1_000e18 ;
1014+ uint256 repayAmount = 225e18 ; // Aggregate quote estimates 750 removed; the queued position removes 1,000.
1015+ vm.prank (facilitator);
1016+ _setFund (intentId, address (mockFund));
1017+ mockFund.setNextCommitState (State.UNLOCKING);
1018+ mockFund.setUnlockAmount (withdrawAmount);
1019+ debtToken.setBalance (address (mockFund), withdrawAmount);
1020+ vm.prank (facilitator);
1021+ facility.create (intentId, withdrawAmount, withdrawAmount, Mode.REDEEM);
1022+
1023+ vm.prank (executor);
1024+ vm.expectRevert (SyncMidasWithdrawal.TargetLtvExceeded.selector );
1025+ flashLoanRequest.execute (
1026+ repayAmount,
1027+ _buildSetRequestParams (intentId, block .timestamp + 1 hours),
1028+ address (syncMidasWithdrawal),
1029+ abi.encodeCall (SyncMidasWithdrawal.run, (address (facility), intentId, withdrawAmount, 0.3e18 , false ))
1030+ );
1031+ }
1032+
9401033 /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9411034 /* I. pullFunds() TESTS */
9421035 /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
0 commit comments