Skip to content

Commit 96cda14

Browse files
committed
add test for verifying CoW order
1 parent c8989f8 commit 96cda14

File tree

6 files changed

+291
-59
lines changed

6 files changed

+291
-59
lines changed

foundry.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"lib/cow": {
33
"branch": {
44
"name": "feat/wrapper",
5-
"rev": "1e8127f476f8fef7758cf25033a0010d325dba8d"
5+
"rev": "277be444ca1fce84ec55053f41ccbd9dc5672ffe"
66
}
77
},
88
"lib/euler-vault-kit": {

test/CowEvcOpenPositionWrapper.t.sol

Lines changed: 226 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ pragma solidity ^0.8;
33

44
import {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

88
import {CowEvcOpenPositionWrapper} from "../src/CowEvcOpenPositionWrapper.sol";
99
import {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

Comments
 (0)