-
Notifications
You must be signed in to change notification settings - Fork 0
feat: collateral swap wrapper #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/close-position-wrapper
Are you sure you want to change the base?
feat: collateral swap wrapper #13
Conversation
|
Claude encountered an error —— View job Claude encountered an error —— View job Code Review in Progress
|
src/CowEvcCollateralSwapWrapper.sol
Outdated
| if (params.kind == KIND_BUY) { | ||
| (uint256 fromVaultPrice, uint256 toVaultPrice) = | ||
| _findRatePrices(settleData, params.fromVault, params.toVault); | ||
| transferAmount = params.swapAmount * toVaultPrice / fromVaultPrice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High Priority: Potential precision loss in price calculation
The calculation params.swapAmount * toVaultPrice / fromVaultPrice can suffer from precision loss, especially when:
- Tokens have different decimal places
- Prices have significant magnitude differences
- Division truncates instead of rounding up
Consider:
- Adding a small buffer/margin to ensure sufficient tokens are transferred
- Rounding up instead of down for
transferAmount - Documenting assumptions about token decimals and price precision
This could cause transactions to fail if the calculated transferAmount is slightly less than what's actually needed for the swap.
src/CowEvcCollateralSwapWrapper.sol
Outdated
|
|
||
| // 2. Settlement call | ||
| items[itemIndex] = IEVC.BatchItem({ | ||
| onBehalfOfAccount: address(this), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent with other wrappers: Should use onBehalfOfAccount: address(this) for consistency.
Both other wrappers use address(this):
CowEvcOpenPositionWrapper.sol:206CowEvcClosePositionWrapper.sol:262
Using address(this) is the correct approach for maintaining proper EVC operation context.
| } | ||
|
|
||
| /// @notice Test swapping collateral from main account | ||
| function test_CollateralSwapWrapper_MainAccount() external { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing comprehensive test coverage
This test suite only covers happy paths. Consider adding tests for:
Security:
- Unauthorized users attempting to swap another user's collateral
- Replay attacks with consumed pre-approved hashes
Edge Cases:
- Expired deadlines
- Insufficient collateral in account
- Invalid/non-existent vault addresses
- Zero swap amounts
- Maximum swap amounts
Health Factor:
- Swaps that would violate collateralization requirements
- Swaps during unhealthy account states
Order Types:
- KIND_BUY orders (all current tests use KIND_SELL)
- Mixed order scenarios
The current security tests (lines 351-392) are minimal and don't cover these important scenarios.
| evc.enableController(account, EWETH); | ||
|
|
||
| // Deposit collateral to the account, and add the approximate amount after swapping the borrowed collateral | ||
| IERC4626(ESUSDS).deposit(collateralAmount + borrowAmount * 2500e18 / 0.99e18, account); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test uses hardcoded price instead of actual prices
The calculation borrowAmount * 2500e18 / 0.99e18 hardcodes:
- WETH price as exactly 2500 sUSDS
- Slippage as 0.99 (1%)
This should use milkSwap.prices(WETH) to match actual test conditions and prevent flakiness if prices change. The hardcoded values may not accurately reflect the vault share amounts needed.
|
|
||
| // Borrow assets from the account. And confiscate the borrowed asset (needs to be called with account as onBehalfOf) | ||
| vm.startPrank(account); | ||
| IBorrowing(EWETH).borrow(borrowAmount, address(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suspicious borrow receiver
The borrowed WETH goes to address(this) (the test contract) instead of the account:
IBorrowing(EWETH).borrow(borrowAmount, address(this));This seems incorrect for "setting up a leveraged position" for the user's account. The borrowed assets should either:
- Go to the
account - Be transferred to the
accountafter borrowing - Have a comment explaining why the test contract receives them
This doesn't match the expected production flow where users borrow into their own accounts.
|
|
||
| r.clearingPrices = new uint256[](2); | ||
| r.clearingPrices[0] = milkSwap.prices(IERC4626(sellVaultToken).asset()); | ||
| r.clearingPrices[1] = milkSwap.prices(IERC4626(buyVaultToken).asset()) * 1 ether / 0.98 ether; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic number without explanation
The * 1 ether / 0.98 ether multiplier applies 2% slippage but lacks explanation.
Consider:
- Using a named constant:
uint256 constant TEST_SLIPPAGE = 0.98 ether; - Adding a comment explaining why 2% slippage is applied
- Testing different slippage values to ensure robustness
Same issue at line 148 with 0.999 ether (0.1%).
| /// @title CowEvcCollateralSwapWrapper | ||
| /// @notice A specialized wrapper for swapping collateral between vaults with EVC | ||
| /// @dev This wrapper enables atomic collateral swaps: | ||
| /// 1. Enable new collateral vault |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation doesn't match execution order
The documented order:
- Enable new collateral vault
- Transfer collateral from subaccount
- Execute settlement
Actual execution order:
- Enable vault (line 190)
- Execute settlement (line 246-251)
- Transfer happens during settlement (line 319-321)
Consider either:
- Updating the documentation to match actual execution order
- Adding a note that this describes the logical workflow, not execution order
src/CowEvcCollateralSwapWrapper.sol
Outdated
| if (params.kind == KIND_BUY) { | ||
| (uint256 fromVaultPrice, uint256 toVaultPrice) = | ||
| _findRatePrices(settleData, params.fromVault, params.toVault); | ||
| transferAmount = params.swapAmount * toVaultPrice / fromVaultPrice; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Potential precision loss in price calculation
transferAmount = params.swapAmount * toVaultPrice / fromVaultPrice;This calculation can suffer from precision loss, especially when:
- Tokens have different decimal places (e.g., WBTC has 8 decimals, sUSDS has 18)
- Prices have significant magnitude differences
- Division truncates instead of rounding up
Risk: The calculated transferAmount could be slightly less than what's actually needed for the swap to succeed, causing the transaction to revert.
Recommendation: Consider either:
- Adding a small buffer (e.g.,
transferAmount = (params.swapAmount * toVaultPrice / fromVaultPrice) + 1) - Rounding up instead of down
- Documenting the precision assumptions and decimal requirements
| evc.enableController(account, EWETH); | ||
|
|
||
| // Deposit collateral to the account, and add the approximate amount after swapping the borrowed collateral | ||
| IERC4626(ESUSDS).deposit(collateralAmount + borrowAmount * 2500e18 / 0.99e18, account); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High Priority: Hardcoded price instead of actual prices
IERC4626(ESUSDS).deposit(collateralAmount + borrowAmount * 2500e18 / 0.99e18, account);This hardcodes:
- WETH price as exactly 2500 sUSDS
- Slippage as 0.99 (1%)
Issue: This doesn't use milkSwap.prices(WETH) to match actual test conditions, which could lead to test flakiness if prices change or calculations don't match reality.
Recommendation:
uint256 wethPrice = milkSwap.prices(WETH);
uint256 susdsPrice = milkSwap.prices(SUSDS);
uint256 approximateSwapOutput = borrowAmount * wethPrice / susdsPrice * 99 / 100; // 1% slippage
IERC4626(ESUSDS).deposit(collateralAmount + approximateSwapOutput, account);|
|
||
| // Borrow assets from the account. And confiscate the borrowed asset (needs to be called with account as onBehalfOf) | ||
| vm.startPrank(account); | ||
| IBorrowing(EWETH).borrow(borrowAmount, address(this)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High Priority: Suspicious borrow receiver
IBorrowing(EWETH).borrow(borrowAmount, address(this));The borrowed WETH goes to address(this) (the test contract) instead of account.
Issue: This doesn't match the stated goal of "setting up a leveraged position" for the user's account. In a real scenario, users would borrow into their own accounts.
Recommendation: Either:
- Borrow to
accountif that's the intended behavior - Transfer the borrowed assets to
accountafter borrowing - Add a comment explaining why the test contract receives the borrowed assets
also found a bug which caused another test to succeed when it shouldn't (afaict) will have to finish this tomorrow
fedgiac
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of comments. My only major concern is about the function _findRatePrices: the price that it computes can be easily manipulated by solvers and may cause things to break even if the solver isn't malicious.
src/CowEvcCollateralSwapWrapper.sol
Outdated
| /// protection mixed in so that signed orders are only valid for specific | ||
| /// this contract. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// protection mixed in so that signed orders are only valid for specific | |
| /// this contract. | |
| /// protection mixed in so that signed orders are only valid for this | |
| /// contract at this address and on this one chain. |
src/CowEvcCollateralSwapWrapper.sol
Outdated
| require(msg.sender == address(EVC), Unauthorized(msg.sender)); | ||
| (address onBehalfOfAccount,) = EVC.getCurrentOnBehalfOfAccount(address(0)); | ||
| require(onBehalfOfAccount == address(this), Unauthorized(onBehalfOfAccount)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no further checks, meaning that if the EVC is in any way insecure or has some unexpected code path then someone would be able to execute arbitrary settlements. I don't think we want to risk that, the peace of mind we get for being able to sleep soundly if the EVC gets hacked is worth the extra checks.
We cam do this by hashing all input parameters and validate them here.
In the current code it wouldn't even be that hard: it's already writing abi.encodeCall(this.evcInternalSwap, (settleData, wrapperData, remainingWrapperData) to memory, so it's just about hashing that and comparing that with the hash of message.data as stored in transient storage.
It would need to account for the possibility of being called multiple times or out of order though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great suggestion! implemented 362e61a
its worth noting that since we are validating the calldata is correct and verifying its only called once (by clearing after the call is complete), we can now remove the onBehalfOfAccount check, which saves us a external contract call! We could hypothetically also remove the msg.sender == address(EVC) check as well but decided to leave it for now since its cheap.
prevents errors and code now explains itself, in theory.
also switch to computed params size it seems like its going to be too unweildly to do the calculation from a security and complexity perspective. Unfortunately it is another `approve()`
| uint256 balanceAfter = IERC20(params.fromVault).balanceOf(params.owner); | ||
|
|
||
| if (balanceAfter > balanceBefore) { | ||
| SafeERC20Lib.safeTransferFrom( | ||
| IERC20(params.fromVault), params.owner, params.account, balanceAfter - balanceBefore, address(0) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using balances is risky if users have any other order or logic changing the balance of their address. Will think a bit more about this tomorrow if there's another solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep that is a possibility, but it seems infitesimably rare and even when it were to happen--the worst case I can think of is maybe in the exact same transaction there is another order to add collateral to the user's account for a loan they have on their main/owner wallet, and then the collateral gets transferred to their subaccount instead of remaining the main account. the user would end up with funds in a place they are not expecting them, and perhaps they get liquidated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also yea I think the only way a user could change the balance in their own account is through some CoW means (either they have a separate order or they specify some other wrapper as part of this order)
in either case the user has to do this to themselves somehow
Co-authored-by: Federico Giacon <[email protected]>
EVC cannot alter remaining wrapper data now and is verified from the contract
src/CowEvcCollateralSwapWrapper.sol
Outdated
| bytes32 separator = DOMAIN_SEPARATOR; | ||
| uint256 paramsSize = PARAMS_SIZE; | ||
| assembly ("memory-safe") { | ||
| structHash := keccak256(params, paramsSize) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed it at first, but this isn't a valid struct hash for EIP712 because the type hash is missing (see here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should add a test that checks the hash matches the expected EIP712 hash as computed by Foundry. We could also consider using the dedicated OpenZeppelin library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this case having a EIP712 for the hash used by approved hashes isnt strictly required because they wont be signed by a user wallet and will not have any relation to the EVC.permit signing flow. but it is indeed good for standardizability I suppose.
## Description A proposal to simplify the current contracts by moving repeating code across different wrappers to the same contract. This is achieved by creating a base abstract contract `CowEvcBaseWrapper` which is supposed to be inherited by all Euler wrappers. It aggregates code related to three main features: - ERC712 hashing (in the form of constants, immutables, constructors and hashing) - EVC batch execution. - Errors. The purpose is making it easier to spot what is different and what is the same between wrappers when reading the code. Ideally, the remaining single-instance wrappers should have little remaining occurrences of remaining code. (One exception is `_evcInternalSettle`. I'm sure it can be simplified but I don't understand well enough the balance checks to make sure things are working after my changes, so I'm skipping it.) The result is kind of nice imho: it makes clear(er) that what matters in the various instances is not how the batch is executed, but rather what's the content of the batches (i.e., the implementation of `_encodeSignedBatchItems`). It also makes it more visible that the settlement is executed before other operations when closing a position and after in the other two wrappers. I considered using a "itemsBefore/itemsAfter" model instead but I decided we can just switch to it if we ever find the need and just went for a flag. The bad part of this PR is that Solidity doesn't have generics. This means that all external accessors like `getSignedCalldata` still need to be repeated on each individual wrapper instance, causing annoying code repetition. Because of that, I also had to play with raw memory pointers, which may be dangerous to do if we abuse assembly to generate structures in memory. Note that I tried to make the minimal changes needed to the existing code base to show the difference, so some design choices I made may be questionable. I don't expect this PR to be merged, if anything because it would make a mess in the current PR stack. ## Out of Scope There should be no changes in the implementation, only in the code structure. ## Testing Instructions To be fair, I didn't go in depth when building the code, I'm trusting CI to tell me if something is off. You may also want to compare the gas cost between the two runs. Here is the diff between PR base e8e9e6c and 0c98278. <details><summary>forge snapshot --diff</summary> ``` Ran 10 test suites in 336.42ms (1.07s CPU time): 124 tests passed, 0 failed, 0 skipped (124 total tests) ━ CowWrapperTest::test_integration_ThreeWrappersChained() (gas: 99125 → 99125 | 0 0.000%) ━ CowWrapperTest::test_next_CallsWrapperAndThenNextSettlement() (gas: 39431 → 39431 | 0 0.000%) ━ CowWrapperTest::test_wrappedSettle_RevertsOnInvalidSettleSelector() (gas: 18822 → 18822 | 0 0.000%) ━ CowWrapperTest::test_wrappedSettle_RevertsWithNotASolver() (gas: 20222 → 20222 | 0 0.000%) ━ CowWrapperHelpersTest::test_immutableAuthenticators() (gas: 10869 → 10869 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_EmptyArrays() (gas: 10605 → 10605 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_EmptyWrapperData() (gas: 40352 → 40352 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_MixedWrapperDataSizes() (gas: 64717 → 64717 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_MultipleWrappers() (gas: 64380 → 64380 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnNotAWrapper() (gas: 22855 → 22855 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnNotAWrapper_SecondWrapper() (gas: 29118 → 29118 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnSettlementContractShouldNotBeSolver() (gas: 60187 → 60187 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnSettlementMismatch() (gas: 667039 → 667039 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnWrapperDataMalformed() (gas: 24869 → 24869 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnWrapperDataNotFullyConsumed() (gas: 29565 → 29565 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnWrapperDataTooLong_FirstWrapper() (gas: 565333 → 565333 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_RevertsOnWrapperDataTooLong_SecondWrapper() (gas: 580581 → 580581 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_SingleWrapper() (gas: 34243 → 34243 | 0 0.000%) ━ CowWrapperHelpersTest::test_verifyAndBuildWrapperData_SucceedsWithMaxLengthData() (gas: 680581 → 680581 | 0 0.000%) ━ PreApprovedHashesUnitTest::testFuzz_ConsumePreApprovedHash(address,bytes32) (gas: 38101 → 38101 | 0 0.000%) ━ PreApprovedHashesUnitTest::testFuzz_MultipleUsersAndHashes(address,address,bytes32,bytes32) (gas: 69188 → 69188 | 0 0.000%) ━ PreApprovedHashesUnitTest::testFuzz_SetPreApprovedHash(address,bytes32) (gas: 27329 → 27329 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_ConsumePreApprovedHash_CannotConsumedTwice() (gas: 37254 → 37254 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_ConsumePreApprovedHash_EmitsEvent() (gas: 37959 → 37959 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_PreApprovedHashesStorage() (gas: 51111 → 51111 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_SetPreApprovedHash_CannotApproveConsumed() (gas: 38401 → 38401 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_SetPreApprovedHash_CannotRevokeConsumed() (gas: 38424 → 38424 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_SetPreApprovedHash_EmitsEvent() (gas: 28524 → 28524 | 0 0.000%) ━ PreApprovedHashesUnitTest::test_SetPreApprovedHash_RevokeAndReapprove() (gas: 46036 → 46036 | 0 0.000%) ━ TestablePreApprovedHashes::testConsumeHash(address,bytes32) (gas: 2611 → 2611 | 0 0.000%) ↓ CowEvcCollateralSwapWrapperUnitTest::test_Constructor_SetsImmutables() (gas: 15749 → 15748 | -1 -0.006%) ↓ CowEvcCollateralSwapWrapperUnitTest::test_ParseWrapperData_EmptySignature() (gas: 13669 → 13668 | -1 -0.007%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_DifferentVaults() (gas: 1235312 → 1235840 | 528 0.043%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_RequiresCorrectOnBehalfOfAccount() (gas: 122723 → 122797 | 74 0.060%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_UnauthorizedInternalSettle() (gas: 13376 → 13386 | 10 0.075%) ↑ CowEvcClosePositionWrapperUnitTest::test_EvcInternalSettle_SubaccountMustBeControlledByOwner() (gas: 108564 → 108665 | 101 0.093%) ↑ CowEvcClosePositionWrapperUnitTest::test_EvcInternalSettle_OnlyEVC() (gas: 10313 → 10323 | 10 0.097%) ↑ CowEvcClosePositionWrapperUnitTest::test_Constructor_SetsDomainSeparator() (gas: 6495 → 6502 | 7 0.108%) ↓ CowEvcCollateralSwapWrapperUnitTest::test_Constructor_SetsDomainSeparator() (gas: 6487 → 6480 | -7 -0.108%) ↑ CowEvcOpenPositionWrapperUnitTest::test_Constructor_SetsDomainSeparator() (gas: 6297 → 6304 | 7 0.111%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_WithLeveragedPosition() (gas: 1257541 → 1259196 | 1655 0.132%) ↓ CowEvcCollateralSwapWrapperUnitTest::test_ParseWrapperData_LongSignature() (gas: 13681 → 13659 | -22 -0.161%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_SuccessfulRepay() (gas: 180219 → 180533 | 314 0.174%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_WithDust() (gas: 179908 → 180222 | 314 0.175%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_RepayAll() (gas: 179749 → 180064 | 315 0.175%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_PartialRepayWhenInsufficientBalance() (gas: 179708 → 180023 | 315 0.175%) ↑ CowEvcClosePositionWrapperUnitTest::test_EvcInternalSettle_RequiresCorrectCalldata() (gas: 40302 → 40378 | 76 0.189%) ↑ CowEvcClosePositionWrapperUnitTest::test_Constructor_SetsImmutables() (gas: 15806 → 15836 | 30 0.190%) ↑ CowEvcOpenPositionWrapperUnitTest::test_Constructor_SetsImmutables() (gas: 15556 → 15586 | 30 0.193%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_Subaccount() (gas: 832224 → 833879 | 1655 0.199%) ↑ CowEvcClosePositionWrapperUnitTest::test_HelperRepay_OnlyEVC() (gas: 11850 → 11876 | 26 0.219%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_MainAccount() (gas: 754466 → 756251 | 1785 0.237%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_NonSolverCannotSettle() (gas: 23877 → 23935 | 58 0.243%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_PartialRepay() (gas: 1160452 → 1163316 | 2864 0.247%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_ThreeUsers_TwoSameOneOpposite() (gas: 3227984 → 3236108 | 8124 0.252%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_WithPreApprovedHash() (gas: 1121302 → 1124410 | 3108 0.277%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_SuccessFullRepay() (gas: 1150134 → 1153699 | 3565 0.310%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetSignedCalldata_EnableControllerItem() (gas: 346019 → 347147 | 1128 0.326%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetSignedCalldata_EnableCollateralItem() (gas: 345765 → 346893 | 1128 0.326%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetSignedCalldata_BorrowItem() (gas: 344090 → 345218 | 1128 0.328%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetSignedCalldata_DepositItem() (gas: 343713 → 344841 | 1128 0.328%) ↑ CowEvcOpenPositionWrapperUnitTest::test_MaxBorrowAmount() (gas: 343645 → 344773 | 1128 0.328%) ↑ CowEvcOpenPositionWrapperUnitTest::test_ZeroCollateralAmount() (gas: 343183 → 344311 | 1128 0.329%) ↓ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_UnauthorizedInternalSwap() (gas: 13642 → 13597 | -45 -0.330%) ↑ CowEvcOpenPositionWrapperUnitTest::test_SameOwnerAndAccount() (gas: 339821 → 340949 | 1128 0.332%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_ParseWrapperData_WithExtraData() (gas: 18082 → 18148 | 66 0.365%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_ThreeUsers_TwoSameOneOpposite() (gas: 2924476 → 2935336 | 10860 0.371%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_WithPreApprovedHash() (gas: 808296 → 811528 | 3232 0.400%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_NonSolverCannotSettle() (gas: 23858 → 23956 | 98 0.411%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_GetSignedCalldata_EnablesNewCollateral() (gas: 115346 → 115830 | 484 0.420%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_GetSignedCalldata_UsesCorrectAccount() (gas: 109357 → 109841 | 484 0.443%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_SwapAmount_Max() (gas: 109040 → 109524 | 484 0.444%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_SwapAmount_Zero() (gas: 108444 → 108928 | 484 0.446%) ↑ CowEvcClosePositionWrapperUnitTest::test_GetSignedCalldata_RepayItem() (gas: 147780 → 148444 | 664 0.449%) ↑ CowEvcClosePositionWrapperUnitTest::test_GetSignedCalldata_PartialRepay() (gas: 143447 → 144111 | 664 0.463%) ↑ CowEvcClosePositionWrapperUnitTest::test_MaxRepayAmount() (gas: 143445 → 144109 | 664 0.463%) ↑ CowEvcClosePositionWrapperUnitTest::test_SameOwnerAndAccount() (gas: 143044 → 143708 | 664 0.464%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_OnlySolver() (gas: 16349 → 16429 | 80 0.489%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_GetApprovalHash_MatchesEIP712() (gas: 13509 → 13577 | 68 0.503%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_Success() (gas: 831084 → 835269 | 4185 0.504%) ↑ CowEvcClosePositionWrapperUnitTest::test_ZeroDebt() (gas: 123403 → 124067 | 664 0.538%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_NonSolverCannotSettle() (gas: 23338 → 23474 | 136 0.583%) ↑ CowEvcClosePositionWrapperUnitTest::test_WrappedSettle_OnlySolver() (gas: 16330 → 16428 | 98 0.600%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_ThreeUsers_TwoSameOneOpposite() (gas: 2120651 → 2133415 | 12764 0.602%) ↑ CowEvcClosePositionWrapperUnitTest::test_ParseWrapperData_WithExtraData() (gas: 18188 → 18298 | 110 0.605%) ↑ CowEvcClosePositionWrapperUnitTest::test_WrappedSettle_WithPreApprovedHash() (gas: 370724 → 373062 | 2338 0.631%) ↑ CowEvcClosePositionWrapperUnitTest::test_EvcInternalSettle_WithSubaccountTransfer() (gas: 145792 → 146712 | 920 0.631%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_SetPreApprovedHash() (gas: 39028 → 39277 | 249 0.638%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_WithPreApprovedHash() (gas: 182305 → 183499 | 1194 0.655%) ↑ CowEvcOpenPositionWrapperUnitTest::test_ParseWrapperData_WithExtraData() (gas: 13672 → 13770 | 98 0.717%) ↑ CowEvcClosePositionWrapperUnitTest::test_EvcInternalSettle_CanBeCalledByEVC() (gas: 40396 → 40701 | 305 0.755%) ↑ CowEvcCollateralSwapWrapperTest::test_CollateralSwapWrapper_ParseWrapperData() (gas: 12157 → 12249 | 92 0.757%) ↑ CowEvcClosePositionWrapperUnitTest::test_WrappedSettle_WithPermitSignature() (gas: 244682 → 246540 | 1858 0.759%) ↑ CowEvcClosePositionWrapperUnitTest::test_ParseWrapperData_EmptySignature() (gas: 13772 → 13882 | 110 0.799%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_SetPreApprovedHash() (gas: 39086 → 39416 | 330 0.844%) ↑ CowEvcOpenPositionWrapperUnitTest::test_WrappedSettle_OnlySolver() (gas: 16095 → 16231 | 136 0.845%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_WithPermitSignature() (gas: 130701 → 131839 | 1138 0.871%) ↑ CowEvcClosePositionWrapperUnitTest::test_WrappedSettle_PreApprovedHashRevertsIfDeadlineExceeded() (gas: 137808 → 139020 | 1212 0.879%) ↑ CowEvcOpenPositionWrapperUnitTest::test_ParseWrapperData_EmptySignature() (gas: 9281 → 9379 | 98 1.056%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_BuildsCorrectBatchWithPreApproved() (gas: 108444 → 109693 | 1249 1.152%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetApprovalHash_MatchesEIP712() (gas: 8805 → 8913 | 108 1.227%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_GetApprovalHash_DifferentForDifferentParams() (gas: 17733 → 17980 | 247 1.393%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_ParseWrapperData() (gas: 12271 → 12462 | 191 1.557%) ↑ CowEvcClosePositionWrapperUnitTest::test_GetApprovalHash_MatchesEIP712() (gas: 13434 → 13653 | 219 1.630%) ↑ CowEvcClosePositionWrapperTest::test_ClosePositionWrapper_ParseWrapperData() (gas: 12446 → 12649 | 203 1.631%) ↑ CowEvcOpenPositionWrapperTest::test_OpenPositionWrapper_UnauthorizedInternalSettle() (gas: 13306 → 13563 | 257 1.931%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_BuildsCorrectBatchWithPermit() (gas: 58149 → 59309 | 1160 1.995%) ↑ CowEvcOpenPositionWrapperUnitTest::test_EvcInternalSettle_RequiresCorrectCalldata() (gas: 33537 → 34244 | 707 2.108%) ↑ CowEvcOpenPositionWrapperUnitTest::test_WrappedSettle_WithPreApprovedHash() (gas: 141235 → 144363 | 3128 2.215%) ↑ CowEvcCollateralSwapWrapperUnitTest::test_WrappedSettle_PreApprovedHashRevertsIfDeadlineExceeded() (gas: 86960 → 89013 | 2053 2.361%) ↑ CowEvcOpenPositionWrapperUnitTest::test_WrappedSettle_PreApprovedHashRevertsIfDeadlineExceeded() (gas: 108507 → 111228 | 2721 2.508%) ↑ CowEvcOpenPositionWrapperUnitTest::test_GetApprovalHash_DifferentForDifferentParams() (gas: 12059 → 12362 | 303 2.513%) ↑ CowEvcOpenPositionWrapperUnitTest::test_EvcInternalSettle_OnlyEVC() (gas: 9736 → 9993 | 257 2.640%) ↑ CowEvcOpenPositionWrapperUnitTest::test_EvcInternalSettle_CanBeCalledByEVC() (gas: 26013 → 26711 | 698 2.683%) ↑ CowEvcClosePositionWrapperUnitTest::test_GetApprovalHash_DifferentForDifferentParams() (gas: 17412 → 18048 | 636 3.653%) ↑ CowEvcOpenPositionWrapperUnitTest::test_WrappedSettle_WithPermitSignature() (gas: 55570 → 58475 | 2905 5.228%) New tests: + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_CanBeCalledByEVC() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_OnlyEVC() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_RequiresCorrectCalldata() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_SameOwnerAndAccount() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_SubaccountMustBeControlledByOwner() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_WithRemainingWrapperData() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_WithSubaccount_KindBuy() + CowEvcCollateralSwapWrapperUnitTest::test_EvcInternalSettle_WithSubaccount_KindSell() -------------------------------------------------------------------------------- Total tests: 116, ↑ 81, ↓ 5, ━ 30 Overall gas change: 96864 (0.348%) ``` </details> The gas impact is visible (especially for opening a position, since it adds a function parameter to an external function) but overall limited (max ~3k gas), which I consider a reasonable tradeoff.
- Add validateWrapperData implementation to CowEvcCollateralSwapWrapper - Remove deprecated parseWrapperData function - Update tests to use validateWrapperData - All tests passing

Description
Implements the
CowEvcCollateralSwapWrapperin order to satisfy a usecase for the Euler integration.Context
Read up on notion
Considerations
CowEvcClosePositionWrapper. It has to enable the destination collateral, however.KIND_BUYandKIND_SELLfrom the COW side so that the user can select either an amount exact in or exact out, and swap it just like they would on a regular DEX.Out of Scope
Every line of code in this PR should be considered in-scope.
Testing Instructions
Follow the test command instructions in the README
We want to make sure:
fixes https://linear.app/cowswap/issue/COW-93/initial-merge-contract-of-cowevccollateralswapwrapper