Skip to content

Commit 0e06bcc

Browse files
committed
add test for CoW
1 parent 201535d commit 0e06bcc

File tree

1 file changed

+214
-0
lines changed

1 file changed

+214
-0
lines changed

test/CowEvcClosePositionWrapper.t.sol

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,73 @@ contract CowEvcClosePositionWrapperTest is CowBaseTest {
190190
vm.stopPrank();
191191
}
192192

193+
/// @notice Helper to set up a leveraged position for any user
194+
/// @dev More flexible version that accepts owner, account, and vault parameters
195+
function _setupLeveragedPositionFor(
196+
address owner,
197+
address account,
198+
address collateralAsset,
199+
address collateralVault,
200+
address borrowVault,
201+
uint256 collateralAmount,
202+
uint256 borrowAmount
203+
) internal {
204+
vm.startPrank(owner);
205+
IERC20(collateralAsset).approve(collateralVault, type(uint256).max);
206+
EVC.enableCollateral(account, collateralVault);
207+
EVC.enableController(account, borrowVault);
208+
IERC4626(collateralVault).deposit(collateralAmount, account);
209+
vm.stopPrank();
210+
211+
vm.prank(account);
212+
IBorrowing(borrowVault).borrow(borrowAmount, owner);
213+
}
214+
215+
/// @notice Setup approvals for a specific user to close their position
216+
function _setupClosePositionApprovalsFor(
217+
address owner,
218+
address account,
219+
address collateralVault,
220+
address repaymentAsset
221+
) internal {
222+
vm.startPrank(owner);
223+
224+
// Approve vault shares from subaccount
225+
IEVC.BatchItem[] memory items = new IEVC.BatchItem[](1);
226+
items[0] = IEVC.BatchItem({
227+
onBehalfOfAccount: account,
228+
targetContract: collateralVault,
229+
value: 0,
230+
data: abi.encodeCall(IERC20.approve, (address(closePositionWrapper), type(uint256).max))
231+
});
232+
EVC.batch(items);
233+
234+
// Approve vault shares for settlement
235+
IEVault(collateralVault).approve(COW_SETTLEMENT.vaultRelayer(), type(uint256).max);
236+
237+
// Approve wrapper to spend repayment asset
238+
IERC20(repaymentAsset).approve(address(closePositionWrapper), type(uint256).max);
239+
240+
vm.stopPrank();
241+
}
242+
243+
/// @notice Create permit signature for any user
244+
function _createPermitSignatureFor(
245+
CowEvcClosePositionWrapper.ClosePositionParams memory params,
246+
uint256 userPrivateKey
247+
) internal returns (bytes memory) {
248+
ecdsa.setPrivateKey(userPrivateKey);
249+
return ecdsa.signPermit(
250+
params.owner,
251+
address(closePositionWrapper),
252+
uint256(uint160(address(closePositionWrapper))),
253+
0,
254+
params.deadline,
255+
0,
256+
closePositionWrapper.getSignedCalldata(params)
257+
);
258+
}
259+
193260
/// @notice Create settlement data for closing a leveraged position
194261
/// @dev Sells vault shares to buy repayment token (WETH)
195262
function getClosePositionSettlement(
@@ -478,4 +545,151 @@ contract CowEvcClosePositionWrapperTest is CowBaseTest {
478545
assertEq(IEVault(EWETH).debtOf(account), 0, "User should have no debt after closing");
479546
assertEq(debtBefore, borrowAmount, "User should have started with debt");
480547
}
548+
549+
/// @notice Test that the wrapper can handle being called three times in the same chain
550+
/// @dev Two users close positions in the same direction (long SUSDS), one user closes opposite (long WETH)
551+
function test_ClosePositionWrapper_ThreeUsers_TwoSameOneOpposite() external {
552+
vm.skip(bytes(forkRpcUrl).length == 0);
553+
554+
// Configure vault LTVs for both directions
555+
vm.startPrank(IEVault(ESUSDS).governorAdmin());
556+
IEVault(ESUSDS).setLTV(EWETH, 0.9e4, 0.9e4, 0);
557+
vm.stopPrank();
558+
559+
// Setup accounts
560+
address account1 = address(uint160(user) ^ 1);
561+
address account2 = address(uint160(user2) ^ 1);
562+
address account3 = address(uint160(user3) ^ 1);
563+
564+
// Setup User1: Long SUSDS (SUSDS collateral, WETH debt). ~1 ETH debt
565+
deal(SUSDS, user, 10000 ether);
566+
_setupLeveragedPositionFor(user, account1, SUSDS, ESUSDS, EWETH, 3500 ether, 1 ether);
567+
568+
// Setup User2: Long SUSDS (SUSDS collateral, WETH debt). ~3 ETH debt
569+
deal(SUSDS, user2, 10000 ether);
570+
_setupLeveragedPositionFor(user2, account2, SUSDS, ESUSDS, EWETH, 10000 ether, 3 ether);
571+
572+
// Setup User3: Long WETH (WETH collateral, SUSDS debt). ~5000 SUSDS debt
573+
deal(WETH, user3, 3 ether);
574+
_setupLeveragedPositionFor(user3, account3, WETH, EWETH, ESUSDS, 3 ether, 5000 ether);
575+
576+
// Verify positions exist
577+
assertEq(IEVault(EWETH).debtOf(account1), 1 ether, "User1 should have WETH debt");
578+
assertEq(IEVault(EWETH).debtOf(account2), 3 ether, "User2 should have WETH debt");
579+
assertEq(IEVault(ESUSDS).debtOf(account3), 5000 ether, "User3 should have SUSDS debt");
580+
581+
// Setup approvals for all users
582+
_setupClosePositionApprovalsFor(user, account1, ESUSDS, WETH);
583+
_setupClosePositionApprovalsFor(user2, account2, ESUSDS, WETH);
584+
_setupClosePositionApprovalsFor(user3, account3, EWETH, SUSDS);
585+
586+
// Create params for all users
587+
CowEvcClosePositionWrapper.ClosePositionParams memory params1 = CowEvcClosePositionWrapper.ClosePositionParams({
588+
owner: user,
589+
account: account1,
590+
deadline: block.timestamp + 1 hours,
591+
borrowVault: EWETH,
592+
collateralVault: ESUSDS,
593+
collateralAmount: 2550 ether,
594+
repayAmount: 1.001 ether,
595+
kind: GPv2Order.KIND_BUY
596+
});
597+
598+
CowEvcClosePositionWrapper.ClosePositionParams memory params2 = CowEvcClosePositionWrapper.ClosePositionParams({
599+
owner: user2,
600+
account: account2,
601+
deadline: block.timestamp + 1 hours,
602+
borrowVault: EWETH,
603+
collateralVault: ESUSDS,
604+
collateralAmount: 7600 ether,
605+
repayAmount: 3.003 ether,
606+
kind: GPv2Order.KIND_BUY
607+
});
608+
609+
CowEvcClosePositionWrapper.ClosePositionParams memory params3 = CowEvcClosePositionWrapper.ClosePositionParams({
610+
owner: user3,
611+
account: account3,
612+
deadline: block.timestamp + 1 hours,
613+
borrowVault: ESUSDS,
614+
collateralVault: EWETH,
615+
collateralAmount: 2.1 ether,
616+
repayAmount: 5005 ether,
617+
kind: GPv2Order.KIND_BUY
618+
});
619+
620+
// Create permit signatures for all users
621+
bytes memory permitSignature1 = _createPermitSignatureFor(params1, privateKey);
622+
bytes memory permitSignature2 = _createPermitSignatureFor(params2, privateKey2);
623+
bytes memory permitSignature3 = _createPermitSignatureFor(params3, privateKey3);
624+
625+
// Create settlement with all three trades
626+
uint32 validTo = uint32(block.timestamp + 1 hours);
627+
628+
address[] memory tokens = new address[](4);
629+
tokens[0] = SUSDS;
630+
tokens[1] = WETH;
631+
tokens[2] = ESUSDS;
632+
tokens[3] = EWETH;
633+
634+
uint256[] memory clearingPrices = new uint256[](4);
635+
clearingPrices[0] = 1 ether; // SUSDS price
636+
clearingPrices[1] = 2500 ether; // WETH price
637+
clearingPrices[2] = 0.99 ether; // eSUSDS price
638+
clearingPrices[3] = 2495 ether; // eWETH price
639+
640+
ICowSettlement.Trade[] memory trades = new ICowSettlement.Trade[](3);
641+
(trades[0],,) =
642+
setupCowOrder(tokens, 2, 1, params1.collateralAmount, params1.repayAmount, validTo, user, user, true);
643+
(trades[1],,) =
644+
setupCowOrder(tokens, 2, 1, params2.collateralAmount, params2.repayAmount, validTo, user2, user2, true);
645+
(trades[2],,) =
646+
setupCowOrder(tokens, 3, 0, params3.collateralAmount, params3.repayAmount, validTo, user3, user3, true);
647+
648+
// Setup interactions
649+
ICowSettlement.Interaction[][3] memory interactions;
650+
interactions[0] = new ICowSettlement.Interaction[](0);
651+
interactions[1] = new ICowSettlement.Interaction[](3);
652+
interactions[2] = new ICowSettlement.Interaction[](0);
653+
654+
// We pull the money out of the euler vaults
655+
interactions[1][0] = getWithdrawInteraction(
656+
ESUSDS, (params1.repayAmount + params2.repayAmount) * clearingPrices[1] / clearingPrices[0]
657+
);
658+
interactions[1][1] = getWithdrawInteraction(EWETH, params3.repayAmount * clearingPrices[0] / clearingPrices[1]);
659+
660+
// We swap. We only need to swap the difference of the 3 closes (since coincidence of wants)
661+
// It comes out to 5000 SUSDS needs to become WETH
662+
interactions[1][2] = getSwapInteraction(SUSDS, WETH, 5000 ether);
663+
664+
// Encode settlement data
665+
bytes memory settleData = abi.encodeCall(ICowSettlement.settle, (tokens, clearingPrices, trades, interactions));
666+
667+
// Chain wrapper data
668+
bytes memory wrapper1Data = abi.encode(params1, permitSignature1);
669+
bytes memory wrapper2Data = abi.encode(params2, permitSignature2);
670+
bytes memory wrapper3Data = abi.encode(params3, permitSignature3);
671+
672+
bytes memory wrapperData = abi.encodePacked(
673+
uint16(wrapper1Data.length),
674+
wrapper1Data,
675+
address(closePositionWrapper),
676+
uint16(wrapper2Data.length),
677+
wrapper2Data,
678+
address(closePositionWrapper),
679+
uint16(wrapper3Data.length),
680+
wrapper3Data
681+
);
682+
683+
// Execute wrapped settlement
684+
address[] memory targets = new address[](1);
685+
bytes[] memory datas = new bytes[](1);
686+
targets[0] = address(closePositionWrapper);
687+
datas[0] = abi.encodeCall(closePositionWrapper.wrappedSettle, (settleData, wrapperData));
688+
solver.runBatch(targets, datas);
689+
690+
// Verify all positions closed successfully
691+
assertEq(IEVault(EWETH).debtOf(account1), 0, "User1 should have no WETH debt after closing");
692+
assertEq(IEVault(EWETH).debtOf(account2), 0, "User2 should have no WETH debt after closing");
693+
assertEq(IEVault(ESUSDS).debtOf(account3), 0, "User3 should have no SUSDS debt after closing");
694+
}
481695
}

0 commit comments

Comments
 (0)