@@ -3,7 +3,7 @@ pragma solidity ^0.8;
33
44import {GPv2Order, IERC20 as CowERC20} from "cow/libraries/GPv2Order.sol " ;
55
6- import {IEVault, IERC4626 , IERC20 } from "euler-vault-kit/src/EVault/IEVault.sol " ;
6+ import {IEVault, IERC4626 , IERC20 , IVault } from "euler-vault-kit/src/EVault/IEVault.sol " ;
77
88import {CowEvcOpenPositionWrapper} from "../src/CowEvcOpenPositionWrapper.sol " ;
99import {ICowSettlement, CowWrapper} from "../src/CowWrapper.sol " ;
@@ -19,15 +19,15 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
1919 CowEvcOpenPositionWrapper public openPositionWrapper;
2020 SignerECDSA internal ecdsa;
2121
22- uint256 constant SUSDS_MARGIN = 2000e18 ;
22+ uint256 constant SUSDS_MARGIN = 5000e18 ;
2323 uint256 constant DEFAULT_BORROW_AMOUNT = 1e18 ;
24- uint256 constant DEFAULT_BUY_AMOUNT = 999e18 ;
24+ uint256 constant DEFAULT_BUY_AMOUNT = 2495e18 ;
2525
2626 function setUp () public override {
2727 super .setUp ();
2828
2929 // Deploy the new open position wrapper
30- openPositionWrapper = new CowEvcOpenPositionWrapper (address (evc ), COW_SETTLEMENT);
30+ openPositionWrapper = new CowEvcOpenPositionWrapper (address (EVC ), COW_SETTLEMENT);
3131
3232 // Add wrapper as a solver
3333 GPv2AllowListAuthentication allowList = GPv2AllowListAuthentication (address (COW_SETTLEMENT.authenticator ()));
@@ -36,7 +36,7 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
3636 allowList.addSolver (address (openPositionWrapper));
3737 vm.stopPrank ();
3838
39- ecdsa = new SignerECDSA (evc );
39+ ecdsa = new SignerECDSA (EVC );
4040
4141 // sUSDS is not currently a collateral for WETH borrow, fix it
4242 vm.startPrank (IEVault (EWETH).governorAdmin ());
@@ -83,8 +83,8 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
8383 function _setupUserPreApprovedFlow (address account , bytes32 hash ) internal {
8484 vm.startPrank (user);
8585 IERC20 (SUSDS).approve (ESUSDS, type (uint256 ).max);
86- evc .setAccountOperator (user, address (openPositionWrapper), true );
87- evc .setAccountOperator (account, address (openPositionWrapper), true );
86+ EVC .setAccountOperator (user, address (openPositionWrapper), true );
87+ EVC .setAccountOperator (account, address (openPositionWrapper), true );
8888 openPositionWrapper.setPreApprovedHash (hash, true );
8989 vm.stopPrank ();
9090 }
@@ -150,35 +150,18 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
150150 address buyVaultToken ,
151151 uint256 sellAmount ,
152152 uint256 buyAmount
153- ) public view returns (SettlementData memory r ) {
153+ ) public returns (SettlementData memory r ) {
154154 uint32 validTo = uint32 (block .timestamp + 1 hours);
155155
156- // Create order data
157- r.orderData = GPv2Order.Data ({
158- sellToken: CowERC20 (sellToken),
159- buyToken: CowERC20 (buyVaultToken),
160- receiver: receiver,
161- sellAmount: sellAmount,
162- buyAmount: buyAmount,
163- validTo: validTo,
164- appData: bytes32 (0 ),
165- feeAmount: 0 ,
166- kind: GPv2Order.KIND_SELL,
167- partiallyFillable: false ,
168- sellTokenBalance: GPv2Order.BALANCE_ERC20,
169- buyTokenBalance: GPv2Order.BALANCE_ERC20
170- });
171-
172- // Get order UID
173- r.orderUid = getOrderUid (owner, r.orderData);
174-
175- // Get trade data
176- r.trades = new ICowSettlement.Trade [](1 );
177- r.trades[0 ] = getTradeData (sellAmount, buyAmount, validTo, owner, r.orderData.receiver, false );
156+ // Create trade and extract order data
178157
179158 // Get tokens and prices
180159 (r.tokens, r.clearingPrices) = getTokensAndPrices ();
181160
161+ r.trades = new ICowSettlement.Trade [](1 );
162+ (r.trades[0 ], r.orderData, r.orderUid) =
163+ setupCowOrder (r.tokens, 0 , 1 , sellAmount, buyAmount, validTo, owner, receiver, false );
164+
182165 // Setup interactions - swap WETH to SUSDS, deposit to vault, and skim
183166 r.interactions = [
184167 new ICowSettlement.Interaction [](0 ),
@@ -187,7 +170,7 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
187170 ];
188171 r.interactions[1 ][0 ] = getSwapInteraction (sellToken, IERC4626 (buyVaultToken).asset (), sellAmount);
189172 r.interactions[1 ][1 ] = getDepositInteraction (buyVaultToken, buyAmount + 1 ether);
190- r.interactions[1 ][2 ] = getSkimInteraction ();
173+ r.interactions[1 ][2 ] = getSkimInteraction (buyVaultToken );
191174 }
192175
193176 /// @notice Test opening a leveraged position using the new wrapper
@@ -206,9 +189,8 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
206189 // Setup user approvals
207190 _setupUserSusdsApproval ();
208191
209- // User signs order (using setPreSignature for testing)
210- vm.prank (user);
211- COW_SETTLEMENT.setPreSignature (settlement.orderUid, true );
192+ // User signs order
193+ // Does not need to run here because its done in `setupCowOrder`
212194
213195 // Create permit signature
214196 bytes memory permitSignature = _createPermitSignature (params);
@@ -328,8 +310,7 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
328310 _setupUserPreApprovedFlow (account, hash);
329311
330312 // User pre-approves the order on CowSwap
331- vm.prank (user);
332- COW_SETTLEMENT.setPreSignature (settlement.orderUid, true );
313+ // Does not need to run here because its executed in `setupCowOrder`
333314
334315 // Record balances before
335316 uint256 debtBefore = IEVault (EWETH).debtOf (account);
@@ -359,4 +340,213 @@ contract CowEvcOpenPositionWrapperTest is CowBaseTest {
359340 _verifyPositionOpened (account, DEFAULT_BUY_AMOUNT + SUSDS_MARGIN, DEFAULT_BORROW_AMOUNT, 1 ether);
360341 assertEq (debtBefore, 0 , "User should start with no debt " );
361342 }
343+
344+ /// @notice Test that the wrapper can handle being called three times in the same chain
345+ /// @dev Two users open positions in the same direction (long SUSDS), one user opens opposite (long WETH)
346+ function test_OpenPositionWrapper_ThreeUsers_TwoSameOneOpposite () external {
347+ vm.skip (bytes (forkRpcUrl).length == 0 );
348+
349+ // Configure vault LTVs for both directions
350+ // Already configured: eSUSDS collateral -> eWETH borrow
351+ // Need to configure: eWETH collateral -> eSUSDS borrow
352+ vm.startPrank (IEVault (ESUSDS).governorAdmin ());
353+ IEVault (ESUSDS).setLTV (EWETH, 0.9e4 , 0.9e4 , 0 );
354+ vm.stopPrank ();
355+
356+ // Setup User1: Has SUSDS, will borrow WETH and swap WETH→SUSDS (long SUSDS). Around 1 ETH
357+ address account1 = address (uint160 (user) ^ 1 );
358+ deal (SUSDS, user, 1000 ether);
359+
360+ // Approve SUSDS spending by eSUSDS for user1
361+ vm.startPrank (user);
362+ IERC20 (SUSDS).approve (ESUSDS, type (uint256 ).max);
363+
364+ // Approve WETH for COW Protocol for user1
365+ IERC20 (WETH).approve (COW_SETTLEMENT.vaultRelayer (), type (uint256 ).max);
366+
367+ vm.stopPrank ();
368+
369+ // Setup User2: Has SUSDS, will borrow WETH and swap WETH→SUSDS. 3x the size (long SUSDS, same direction as user1). Around 3 ETH
370+ address account2 = address (uint160 (user2) ^ 1 );
371+ deal (SUSDS, user2, 1000 ether);
372+
373+ // Approve SUSDS spending by eSUSDS for user2
374+ vm.startPrank (user2);
375+ IERC20 (SUSDS).approve (ESUSDS, type (uint256 ).max);
376+
377+ // Approve WETH for COW Protocol for user2
378+ IERC20 (WETH).approve (COW_SETTLEMENT.vaultRelayer (), type (uint256 ).max);
379+
380+ vm.stopPrank ();
381+
382+ // Setup User3: Has WETH, will borrow SUSDS and swap SUSDS→WETH (long WETH, opposite direction). Around $5000
383+ address account3 = address (uint160 (user3) ^ 1 );
384+ deal (WETH, user3, 1 ether);
385+
386+ // Approve WETH spending by eWETH for user2
387+ vm.startPrank (user3);
388+ IERC20 (WETH).approve (EWETH, type (uint256 ).max);
389+
390+ // Approve SUSDS for COW Protocol for user3
391+ IERC20 (SUSDS).approve (COW_SETTLEMENT.vaultRelayer (), type (uint256 ).max);
392+
393+ vm.stopPrank ();
394+
395+ // Create params for User1: Deposit SUSDS, borrow WETH
396+ CowEvcOpenPositionWrapper.OpenPositionParams memory params1 = CowEvcOpenPositionWrapper.OpenPositionParams ({
397+ owner: user,
398+ account: account1,
399+ deadline: block .timestamp + 1 hours,
400+ collateralVault: ESUSDS,
401+ borrowVault: EWETH,
402+ collateralAmount: 1000 ether,
403+ borrowAmount: 1 ether
404+ });
405+
406+ // Create params for User2: Deposit SUSDS, borrow WETH (same direction as User1)
407+ CowEvcOpenPositionWrapper.OpenPositionParams memory params2 = CowEvcOpenPositionWrapper.OpenPositionParams ({
408+ owner: user2,
409+ account: account2,
410+ deadline: block .timestamp + 1 hours,
411+ collateralVault: ESUSDS,
412+ borrowVault: EWETH,
413+ collateralAmount: 1000 ether,
414+ borrowAmount: 3 ether
415+ });
416+
417+ CowEvcOpenPositionWrapper.OpenPositionParams memory params3 = CowEvcOpenPositionWrapper.OpenPositionParams ({
418+ owner: user3,
419+ account: account3,
420+ deadline: block .timestamp + 1 hours,
421+ collateralVault: EWETH,
422+ borrowVault: ESUSDS,
423+ collateralAmount: 1 ether,
424+ borrowAmount: 5000 ether
425+ });
426+
427+ // Create permit signatures for all users
428+ ecdsa.setPrivateKey (privateKey);
429+ bytes memory permitSignature1 = ecdsa.signPermit (
430+ params1.owner,
431+ address (openPositionWrapper),
432+ uint256 (uint160 (address (openPositionWrapper))),
433+ 0 ,
434+ params1.deadline,
435+ 0 ,
436+ openPositionWrapper.getSignedCalldata (params1)
437+ );
438+
439+ ecdsa.setPrivateKey (privateKey2);
440+ bytes memory permitSignature2 = ecdsa.signPermit (
441+ params2.owner,
442+ address (openPositionWrapper),
443+ uint256 (uint160 (address (openPositionWrapper))),
444+ 0 ,
445+ params2.deadline,
446+ 0 ,
447+ openPositionWrapper.getSignedCalldata (params2)
448+ );
449+
450+ ecdsa.setPrivateKey (privateKey3);
451+ bytes memory permitSignature3 = ecdsa.signPermit (
452+ params3.owner,
453+ address (openPositionWrapper),
454+ uint256 (uint160 (address (openPositionWrapper))),
455+ 0 ,
456+ params3.deadline,
457+ 0 ,
458+ openPositionWrapper.getSignedCalldata (params3)
459+ );
460+
461+ // Create settlement with all three trades
462+ uint32 validTo = uint32 (block .timestamp + 1 hours);
463+
464+ // Setup tokens array: WETH, eSUSDS, SUSDS, eWETH
465+ address [] memory tokens = new address [](4 );
466+ tokens[0 ] = SUSDS;
467+ tokens[1 ] = WETH;
468+ tokens[2 ] = ESUSDS;
469+ tokens[3 ] = EWETH;
470+
471+ uint256 [] memory clearingPrices = new uint256 [](4 );
472+ clearingPrices[0 ] = 1 ether ; // SUSDS price
473+ clearingPrices[1 ] = 2500 ether ; // WETH price
474+ clearingPrices[2 ] = 0.99 ether ; // eSUSDS price
475+ clearingPrices[3 ] = 2495 ether ; // eWETH price
476+
477+ // Create trades and extract orders
478+ ICowSettlement.Trade[] memory trades = new ICowSettlement.Trade [](3 );
479+
480+ // Trade 1: User1 sells WETH for eSUSDS
481+ (trades[0 ],,) = setupCowOrder (tokens, 1 , 2 , params1.borrowAmount, 0 , validTo, user, account1, false );
482+
483+ // Trade 2: User2 sells WETH for eSUSDS (same direction as User1)
484+ (trades[1 ],,) = setupCowOrder (tokens, 1 , 2 , params2.borrowAmount, 0 , validTo, user2, account2, false );
485+
486+ // Trade 3: User3 sells SUSDS for eWETH (opposite direction)
487+ (trades[2 ],,) = setupCowOrder (tokens, 0 , 3 , params3.borrowAmount, 0 , validTo, user3, account3, false );
488+
489+ // Setup interactions to handle the swaps and deposits
490+ ICowSettlement.Interaction[][3 ] memory interactions;
491+ interactions[0 ] = new ICowSettlement.Interaction [](0 );
492+ interactions[1 ] = new ICowSettlement.Interaction [](5 );
493+ interactions[2 ] = new ICowSettlement.Interaction [](0 );
494+
495+ // Trade 1 & 2: coincidence of wants: WETH → SUSDS for the difference in all the users trades (2 WETH total difference)
496+ interactions[1 ][0 ] = getSwapInteraction (WETH, SUSDS, 2 ether);
497+ // Deposit SUSDS to eSUSDS vault for both user1 and user2
498+ interactions[1 ][1 ] = getDepositInteraction (ESUSDS, 10000 ether);
499+ // Deposit WETH to eWETH vault
500+ interactions[1 ][2 ] = getDepositInteraction (ESUSDS, 2 ether);
501+
502+ // Skim eSUSDS vault
503+ interactions[1 ][3 ] = getSkimInteraction (tokens[2 ]);
504+ // Skim eWETH vault
505+ interactions[1 ][4 ] = getSkimInteraction (tokens[3 ]);
506+
507+ // Encode settlement data
508+ bytes memory settleData = abi.encodeCall (ICowSettlement.settle, (tokens, clearingPrices, trades, interactions));
509+
510+ // Chain wrapper data: wrapper(user1) → wrapper(user2) → wrapper(user3) → settlement
511+ // Format: [2-byte len][wrapper1 data][next wrapper address][2-byte len][wrapper2 data][next wrapper address][2-byte len][wrapper3 data]
512+ bytes memory wrapper1Data = abi.encode (params1, permitSignature1);
513+ bytes memory wrapper2Data = abi.encode (params2, permitSignature2);
514+ bytes memory wrapper3Data = abi.encode (params3, permitSignature3);
515+
516+ bytes memory wrapperData = abi.encodePacked (
517+ uint16 (wrapper1Data.length ),
518+ wrapper1Data,
519+ address (openPositionWrapper),
520+ uint16 (wrapper2Data.length ),
521+ wrapper2Data,
522+ address (openPositionWrapper),
523+ uint16 (wrapper3Data.length ),
524+ wrapper3Data
525+ );
526+
527+ // Execute wrapped settlement through solver
528+ // Note: We don't use expectEmit here because there are many Transfer events
529+ // from the complex multi-user settlement that interfere with strict event matching
530+ address [] memory targets = new address [](1 );
531+ bytes [] memory datas = new bytes [](1 );
532+ targets[0 ] = address (openPositionWrapper);
533+ datas[0 ] = abi.encodeCall (openPositionWrapper.wrappedSettle, (settleData, wrapperData));
534+ solver.runBatch (targets, datas);
535+
536+ // Verify all three positions were opened successfully
537+ // User1: Should have SUSDS collateral and WETH debt
538+ _verifyPositionOpened (account1, 1000 ether + 2500 ether, 1 ether, 100 ether);
539+
540+ // User2: Should have SUSDS collateral and WETH debt (same as User1)
541+ _verifyPositionOpened (account2, 1000 ether + 7500 ether, 3 ether, 100 ether);
542+
543+ // User3: Should have WETH collateral and SUSDS debt
544+ assertApproxEqAbs (
545+ IEVault (EWETH).convertToAssets (IERC20 (EWETH).balanceOf (account3)),
546+ 1 ether + 2 ether,
547+ 0.2 ether,
548+ "User3 should have WETH collateral deposited "
549+ );
550+ assertEq (IEVault (ESUSDS).debtOf (account3), 5000 ether, "User3 should have SUSDS debt " );
551+ }
362552}
0 commit comments