# Bug Report: Settlement price check incorrectly ignores side-specific rounding for JIT/EIP-1271 orders ## Summary - **Issue**: The settlement "limit price" check at line 368 of `GPv2Settlement.sol` treats prices without considering the side-specific rounding (ceil/floor) used later for fund transfers, causing valid JIT executions to revert with "GPv2: limit price not respected". - **Impact**: Blocks valid JIT orders (including CoW AMM JIT liquidity and any third-party JIT) even when executed amounts and prices exactly match what the pool would trade. - **Evidence**: The failing simulation would succeed if the L368 check were removed; the EIP-1271 order had already passed `isValidSignature`. ## Evidence - **Contract reference**: See the limit-price check around L368–L400 in `GPv2Settlement.sol` ([link](https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol#L368-L400)). - **Local calculation showing correct rounding alignment**: ```python def ceil_div(a: int, b: int) -> int: return -(a // -b) # amounts at which cowamm pool would trade (see isValidSignature in tenderly sim) sell_amount = 1643088602855085104587 buy_amount = 82539116533970260 # prices provided by the solver that yield the amounts above with ceil division # used for sell orders sell_token_price = 44400000000 buy_token_price = 883861337875367 # proof math works assert ceil_div(sell_amount * sell_token_price, buy_token_price) == buy_amount # replicate revert assert sell_amount * sell_token_price >= buy_amount * buy_token_price, "GPv2: limit price not respected" ``` - **Simulation**: Revert occurs at the line 368 check; removing this check would let the settlement succeed. The EIP-1271 order was validated by `isValidSignature`. See Tenderly run: [shared simulation](https://dashboard.tenderly.co/shared/simulation/f87631c0-d23c-412f-b184-0cd7e7faf40d). ## Expected behavior - The settlement price check should use the same rounding convention as the subsequent fund transfer computation: - **Sell-side constrained/JIT where buy leg is derived**: use ceil division on the buy leg. - **Buy-side constrained where sell leg is derived**: use floor division on the sell leg. - A JIT order that exactly matches the pool's executable amount under those roundings should pass. ## Actual behavior - The check at L368 compares unrounded products, which can reject a trade that is valid once the side-specific rounding is applied, resulting in a revert with "GPv2: limit price not respected." ## Root cause - Mismatch between the high-level limit-price inequality and the side-specific rounding used to derive actual transfer amounts. The check does not replicate the ceil/floor distinction the contract later applies for fund transfers. ## Scope - Affects CoW AMM JIT orders and any JIT order where execution amounts depend on rounding to meet pool constraints. ## Proposed fix (Solidity outline) - Perform the limit price check on the same rounded quantities used for transfers. - Example (pseudocode; adapt to existing types and helpers): ```solidity if (isSellOrder) { // Compare using ceil on the buy leg (quote) as done for transfers. uint256 effectiveBuy = ceilDiv(executedSellAmount * sellTokenPrice, buyTokenPrice); require( effectiveBuy >= executedBuyAmount, "GPv2: limit price not respected" ); } else { // Compare using floor on the sell leg (base) as done for transfers. uint256 effectiveSell = (executedBuyAmount * buyTokenPrice) / sellTokenPrice; // floor require( executedSellAmount >= effectiveSell, "GPv2: limit price not respected" ); } ``` - Alternatively, compute the exact transfer amounts first (using the same rounding paths as the fund transfer logic) and assert the limit-price condition on those derived amounts. ## Notes - The Tenderly trace confirms the EIP-1271 signature was accepted by `isValidSignature` before the revert, and removing L368's check allows a successful execution, reinforcing that the rounding mismatch is the blocker. - The numeric example in `notes.md` matches the pool's executable amount via `ceil_div` and satisfies the inequality when rounding is aligned. ## References - Contract lines: `GPv2Settlement.sol` L368–L400 ([link](https://github.com/cowprotocol/contracts/blob/main/src/contracts/GPv2Settlement.sol#L368-L400)) - Tenderly simulation: [shared simulation](https://dashboard.tenderly.co/shared/simulation/f87631c0-d23c-412f-b184-0cd7e7faf40d)