@@ -83,6 +83,28 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
8383 vm.stopPrank ();
8484 }
8585
86+ /// @notice Helper to set up a leveraged position for any user
87+ /// @dev More flexible version that accepts owner, account, and vault parameters
88+ function _setupLeveragedPositionFor (
89+ address owner ,
90+ address account ,
91+ address collateralAsset ,
92+ address collateralVault ,
93+ address borrowVault ,
94+ uint256 collateralAmount ,
95+ uint256 borrowAmount
96+ ) internal {
97+ vm.startPrank (owner);
98+ IERC20 (collateralAsset).approve (collateralVault, type (uint256 ).max);
99+ EVC.enableCollateral (account, collateralVault);
100+ EVC.enableController (account, borrowVault);
101+ IERC4626 (collateralVault).deposit (collateralAmount, account);
102+ vm.stopPrank ();
103+
104+ vm.prank (account);
105+ IBorrowing (borrowVault).borrow (borrowAmount, owner);
106+ }
107+
86108 struct SettlementData {
87109 bytes orderUid;
88110 GPv2Order.Data orderData;
@@ -126,6 +148,23 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
126148 );
127149 }
128150
151+ /// @notice Create permit signature for any user
152+ function _createPermitSignatureFor (
153+ CowEvcCollateralSwapWrapper.CollateralSwapParams memory params ,
154+ uint256 userPrivateKey
155+ ) internal returns (bytes memory ) {
156+ ecdsa.setPrivateKey (userPrivateKey);
157+ return ecdsa.signPermit (
158+ params.owner,
159+ address (collateralSwapWrapper),
160+ uint256 (uint160 (address (collateralSwapWrapper))),
161+ 0 ,
162+ params.deadline,
163+ 0 ,
164+ collateralSwapWrapper.getSignedCalldata (params)
165+ );
166+ }
167+
129168 /// @notice Encode wrapper data with length prefix
130169 function _encodeWrapperData (CowEvcCollateralSwapWrapper.CollateralSwapParams memory params , bytes memory signature )
131170 internal
@@ -146,26 +185,26 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
146185 }
147186
148187 /// @notice Setup user approvals for collateral swap on subaccount
149- function _setupSubaccountApprovals (address account , CowEvcCollateralSwapWrapper.CollateralSwapParams memory params )
188+ function _setupSubaccountApprovals (CowEvcCollateralSwapWrapper.CollateralSwapParams memory params )
150189 internal
151190 {
152- vm.startPrank (user );
191+ vm.startPrank (params.owner );
153192
154193 // Approve vault shares from main account for settlement
155194 IEVault (params.fromVault).approve (COW_SETTLEMENT.vaultRelayer (), type (uint256 ).max);
156195
157196 // Approve transfer of vault shares from the subaccount to wrapper
158197 IEVC.BatchItem[] memory items = new IEVC.BatchItem [](1 );
159198 items[0 ] = IEVC.BatchItem ({
160- onBehalfOfAccount: account,
199+ onBehalfOfAccount: params. account,
161200 targetContract: params.fromVault,
162201 value: 0 ,
163202 data: abi.encodeCall (IERC20 .approve, (address (collateralSwapWrapper), type (uint256 ).max))
164203 });
165204 EVC.batch (items);
166205
167206 // Set wrapper as operator for the subaccount
168- EVC.setAccountOperator (account, address (collateralSwapWrapper), true );
207+ EVC.setAccountOperator (params. account, address (collateralSwapWrapper), true );
169208
170209 // Pre-approve the operation hash
171210 bytes32 hash = collateralSwapWrapper.getApprovalHash (params);
@@ -305,7 +344,7 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
305344 vm.stopPrank ();
306345
307346 // Setup subaccount approvals and pre-approved hash
308- _setupSubaccountApprovals (account, params);
347+ _setupSubaccountApprovals (params);
309348
310349 // Record balances before swap
311350 uint256 susdsBalanceBefore = IERC20 (ESUSDS).balanceOf (account);
@@ -402,7 +441,7 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
402441 // User signs the order on cowswap (already done in setupCowOrder)
403442
404443 // Setup subaccount approvals and pre-approved hash
405- _setupSubaccountApprovals (account, params);
444+ _setupSubaccountApprovals (params);
406445
407446 // Record balances and debt before swap
408447 uint256 susdsBalanceBefore = IERC20 (ESUSDS).balanceOf (account);
@@ -434,4 +473,159 @@ contract CowEvcCollateralSwapWrapperTest is CowBaseTest {
434473 assertGt (IERC20 (EWBTC).balanceOf (account), wbtcBalanceBefore, "Account should have more EWBTC after swap " );
435474 assertEq (IEVault (EWETH).debtOf (account), debtBefore, "Debt should remain unchanged after swap " );
436475 }
476+
477+ /// @notice Test that the wrapper can handle being called three times in the same chain
478+ /// @dev Two users close positions in the same direction (long SUSDS), one user closes opposite (long WETH)
479+ function test_CollateralSwapWrapper_ThreeUsers_TwoSameOneOpposite () external {
480+ vm.skip (bytes (forkRpcUrl).length == 0 );
481+
482+ // Configure vault LTVs for both directions
483+ vm.startPrank (IEVault (ESUSDS).governorAdmin ());
484+ IEVault (ESUSDS).setLTV (EWETH, 0.9e4 , 0.9e4 , 0 );
485+ IEVault (ESUSDS).setLTV (EWBTC, 0.9e4 , 0.9e4 , 0 );
486+ vm.stopPrank ();
487+ vm.startPrank (IEVault (EWETH).governorAdmin ());
488+ IEVault (EWETH).setLTV (ESUSDS, 0.9e4 , 0.9e4 , 0 );
489+ IEVault (EWETH).setLTV (EWBTC, 0.9e4 , 0.9e4 , 0 );
490+ vm.stopPrank ();
491+
492+ // Setup accounts
493+ address account1 = address (uint160 (user) ^ 1 );
494+ address account2 = address (uint160 (user2) ^ 1 );
495+ address account3 = address (uint160 (user3) ^ 1 );
496+
497+ // Setup User1: Long SUSDS (SUSDS collateral, WETH debt). ~1 ETH debt
498+ deal (SUSDS, user, 10000 ether);
499+ _setupLeveragedPositionFor (user, account1, SUSDS, ESUSDS, EWETH, 3500 ether, 1 ether);
500+
501+ // Setup User2: Long SUSDS (SUSDS collateral, WETH debt). ~3 ETH debt
502+ deal (SUSDS, user2, 10000 ether);
503+ _setupLeveragedPositionFor (user2, account2, SUSDS, ESUSDS, EWETH, 10000 ether, 3 ether);
504+
505+ // Setup User3: Long WETH (WETH collateral, SUSDS debt). ~5000 SUSDS debt
506+ deal (WETH, user3, 3 ether);
507+ _setupLeveragedPositionFor (user3, account3, WETH, EWETH, ESUSDS, 3 ether, 5000 ether);
508+
509+ // Verify positions exist
510+ assertEq (IEVault (EWETH).debtOf (account1), 1 ether, "User1 should have WETH debt " );
511+ assertEq (IEVault (EWETH).debtOf (account2), 3 ether, "User2 should have WETH debt " );
512+ assertEq (IEVault (ESUSDS).debtOf (account3), 5000 ether, "User3 should have SUSDS debt " );
513+
514+ // Create params for all users
515+ CowEvcCollateralSwapWrapper.CollateralSwapParams memory params1 = CowEvcCollateralSwapWrapper.CollateralSwapParams ({
516+ owner: user,
517+ account: account1,
518+ deadline: block .timestamp + 1 hours,
519+ fromVault: ESUSDS,
520+ toVault: EWETH,
521+ swapAmount: 1000 ether,
522+ kind: GPv2Order.KIND_SELL
523+ });
524+
525+ CowEvcCollateralSwapWrapper.CollateralSwapParams memory params2 = CowEvcCollateralSwapWrapper.CollateralSwapParams ({
526+ owner: user2,
527+ account: account2,
528+ deadline: block .timestamp + 1 hours,
529+ fromVault: ESUSDS,
530+ toVault: EWBTC,
531+ swapAmount: 0.005e8 , // about 500 ESUSDS
532+ kind: GPv2Order.KIND_BUY
533+ });
534+
535+ CowEvcCollateralSwapWrapper.CollateralSwapParams memory params3 = CowEvcCollateralSwapWrapper.CollateralSwapParams ({
536+ owner: user3,
537+ account: account3,
538+ deadline: block .timestamp + 1 hours,
539+ fromVault: EWETH,
540+ toVault: ESUSDS,
541+ swapAmount: 1000 ether,
542+ kind: GPv2Order.KIND_BUY
543+ });
544+
545+ // Create permit signatures for all users
546+ bytes memory permitSignature1 = _createPermitSignatureFor (params1, privateKey);
547+ bytes memory permitSignature2 = _createPermitSignatureFor (params2, privateKey2);
548+ bytes memory permitSignature3 = _createPermitSignatureFor (params3, privateKey3);
549+
550+ // Setup approvals for all users
551+ _setupSubaccountApprovals (params1);
552+ _setupSubaccountApprovals (params2);
553+ _setupSubaccountApprovals (params3);
554+
555+ // Create settlement with all three trades
556+ uint32 validTo = uint32 (block .timestamp + 1 hours);
557+
558+ address [] memory tokens = new address [](3 );
559+ tokens[0 ] = ESUSDS;
560+ tokens[1 ] = EWETH;
561+ tokens[2 ] = EWBTC;
562+
563+ uint256 [] memory clearingPrices = new uint256 [](3 );
564+ clearingPrices[0 ] = 1 ether ; // eSUSDS price
565+ clearingPrices[1 ] = 2500 ether ; // eWETH price
566+ clearingPrices[2 ] = 100000 ether * 1e10 ; // eWBTC price
567+
568+ ICowSettlement.Trade[] memory trades = new ICowSettlement.Trade [](3 );
569+ (trades[0 ],,) =
570+ setupCowOrder (tokens, 0 , 1 , params1.swapAmount, 0 , validTo, user, account1, false );
571+ (trades[1 ],,) =
572+ setupCowOrder (tokens, 0 , 2 , 1e24 , params2.swapAmount, validTo, user2, account2, true );
573+ (trades[2 ],,) =
574+ setupCowOrder (tokens, 1 , 0 , 1e24 , params3.swapAmount, validTo, user3, account3, true );
575+
576+ // Setup interactions
577+ ICowSettlement.Interaction[][3 ] memory interactions;
578+ interactions[0 ] = new ICowSettlement.Interaction [](0 );
579+ interactions[1 ] = new ICowSettlement.Interaction [](4 );
580+ interactions[2 ] = new ICowSettlement.Interaction [](0 );
581+
582+ // We pull the money out of the euler vaults (we only need to withdraw for the BTC exchange)
583+ interactions[1 ][0 ] = getWithdrawInteraction (
584+ ESUSDS, 500 ether
585+ );
586+ // we dont need to withdraw the WETH vault because we already have the WETH to direct exchange
587+ //interactions[1][1] = getWithdrawInteraction(EWETH, );
588+
589+ // We swap. Since WETH <> SUSD trades are already coincidence of wants, we trade only the SUSD -> WBTC trade
590+ interactions[1 ][1 ] = getSwapInteraction (SUSDS, WBTC, 500 ether);
591+
592+ // We deposit back into WBTC
593+ interactions[1 ][2 ] = getDepositInteraction (EWBTC, 0.005e8 );
594+
595+ // We "skim" to get the tokens
596+ interactions[1 ][3 ] = getSkimInteraction (EWBTC);
597+
598+ // Encode settlement data
599+ bytes memory settleData = abi.encodeCall (ICowSettlement.settle, (tokens, clearingPrices, trades, interactions));
600+
601+ // Chain wrapper data
602+ bytes memory wrapper1Data = abi.encode (params1, permitSignature1);
603+ bytes memory wrapper2Data = abi.encode (params2, permitSignature2);
604+ bytes memory wrapper3Data = abi.encode (params3, permitSignature3);
605+
606+ bytes memory wrapperData = abi.encodePacked (
607+ uint16 (wrapper1Data.length ),
608+ wrapper1Data,
609+ address (collateralSwapWrapper),
610+ uint16 (wrapper2Data.length ),
611+ wrapper2Data,
612+ address (collateralSwapWrapper),
613+ uint16 (wrapper3Data.length ),
614+ wrapper3Data
615+ );
616+
617+ // Execute wrapped settlement
618+ address [] memory targets = new address [](1 );
619+ bytes [] memory datas = new bytes [](1 );
620+ targets[0 ] = address (collateralSwapWrapper);
621+ datas[0 ] = abi.encodeCall (CowWrapper.wrappedSettle, (settleData, wrapperData));
622+ solver.runBatch (targets, datas);
623+
624+ // Verify all positions closed successfully
625+ assertEq (IEVault (EWETH).debtOf (account1), 1 ether, "User1 should have WETH debt " );
626+ assertEq (IEVault (EWETH).debtOf (account2), 3 ether, "User2 should have WETH debt " );
627+ assertEq (IEVault (ESUSDS).debtOf (account3), 5000 ether, "User3 should have SUSDS debt " );
628+
629+ // TODO: check collaterals
630+ }
437631}
0 commit comments