From 35a97914c9b2fcd27372a124bc8749f0ea850adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= <15343819+jmg-duarte@users.noreply.github.com> Date: Wed, 13 May 2026 16:53:29 +0100 Subject: [PATCH 1/2] Upgrade to v2.361.1 (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make alloy gas estimator configurable (#4081) # Description Adds the ability to configure past_blocks and reward_percentile parameters for the EIP-1559 gas price estimator. Previously, alloy's hardcoded defaults (10 blocks, 20th percentile) were always used. # Changes [x] Add configurable_alloy.rs - a gas estimator that calls eth_feeHistory with custom parameters, then uses alloy's default estimation algorithm [x] Extend GasEstimatorType::Alloy config variant with optional past-blocks and reward-percentile fields [x] Default values match alloy's hardcoded constants (10 blocks, 20.0 percentile) for backwards compatibility * Improve handling of unverifiable quotes (#4085) # Description Currently the quote verification leads to weird results - especially for Ondo tokens. Routing these tokens requires the use of a proprietary API which does not give out usable call data without an actual trade intent. To adhere to the API solvers simply leave the execution plan of their solution blank (pre interactions, regular interactions, JIT orders). Normally this would lead to a revert in the trade simulation which would in turn cause our system to keep the quotes but mark them as unverifiable. However, if the settlement contract has enough buy_tokens to pay for the entire quoted amount the simulation will not revert but the analysis afterwards will sniff out that the quote is giving money away for free. This will then lead to the quote getting discarded entirely. This causes 2 main issues: 1. it is possible to get a `fast` quote (which skips verification alltogether) and then end up with `NoLiquidity` errors for `optimal` quotes which is confusing 2. said `NoLiquidity` errors then prevent users from placing orders # Changes Since this needs to be resolved urgently I went for a relatively simple approach where we detect whether a solution contains any execution plan at all. Now we only discard quotes that are too inaccurate if the solver actually tried to provide such a plan. If no plan is provided we simply assume it's because no plan could be provided. Note that there is still an incentive to provide verifiable calldata because any verifiable quote will be preferred over any non-verifiable quote. So solvers that don't make the effort to provide the calldata will basically never win quotes for trades where it's possible to provide calldata. Minor other changes: * renamed `Error::TooInaccurate` to `Error::BuffersPayForOrder` to hopefully make the error case more self explanatory * adjusted some getter functions to return `impl Iterator` instead of `Vec` to avoid unnecessary cloning ## How to test This is very hard to test with unit or e2e tests. Given how small the actual change is I think existing e2e tests should be enough to cover the correctness of the regular case and a deployment to prod will show if we now indeed handle the Ondo token case better. * Log solve request data transfer (#4082) # Description Some solvers reported that some requests come significantly delayed (judging by the auction deadline). Currently we have no way to distinguish between receiving the start of the `/solve` request and streaming the actual data. This PR makes this possible by making the `solve` handler take a raw http request and stream the body afterwards. # Changes Instead of: 1. collecting the whole body into a `String` (including utf8 check) 2. logging that we received a request 3. putting that `String` into an `Arc` to make copying it cheap 4. deserializing the string into a `SolveRequest` We now do: 1. receive raw http request 2. log that we received it 3. stream the body into a cheaply copyable `Bytes` type 4. log how long the data transfer took 5. deserialize raw bytes into `SolveRequest` Since handling the raw request seems to bypass axum's request size checks I did it manually for this endpoint. ## How to test Existing tests should suffice --------- Co-authored-by: ilya * Replace RPC mempool API with in-memory tracking (#4086) # Description In order to know which gas price we have to beat at least (in case of cancellations) we made the driver scan the RPC node's mempool using the respective API as this is the ultimate source of truth. However, this has 2 issues: 1. not widely supported 2. introduces latency (apparently up to 2s on mainnet at times) Especially the latency seemingly causes us to not notify the connected solver about the tx submission at times. The submission process works as follows: 1. driver receives a `/settle` call and starts the submission 2. driver does the usual tx submission where it monitors the submission deadline and initiate the cancellation if necessary 3. due to an [issue](https://github.com/cowprotocol/services/pull/3427) with dead block streams the driver also monitors if the autopilot is still waiting for the response for the `/settle` call 4. if the autopilot terminates the `/settle` call the driver only polls the submission future for 1 more second but otherwise simply stops polling it ([code](https://github.com/cowprotocol/services/blob/main/crates/driver/src/domain/competition/mod.rs#L630-L643)) Usually the submission future and autopilot detect the breach of the submission deadline at the same time so the settle future naturally executes the cancellation logic during that grace period. However, with the latency introduced by the mempool API this grace period is often not sufficient anymore (especially on mainnet). Doing some back of the napkin calculation using logs it appears as if the driver is currently not cancelling and submitting the respective notification for ~40% of the `/settle` calls. There is an argument to be made that the submission strategy should be refactored more broadly to ensure that cancellations always get initiated (instead of just stopping to poll the settle future) but this PR should at least already resolve the current issue. # Changes Instead of using the RPC's `mempool` API we simply store the last successfully submitted transactions in memory. Now that we only have to lookup a key in a `Dashmap` the latency will be as it was before. * Optimize live orders queries based on confirmed_valid_to column (#4055) # Description Second part of https://github.com/cowprotocol/services/pull/4047 which introduced optimized queries based on the introduced confirmed_valid_to column It is **crucial** for the database to be already migrated manually as described in previous PR before applying this one. # Changes - [x] Adapt user_orders_with_quote query to use new column - [x] Adapt solvable_orders query to use new column ## How to test Tested on a test-db created by @MartinquaXD which contains a snapshot of prod data. The optimized queries run significantly faster due to changes in `orders` table and new indices. ## Related Issues https://github.com/cowprotocol/services/pull/4021 --------- Co-authored-by: ilya Co-authored-by: José Duarte Co-authored-by: Claude Co-authored-by: Martin Magnus * [TRIVIAL] Remove atty and move maplit to dev-dependencies (#4089) # Description While migrating the orderbook to axum I did another pass over the dependencies and found that atty not only is deprecated, it has a RUSTSEC because its unmaintained and a proper replacement since Rust 1.70 # Changes - [ ] Replace deprecated atty crate with std::io::IsTerminal (https://github.com/softprops/atty/blob/master/README.md?plain=1#L3-L7) - [ ] Move maplit to dev-dependencies ## How to test Compilation --------- Co-authored-by: Claude * Remove useless logs (#4084) # Description Our services are extremely chatty which is annoying for debugging and overwhelms our logging infra. This PR removes or strips down logs that should not be needed. # Changes - removes huge structs like calldata, access lists, and duplicated transactions - calldata is still preserved where it matters most (when resimulating quotes, or in revert errors) - removes tempo items that needlessly get printed in every log of the respective trace (where it seemed useful I added 1 log that contained the data) - removes logs when solutions could not be merged (this is an optimistic optimization and solutions are not expected to always be mergeable) - downgraded some logs from `debug` to `trace` (the ones I think I never used for any debugging but on the surface level seemed like they might be useful eventually) - 404 errors from `/notify` requests * Fetch inflight orders from DB (#4087) # Description In order to avoid solver solutions conflicting with each other once a solution for an order was proposed it will get removed from the auction until its submission deadline has been reached. So far this was managed entirely in-memory which can lead to issues whenever the autopilot gets restarted. # Changes Since the DB scheme refactor a while ago we now have all the data we need to recover inflight orders from the DB. This PR replaces the in-memory inflight order handling by looking them up from the DB. To make the query fast enough I added an index on the deadline column on the `competition_auctions` table. With that the query takes ~0.1ms to look up 10 auctions worth of inflight orders.
execution plan ``` "Unique (cost=1352.80..1352.98 rows=35 width=57) (actual time=0.041..0.043 rows=1 loops=1)" " -> Sort (cost=1352.80..1352.89 rows=35 width=57) (actual time=0.040..0.041 rows=1 loops=1)" " Sort Key: pte.order_uid" " Sort Method: quicksort Memory: 25kB" " -> Nested Loop (cost=1.86..1351.90 rows=35 width=57) (actual time=0.028..0.033 rows=1 loops=1)" " -> Nested Loop Anti Join (cost=1.29..1339.25 rows=4 width=24) (actual time=0.023..0.028 rows=1 loops=1)" " Join Filter: (s.solution_uid = ps.uid)" " -> Nested Loop (cost=0.86..1171.92 rows=4 width=24) (actual time=0.013..0.020 rows=2 loops=1)" " -> Index Scan using competition_auction_deadline on competition_auctions ca (cost=0.43..11.96 rows=5 width=8) (actual time=0.005..0.006 rows=2 loops=1)" " Index Cond: (deadline > 24300390)" " -> Index Scan using proposed_solutions_pkey on proposed_solutions ps (cost=0.43..231.80 rows=19 width=16) (actual time=0.003..0.006 rows=1 loops=2)" " Index Cond: (auction_id = ca.id)" " Filter: is_winner" " Rows Removed by Filter: 8" " -> Index Scan using settlements_auction_id on settlements s (cost=0.43..41.69 rows=11 width=16) (actual time=0.003..0.003 rows=0 loops=2)" " Index Cond: (auction_id = ca.id)" " -> Index Only Scan using proposed_trade_executions_pkey on proposed_trade_executions pte (cost=0.56..3.15 rows=1 width=73) (actual time=0.004..0.004 rows=1 loops=1)" " Index Cond: ((auction_id = ps.auction_id) AND (solution_uid = ps.uid))" " Heap Fetches: 1" "Planning Time: 0.543 ms" "Execution Time: 0.079 ms" ```
## How to test added a new unit test for the DB query * Stop enforcing body size limit (#4092) # Description The shadow competition broke because the driver is now rejecting `/solve` requests that are too large due to this new [code](https://github.com/cowprotocol/services/pull/4082/changes#diff-b997d6f696c5591860aef8658bb56d2a03fc4fa6b37b5e0432ce8e5e4e356aa9R61-R64). This was surprising to me because that code was added specifically because the new handler is bypassing the original content length limiting layer so I would have expected huge requests to already cause issues. During the investigation I confirmed using the `/solve` requests stored on S3 that recent auctions are indeed larger than. 10MB. Afterwards I spun up a driver locally and sent that solve request to the original code to confirm that it's indeed not throwing any errors. I further investigated and concluded that the issue is how we build the driver's http router. The size limiting layer is the first thing that gets added to the router but it should actually be the last. This caused the size limit to never go into effect. # Changes To resolve the issue quickly and remove this breaking change ASAP I simply removed the new size limiting logic from the `/solve` request. In a follow up PR I'll make the size limit configurable and fix the router. ## How to test manual test * Revert "Optimize live orders queries based on confirmed_valid_to colu… (#4094) # Description This reverts commit 7081b03f66c5154cc84ae93f79542425803b4639. (PR https://github.com/cowprotocol/services/pull/4055) The migrations will be revisited as they could not be applied to prod due to lockup and long duration. * Add no-op placeholder migrations for the numbering to be continuous (#4096) # Description We needed to revert migrations V098, V099, and V097 was spelled wrongly (lowercase v). Since then V100 has been added and it makes flyway complain about missing interim migrations. Adding no-op migrations is enough to keep the continuity. # Changes Adds no-op migrations V097, V098 and V099 * Rewrite migration V100 to be optional (#4098) # Description The migration V100 creates index on competition_auction_deadline on competition_auctions. To make the prod deployment viable it needs to be optional (IF NOT EXISTS) which will enable to apply it manually beforehand. # Changes Update the V100 migration to specify IF NOT EXISTS. ## How to test Will apply the migration manually and deploy on staging to verify. * Move order outside market log to callsites (#4090) # Description The log inside the unwrap does not provide an actionable info, the lack or order ID, from address, quote ID, make it extremely hard to follow up on. More context in https://cowservices.slack.com/archives/C0375NV72SC/p1769440848303459 # Changes - [ ] Remove the log from the unwrap - [ ] Place it in the (seemingly) more relevant callsites ## How to test NA * Add configurable database connection pool size (#4097) # Description We had an incident where latency increase due to queries waiting for available connections. This PR provides a configuration for that. Adds `--db-max-connections` (env: DB_MAX_CONNECTIONS) flag to configure the maximum database pool size. Default is 10. # Changes - [ ] New config for DB connection pool size - [ ] Add it to autopilot, orderbook, refunder ## How to test E2E + staging (?) --------- Co-authored-by: Claude * Fix migrations introducing indexes on true_valid_to (#4095) # Description Reinstates https://github.com/cowprotocol/services/pull/4055 with improved migrations that should successfully apply to prod. The previous migrations tended to lock-up indefinitely on prod database when running UPDATE on all rows in the `orders` table to ensure `true_valid_to` is not null. This was done as an additional safety layer as these rows have been manually backfilled previously, so it is no longer needed and turned out to be problematic. Additionally, the index creation can take more time than is allowed for release deployment which causes them to be aborted. It is easier to create them manually and have the migration CREATE INDEX IF NOT EXISTS. # Changes - Had to move migrations from 098, 099 to 101, 102 as there was a migration 100 merged in the meantime. - Moves migration 098 to 101. Removes conservative backfill of empty `true_valid_to` which caused a lock-up on the prod database. - Moves migration 099 to 102 Makes index creation optional (CREATE INDEX IF NOT EXISTS) as they will be created manually, to ensure smooth deployment. --------- Co-authored-by: ilya Co-authored-by: José Duarte Co-authored-by: Claude Co-authored-by: Martin Magnus * Migrate gas estimation to match alloy's type (#4054) > [!CAUTION] > Review with care! The changes are non trivial and there is one breaking change, the gas price returned by the driver no longer includes the base fee! # Description Refactors gas price handling to use integer arithmetic and alloy's native types instead of floating-point calculations. This eliminates precision loss in gas price calculations and better aligns with alloy's conventions. The removal of the base fee is not a true removal, before this change, the base fee was either 0 or the max value available, both leading to inaccurate results. The new code removes the base fee from the type that was being used to describe the estimations (because the base_fee isn't an estimate, it comes in the previous block); but starts querying the chain for the latest block so it's able to get the proper base_fee (if available). The gas estimates themselves should suffer a big change (since the estimate code is the same) but the effective gas price should become more accurate due to the inclusion of the base fee in the calculations. # Changes - Replace custom GasPrice1559 with alloy's Eip1559Estimation throughout codebase - Change GasPrice::base from FeePerGas to Option to match alloy's base fee representation - Migrate gas calculations from f64 to u128/U256 integer arithmetic - Implement calc_effective_gas_price from alloy for effective gas price calculations - Add base_fee: Option to BlockInfo for proper EIP-1559 support - Update API responses to return u128 directly instead of wrapped U256 - Add scaling helper methods (scaled_by_pct, scaled_by_pml) for clearer gas price adjustments # How to test > [!NOTE] > Tested on staging, starting at Mon, 19 Jan 2026 12:10:18 +0000. > Performed a successful trade: https://staging.explorer.cow.fi/lens/orders/0x06677572a2715cc28241a34f5d669247fba167c8d9adc3fcd338e40a3c52ea4109fbad1ea29c36dfe4f8f7baa87c5edf85e0d9f3696e28f5 1. Run existing test suite: cargo test 2. Verify gas price estimation endpoints return expected values 3. Test refunder gas price calculations with various scenarios (new tx, replacement tx, max gas price limits) 4. Verify settlement submissions use correct gas parameters --------- Co-authored-by: Claude Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * [TRIVIAL] Add Plasma to OpenApi (#4100) Adds Plasma URLs to the orderbook's OpenAPI. * Optimize order queries by using true_valid_to (#4104) # Description Reinstates queries from https://github.com/cowprotocol/services/pull/4055 to accelerate live order queries based on the newly introduced `true_valid_to` column and its indexes. Tested on a test-db created by @MartinquaXD which contains a snapshot of prod data. The optimized queries run significantly faster due to changes in `orders` table and new indices. # Changes - [x] Adapt user_orders_with_quote query to use new column - [x] Adapt solvable_orders query to use new column ## How to test Tested on the test database created by @MartinquaXD by analyzing query plan (`EXPLAIN (ANALYZE, BUFFERS)`). In the worst case, the latency has improved 40x (from 20s to 0.5s). * Fix haircut logic (#4093) # Description The haircut feature had a critical bug where the driver-reported `sell_amount` would exceed the user's signed one. For example: - User signed: sell_amount = 5 ETH - Solver proposed a solution with the same sell amount - Driver reported: sellAmount = 5.25 ETH (with 5% haircut added) - The settlement executed onchain, but autopilot couldn't make sense of it due to the unexpected sell amount - The circuit breaker also detects this and bans the solver # Changes 1. Removed haircut from `sell_amount()` - Now returns only executed + fee, which is the actual amount that left the user's wallet 2. Added `haircut_in_sell_token()` helper - Computes haircut amount converted to sell token 3. Updated `custom_prices()` - Applies haircut only for quotes/scoring purposes, making bids more conservative without affecting reported amounts 4. Added `Jit::custom_prices()` - JIT orders don't have a haircut (for now), so they use simple sell/buy amount derivation ## How to test Adjusted existing and added new tests that fail on the `main` branch, but work with the fix. * [TRIVIAL] Drop the db solver participation guard (#4099) # Description Cleans up the codebase by removing the DB solver participation guard. It's been used in a log-only mode for a while. Given the lack of demand for this functionality, it doesn't make sense to keep it. Also, even if it were decided to enable it, the logic would need to be reworked to cover some edge cases, which would take some time to implement. * [TRIVIAL] Fix playground configs (#4108) # Description Estimators were expecting different strings and the tx gas was missing from the driver # Changes - [ ] Remove Native from gas estimators - [ ] Add "Driver" to the native price estimators - [ ] Add tx-gas-limit to the driver config ## How to test Run docker compose and check if autopilot, orderbook and driver are up Co-authored-by: Claude * Update playground frontend Dockerfile (#4103) # Description Fixes Docker pnpm version error. # Changes Enable and prepare corepack version before running pnpm install. # Fixes https://github.com/cowprotocol/services/issues/4101 * Normalize ECDSA signature `v` parameter for Solidity `ecrecover` compatibility (#4107) # Description This PR fixes an issue where EIP-712 signatures with `v = 0` or `1` (modern EIP-2 format) pass off-chain validation but fail on-chain settlement with `GPv2: invalid signature`. # Problem Some wallets (e.g., Bitget Wallet) produce ECDSA signatures using the modern EIP-2 format, where `v ∈ {0, 1}`, while Solidity's `ecrecover` precompile expects the legacy format where `v ∈ {27, 28}`. Off-chain (Alloy library): The https://github.com/alloy-rs/core/blob/main/crates/primitives/src/signature/sig.rs internally normalizes `v` to a boolean parity before recovery, so signatures with `v = 0` or `1` recover correctly. On-chain (Solidity): The `ecrecover` precompile https://coders-errand.com/ecrecover-signature-verification-ethereum/. When given `v = 0`, it returns `address(0)`, which triggers the https://github.com/cowprotocol/contracts/blob/main/src/contracts/mixins/GPv2Signing.sol#L207-L208: ``` signer = ecrecover(message, v, r, s); require(signer != address(0), "GPv2: invalid ecdsa signature"); ``` This mismatch causes orders to pass orderbook validation but fail at settlement. # Solution Normalize `v` to the legacy format (`27/28`) at signature parsing time in `EcdsaSignature::from_bytes()`: ``` let normalized_v = match v { 0 | 27 => 27, 1 | 28 => 28, _ => anyhow::bail!("invalid signature v value: {v}, expected 0, 1, 27, or 28"), }; ``` This ensures: 1. Signatures are stored with normalized `v` values 2. Both off-chain validation and on-chain `ecrecover` receive compatible parameters 3. The fix applies to all entry points (`Signature::from_bytes`, JSON deserialization) # Reproducing the Issue The issue can be verified using a real failed order and Foundry's cast tool to call the `ecrecover` precompile directly: Failed order: - Order UID: `0xb8e19962dd762067afb9f169684abfcbf2cb13bdc7a62ae2e680ebd5ce18c9bcca0c9c4a650cc4ed406d4a6dd031cdd9d4ebf0dc697a0686` - Order hash (struct hash): `0xb8e19962dd762067afb9f169684abfcbf2cb13bdc7a62ae2e680ebd5ce18c9bc` - Expected signer (owner): `0xca0c9c4a650cc4ed406d4a6dd031cdd9d4ebf0dc` - Signature: `0xAB2E74AA0D67233ADC7B52C3B832357ED35F2052338D820D4DA66210EFA7A9684601726CB76BD26DDD958EFE291CFB57E02C39B3F60FBB8BBED1E891FB14CB5D00` - r: `0xAB2E74AA0D67233ADC7B52C3B832357ED35F2052338D820D4DA66210EFA7A968` - s: `0x4601726CB76BD26DDD958EFE291CFB57E02C39B3F60FBB8BBED1E891FB14CB5D` - v: `0x00` ← the problem ## Step 1: Compute the EIP-712 message hash To avoid computing it manually, I grabbed it from a Tenderly simulation[[URL](https://dashboard.tenderly.co/cow-protocol/barn/simulator/babc6191-e15a-470c-83e0-5825b8a4501b/debugger?trace=0.0.4.1.1.0)]. ``` MESSAGE_HASH="0xb8e19962dd762067afb9f169684abfcbf2cb13bdc7a62ae2e680ebd5ce18c9bc" ``` ## Step 2: Test ecrecover with `v=0` (returns zero address - FAILS on-chain) ``` cast call 0x0000000000000000000000000000000000000001 \ "${MESSAGE_HASH}0000000000000000000000000000000000000000000000000000000000000000AB2E74AA0D67233ADC7B52C3B832357ED35F2052338D820D4DA66210EFA7A9684601726CB76BD26DDD958EFE291CFB57E02C39B3F60FBB8BBED1E891FB14CB5D" \ --rpc-url https://eth.llamarpc.com ``` ### Returns: `0x0000000000000000000000000000000000000000000000000000000000000000` ## Step 3: Test ecrecover with `v=27` (returns correct signer - WORKS) ``` cast call 0x0000000000000000000000000000000000000001 \ "${MESSAGE_HASH}000000000000000000000000000000000000000000000000000000000000001bAB2E74AA0D67233ADC7B52C3B832357ED35F2052338D820D4DA66210EFA7A9684601726CB76BD26DDD958EFE291CFB57E02C39B3F60FBB8BBED1E891FB14CB5D" \ --rpc-url https://eth.llamarpc.com ``` ### Returns: `0x000000000000000000000000ca0c9c4a650cc4ed406d4a6dd031cdd9d4ebf0dc` ✅ * Fix haircut mismatch between reported and on-chain amounts (#4109) # Description Fixes the mismatch between driver-reported amounts and on-chain executed amounts when the haircut is configured. Previously, the driver reported higher buy amounts than users actually received on-chain (for sell orders), resulting in a discrepancy that matched the configured haircut. Root cause: `sell_amount()` and `buy_amount()` did NOT include haircut, but `custom_prices()` (used for on-chain encoding) DID. This caused reported amounts to differ from on-chain execution. # Changes Include haircut effects in `sell_amount()` and `buy_amount()` so that: - Reported amounts include haircut - On-chain execution matches reported amounts - Autopilot scores based on actual (haircutted) amounts For sell orders: - `sell_amount()` → unchanged (user sells exactly what they signed) - `buy_amount()` → reduced by haircut (user receives less) For buy orders: - `sell_amount()` → increased by haircut (user pays more) - `buy_amount()` → unchanged (user receives exactly what they signed for) ## How to test Adjusted existing tests. --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Support fractional vol fee bips in orderbook (#4112) # Description We were rounding fractional bips (e.g. `0.3`) to zero. This PR increases the scale so we can handle basis point values lower than 0. At this moment we have volume fee overrides for correlated assets sets to 0.3 and it works fine in autopilot, but /quote endpoint rounds to zero instead, so this fix is needed. ## How to test Unit tests & I tested by deploying this branch to staging. --------- Co-authored-by: ilya * Improve autopilot maintenance (#4113) # Description While looking into the degraded time to happy moo SLI it became apparent that ethflow orders have a significantly worse SLI compared to "regular" orders. Ethflow orders are not harder to solve for than any other orders but they are special in the way they enter the system. Instead of having a REST API call that puts those orders into the DB they get placed by calling the ethflow contract onchain. The autopilot then indexes those events and puts them into the DB. Since the autopilot run loop is synced to the block chain (start a new auction right after seeing a new block) ethflow orders are comparable to regular orders that ALWAYS get placed at the worst possible time (immediately before cutting the auction). Due to being overwhelmed with indexing ethflow orders because of a trade inventive we moved ethflow indexing off of the critical path (see [here](https://github.com/cowprotocol/services/pull/3849)) but that also had the consequence of more ethflow orders not making it into the first possible auction which immediately delays them at least by 12s. # Changes This PR puts ethflow order indexing back on the critical path while still avoiding the issue that caused us to move it off the critical path in the first place. Instead of having a system where the autopilot triggers the maintenance to happen before a new auction or after new block appearing (when waiting for submitted solutions) with an additional background task that checks every second for new ethflow orders that need indexing. This PR moves autopilot maintenance (i.e. block indexing) completely into a background task which triggers ASAP when the system sees a new block. In order to build the auction only after the blocks have been indexed this background tasks feeds a channel of processed blocks. The autopilot then only has to wait for this channel to yield a block with a high enough block number. So the properties of the new solutions are: * event indexing has as little delay as possible * indexing runs concurrently so it's as fast as possible (without speeding up the individual code paths) * autopilot can wait for data from a given block to be processed fully * autopilot stops waiting after a configurable amount of time to keep running auctions even if indexing is slow for whatever reason ## How to test Covered by existing e2e tests * Remove ethcontract+web3+primitive-types (#4106) Completes the Alloy migration by removing the last remaining legacy Ethereum libraries: `ethcontract`, `web3`, and `primitive-types`. These dependencies are no longer needed and can be fully removed, simplifying the dependency tree. **Key change**: The labelling layer for observability now operates at the `Web3` wrapper level instead of directly on `DynProvider`, ensuring the wallet state is properly preserved when creating labeled provider instances. # Changes - [x] Remove `ethcontract`, `web3`, and `primitive-types` from workspace dependencies - [x] Delete unused legacy ethrpc implementations (`buffered.rs`, `http.rs`, `instrumented.rs`, `alloy/conversions.rs`) - [x] Migrate `ProviderLabelingExt` from `DynProvider` to `Web3` wrapper, preserving wallet state across labeled instances - [x] Clean up ethrpc module structure and simplify exports - [x] Update imports across affected crates to use Alloy types only - [x] Remove legacy references from contract vendoring script and test setup ## How to test Existing tests * Trait‑Based Architecture for Refunder Service (#4051) ### Description This PR follows up on #4029 and introduces a **trait‑based architecture** for the `refunder` crate. By decoupling the `RefundService` from concrete database and blockchain implementations, we can now write unit tests without relying on (heavyweight) integration tests. ### Changes - Added a new `traits.rs` module that defines `DbRead`, `ChainRead`, and `ChainWrite` traits to abstract the two main boundaries of the system. - Created an `infra/` module housing the previous concrete implementations of those traits: - `AlloyChain` implements `ChainRead` - `Postgres` implements `DbRead` - Made `RefundService` generic over those traits, so we can use mocks for unit testing it (and thes real/production implementations) as needed. - Extracted `identify_uids_refunding_status` into its own function, to simplify testing. - Moved the `RefundStatus` enum into `traits.rs` so it lives alongside the related abstractions. - Reorganized the service construction inside `run()` for clearer flow. - Added a suite of unit tests that use mocks to cover a variety of scenarios. ### How to test Run the unit tests with: ```bash cargo nextest run -p refunder ``` * Fix tini zombie reaping with shared process namespace (#4114) ## Summary Adds the `-s` (subreaper) flag to tini in the Dockerfile entrypoint to fix zombie process reaping when `shareProcessNamespace: true` is set in Kubernetes deployments. ## Problem Our Kubernetes deployments use `shareProcessNamespace: true` to allow sidecar containers (like the memory monitor) to access `/proc` of the main process. However, this causes the following warning: ``` [WARN tini (82)] Tini is not running as PID 1 and isn't registered as a child subreaper. Zombie processes will not be re-parented to Tini, so zombie reaping won't work. To fix the problem, use the -s option or set the environment variable TINI_SUBREAPER to register Tini as a child subreaper, or run Tini as PID 1. ``` When `shareProcessNamespace` is enabled, Kubernetes' pause container becomes PID 1 instead of tini: ``` PID 1: pause (Kubernetes infrastructure) ├── tini -- autopilot │ └── autopilot └── /bin/sh -c (memory-monitor sidecar) ``` Without PID 1 status, tini cannot reap zombie (orphaned) child processes by default. ## Solution The `-s` flag tells tini to register as a **child subreaper** via the `PR_SET_CHILD_SUBREAPER` prctl. This Linux kernel feature allows a non-PID-1 process to adopt and reap orphaned descendant processes, restoring proper zombie cleanup. * [TRIVIAL] Fix vulnerability by bumping bytes crate version (#4121) # Description The `cargo audit` action complained about the `bytes` crate being vulnerable. The recommended fix is to upgrade `bytes` to version `1.11.1` (patch version bump). ## How to test `cargo audit` action * [TRIVIAL] Fix verbose log (#4120) # Description There was a slight oversight in https://github.com/cowprotocol/services/pull/4084. Instead of printing only the solver name we now print all the internals which is quite a lot. # Changes * only log the solver name again * stop logging the weth address as well * [TRIVIAL] Remove unused error (#4124) # Description Removes an unused error, this could actually have made it in #4106 but VSCode looked like an Xmas tree, I wasn't expecting this to be this simple. # Changes - [ ] Removes unused error - [ ] Removes accompanying From * [TRIVIAL] Rename Web3's DynProvider alloy -> provider (#4123) # Description Renames alloy to provider. Done in a separate PR from the removal due to the number of changes # Changes - [ ] `alloy` -> `provider` * Log tracing spans in JSON logger (#4117) # Description In order to reduce load on the logging infra we want to switch to structured logging (JSON). However, when we tested the current setup we realized that the `request_id` (among other things) was not logged which made debugging things basically impossible. # Changes Adjusted the custom JSON formatter to iterate over parent spans and serialize their names and associated fields. Conceptually the current logic is slightly awkward as the field formatter formats the fields to JSON and later when we format the whole log line we re-parse the formatted string to JSON and the serialize it again. But unless this is actually causing issues when it's deployed I'll not address in order to unblock structured logging ASAP. ## How to test Manual tests (spans are at the end) original version: ``` { "timestamp":"2026-02-03T09:51:18.773085+00:00", "level":"INFO", "fields":{ "message":"- \"GET /api/v1/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/native_price HTTP/1.1\" 404 \"-\" \"curl/8.7.1\" 5.229292ms", "log.target":"orderbook::api::request_summary", "log.module_path":"warp::filters::log", "log.file":"/Users/martin/.cargo/git/checkouts/warp-ee983b87d3028bb6/586244e/src/filters/log.rs", "log.line":37 }, "target":"log", "trace_id":"4aaa6c6e6e56f103d5cf975005c15d85" ``` default JSON logger: ``` { "timestamp":"2026-02-03T09:40:00.265994Z", "level":"INFO", "fields":{ "message":"- \"GET /api/v1/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/native_price HTTP/1.1\" 404 \"-\" \"curl/8.7.1\" 2.721875ms", "log.target":"orderbook::api::request_summary", "log.module_path":"warp::filters::log", "log.file":"/Users/martin/.cargo/git/checkouts/warp-ee983b87d3028bb6/586244e/src/filters/log.rs", "log.line":37 }, "target":"orderbook::api::request_summary", "span":{ "request_id":"6", "name":"http_request" }, "spans":[ { "request_id":"6", "name":"http_request" } ] } ``` new version: ``` { "timestamp":"2026-02-03T09:41:25.529338+00:00", "level":"INFO", "fields":{ "message":"- \"GET /api/v1/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/native_price HTTP/1.1\" 404 \"-\" \"curl/8.7.1\" 6.522583ms", "log.target":"orderbook::api::request_summary", "log.module_path":"warp::filters::log", "log.file":"/Users/martin/.cargo/git/checkouts/warp-ee983b87d3028bb6/586244e/src/filters/log.rs", "log.line":37 }, "target":"log", "trace_id":"cd6bf89be55582b19fd046b7c111f2b1", "spans":{ "http_request":{ "request_id":"0" } } } ``` * Initial Claude setup (#4122) # Description Add instructions for Claude to work more efficiently. Expect .env.claude with secrets filled in. Contact me. None of this was written by hand. To put together the order debugging document I took transcripts of both Felix's talks on the topic and our internal docs and threw it at Claude. Then I tried it for an order and made some edits. # Changes - [x] Sets up MCP servers for main DB and analytics DB - [x] Sets up MPC for fetch - [x] Add CLAUDE.md instructing Claude about the project and coding practices (formatting, style, etc.) - [x] Adds a document telling Claude how to debug orders so it knows what to do next time you ask him "y order {uid} not filled" - [x] Added a comman so you can run `/debug-order 0xd997dc715a7610c75e5f97548685befacb7ea5ad878cb4bac1816903514ed84d1dffc418c0d83bd8b98ab3d2e07b83bf5439f4236981a392` within Claude Code ## How to test Ask Claude Coe to do stuff for you. * Don't notify solvers about failed solutions with a haircut fee (#4115) # Description This PR addresses [a comment](https://github.com/cowprotocol/services/pull/4049#pullrequestreview-3687251046) that suggests avoiding notifying solvers on failed solutions encoding that were configured with a haircut fee. Also, updates the metric to easily identify the error rate of solutions with the haircut fee to configure a new alert to take any action. * Optimize total_surplus query (#4116) # Description We've been experiencing latency spikes on several endpoints, we've pinned this down to the time it takes to acquire DB connections from the pool; when checking RDS monitoring, the surplus query always shows up at the top. The current theory is that the query is a bit slower than it could be, as more users request the main swap page, their surplus is loaded (even if they don't request it — i.e. load the wallet modal) and if some of these users have a larger amount of orders, they're taking up connections that other endpoints aren't getting. I looked into the user distribution, here are the results: |log_bucket|bucket_start|num_users|pct|cumulative_users|cumulative_pct| |----------|------------|---------|---|----------------|--------------| |0|1|295329|83.86|295329|83.86| |1|10|51334|14.58|346663|98.44| |2|100|5071|1.44|351734|99.88| |3|1000|306|0.09|352040|99.96| |4|10000|106|0.03|352146|99.99| |5|100000|28|0.01|352174|100.00|
Distribution Query

```sql WITH buckets AS ( SELECT floor(log(order_count))::int as log_bucket, power(10, floor(log(order_count)))::int as bucket_start, count(*) as num_users FROM ( SELECT owner, count(*) as order_count FROM orders GROUP BY owner ) sub GROUP BY 1, 2 ) SELECT log_bucket, bucket_start, num_users, round(100.0 * num_users / sum(num_users) over(), 2) as pct, sum(num_users) over(order by log_bucket) as cumulative_users, round(100.0 * sum(num_users) over(order by log_bucket) / sum(num_users) over(), 2) as cumulative_pct FROM buckets ORDER BY log_bucket; ```

However, if it was just this, it would be too simple. Depending on the user, they might have no orders and only onchain orders (note that the max number of onchain orders is around ~10k), some will have skewed data distributions across tables too, leading to analyzing and optimizing this query a bit tricky. There are two crucial changes to the query — removing ARRAY_AGG and adding indexes — the first makes it so that the DB does not have to materialize a potentially big array in memory, which would otherwise lead to bad estimations too; the second provides better "access paths" to some of the information the query requires. ### RDS Stats (1h) Before Screenshot 2026-02-05 at 11-12-43
CloudWatch eu-central-1 After Screenshot 2026-02-05 at 11-14-53
CloudWatch eu-central-1 # Changes - [ ] Replace the query with the optimized one - [ ] Create indexes (already done to avoid issues during migration) ## Query Plans
Before

``` Aggregate (cost=1033006.55..1033006.56 rows=1 width=8) (actual time=130139.025..130143.456 rows=1 loops=1) -> Append (cost=1032650.26..1033006.51 rows=2 width=100) (actual time=31175.499..130090.776 rows=10998 loops=1) -> Subquery Scan on "*SELECT* 1" (cost=1032650.26..1032856.24 rows=1 width=100) (actual time=31175.498..130085.109 rows=10998 loops=1) -> Nested Loop (cost=1032650.26..1032856.23 rows=1 width=100) (actual time=31175.497..130079.454 rows=10998 loops=1) Join Filter: (t.order_uid = o.uid) InitPlan 3 (returns $5) -> Finalize Aggregate (cost=1032601.57..1032601.58 rows=1 width=32) (actual time=30761.210..30765.632 rows=1 loops=1) -> Gather (cost=1032601.35..1032601.56 rows=2 width=32) (actual time=30678.267..30689.666 rows=3 loops=1) Workers Planned: 2 Workers Launched: 2 -> Partial Aggregate (cost=1031601.35..1031601.36 rows=1 width=32) (actual time=30675.862..30675.863 rows=1 loops=3) -> Parallel Bitmap Heap Scan on orders (cost=24591.99..1030861.56 rows=295913 width=57) (actual time=278.099..30619.932 rows=226281 loops=3) Recheck Cond: (owner = '\x10dad59905d93ca37cd25a35f25349cb5956ba8e'::bytea) Rows Removed by Index Recheck: 737927 Heap Blocks: exact=15162 lossy=55571 -> Bitmap Index Scan on order_owner (cost=0.00..24414.44 rows=710192 width=0) (actual time=271.994..271.994 rows=749143 loops=1) Index Cond: (owner = '\x10dad59905d93ca37cd25a35f25349cb5956ba8e'::bytea) InitPlan 4 (returns $6) -> Aggregate (cost=47.00..47.01 rows=1 width=32) (actual time=0.021..0.022 rows=1 loops=1) -> Bitmap Heap Scan on onchain_placed_orders (cost=4.09..46.97 rows=11 width=57) (actual time=0.017..0.018 rows=0 loops=1) Recheck Cond: (sender = '\x10dad59905d93ca37cd25a35f25349cb5956ba8e'::bytea) -> Bitmap Index Scan on order_sender (cost=0.00..4.08 rows=11 width=0) (actual time=0.013..0.013 rows=0 loops=1) Index Cond: (sender = '\x10dad59905d93ca37cd25a35f25349cb5956ba8e'::bytea) -> Nested Loop (cost=1.12..136.08 rows=2 width=188) (actual time=31172.849..109529.707 rows=10914 loops=1) -> Index Scan using orders_pkey on orders o (cost=0.56..85.71 rows=10 width=123) (actual time=31172.113..101635.999 rows=678843 loops=1) Index Cond: (uid = ANY (array_cat($5, $6))) -> Index Only Scan using order_rewards_pkey on order_execution oe (cost=0.56..5.01 rows=3 width=65) (actual time=0.011..0.011 rows=0 loops=678843) Index Cond: (order_uid = o.uid) Heap Fetches: 170 -> Index Scan using trade_order_uid on trades t (cost=0.56..0.74 rows=2 width=81) (actual time=0.416..0.416 rows=1 loops=10914) Index Cond: (order_uid = oe.order_uid) SubPlan 1 -> Index Scan using auction_prices_pkey on auction_prices ap (cost=0.58..35.00 rows=16 width=11) (actual time=1.428..1.428 rows=1 loops=9675) Index Cond: ((auction_id = oe.auction_id) AND (token = o.buy_token)) SubPlan 2 -> Index Scan using auction_prices_pkey on auction_prices ap_1 (cost=0.58..35.00 rows=16 width=11) (actual time=1.596..1.596 rows=1 loops=1323) Index Cond: ((auction_id = oe.auction_id) AND (token = o.sell_token)) -> Subquery Scan on "*SELECT* 2" (cost=2.09..150.26 rows=1 width=100) (actual time=0.010..0.013 rows=0 loops=1) -> Nested Loop (cost=2.09..150.25 rows=1 width=100) (actual time=0.009..0.012 rows=0 loops=1) Join Filter: (j.uid = t_1.order_uid) -> Nested Loop (cost=1.54..79.45 rows=1 width=192) (actual time=0.009..0.010 rows=0 loops=1) -> Nested Loop Anti Join (cost=0.98..74.32 rows=1 width=127) (actual time=0.009..0.010 rows=0 loops=1) -> Index Scan using jit_user_order_creation_timestamp on jit_orders j (cost=0.42..33.62 rows=8 width=127) (actual time=0.008..0.009 rows=0 loops=1) Index Cond: (owner = '\x10dad59905d93ca37cd25a35f25349cb5956ba8e'::bytea) -> Index Only Scan using orders_pkey on orders o_1 (cost=0.56..5.08 rows=1 width=57) (never executed) Index Cond: (uid = j.uid) Heap Fetches: 0 -> Index Only Scan using order_rewards_pkey on order_execution oe_1 (cost=0.56..5.11 rows=3 width=65) (never executed) Index Cond: (order_uid = j.uid) Heap Fetches: 0 -> Index Scan using trade_order_uid on trades t_1 (cost=0.56..0.74 rows=2 width=81) (never executed) Index Cond: (order_uid = oe_1.order_uid) SubPlan 5 -> Index Scan using auction_prices_pkey on auction_prices ap_2 (cost=0.58..35.00 rows=16 width=11) (never executed) Index Cond: ((auction_id = oe_1.auction_id) AND (token = j.buy_token)) SubPlan 6 -> Index Scan using auction_prices_pkey on auction_prices ap_3 (cost=0.58..35.00 rows=16 width=11) (never executed) Index Cond: ((auction_id = oe_1.auction_id) AND (token = j.sell_token)) Planning Time: 4.788 ms Execution Time: 130157.850 ms ```

After

``` Aggregate (cost=14614.00..14614.01 rows=1 width=8) (actual time=1901.439..1902.539 rows=1 loops=1) Buffers: shared hit=11838 read=4075 I/O Timings: shared read=2240.469 -> Gather Merge (cost=14557.86..14604.91 rows=404 width=136) (actual time=1900.725..1902.018 rows=917 loops=1) Workers Planned: 2 Workers Launched: 2 Buffers: shared hit=11838 read=4075 I/O Timings: shared read=2240.469 -> Sort (cost=13557.83..13558.25 rows=168 width=136) (actual time=762.591..762.628 rows=306 loops=3) Sort Key: "*SELECT* 2".uid Sort Method: quicksort Memory: 49kB Buffers: shared hit=11838 read=4075 I/O Timings: shared read=2240.469 Worker 0: Sort Method: quicksort Memory: 115kB Worker 1: Sort Method: quicksort Memory: 25kB -> Parallel Append (cost=2.25..13551.63 rows=168 width=136) (actual time=2.247..762.040 rows=306 loops=3) Buffers: shared hit=11824 read=4075 I/O Timings: shared read=2240.469 -> Subquery Scan on "*SELECT* 2" (cost=24.42..13550.79 rows=53 width=136) (actual time=3.496..1892.483 rows=709 loops=1) Buffers: shared hit=9702 read=3415 I/O Timings: shared read=1854.511 -> Nested Loop Left Join (cost=24.42..13550.26 rows=53 width=136) (actual time=3.495..1892.175 rows=709 loops=1) Buffers: shared hit=9702 read=3415 I/O Timings: shared read=1854.511 -> Nested Loop (cost=23.84..13414.32 rows=53 width=155) (actual time=1.455..762.309 rows=709 loops=1) Buffers: shared hit=8050 read=1521 I/O Timings: shared read=736.402 -> Nested Loop (cost=23.28..12573.83 rows=195 width=245) (actual time=1.418..667.512 rows=709 loops=1) Buffers: shared hit=5387 read=1337 I/O Timings: shared read=647.346 -> Nested Loop (cost=22.73..9169.44 rows=796 width=180) (actual time=1.397..584.403 rows=711 loops=1) Buffers: shared hit=2649 read=1211 I/O Timings: shared read=570.801 -> Bitmap Heap Scan on onchain_placed_orders opo (cost=22.17..2343.76 rows=796 width=57) (actual time=0.699..6.255 rows=711 loops=1) Recheck Cond: (sender = '\x8ef4fb956d0cb06ca9e3db76040f08154e8d0122'::bytea) Buffers: shared hit=56 read=248 I/O Timings: shared read=2.028 -> Bitmap Index Scan on order_sender (cost=0.00..21.97 rows=796 width=0) (actual time=0.044..0.044 rows=711 loops=1) Index Cond: (sender = '\x8ef4fb956d0cb06ca9e3db76040f08154e8d0122'::bytea) Buffers: shared hit=4 -> Index Scan using orders_pkey on orders o (cost=0.56..8.57 rows=1 width=123) (actual time=0.812..0.812 rows=1 loops=711) Index Cond: (uid = opo.uid) Filter: (owner <> '\x8ef4fb956d0cb06ca9e3db76040f08154e8d0122'::bytea) Buffers: shared hit=2593 read=963 I/O Timings: shared read=568.773 -> Index Only Scan using order_rewards_pkey on order_execution oe (cost=0.56..4.25 rows=3 width=65) (actual time=0.115..0.115 rows=1 loops=711) Index Cond: (order_uid = o.uid) Heap Fetches: 16 Buffers: shared hit=2738 read=126 I/O Timings: shared read=76.545 -> Index Only Scan using trades_covering on trades t (cost=0.56..4.29 rows=2 width=81) (actual time=0.132..0.133 rows=1 loops=709) Index Cond: (order_uid = o.uid) Heap Fetches: 0 Buffers: shared hit=2663 read=184 I/O Timings: shared read=89.056 -> Index Scan using auction_prices_pkey on auction_prices ap (cost=0.58..31.94 rows=16 width=40) (actual time=1.590..1.590 rows=1 loops=709) Index Cond: ((auction_id = oe.auction_id) AND (token = CASE o.kind WHEN 'sell'::orderkind THEN o.buy_token ELSE o.sell_token END)) Buffers: shared hit=1652 read=1894 I/O Timings: shared read=1118.109 -> Subquery Scan on "*SELECT* 1" (cost=2.25..4428.09 rows=350 width=136) (actual time=3.233..393.256 rows=208 loops=1) Buffers: shared hit=2119 read=660 I/O Timings: shared read=385.958 -> Nested Loop (cost=2.25..4424.59 rows=350 width=136) (actual time=3.233..393.181 rows=208 loops=1) Join Filter: (t_1.order_uid = o_1.uid) Buffers: shared hit=2119 read=660 I/O Timings: shared read=385.958 -> Nested Loop Left Join (cost=1.70..3742.63 rows=156 width=149) (actual time=3.212..369.737 rows=208 loops=1) Buffers: shared hit=1320 read=618 I/O Timings: shared read=364.992 -> Nested Loop (cost=1.12..3316.62 rows=156 width=188) (actual time=1.266..28.471 rows=208 loops=1) Buffers: shared hit=849 read=49 I/O Timings: shared read=26.775 -> Index Only Scan using orders_owner_covering on orders o_1 (cost=0.56..187.36 rows=638 width=123) (actual time=1.247..2.194 rows=210 loops=1) Index Cond: (owner = '\x8ef4fb956d0cb06ca9e3db76040f08154e8d0122'::bytea) Heap Fetches: 1 Buffers: shared hit=24 read=7 I/O Timings: shared read=2.038 -> Index Only Scan using order_rewards_pkey on order_execution oe_1 (cost=0.56..4.87 rows=3 width=65) (actual time=0.124..0.124 rows=1 loops=210) Index Cond: (order_uid = o_1.uid) Heap Fetches: 4 Buffers: shared hit=825 read=42 I/O Timings: shared read=24.736 -> Index Scan using auction_prices_pkey on auction_prices ap_1 (cost=0.58..34.99 rows=16 width=40) (actual time=1.639..1.639 rows=1 loops=208) Index Cond: ((auction_id = oe_1.auction_id) AND (token = CASE o_1.kind WHEN 'sell'::orderkind THEN o_1.buy_token ELSE o_1.sell_token END)) Buffers: shared hit=471 read=569 I/O Timings: shared read=338.217 -> Index Only Scan using trades_covering on trades t_1 (cost=0.56..4.29 rows=2 width=81) (actual time=0.109..0.109 rows=1 loops=208) Index Cond: (order_uid = oe_1.order_uid) Heap Fetches: 0 Buffers: shared hit=799 read=42 I/O Timings: shared read=20.966 -> Subquery Scan on "*SELECT* 3" (cost=2.67..94.60 rows=1 width=136) (actual time=0.012..0.014 rows=0 loops=1) Buffers: shared hit=3 -> Nested Loop Left Join (cost=2.67..94.59 rows=1 width=136) (actual time=0.011..0.013 rows=0 loops=1) Buffers: shared hit=3 -> Nested Loop (cost=2.09..91.83 rows=1 width=159) (actual time=0.011..0.013 rows=0 loops=1) Buffers: shared hit=3 -> Nested Loop (cost=1.54..87.55 rows=1 width=208) (actual time=0.011..0.012 rows=0 loops=1) Buffers: shared hit=3 -> Nested Loop Anti Join (cost=0.98..82.50 rows=1 width=127) (actual time=0.011..0.011 rows=0 loops=1) Buffers: shared hit=3 -> Index Scan using jit_user_order_creation_timestamp on jit_orders j (cost=0.42..37.21 rows=9 width=127) (actual time=0.010..0.010 rows=0 loops=1) Index Cond: (owner = '\x8ef4fb956d0cb06ca9e3db76040f08154e8d0122'::bytea) Buffers: shared hit=3 -> Index Only Scan using orders_pkey on orders o_2 (cost=0.56..5.02 rows=1 width=57) (never executed) Index Cond: (uid = j.uid) Heap Fetches: 0 -> Index Only Scan using trades_covering on trades t_2 (cost=0.56..5.03 rows=2 width=81) (never executed) Index Cond: (order_uid = j.uid) Heap Fetches: 0 -> Index Only Scan using order_rewards_pkey on order_execution oe_2 (cost=0.56..4.25 rows=3 width=65) (never executed) Index Cond: (order_uid = t_2.order_uid) Heap Fetches: 0 -> Index Scan using auction_prices_pkey on auction_prices ap_2 (cost=0.58..35.01 rows=16 width=40) (never executed) Index Cond: ((auction_id = oe_2.auction_id) AND (token = CASE j.kind WHEN 'sell'::orderkind THEN j.buy_token ELSE j.sell_token END)) Planning: Buffers: shared hit=892 read=21 I/O Timings: shared read=14.023 Planning Time: 18.375 ms Execution Time: 1902.681 ms ```

## How to test Due to the fact that floating point addition is not commutative and the order specified in the old query is not deterministic (the ORDER BY uid is merely an approximation that matches) the validation script leaves some room for differences, 1e-9 to be precise.
Validation script

``` #!/usr/bin/env python3 """ Compare original and optimized surplus queries for correctness. Picks random addresses and verifies both queries return identical results. """ import os import sys import psycopg2 from psycopg2 import sql from decimal import Decimal # Connection settings - override with environment variables DB_CONFIG = { "host": os.getenv("DB_HOST", "localhost"), "port": os.getenv("DB_PORT", "5432"), "dbname": os.getenv("DB_NAME", "your_database"), "user": os.getenv("DB_USER", "your_user"), "password": os.getenv("DB_PASSWORD", ""), } ORIGINAL_QUERY = """ WITH regular_orders AS ( SELECT ARRAY_AGG(uid) AS ids FROM orders WHERE owner = $1 ), onchain_orders AS ( SELECT ARRAY_AGG(uid) AS ids FROM onchain_placed_orders WHERE sender = $1 ), trade_components AS ( SELECT CASE kind WHEN 'sell' THEN t.buy_amount WHEN 'buy' THEN t.sell_amount - t.fee_amount END AS trade_amount, CASE kind WHEN 'sell' THEN (t.sell_amount - t.fee_amount) * o.buy_amount / o.sell_amount WHEN 'buy' THEN t.buy_amount * o.sell_amount / o.buy_amount END AS limit_amount, o.kind, CASE kind WHEN 'sell' THEN (SELECT price FROM auction_prices ap WHERE ap.token = o.buy_token AND ap.auction_id = oe.auction_id) WHEN 'buy' THEN (SELECT price FROM auction_prices ap WHERE ap.token = o.sell_token AND ap.auction_id = oe.auction_id) END AS surplus_token_native_price FROM orders o JOIN trades t ON o.uid = t.order_uid JOIN order_execution oe ON o.uid = oe.order_uid WHERE o.uid = ANY(ARRAY_CAT((SELECT ids FROM regular_orders), (SELECT ids FROM onchain_orders))) UNION ALL SELECT CASE j.kind WHEN 'sell' THEN t.buy_amount WHEN 'buy' THEN t.sell_amount - t.fee_amount END AS trade_amount, CASE j.kind WHEN 'sell' THEN (t.sell_amount - t.fee_amount) * j.buy_amount / j.sell_amount WHEN 'buy' THEN t.buy_amount * j.sell_amount / j.buy_amount END AS limit_amount, j.kind, CASE j.kind WHEN 'sell' THEN (SELECT price FROM auction_prices ap WHERE ap.token = j.buy_token AND ap.auction_id = oe.auction_id) WHEN 'buy' THEN (SELECT price FROM auction_prices ap WHERE ap.token = j.sell_token AND ap.auction_id = oe.auction_id) END AS surplus_token_native_price FROM jit_orders j JOIN trades t ON j.uid = t.order_uid JOIN order_execution oe ON j.uid = oe.order_uid WHERE j.owner = $1 AND NOT EXISTS ( SELECT 1 FROM orders o WHERE o.uid = j.uid ) ), trade_surplus AS ( SELECT CASE kind WHEN 'sell' THEN (trade_amount - limit_amount) * surplus_token_native_price WHEN 'buy' THEN (limit_amount - trade_amount) * surplus_token_native_price END / POWER(10, 18) AS surplus_in_wei FROM trade_components ) SELECT COALESCE(SUM(surplus_in_wei), 0) AS total_surplus_in_wei FROM trade_surplus; """ OPTIMIZED_QUERY = """ WITH trade_components AS ( -- Regular orders: join trades first, then order_execution SELECT o.uid, CASE o.kind WHEN 'sell' THEN t.buy_amount WHEN 'buy' THEN t.sell_amount - t.fee_amount END AS trade_amount, CASE o.kind WHEN 'sell' THEN (t.sell_amount - t.fee_amount) * o.buy_amount / o.sell_amount WHEN 'buy' THEN t.buy_amount * o.sell_amount / o.buy_amount END AS limit_amount, o.kind, ap.price AS surplus_token_native_price FROM orders o JOIN trades t ON t.order_uid = o.uid JOIN order_execution oe ON oe.order_uid = t.order_uid LEFT JOIN auction_prices ap ON ap.auction_id = oe.auction_id AND ap.token = CASE o.kind WHEN 'sell' THEN o.buy_token ELSE o.sell_token END WHERE o.owner = $1 UNION ALL -- Onchain placed orders (if sender differs from owner) SELECT o.uid, CASE o.kind WHEN 'sell' THEN t.buy_amount WHEN 'buy' THEN t.sell_amount - t.fee_amount END AS trade_amount, CASE o.kind WHEN 'sell' THEN (t.sell_amount - t.fee_amount) * o.buy_amount / o.sell_amount WHEN 'buy' THEN t.buy_amount * o.sell_amount / o.buy_amount END AS limit_amount, o.kind, ap.price AS surplus_token_native_price FROM onchain_placed_orders opo JOIN orders o ON o.uid = opo.uid AND o.owner != $1 JOIN trades t ON t.order_uid = o.uid JOIN order_execution oe ON oe.order_uid = t.order_uid LEFT JOIN auction_prices ap ON ap.auction_id = oe.auction_id AND ap.token = CASE o.kind WHEN 'sell' THEN o.buy_token ELSE o.sell_token END WHERE opo.sender = $1 UNION ALL -- JIT orders SELECT j.uid, CASE j.kind WHEN 'sell' THEN t.buy_amount WHEN 'buy' THEN t.sell_amount - t.fee_amount END AS trade_amount, CASE j.kind WHEN 'sell' THEN (t.sell_amount - t.fee_amount) * j.buy_amount / j.sell_amount WHEN 'buy' THEN t.buy_amount * j.sell_amount / j.buy_amount END AS limit_amount, j.kind, ap.price AS surplus_token_native_price FROM jit_orders j JOIN trades t ON t.order_uid = j.uid JOIN order_execution oe ON oe.order_uid = t.order_uid LEFT JOIN auction_prices ap ON ap.auction_id = oe.auction_id AND ap.token = CASE j.kind WHEN 'sell' THEN j.buy_token ELSE j.sell_token END WHERE j.owner = $1 AND NOT EXISTS (SELECT 1 FROM orders o WHERE o.uid = j.uid) ) SELECT COALESCE(SUM(surplus_in_wei ORDER BY uid), 0) AS total_surplus_in_wei FROM ( SELECT uid, CASE kind WHEN 'sell' THEN (trade_amount - limit_amount) * surplus_token_native_price WHEN 'buy' THEN (limit_amount - trade_amount) * surplus_token_native_price END / POWER(10, 18) AS surplus_in_wei FROM trade_components ) ts; """ # Query to get random addresses with varying order counts SAMPLE_ADDRESSES_QUERY = """ WITH address_order_counts AS ( SELECT owner AS address, COUNT(*) AS order_count FROM orders GROUP BY owner ), bucketed AS ( SELECT address, order_count, CASE WHEN order_count < 10 THEN '0: 1-9' WHEN order_count < 100 THEN '1: 10-99' WHEN order_count < 1000 THEN '2: 100-999' WHEN order_count < 10000 THEN '3: 1000-9999' ELSE '4: 10000+' END AS bucket FROM address_order_counts ) SELECT address, order_count, bucket FROM ( SELECT address, order_count, bucket, ROW_NUMBER() OVER (PARTITION BY bucket ORDER BY RANDOM()) AS rn FROM bucketed ) ranked WHERE rn <= %s ORDER BY bucket, order_count; """ def get_connection(): return psycopg2.connect(**DB_CONFIG) def fetch_sample_addresses(conn, samples_per_bucket=5): """Get random addresses from each order count bucket.""" with conn.cursor() as cur: cur.execute(SAMPLE_ADDRESSES_QUERY, (samples_per_bucket,)) return cur.fetchall() def run_query(conn, query, address): """Run a query with the given address parameter.""" # Convert $1 placeholder to %s for psycopg2 pg_query = query.replace("$1", "%s") # Count how many parameters we need param_count = pg_query.count("%s") with conn.cursor() as cur: cur.execute(pg_query, tuple([address] * param_count)) result = cur.fetchone() return result[0] if result else None def compare_results(original, optimized, tolerance=1e-9): """Compare two numeric results with floating point tolerance.""" if original is None and optimized is None: return True, "both NULL" if original is None or optimized is None: return False, f"NULL mismatch: original={original}, optimized={optimized}" # Convert to float for comparison orig_float = float(original) opt_float = float(optimized) if orig_float == opt_float: return True, "exact match" # Check relative difference for non-zero values if orig_float != 0: rel_diff = abs(orig_float - opt_float) / abs(orig_float) if rel_diff < tolerance: return True, f"within tolerance (rel_diff={rel_diff:.2e})" # Check absolute difference for values near zero abs_diff = abs(orig_float - opt_float) if abs_diff < tolerance: return True, f"within tolerance (abs_diff={abs_diff:.2e})" return False, f"MISMATCH: original={orig_float}, optimized={opt_float}, diff={abs_diff}" def main(): samples_per_bucket = int(sys.argv[1]) if len(sys.argv) > 1 else 5 print(f"Comparing original vs optimized query ({samples_per_bucket} samples per bucket)") print("=" * 80) conn = get_connection() # Get sample addresses print("\nFetching sample addresses...") addresses = fetch_sample_addresses(conn, samples_per_bucket) print(f"Found {len(addresses)} addresses across buckets\n") passed = 0 failed = 0 current_bucket = None for address, order_count, bucket in addresses: if bucket != current_bucket: current_bucket = bucket print(f"\n--- Bucket: {bucket} ---") # Run both queries original_result = run_query(conn, ORIGINAL_QUERY, address) optimized_result = run_query(conn, OPTIMIZED_QUERY, address) # Compare match, reason = compare_results(original_result, optimized_result) addr_hex = "0x" + address.hex() if isinstance(address, (bytes, memoryview)) else str(address) status = "✓" if match else "✗" print(f" {status} {addr_hex[:18]}... ({order_count:>5} orders): {reason}") if match: passed += 1 else: failed += 1 # Print detailed values on failure print(f" Original: {original_result}") print(f" Optimized: {optimized_result}") # Summary print("\n" + "=" * 80) print(f"SUMMARY: {passed} passed, {failed} failed out of {passed + failed} tests") conn.close() return 0 if failed == 0 else 1 if __name__ == "__main__": sys.exit(main()) ```

To validate the performance, I think it's best we give it a run in prod for anywhere from 30 minutes to 2 hours. Even while requiring indexes, the new query *should* be faster. * Filter refunded ethflow orders from all solvable orders query (#4129) # Description In a recent change the query to return ALL solvable orders suddenly returned ~45K orders more. This caused an increased memory usage on the start of the autopilot and made the DB query slower than it could be. # Changes Filter out refunded ethflow orders from solvable orders query. ## How to test confirmed with a manual query execution that the number of returned orders is in line with the final_auction_size + filtered_out_queries indicating that the all_solvable_orders query and the incremental logic afterwards agree with each other. increase in memory usage Screenshot 2026-02-06 at 10 45 44 first auction after restart is huge Screenshot 2026-02-06 at 10 53 45 * Upgrade `time` crate (#4130) Upgrades the `time` crate to satisfy `cargo audit`. * Tighter filter on all solvable orders query (#4131) # Description Forgot that I already hit `merge when ready` on https://github.com/cowprotocol/services/pull/4129 so I was to slow to merge the same optimization for the open orders query. * Do not panic on failure to acquire connections to store events (#4132) # Description During a particularly bad period of volatility we failed to acquire a DB connection on time which lead to a panic
Partial Stacktrace

``` 1770314825856 2026-02-05T18:07:05.856Z 2026-02-05T18:07:03.033Z ERROR observe::tracing: thread 'tokio-runtime-worker' panicked at /src/crates/autopilot/src/infra/persistence/mod.rs:301:54: 1770314825856 2026-02-05T18:07:05.856Z failed to acquire tx: PoolTimedOut 1770314825856 2026-02-05T18:07:05.856Z stack backtrace: 1770314825856 2026-02-05T18:07:05.856Z 0: observe::tracing::tracing_panic_hook 1770314825856 2026-02-05T18:07:05.856Z at ./src/crates/observe/src/tracing.rs:187:21 1770314825856 2026-02-05T18:07:05.856Z 1: as core::ops::function::Fn>::call 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/alloc/src/boxed.rs:2220:9 1770314825856 2026-02-05T18:07:05.856Z 2: observe::panic_hook::install::{{closure}} 1770314825856 2026-02-05T18:07:05.856Z at ./src/crates/observe/src/panic_hook.rs:14:9 1770314825856 2026-02-05T18:07:05.856Z 3: as core::ops::function::Fn>::call 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/alloc/src/boxed.rs:2220:9 1770314825856 2026-02-05T18:07:05.856Z 4: std::panicking::panic_with_hook 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/std/src/panicking.rs:833:13 1770314825856 2026-02-05T18:07:05.856Z 5: std::panicking::panic_handler::{{closure}} 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/std/src/panicking.rs:698:13 1770314825856 2026-02-05T18:07:05.856Z 6: std::sys::backtrace::__rust_end_short_backtrace 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/std/src/sys/backtrace.rs:176:18 1770314825856 2026-02-05T18:07:05.856Z 7: __rustc::rust_begin_unwind 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/std/src/panicking.rs:689:5 1770314825856 2026-02-05T18:07:05.856Z 8: core::panicking::panic_fmt 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/core/src/panicking.rs:80:14 1770314825856 2026-02-05T18:07:05.856Z 9: core::result::unwrap_failed 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/core/src/result.rs:1867:5 1770314825856 2026-02-05T18:07:05.856Z 10: core::result::Result::expect 1770314825856 2026-02-05T18:07:05.856Z at ./rustc/254b59607d4417e9dffbc307138ae5c86280fe4c/library/core/src/result.rs:1185:23 1770314825856 2026-02-05T18:07:05.856Z 11: autopilot::infra::persistence::Persistence::store_order_events::{{closure}} 1770314825856 2026-02-05T18:07:05.856Z at ./src/crates/autopilot/src/infra/persistence/mod.rs:301:54 ```

# Changes - [ ] Remove the panic - [ ] Log on error and continue ## How to test NA * Normalize approximation native token decimals (#4125) # Description Instead of validating that native price approximation token pairs have matching decimals at startup, this PR normalizes prices based on the decimal difference between tokens, as was suggested in another PR comment[[link](https://github.com/cowprotocol/services/pull/4119#pullrequestreview-3749864175)]. # Changes - Added `ApproximationToken` type that stores both the approximation address and a normalization factor `(10^(to_decimals - from_decimals))` - Factory fetches decimals for all approximation token pairs at startup and computes normalization factors - Price approximation now multiplies the fetched price by the normalization factor, correctly handling tokens with different decimals ## How to test Added unit tests for normalization in both directions (source > target decimals and target > source decimals) * Create composite index orders(owner, class, true_valid_to) (#4133) # Description The create_order endpoint is suffering from latency issues due to the `user_orders_with_quote` being super slow (we've seen up to 50s), this PR adds an index and removes the MATERIALIZE keyword to provide more optimization opportunities for the query planner
Plan before

``` Nested Loop (cost=29.14..122.50 rows=1 width=58) (actual time=0.036..0.038 rows=0 loops=1) Buffers: shared hit=4 -> Nested Loop Anti Join (cost=28.58..113.92 rows=1 width=79) (actual time=0.036..0.037 rows=0 loops=1) Buffers: shared hit=4 -> Nested Loop Anti Join (cost=28.44..97.75 rows=1 width=79) (actual time=0.036..0.037 rows=0 loops=1) Buffers: shared hit=4 -> Nested Loop Anti Join (cost=28.17..89.44 rows=1 width=79) (actual time=0.036..0.037 rows=0 loops=1) Buffers: shared hit=4 -> Nested Loop Anti Join (cost=27.89..73.21 rows=1 width=79) (actual time=0.036..0.037 rows=0 loops=1) Buffers: shared hit=4 -> Bitmap Heap Scan on orders o (cost=27.62..64.90 rows=1 width=79) (actual time=0.035..0.036 rows=0 loops=1) Recheck Cond: ((true_valid_to >= '4294967295'::bigint) AND (owner = '\xfd659bc79a2b542728e3f372870d22ed31358ed6'::bytea)) Filter: ((cancellation_timestamp IS NULL) AND (class = 'limit'::orderclass) AND (((kind = 'sell'::orderkind) AND (COALESCE((SubPlan 1), '0'::numeric) < sell_amount)) OR ((kind = 'buy'::orderkind) AND (COALESCE((SubPlan 2), '0'::numeric) < buy_amount)))) Buffers: shared hit=4 -> BitmapAnd (cost=27.62..27.62 rows=1 width=0) (actual time=0.033..0.034 rows=0 loops=1) Buffers: shared hit=4 -> Bitmap Index Scan on orders_true_valid_to (cost=0.00..4.51 rows=10 width=0) (actual time=0.023..0.023 rows=312 loops=1) Index Cond: (true_valid_to >= '4294967295'::bigint) Buffers: shared hit=3 -> Bitmap Index Scan on order_owner (cost=0.00..22.86 rows=381 width=0) (actual time=0.003..0.003 rows=0 loops=1) Index Cond: (owner = '\xfd659bc79a2b542728e3f372870d22ed31358ed6'::bytea) Buffers: shared hit=1 SubPlan 1 -> Aggregate (cost=16.62..16.63 rows=1 width=32) (never executed) -> Index Scan using trade_order_uid on trades (cost=0.56..16.61 rows=3 width=11) (never executed) Index Cond: (order_uid = o.uid) SubPlan 2 -> Aggregate (cost=16.62..16.63 rows=1 width=32) (never executed) -> Index Scan using trade_order_uid on trades trades_1 (cost=0.56..16.61 rows=3 width=11) (never executed) Index Cond: (order_uid = o.uid) -> Index Only Scan using ethflow_refunds_pkey on ethflow_refunds r (cost=0.27..4.29 rows=1 width=57) (never executed) Index Cond: (order_uid = o.uid) Heap Fetches: 0 -> Index Only Scan using invalidations_order_uid on invalidations i (cost=0.28..8.29 rows=1 width=57) (never executed) Index Cond: (order_uid = o.uid) Heap Fetches: 0 -> Index Only Scan using onchain_order_invalidations_pkey on onchain_order_invalidations oi (cost=0.27..4.29 rows=1 width=57) (never executed) Index Cond: (uid = o.uid) Heap Fetches: 0 -> Index Only Scan using okay_onchain_orders on onchain_placed_orders op (cost=0.14..8.16 rows=1 width=57) (never executed) Index Cond: (uid = o.uid) Heap Fetches: 0 -> Index Scan using order_quotes_pkey on order_quotes o_quotes (cost=0.56..8.57 rows=1 width=93) (never executed) Index Cond: (order_uid = o.uid) Planning: Buffers: shared hit=95 Planning Time: 0.816 ms Execution Time: 0.101 ms ```

Plan after

``` Hash Anti Join (cost=2977.50..115918.76 rows=1386 width=67) (actual time=0.012..0.014 rows=0 loops=1) Hash Cond: (o.uid = op.uid) Buffers: shared hit=4 -> Hash Anti Join (cost=2918.73..115842.49 rows=1386 width=124) (actual time=0.012..0.013 rows=0 loops=1) Hash Cond: (o.uid = i.order_uid) Buffers: shared hit=4 -> Nested Loop (cost=2798.94..115705.20 rows=1386 width=124) (actual time=0.012..0.013 rows=0 loops=1) Buffers: shared hit=4 -> Hash Anti Join (cost=2798.38..103609.69 rows=1412 width=81) (actual time=0.012..0.013 rows=0 loops=1) Hash Cond: (o.uid = oi.uid) Buffers: shared hit=4 -> Hash Anti Join (cost=2186.48..102979.96 rows=1414 width=81) (actual time=0.012..0.012 rows=0 loops=1) Hash Cond: (o.uid = r.order_uid) Buffers: shared hit=4 -> Index Scan using idx_orders_owner_class_valid on orders o (cost=0.56..100776.17 rows=1419 width=81) (actual time=0.011..0.012 rows=0 loops=1) Index Cond: ((owner = '\xfd659bc79a2b542728e3f372870d22ed31358ed6'::bytea) AND (class = 'limit'::orderclass) AND (true_valid_to >= '4294967295'::bigint)) Filter: ((cancellation_timestamp IS NULL) AND (((kind = 'sell'::orderkind) AND (COALESCE((SubPlan 1), '0'::numeric) < sell_amount)) OR ((kind = 'buy'::orderkind) AND (COALESCE((SubPlan 2), '0'::numeric) < buy_amount)))) Buffers: shared hit=4 SubPlan 1 -> Aggregate (cost=8.60..8.61 rows=1 width=32) (never executed) -> Index Only Scan using idx_trades_covering on trades (cost=0.56..8.59 rows=2 width=9) (never executed) Index Cond: (order_uid = o.uid) Heap Fetches: 0 SubPlan 2 -> Aggregate (cost=8.60..8.61 rows=1 width=32) (never executed) -> Index Only Scan using idx_trades_covering on trades trades_1 (cost=0.56..8.59 rows=2 width=11) (never executed) Index Cond: (order_uid = o.uid) Heap Fetches: 0 -> Hash (cost=1515.41..1515.41 rows=53641 width=57) (never executed) -> Seq Scan on ethflow_refunds r (cost=0.00..1515.41 rows=53641 width=57) (never executed) -> Hash (cost=398.62..398.62 rows=17062 width=57) (never executed) -> Seq Scan on onchain_order_invalidations oi (cost=0.00..398.62 rows=17062 width=57) (never executed) -> Index Scan using order_quotes_pkey on order_quotes o_quotes (cost=0.56..8.57 rows=1 width=100) (never executed) Index Cond: (order_uid = o.uid) -> Hash (cost=78.24..78.24 rows=3324 width=57) (never executed) -> Seq Scan on invalidations i (cost=0.00..78.24 rows=3324 width=57) (never executed) -> Hash (cost=53.80..53.80 rows=398 width=57) (never executed) -> Index Only Scan using okay_onchain_orders on onchain_placed_orders op (cost=0.27..53.80 rows=398 width=57) (never executed) Heap Fetches: 0 Planning: Buffers: shared hit=95 Planning Time: 0.833 ms Execution Time: 0.071 ms ```

# Changes - [ ] Adding the composite index - [ ] Dropping the MATERIALIZE ## How to test Create index in prod, push a test image, though this should be an easy win * Update CLAUDE.md (#4128) Adds some details about the project that help Claude provide better results. * Mount flyway.conf in docker-compose.yaml (#4138) # Description Concurrent index creation with flyway requires a configuration parameter to be set to avoid deadlocks in postgres. This setting was already configured and shipped in the built docker images but the `docker-compose.yaml` file that's used to spin up a DB locally for running e2e tests for example does not mount that file yet which leads to migrations deadlocking when you run `docker compose up` # Changes mount `flyway.conf` in `docker-compose.yaml`. ## How to test run `docker compose up` from the repo root and check that migrations finished successfully Screenshot 2026-02-10 at 09 59 01 * Fix flaky autopilot follower e2e test (#4139) # Description Fixes the flaky autopilot follower e2e test that fails almost always on #4136. After adding some logs, etc, I could finally find all the possible race conditions and fix the test assertions. * Optimize autopilot maintenance (#4141) # Description Currently the autopilot maintenance is on the critical path before we can build a new auction. But some of the work that's being done is actually not necessary to build fully updated auctions. Steps that are currently not essential are: * pruning the DB * associating a settlement with a proposed solution (as long as we process all events of a settlement we know which orders have been filled) Ever since there are smart contracts that are listed as solvers attributing a settlement to a solution is pretty time consuming since we need to use `debug_traceTransaction`. This regularly takes hundreds of milliseconds so moving this off of the hot path will be a huge win. # Changes To account for that the maintenance now distinguishes between essential tasks and optional tasks. In order to move settlement attribution our of the essentials I moved the settlement observer out of the settlement event indexer into the autopilot maintenance component. Also now that we have this split we can move ethflow refund indexing into the autopilot maintenance under the optional category. ## How to test existing e2e tests for general correctness manual deployment to prod to confirm speed improvement Tested on prod together with some other change. 1: current setup 2: new node infra 3: new node infra + this PR Screenshot 2026-02-11 at 13 31 44 * [M2] Integrate block explorer URL override for Otterscan (#4077) ## Summary This is a copy of [a stale PR](https://github.com/cowprotocol/services/pull/4012) This PR delivers part of **Milestone 2: Frontend Integration** of the [CoW Grants Program RFP: CoW Protocol Playground Block Explorer Integration](https://forum.cow.fi/t/grant-application-cow-protocol-playground-block-explorer-integration/3284/1) proposal by CoBuilders. Updates the CoW Swap and Explorer Dockerfiles to use `REACT_APP_BLOCK_EXPLORER_URL`, enabling all block explorer links to point to the local Otterscan instance (port 8003) instead of Etherscan. ![demo_after](https://github.com/user-attachments/assets/ec25432e-9be5-430e-bb1b-582a7ed5a454) ## Milestone 2 Deliverables ### CoW Swap Frontend Integration - [x] Configure `REACT_APP_BLOCK_EXPLORER_URL=http://localhost:8003` in build - [x] Transaction hash links point to local Otterscan - [x] Address links point to local Otterscan - [x] All navigation flows work correctly ### CoW Explorer Frontend Integration - [x] Configure `REACT_APP_BLOCK_EXPLORER_URL=http://localhost:8003` in build - [x] "View on Blockchain" links point to local Otterscan - [x] Address and token links point to local Otterscan - [x] All navigation flows work correctly ### Success Criteria > *"One-click navigation from frontends to the local block explorer for all transactions and addresses"* - Clicking any explorer link in CoW Swap (port 8000) → Opens in Otterscan (port 8003) - Clicking any explorer link in CoW Explorer (port 8001) → Opens in Otterscan (port 8003) ## Changes ### Modified Files | File | Changes | |------|---------| | `Dockerfile.cowswap` | Added `REACT_APP_BLOCK_EXPLORER_URL` build arg | | `Dockerfile.explorer` | Added `REACT_APP_BLOCK_EXPLORER_URL` env var | ## Architecture ``` ┌─────────────┐ ┌─────────────┐ │ CoW Swap │ │ CoW Explorer│ │ (port 8000) │ │ (port 8001) │ └──────┬──────┘ └──────┬──────┘ │ │ │ All explorer │ │ links now │ │ point to ───────┤ │ │ └───────────┬───────┘ │ ▼ ┌─────────────┐ │ Otterscan │ │ (port 8003) │ └─────────────┘ ``` ## Dependencies This PR depends on: - **PR:** cowprotocol/cowswap#XXX - Adds `REACT_APP_BLOCK_EXPLORER_URL` env var support ## Testing 1. Start the playground: ```bash docker compose -f docker-compose.fork.yml up --build ``` 2. Open CoW Swap at `http://localhost:8000`: - Make a swap or view transaction history - Click any "View on Explorer" or transaction link - **Expected:** Opens `http://localhost:8003/tx/{hash}` 3. Open CoW Explorer at `http://localhost:8001`: - Browse any order or transaction - Click any address or transaction link - **Expected:** Opens `http://localhost:8003/address/{addr}` 4. Verify Otterscan shows the transaction/address details with full traces ## Demo 1. User clicks transaction in CoW Swap 2. Otterscan opens with full transaction details 3. Traces, logs, and gas profiling visible locally ## Milestones | Milestone | Description | Status | |-----------|-------------|--------| | M1 | Otterscan Integration | [PR #4000](https://github.com/cowprotocol/services/pull/4000) | | M2 | Frontend Integration | **This PR** (+ cowswap PR) | | M3 | Documentation | Pending | --- *Submitted by [CoBuilders](https://cobuilders.xyz) as part of the CoW Grants Program* --------- Co-authored-by: Ignacio Co-authored-by: Claude Opus 4.5 * Fix true_valid_to updating (#4134) # Description `true_valid_to` is not always updated as needed. It can happen that ethflow order events are parsed first, before order creation takes place which results in `true_valid_to` to first be set correctly, and then set to `u32::MAX` when inserting the order. Ethflow orders as a whole get parsed off onchain events and the logic (regarding the above) currently is as follows: Custom onchain data parser would pick up on the Ethflow related events and insert the ethflow_orders row with correct order uid and valid_to. I have included logic to update related order's true_valid_to to the same value, but it does not exist in the db yet Orders parsed from order placement events will get inserted into the database, ignoring conflicts. Since the newly created ethflow order does not yet exist in the database, it will get inserted with the valid_to set as u32::MAX and true_valid_to having the same value. There is no other point in time for the true_valid_to to get correctly updated, which leaves us with two options: Use the smaller of two: ethflow_orders.valid_to, orders.valid_to when inserting an order. Change the order of how onchain events are parsed - first parse the order creation events and only then append the custom info (which includes ethflow). The solution 1 seems more robust since We will always end up with the smallest value when inserting an order. I am not sure what might the implications of changing the onchain event parsing order. # Changes Adds a clause ```sql COALESCE( (SELECT LEAST($21, valid_to) FROM ethflow_orders WHERE uid = $1), $21 ) ``` to INSERT_ORDER query which will always take the smallest of `ethflow_orders.valid_to` and the value currently being inserted. # Deployment When hotfix is applied, services will correctly attribute `true_valid_to` and manual backfill will be performed. ## How to test Tested on ethflow E2E test `local_node_eth_flow_tx` confirmed the faulty behaviour can occur and added assert that ensures the resulting true_valid_to is **not** `u32::MAX` --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Martin Magnus * Shared native price cache (#4136) # Description Replaces #4044. Once we started forwarding native price estimates from the orderbook to autopilot, CoinGecko API usage went up. This happened because the estimator moved to autopilot, which now handles all requests and also relies on CoinGecko. This PR refactors native price estimation in a way described below, according to the diagram created by @MartinquaXD: image # Changes - Split `CachingNativePriceEstimator` into three focused components: a passive `Cache` (shared data store), a `CachingNativePriceEstimator` (on-demand price fetching with caching), and a `NativePriceUpdater` (background maintenance worker). - The autopilot now creates a single shared `Cache` instance used by both the API-facing estimator and the auction competition estimator, eliminating duplicate price fetches for the same tokens. - Added an `--api-native-price-estimators` flag to the autopilot, allowing the API endpoint to use different native price estimator sources than the auction pipeline (falls back to `--native-price-estimators`, if unset). - Replaced the implicit priority-based update system (`high_priority` + `replace_high_priority`) with an explicit token tracking via `set_tokens_to_update()`, called during the solvable orders cache building in the autopilot. - Moved cache + updater wiring out of the shared factory into each binary, so each service composes the shared building blocks as needed: - Orderbook: creates its own `Cache` + `CachingNativePriceEstimator`. - Autopilot: Creates 2 separate `CachingNativePriceEstimators` that share a single instance of `Cache`. One of the estimators is wrapped with the `NativePriceUpdater`, which is used in the auction competition. Another one serves the autopilot's API request without a maintenance task. - Removed the unused `--native-price-cache-max-update-size` flag (dead code after the refactoring removed the truncation logic). ## How to test Existing tests + staging and prod. # Follow-up tasks - The cache is unbounded. This needs to be revisited, and probably adding a simple LRU cache would be safer. * Skip checks for defined appCodes (#4118) # Description For the integration with Euler we want to skip balance checks, because the order will get the necessary funds from wrapper execution. This PR skip balance checks where wrappers are defined, mirroring what we do for flash loans with flash loan hints. Signature checks are skipped for all 1271 orders, but kept for presign/Eip712 as these should always be valid independent of prehooks/wrappers. We have been running this way in prod for a while and I just cleaned up the flags. # Changes - [x] Skip balance validation for ordres with wrappers - [x] A cache so we don't keep parsing appdata for every auction - [x] Nuked disable_1271_order_sig_filter and disable_1271_order_balance_filter config flags - 1271 now just always skips these checks ## How to test Unit tests. * Migrate orderbook API from warp to axum (#4080) # Description This PR migrates the orderbook API from Warp to Axum. The migration modernizes our web framework while maintaining API compatibility, with improved code organization and reduced complexity (~200 lines removed). # Changes - Replace Warp filters with Axum Router and centralized AppState for dependency injection - Migrate all 18 API endpoints (v1 and v2) to Axum handler functions - Update dependencies: remove warp, add axum, tower, tower-http - Reorganize routes hierarchically with .nest(), alphabetically by prefix - Fix route conflicts using .merge() for multiple HTTP methods on same path - Fix bug in get_trades_v2 (use database_read instead of database_write) - Add comprehensive E2E test suite for HTTP behavior validation in malformed_requests.rs # How to test Existing tests + staging I deployed these changes to base staging to ensure metrics were working, here's the graph Screenshot 2026-01-23 at 12-42-46
Edit panel - GPv2 - Dashboards - Amazon Managed Grafana Follow up: image Unmarked spots are running `main`, the wrong image was labeling endpoints with unknown because it was a fallback. That code has been removed and replaced with the current metric labeling scheme. That scheme can be improved (read, made less verbose and error prone while being more general) but we need to change the metrics label, which I didn't do to minimize changes. --------- Co-authored-by: Claude * Propagate request ID from Forwarder (#4150) # Description I wanted to get all logs for a quote request, but when I searched for request ID in OpenSearch I only got requests from the orderbook but none from the autopilot which should have shown native price fetching. This PR adds request ID propagation to the forwarder. * Add openAPI reference to ink (#4111) # Description Added Ink staging and prod URLs for OpenAPI spec * Don't re-hash appdata every time (#4152) # Description A surprising amount of time when building a new auction is lost when assembling the final orders. This step involves inspecting the appdata and recovering the partner fee from that. The current implementation has 2 major flaws: * it re-parses the appdata during every auction * it parses AND hashes the data in every auction While the ideal fix would be to only parse the appdata once this is more involved and we can already get an easy win by just omitting the hashing part. I'll follow up with a PR that avoids the unnecessary parsing as well. # Changes - don't validate the appdata only parse it since that skips the re-hashing of the data which we don't need at this point ## How to test Measured performance with tempo `assemble_orders` went from 88ms to 50ms Before Screenshot 2026-02-13 at 08 22 33 After Screenshot 2026-02-13 at 08 22 40 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Move storing order events fully off the hot path (#4149) # Description While building a new auction we also store a bunch of order events for debugging purposes. This already happens mostly in a background task but the conversion from the owned collection to a collection of the type the persistence layer expects still happens on the hot path. # Changes Introduce a second function for storing order events which takes a collection (`impl IntoIterator`) and a conversion function. This then allows us to move the collection as is into the background task and do the conversion there. ## How to test I measured the optimization using tempo but the exact numbers are a bit hard to nail down because only some of the instrumented logic is on the hot path and it doesn't get displayed that well in the tempo UI. Based on the time and size of the `assemble_orders` span it looks like the new version shaves off ~45ms or so. Before Screenshot 2026-02-13 at 07 51 15 After Screenshot 2026-02-13 at 07 51 27 * Make autopilot sync more granular (#4144) # Description While https://github.com/cowprotocol/services/pull/4141 achieved the goal of reducing the time before we build a fresh auction it also came with the oddity that the metrics suggest slower order execution while the time to happy moo SLI was very healthy. The reason for the weird metrics is that the autopilot task that monitors for landed transactions previously waited for ALL of the maintenance task to finish before looking up the settlement from the DB (see [here](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/run_loop.rs#L843-L850)) while the PR unintentionally changed this part as well to only wait for the minimum processing. Improved latency metrics Screenshot 2026-02-11 at 16 15 19 Degraded runloop and settle metrics Screenshot 2026-02-11 at 16 15 27 Despite not degraded time to happy moo Screenshot 2026-02-11 at 16 16 14 # Changes Fix metrics by allowing `MaintenanceSync` to either wait for partial or full processing of the given block. Before building the auction we now explicitly await partial processing to be done while later on when we determine whether a solution was submitted we wait for the block to be fully processed. # Alternatives Considered * have 1 update channel for every maintenance sub task and have it push updates => discarded because it's more granular than we need * replace watch stream with struct with atomic counters for every update type => discarded because awaiting changes on those counters is more complicated than just 2 watch streams Overall I'm not super happy with the solution so I'm happy to hear alternative suggestions if you have any. * Store orders and quotes reference counted in auction (#4153) # Description Currently the solvable orders cache stores raw owned instances of orders and quotes. This is painful for 2 reasons: * `solvable_orders_after()` clones all orders and quotes to do the incremental update (see [here](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/solvable_orders.rs#L377-L378)) * `update()` clones all orders at the start (see [here](https://github.com/cowprotocol/services/blob/main/crates/autopilot/src/solvable_orders.rs#L172-L176)) Both of these can be made very cheap by storing `Arc`ed instances. # Alternatives considered Theoretically one could also collect `&Order` when building the auction and use `std::mem::take()` to move the existing hashmaps into `solvable_orders_after()`. But using `&Order` is less flexible and more complicated and `std::mem::take()` requires extra attention when handling errors in `solvable_orders_after()` to not leave the cache in a broken state or completely empty. # Changes - put all orders and quotes in `Arc`s. ## How to test I only tested the approach with using `&Order` but it's reasonable to assume that the improvement for `cloning_relevant_orders()` will be very similar when cloning `Arc`s instead of collecting references. Also I expect the highlighted section of `get_solvable_orders` (1st screenshot) to disappear as well with this PR. Both speed ups would combine to ~50ms. Before Screenshot 2026-02-13 at 08 51 46 After Screenshot 2026-02-13 at 08 52 09 * Skip tx simulation on endpoints that don't mine reverts (#4143) # Description Based on our logs it seems like simulating a tx before submission can take up to 500ms. Since our submission logic know which mempool mines reverting txs and which don't we can simply skip the simulation step when the current mempool does not mine reverting txs. # Changes skip tx simulation (estimate_gas()) in mempools that don't mine reverting txs (e.g. mevblocker). * Remove price filtering from autopilot (#4148) # Description First PR in a series of small optimizations in the auction building logic. # Changes removes filtering of orders based on the existing native prices this feature was never enabled and with the stronger focus on filtering as little as possible in the autopilot it should also not be enabled in the future * Speed up removing old orders and quotes (#4151) # Description The incremental solvable orders query so far blindly inserted updated orders in to the set of open orders and then ran a filtering step over ALL open orders and quotes separately. This is pretty wasteful since the new/updated orders are significantly fewer than the set of ALL orders and 3 of 4 things we check we only need to check on the new/updated orders. Also we don't have to go over ALL quotes if we just remove the quotes together with the orders. # Changes - determine whether to insert or remove orders based on the new information returned by the incremental DB query - whenever we remove an order we also remove the associated quote to never run do a `.retain()` on ALL quotes - still do a `.retain()` on the open orders to find expired orders as the incremental orders query will not flag those - while we still scan the whole list twice the most complicated checks could be moved to the part that only runs on the updated orders so this `.retain()` still is a lot faster than before. ## How to test Measured performance with tempo Retain orders went from 2.5ms to 283µs and we dropped the 6.6ms from retaining quotes completely Before Screenshot 2026-02-13 at 08 13 38 After Screenshot 2026-02-13 at 08 13 24 * Speed up auction metrics (#4155) # Description A surprisingly big amount of time in the auction building process is wasted with metrics. By far the biggest offender is the function `checkpoint()` which figures out which orders got filtered out since the last checkpoint. It does it by cloning the currently alive orders and removing the still alive orders from that map one by one. All the orders that are left over are no longer alive. This regularly incurs an overhead of a couple milliseconds each while the alternative function `checkpoint_by_invalid_orders()` (which gets the removed orders passed in) only takes a few microseconds. Additionally constructing the an `OrderFilterCounter` also take ~5ms because it clones the original set of orders and counts them already there. Screenshot 2026-02-13 at 12 29 01 # Changes - drop `OrderFilterCounter` in favor of function on the metrics - from all functions return a list of filtered orders such that we don't have to do a ton of computations to diff the orders before and after a filter function - fixed resulting compile errors in the tests This technically introduces breaking changes but IMO those are well worth it: * we don't populate the gauges once at the start of the process and once at the end - instead we update individual gauges as we go * some orders can **technically** be double counted. Since filtering for banned users, unsupported tokens and invalid signatures happens in parallel and happens separately the same order can be counted for multiple things. The orders filtered that way are relatively few and having the chance that orders fit multiple categories is even lower. On the plus side if there are multiple things wrong with an order we now learn about it. Since the exact numbers of filtered orders are not super interesting anyway one can even consider this a debugging improvement. * Remove unused config options / features (#4156) # Description This started out as an investigation into speeding up deny-listed token filtering in the auction building process but I realized that there is still some unfinished work of migrating away from the tracing based bad token detection. We already moved away from that bad token detection mechanism a while ago but I didn't fully delete all the related code yet in case the new mechanism doesn't work well enough. Since then we've been running the services without the feature for many months so it's time to remove that stuff now. We are deleting so much here because only the tracing detector was using all the logic to find owners of certain tokens onchain so that also went with it. Once that was done I also went ahead and tried to find any other args that are unused. # Changes - remove `tracing_url` parameter which made the tracing bad token detector unused - removed tracing detector which made token owner finding stuff unused - removed token owner finding stuff which made liquidity source configs unused - removed any other args that weren't used ## How to test compiler * Bound native price cache with moka (#4154) # Description The native price cache is backed by an unbounded `Mutex>`. Entries are inserted on every price estimate but never removed. Once at steady state with many unique tokens, the cache grows without bound. The size limit is currently hardcoded at 20k, since on mainnet we have around that number of unique tokens: ```sql SELECT COUNT(*) AS unique_token_count FROM (SELECT sell_token AS token FROM orders UNION SELECT buy_token AS token FROM orders) t; Returns -> 20109 ``` Per cache entry breakdown: - CachedResult value: ~32 bytes - Address key: 20 bytes - Moka internal overhead: ~100-120 bytes - Total per entry: ~170 bytes At 20,000 entries: ~3.4 MB upper bound # Changes - Replace the `Mutex>` cache with `moka::sync::Cache`. - Adapt get_cached_price(): - No more MutexGuard parameter. Use data.get(&token) which returns Option (moka clones the value). Check staleness + is_ready() on the returned value. No `requested_at` to update, since moka tracks access internally. - Remove the `requested_at` field from the `CachedResult` since it is not needed anymore and turned out to be dead code. - Adapt the `insert()` function to use `entry_by_ref().and_compute_with()`, which gives gives atomic read-modify-write for `accumulative_errors_count`. ## How to test Existing tests. * Simplify deny listed tokens (#4157) # Description Originally the orderbook and autopilot tried to detect tokens with unsupported behavior with sophisticated simulations. Since then we: * onboarded more solvers that could handle tokens that were previously causing issues * introduced quote simulation that can detect such tokens more reliably * moved towards minimal intervention for problematic tokens in the protocol itself Today the only thing we actually make use of is a list of tokens that are actually deny listed. Those should probably be revisited as well but for now I decided to only simplify the code we have today while keeping feature parity. # Changes - turned `ListBasedDetector` into a simple wrapper around a HashSet - given that there is no more complicated or fallible logic in the detection mechanism I decided to drop the instrumentation as well This simplification should also drastically reduce the ~30ms we currently spend on filtering out orders that trade deny-listed tokens. Screenshot 2026-02-13 at 16 57 47 ## How to test existing e2e tests * Extract common utils, serialization to shared and serde-ext (#4142) # Description The simulation refactoring PR (#4140) is large due to some common types being moved out to `shared`. This PR covers these aspects and moves common `driver` utilities to `shared`. Alongside these common (de)serialization logic is moved to new crate `serde-ext` # Changes - [x] Move driver::util::bytes to `shared` - [x] Remove util::Bytes from `autopilot` - [x] Rename shared::ethrpc to web3 for a better name and to avoid naming conflicts with the `ethrpc` crate - [x] Move common (de)serialization logic to new `serde-ext` and use it throughout `driver` and `solvers-dto` ## How to test - E2E tests - [ ] Staging deployment works * Add TOML configuration to the autopilot (#4147) # Description Kicks off #4005; to start only one of the more annoying arguments was migrated to validate the approach. # Changes - [ ] Add an optional config path - [ ] Remove the drivers CLI arg - [ ] Move the drivers argument to the TOML parsing ## How to test * Migrate the drivers argument to TOML in the infra repo * Run in staging * autopilot: send `/solve` requests with ref-counted body (#4159) # Description Currently the way the autopilot serializes `/solve` requests is still sub optimal. While it does indeed only serialize the `/solve` requests once it still allocates memory for every HTTP request it ultimately sends. Since `reqwest` also supports `Bytes` as the request body which are already references counted we can save a bunch of allocating and copying data by serializing the auction data into a `Bytes` instance and cloning that into the individual `/solve` HTTP requests. # Changes - changes internal members of `solve::Request` from `serde_json::RawValue` to `Bytes` - adds a new `X-Auction-Id` header which is important for another optimization but that has to be merged before said optimization - moved original serialization from the executor thread to a background task that's intended for blocking operations since serialization takes quite a bit of time ## How to test existing e2e tests * Only stream 1 `/solve` request body in driver (#4160) # Description Because the driver serves multiple solvers it receives a bunch of duplicated `/solve` requests. There is already logic to deduplicate the pre-processing but we there is still one part left that's done unnecessarily often: streaming the HTTP body. Streaming the http body currently takes up to 700ms which is surprisingly slow considering that the HTTP request goes from one k8s pod to another and not via the public internet. I suspect the problem is that we are actually streaming ~10MB `/solve` requests 23 times in parallel (numbers from mainnet). https://github.com/cowprotocol/services/pull/4159 introduced a new header (`X-Auction-Id`) that can be used to detect which auction a request is related to without having to stream the entire body. With this change everything but prioritizing (i.e. sorting and allocating balances for orders) and the serialization of the driver's `/solve` request will be de-duplicated. That means adding more solvers to the driver will be less costly. If we consider enforcing the same prioritization logic for ALL solvers that could also be de-duplicated leading to more or less 0 overhead for adding more solvers to the same driver. # Changes - inspect `X-Auction-Id` header to figure out whether we have to process the request or just await an existing pre-processing task Note that this change must be released AFTER https://github.com/cowprotocol/services/pull/4159`. The reason is that k8s first rolls out `driver` pods so there would be a period where the old `autopilot` is still sending requests without the `X-Auction-Id` header. ## How to test e2e tests --------- Co-authored-by: ilya * Add more tracing spans (#4162) # Description This PR just adds more tracing spans to further improve our coverage with tempo. Mostly focuses on the autopilot: building new auction, indexing events, winner selection * [TRIVIAL] Fix granular autopilot sync (#4165) # Description https://github.com/cowprotocol/services/pull/4144 introduced more granular sync mechanisms for the autopilot run loop. In a commit addressing some comments an error must have sneaked which causes the autopilot run loop to wait for fully processed blocks and the `/settle` detection to wait for partially indexed blocks - the exact opposite. This causes us to wait a lot longer than necessary before building a new auction. Also our metrics still suggest that the run loop was 1 block longer than it actually was. * Upgrade axum 0.6 → 0.8 and related HTTP dependencies (#4164) # Description Upgrade the HTTP dependency stack: - axum: 0.6 → 0.8 - hyper: 0.14 → 1 - http-body: 0.4 → 1 - tower: 0.4 → 0.5 - tower-http: 0.4 → 0.6 - reqwest: 0.11 → 0.12 (version 0.13 changes the default TLS stack so it will be further upgraded separately) # Changes - [ ] Replace axum::Server with tokio::net::TcpListener + axum::serve - [ ] Update route parameter syntax from :param to {param} - [ ] Remove body generic from middleware Next type - [ ] Replace hyper::body::to_bytes with axum::body::to_bytes - [ ] Replace hyper::StatusCode with axum::http::StatusCode throughout - [ ] Update return types from hyper::Error to std::io::Error - [ ] Replace HttpBody trait usage with axum::body::to_bytes helper - [ ] Remove now-unused direct hyper/http-body deps from crates - [ ] Convert Mock solver Default impl to async new() since TcpListener::bind is async ## How to test Staging * Orderbook native price estimators fallback (#4161) # Background The orderbook's native price estimator is currently configured to use a Forwarder estimator, which is basically the Autopilot's API. In case Autopilot is down, quote competition can't proceed without native prices, and no new orders can be placed during that time. # Description Adds an optional fallback native price estimator that kicks in when the primary estimator experiences sustained failures. This protects native price availability during primary estimator outages. The fallback estimator tracks consecutive `ProtocolInternal` errors from the primary. After a configurable threshold (3 errors), it switches to the fallback estimator and periodically probes the primary to detect recovery. # Changes - New `FallbackNativePriceEstimator`, which wraps a primary and fallback estimator with automatic failover logic: - Tracks consecutive `ProtocolInternal` errors on the primary - Switches to fallback after 3 consecutive errors - Probes primary every 60s while in fallback mode - Recovers to primary when a probe succeeds, otherwise, continue using the fallback - `Forwarder` error mapping: HTTP send failures now return `ProtocolInternal` instead of a generic error, so the fallback estimator can detect them - New CLI argument `--native-price-estimators-fallback` on the orderbook to optionally configure fallback estimators - New factory method `caching_native_price_estimator_from_inner` to allow injecting a pre-built inner estimator (with fallback wrapping) into the caching layer ## How to test New unit and e2e tests. * Fix limit layer in driver and bump limit to 20Mb (#4170) # Description The upgrade to axum 0.8 required an upper bound on loading requests completely into memory, so I reused the driver's limit. Turns out, the limit was not correctly applied and the new API forced it. This PR fixes the layer to work correctly and bumps the max request body size to 20MB # Changes - [ ] Fix the limit layer - [ ] Bump the default limit to 20MB ## How to test Staging * Remove request body limit in the driver (#4171) # Description 20Mb was not enough, this PR removes the limit. # Changes - [ ] Remove the limit layer ## How to test Staging * [TRIVIAL] Bump alloy to 1.7.3 (#4168) # Description While looking into the dependencies I noticed our alloy version is a bit behind, this PR updates it. I've reviewed the changelog and there does not seem to be anything that will surprise us. It brings the bonus of some preparations being made for the glamsterdam network upgrade. # Changes - [ ] Alloy version bump to 1.7.3 ## How to test Compiler + Staging * [TRIVIAL] Bump opentelemetry->0.31 & tracing-opentelemetry->0.32.1 (#4169) # Description One more for the upgrade pile. I'd like to think that this will slightly improve service health due to some of the points in the changelog — https://github.com/open-telemetry/opentelemetry-rust/blob/main/opentelemetry-sdk/CHANGELOG.md#0310 > * Fix: Restore true parallel exports in the async-native BatchSpanProcessor by honoring OTEL_BSP_MAX_CONCURRENT_EXPORTS (https://github.com/open-telemetry/opentelemetry-rust/pull/3028). A regression in https://github.com/open-telemetry/opentelemetry-rust/pull/2685 inadvertently awaited the export() future directly in opentelemetry-sdk/src/trace/span_processor_with_async_runtime.rs instead of spawning it on the runtime, forcing all exports to run sequentially. > * Fix: batch size accounting in BatchSpanProcessor when queue is full (https://github.com/open-telemetry/opentelemetry-rust/pull/3089). # Changes - [ ] Bump opentelemetry-* crates to 0.31 - [ ] Bump tracing-opentelemetry to 0.32.1 ## How to test Compile * Update old de/serialization errors (#4163) # Description The error handling that is being removed was added to avoid breaking any solver/partner code; the change was upped today (Feb 17th) and the teams with touch points to the affected parties were notified * https://nomevlabs.slack.com/archives/C036JAGRQ04/p1770811138347829 * https://nomevlabs.slack.com/archives/C0369B2UF6J/p1770810493856389 This PR "brings back" the newer and more accurate errors, relying on Axum's default extractor behavior. # Changes - [ ] Remove custom deserialization code to provide old error codes - [ ] Update E2E tests - [ ] Update orderbook/openapi.yml ## How to test E2E tests * Fix tracing spans on solvers (#4173) # Description https://github.com/cowprotocol/services/pull/4169 changed the `set_parent` from silently failing to "returning an error" which I proceeded to log as error, this is very spammy as it happens on every HTTP request Digging further, the issue is that the baseline solver does not set the "main parent span" which we would like to have; so this PR also addresses that (still needs to be enabled in infra) # Changes - [ ] Fix the log level - [ ] Add & use tracing arguments in solvers ## How to test Run a test in staging Test in base, deployment around 14:52 Screenshot 2026-02-18 at 14 54 59 * Upgrade reqwest to 0.13 (#4172) # Description Upgrades reqwest to 0.13 — this carries the change from native-tls to rustls by default, I tested this out and nothing broke # Changes - [ ] Upgrade reqwest to 0.13 ## How to test Tested on base staging, didn't see any issues * [TRIVIAL] Remove direct dependency on derivative crate (#4174) # Description I was reading through the issues when I saw the cargo audit one, even though we still depend on derivative transitively (thus we can't remove it from the audit list), getting Claude to remove our direct dependencies was less than 5 minutes. # Changes - [ ] Replace derivative proc-macro usage with manual trait implementations: ## How to test Compilation + existing tests * Update Claude's victoria logs instructions (#4177) # Description An update of Claude's instructions after we migrated the logging system. Also puts the querying behind a wrapper script so you can click "allow reading logs" once instead of having to approve every single query. Update your .env.claude from known sources. * Add extra label to distinguish requests with the same path (#4176) # Description The orderbook has some paths that are getting merged in the grafana metrics, this fixes it by formatting it as "{HTTP Method} {Path}" # Changes - [ ] Change the label name / format to "{HTTP Method} {Path}" ## How to test Staging * Make sure payload is correctly formatted (#4178) # Description Makes sure JSON payload is correctly escaped. * Bump sqlx to 0.8 and bigdecimal to 0.4 (#4175) # Description Since the newer axum landed and its friends are also being upgraded, upgrading SQLx removes the "this code won't compile in a future version of Rust warning, solves the cargo-audit part of the issue and even reduces the size of some structs. # Changes - [ ] sqlx 0.7 -> 0.8; add 'derive' feature (required in 0.8 for #[derive(sqlx::FromRow)] and #[derive(sqlx::Type)]) - [ ] bigdecimal 0.3 -> 0.4 (required by sqlx-postgres 0.8) - [ ] Remove unfulfilled #[expect(clippy::large_enum_variant)] in LeaderLockTracker (no longer needed because sqlx 0.8 reduced the type of the Postgres connection by boxing it) - [ ] Remove RUSTSEC-2024-0363 audit ignore (fixed by upgrading to sqlx 0.8.1+) ## How to test Compile + testing in staging * autopilot: filter `&Order` instead of `Arc` (#4179) # Description In https://github.com/cowprotocol/services/pull/4153 I conjectured that running the filter passes over `Vec>` will likely be as fast as `Vec<&Order>`. To validate that claim I temporarily deployed this PR and it did indeed drop the average average time to update solvable orders by around 30ms. I suspect this is because memory pre-fetching works better with pure slices of references instead of smart pointers which may differ in layout depending on the compiled language. Screenshot 2026-02-19 at 08 04 39 # Changes - run all functions in solvable orders cache over `Vec<&Order>` instead of `Vec>`. ## How to test measured on prod * Move fee policy configuration from CLI args to config file (#4158) # Description Extract FeeFactor from shared::arguments into its own shared::fee_factor module, adding Serialize/Deserialize support Requires https://github.com/cowprotocol/infrastructure/pull/4620 to be merged before # Changes - [ ] Move FeePoliciesConfig, FeePolicy, FeePolicyKind, FeePolicyOrderClass, and UpcomingFeePolicies from autopilot CLI arguments to autopilot::config::fee_policy as serde-based config structs - [ ] Read fee_policies_config from the TOML config file (flattened into Configuration) instead of CLI flags - [ ] Update all e2e tests to construct fee policy config via Configuration structs and pass them through the config file ## How to test E2E + staging * Remove deny_unknown_fields for the new autopilot config (#4182) # Description Having this setting enabled leaves us in a chicken/egg problem where deploying the services part breaks due to no config, and deploying infra first breaks because the setting is unknown The current strategy is: * Infra first with the old env var set so the old autopilot can still run * Autopilot second, which will ignore the env var and work with the config # Changes - [ ] Remove deny_unknown_fields from the autopilot config ## How to test Next migration we'll find out if this truly works --------- Co-authored-by: Marcin Szymczak * Revise pull request template format (#4183) Updated the pull request template to use bullet points for changes. # Description The `Changes` section contains a list of changes, not a list of checks that require a checkbox. Latest example: https://github.com/cowprotocol/services/pull/4182 # Changes * changes section in template is now bullet points and not checkboxes * Migrate trusted tokens to the config (#4189) # Description Migrates the trusted tokens to the main config. I've grouped to be easier to read/find in config files # Changes * Removes the trusted tokens from the CliArgs * Adds them to the new config file ## How to test Staging * Optimize uni v3 liquidity fetching (#4188) # Description The majority of time fetching uni v3 liquidity is spent cloning pool states. This makes up 3.6% of the entire CPU time of the driver. Screenshot 2026-02-19 at 22 50 02 # Changes This PR applies 2 optimizations: * reuse existing allocation with `into_iter().filter().collect()` * store `PoolInfo` in `Arc`s for cheap clones I'd like to highlight the use of `Arc::make_mut`. If there is no `Arc` pointing to the same allocation as our `Arc` we can simply get a mutable reference. If there are other `Arc`s pointing to our allocation we deep clone it and point our `Arc` to the new allocation (clone on write). This is quite cool because together with the fact that `append_events` takes an exclusive reference to the `pools` we know that there can be no new `Arc` pointing to a recently updated `PoolInfo` before the function is over. This ensures that every `PoolInfo` that needs to be updated will always be cloned at most once. ## How to test Briefly tested in prod This yielded surprising results. The amount of time spent inside the uni v3 liquidity fetching logic remained unchanged and even the time used for allocations was pretty much unchanged. But since the cloning of the data was moved to when it gets updated instead of every time it's needed for a request the time needed to fetch all the liquidity improved significantly. (still nothing to write home about, though) Screenshot 2026-02-19 at 23 31 05 Also some other CPU bound pre-processing tasks got faster as well: Screenshot 2026-02-19 at 23 31 14 Screenshot 2026-02-19 at 23 31 31 Screenshot 2026-02-19 at 23 31 23 * Compress S3 body in blocking task (#4185) # Description Serializing the auction and compressing the bytes is CPU bound work and takes quite long. That stuff should be spawned on a block_task to not hog one of the regular tokio runtime threads and cause spikes in tail latency. # Changes * spawn blocking task for serializing and compression s3 upload * Speedup event indexing (#4180) # Description Currently the event indexing logic is dominated by 2 calls: * eth_getBlock * eth_getLogs (events of fetched block) This PR eliminates `eth_getLogs` calls. Since the rest of the indexing logic is not dominated by `eth_getLogs` I think the only way how we could meaningfully speed up the process further is by subscribing to log filters and streaming the results as soon as a new block was processed by the ethereum node. # Changes Since we already have a component that keeps track of the latest block I created a new type that implements `BlockRetrieving` that simply forwards `get_current_block()` calls to the block stream instead of sending an RPC request. Also adds a few tracing spans and replaces the awkward request batching logic of the event handler with something easier to read. ## How to test tested on staging mainnet (indexing overhead should be identical to prod) the time the autopilot waits for essential processing to be done decreased from ~65ms to ~45ms Screenshot 2026-02-19 at 13 04 48 Screenshot 2026-02-19 at 13 03 12 Screenshot 2026-02-19 at 13 05 44 * Upgrade prometheus to 0.14 (#4193) # Description Upgrades prometheus to 0.14 & the metrics macro to 0.6 Closes https://github.com/cowprotocol/services/issues/3338 # Changes * Upgrades prometheus to 0.14 & the metrics macro to 0.6 * Changes some callsites to match the new with_label_values signatures ## How to test Staging * Fix order of tokens to improve caching (#4197) # Description When investigating CoinGecko usage I noticed our nginx cache is not being optimally used, it is in fact used very little. The cache can only trigger when the URL is the same. There are two issues: the batches have different compositions and the tokens are not in deterministic order. This PR fixes the second problem. * Bulk insert order events query (#4184) # Description Because the insert order_events query tries to avoid duplicating the last event entry it's quite awkward. That's probably why we didn't replace it with a bulk request earlier. However, flamegraphs showed that we spend a ridiculous 49% of our CPU time on inserting orders. # Changes Implemented awkward bulk insert query ## How to test single insert variant was already tested before 1 new unit test for multi insert variant Reduced CPU usage: **0.55 cores -> 0.2 cores** Screenshot 2026-02-19 at 20 28 27 Faster at handling other CPU bound work Screenshot 2026-02-19 at 20 28 55 Total time spent on that query: **49% -> 3%** Screenshot 2026-02-19 at 20 30 08 Screenshot 2026-02-19 at 20 30 17 * Avoid unnecessary overhead from logging (#4191) # Description To my surprise do `tracing` macros not lazily evaluate expressions. So in following code: ```rust tracing::trace!( path = &url.path(), body = %payload.body_to_string(), "solver request", ); ``` `payload.body_to_string()` always gets evaluated but the `Display` implementation of the resulting `String` gets called only when the log is enabled. This leads to the autopilot currently spending ~2% of the time in `body_to_string()`. To address this I introduced the `Lazy` type that delays the execution of the passed in expression to when the `tracing` machinery actually executed the `Display` or `Debug` functionality. Additionally flamegraphs showed that the driver is currently burning 2.2% of its CPU time on logging auction deadlines. I downgraded that log to `trace` since it's not really that useful. # Changes used `Lazy` in the autopilot downgraded expensive log to `trace` # Test Manually tested that `body_to_string()` does not get called by injecting a `panic!()` without causing tests to fail added unit tests for `Lazy` in `observe` crate * Migrate banned users, order events cleanup, and S3 config to config file (#4192) # Description Move banned_users, order_events_cleanup_interval/threshold, and S3 upload settings from CLI arguments to the TOML config file. # Changes - Add config modules: banned_users, order_events_cleanup, s3 - Remove infra::persistence::cli (S3 CLI args) - Delete corresponding CLI arguments and Display impl entries - Wire config values through run.rs ## How to test Staging Co-authored-by: Claude Opus 4.6 * Serialize auction in blocking task (#4186) # Description The task that writes the current auction to the DB has 2 performance issues: * unnecessary conversions: instead of serializing the DTO to a JSON string we convert it to `JsonValue` and let `sqlx` do the conversion to string * conversions on wrong task: serializing data is very slow and blocks an entire runtime thread if we don't do the sync work in a blocking task (the entire process often takes more than 1s) # Changes * skip 1 conversion by directly serializing the DTO to string and writing a string to the DB (same pattern already applied by some other JSON upload) * serialize the DTO on a blocking task ## How to test adjusted existing e2e tests, they show that the serialized data can be read back into the exact JSON value we serialized in the first place * Restructure observe crate (#4196) # Description Follow up to this [comment](https://github.com/cowprotocol/services/pull/4191#discussion_r2837775501) suggesting a different structure for the `observe` crate. This PR goes a bit further than the comment suggested but I think this setup makes the most sense. # Changes old structure: ``` distributed_tracing - request_id.rs - trace_id_format.rs - tracing_axum.rs lazy.rs tracing.rs tracing_reload_handler.rs ``` new structure: ``` tracing - distributed - axum.rs - headers.rs - request_id.rs - trace_id_format.rs - lazy.rs - init.rs - reload_handler.rs ``` * Remove backported header injector & extractor (#4200) # Description The removed structs + impls were here because of incompatibility issues. We've since fixed them, it's time to remove them 🗑️ # Changes * Add opentelemetry-http dep * Remove the HeaderInjector + HeaderExtractor ## How to test Staging but this should be basically 1:1 * Improve CoinGecko batching mechanism (#4202) # Description `estimate_prices_and_update_cache` iterates all tracked tokens, most of which are cache hits that resolve instantly. With .buffered() a pending cache miss at the front blocks yielding ready results behind it, keeping all remaining slots occupied and preventing new futures from entering the stream. This means expired tokens trickle into BufferedRequest too slowly producing batches of 2-3 tokens instead of the max 20. https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=63d75fe56901ecfe850ec03875615596 demonstrates this. * [TRIVIAL] Remove noisy tracing spans (#4203) # Description This function gets called hundreds of times within one span leading to a tempo view that scrolls many pages which makes it hard to get the big picture of the call. The caller of the noisy call is also instrumented and measures the execution time of all sub calls together so we'll not lose much information. # Changes remove instrumentation of frequently called function * Autopilot measure network transfer (#4204) # Description Some solvers reported that `/solve` requests arrive with varying delays. It's quite hard to pin point where the issues lies. While some improvements on the autopilot definitely reduced the variance of the delay greatly there are still new reports about this. In order to get more data on this (especially from solvers NOT using the CoW hosted reference driver) this PR tries to approximate the data transfer times from the autopilot side. This is accomplished by writing a slim wrapper around the fully serialized payload `Bytes` that turns it into a stream. That way we can at least measure the time it takes `hyper` to write the bytes to the network stack. Since the consumer (external driver) will have back-pressure on the transfer this measurement should come reasonably close to the actual data transfer times. # Changes - added `BytesStream` that yields `Bytes` in 1MB chunks - also measures time to start the transfer and the entire transfer time - logs the result using the original tracing span (which now contains the auction_id, and driver) ## How to test added unit test for correctness of the yielded data ran in e2e test to confirm the logs contain the expected spans --------- Co-authored-by: José Duarte * Only check allowance of liquidity sources that are actually used (#4190) # Description Currently a lot of CPU time (~3.5%) is spent on fetching allowances for the liquidity sources we index. This is problematic for 2 reasons: * most liquidity sources are not used so fetching the settlement contract's allowance is wasted time * the driver seems to not use the fetched allowance and fetches them again when encoding the tx (see [1](https://github.com/cowprotocol/services/blob/main/crates/driver/src/domain/competition/solution/encoding.rs#L175), [2](https://github.com/cowprotocol/services/blob/main/crates/driver/src/domain/competition/solution/mod.rs#L294-L299), [3](https://github.com/cowprotocol/services/blob/main/crates/driver/src/domain/competition/solution/mod.rs#L427), [4](https://github.com/cowprotocol/services/blob/main/crates/driver/src/domain/competition/solution/interaction.rs#L33-L62)) # Changes deleted `solver::interactions::allowances` and it's usages ## How to test e2e tests still work * Migrate autopilot native price estimation config from CLI args to config file (#4194) # Description Move native price estimator settings (estimators, api_estimators, results_required, cache_refresh_interval, native_price_prefetch_time) from CLI arguments to the TOML config file. # Changes * Move the autopilot native price estimator settings * Add tests # Migration Guide Autopilot native price estimator CLI arguments have been moved to the TOML config file under the `[native-price-estimation]` table. ## Estimator format Previously, estimator variants were specified as `|`-separated strings. Now they are inline tables with a `type` field: | Old (CLI) | New (TOML) | |---|---| | `CoinGecko` | `{ type = "CoinGecko" }` | | `OneInchSpotPriceApi` | `{ type = "OneInchSpotPriceApi" }` | | `Driver\|solver1\|http://localhost:8080` | `{ type = "Driver", name = "solver1", url = "http://localhost:8080" }` | | `Forwarder\|http://localhost:12088` | `{ type = "Forwarder", url = "http://localhost:12088" }` | Stages (previously separated by `;`) become separate inner arrays. Estimators within a stage (previously separated by `,`) are elements of the same inner array. **Old (CLI):** ``` CoinGecko,OneInchSpotPriceApi;Driver|solver1|http://localhost:8080;Forwarder|http://localhost:12088 ``` **New (TOML):** ```toml [native-price-estimation] estimators = [ [{ type = "CoinGecko" }, { type = "OneInchSpotPriceApi" }], [{ type = "Driver", name = "solver1", url = "http://localhost:8080" }], [{ type = "Forwarder", url = "http://localhost:12088" }], ] ``` ## Arguments - `--native-price-estimators` (CLI) / `NATIVE_PRICE_ESTIMATORS` (env): ```toml [native-price-estimation] estimators = [] ``` - `--api-native-price-estimators` (CLI) / `API_NATIVE_PRICE_ESTIMATORS` (env): ```toml [native-price-estimation] api-estimators = [] ``` - `--native-price-estimation-results-required` (CLI) / `NATIVE_PRICE_ESTIMATION_RESULTS_REQUIRED` (env): ```toml [native-price-estimation] results-required = 2 ``` - `--native-price-cache-refresh` (CLI) / `NATIVE_PRICE_CACHE_REFRESH` (env): ```toml [native-price-estimation] cache-refresh-interval = "1s" ``` - `--native-price-prefetch-time` (CLI) / `NATIVE_PRICE_PREFETCH_TIME` (env): ```toml [native-price-estimation] prefetch-time = "80s" ``` ## How to test Staging --------- Co-authored-by: Claude Opus 4.6 * [TRIVIAL] Change gzip level to 3 (#4209) # Description gzip is super heavy on the CPU and currently we are compressing data with the maximum level. However, gzip quickly reaches a level of diminishing returns so this PR picks a more reasonable gzip compression level based on a test. ``` Level 1: 2214921 bytes (62ms) Level 2: 2179485 bytes (62ms) Level 3: 2149025 bytes (65ms) Level 4: 1981237 bytes (116ms) Level 5: 1927338 bytes (133ms) Level 6: 1883464 bytes (145ms) Level 7: 1864462 bytes (159ms) Level 8: 1844328 bytes (225ms) Level 9: 1840352 bytes (301ms) ``` I picked a recently uploaded auction instance (~11MB) and compressed it once with each level. This showed that level 3 cuts the runtime by ~80% while degrading the compressed size by only 15% (from 1.84MB to 2.14MB). This caused other blocking operations in the autopilot to run faster (e.g. request serialization goes from ~90ms to ~70ms). Screenshot 2026-02-25 at 07 54 37 # Changes Reduced gzip compression level from max (i.e. 9) to 3. * Move orderbook configuration from CLI args to TOML config file (#4195) # Description Migrate order validation, IPFS, volume fee, and misc orderbook settings (unsupported tokens, banned users, EIP-1271 validation, gas limits, same-tokens policy, etc.) from clap CLI arguments to a TOML configuration file. # Changes * Migrates the parameters above * Groups IPFS, order validation and volume fee * Refactors where applicable * Adds unit tests for (de)serialization # Orderbook Configuration Migration: CLI to TOML # Migration Guide The orderbook no longer accepts certain settings as CLI arguments. They must now be provided in a TOML config file, passed via the new **required** `--config` flag. ```bash orderbook --config=/path/to/orderbook.toml ... ``` ## Migrated settings | Old CLI Argument | TOML Key | |---|---| | `--min-order-validity-period` | `order-validation.min-order-validity-period` | | `--max-order-validity-period` | `order-validation.max-order-validity-period` | | `--max-limit-order-validity-period` | `order-validation.max-limit-order-validity-period` | | `--max-limit-orders-per-user` | `order-validation.max-limit-orders-per-user` | | `--max-gas-per-order` | `order-validation.max-gas-per-order` | | `--same-tokens-policy` | `order-validation.same-tokens-policy` | | `--unsupported-tokens` | `unsupported-tokens` | | `--banned-users` | `banned-users.addresses` | | `--banned-users-max-cache-size` | `banned-users.max-cache-size` | | `--eip1271-skip-creation-validation` | `eip1271-skip-creation-validation` | | `--ipfs-gateway` | `ipfs.gateway` | | `--ipfs-pinata-auth` | `ipfs.auth-token` | | `--app-data-size-limit` | `app-data-size-limit` | | `--active-order-competition-threshold` | `active-order-competition-threshold` | | `--volume-fee-factor` | `volume-fee.factor` | | `--volume-fee-effective-timestamp` | `volume-fee.effective-from-timestamp` | ## Example Before: ```bash orderbook \ --min-order-validity-period=2m \ --max-order-validity-period=6h \ --banned-users=0xdead000000000000000000000000000000000000 \ --ipfs-gateway=https://gateway.pinata.cloud/ipfs/ \ --ipfs-pinata-auth=my-secret-key \ --volume-fee-factor=0.0002 \ --volume-fee-effective-timestamp=2025-06-01T00:00:00Z \ --same-tokens-policy=allow-sell \ --bind-address=0.0.0.0:8080 ``` After — create `orderbook.toml`: ```toml same-tokens-policy = "allow-sell" [order-validation] min-order-validity-period = "2m" max-order-validity-period = "6h" [banned-users] addresses = ["0xdead000000000000000000000000000000000000"] [ipfs] gateway = "https://gateway.pinata.cloud/ipfs/" pinata-auth = "my-secret-key" [volume-fee] factor = 0.0002 effective-from-timestamp = "2025-06-01T00:00:00Z" ``` ## How to test Tests + Staging --------- Co-authored-by: Claude Opus 4.6 * [M3] docs(playground): add Using Otterscan user guide (#4146) ## Summary This PR completes **Milestone 3: Documentation** of the [CoW Grants Program RFP: CoW Protocol Playground Block Explorer Integration](https://forum.cow.fi/t/grant-application-cow-protocol-playground-block-explorer-integration/3284/1) proposal by CoBuilders. ### Documentation Strategy Most of the technical documentation for this integration was delivered inline with the implementation in M1 and M2: | Milestone | Documentation Delivered | |-----------|------------------------| | **M1** ([#4000](https://github.com/cowprotocol/services/pull/4000)) | README sections for Sourcify configuration, contract verification, component table updates | | **M2** ([#4077](https://github.com/cowprotocol/services/pull/4077), [cowswap#6774](https://github.com/cowprotocol/cowswap/pull/6774)) | Environment variable documentation in `.env` files, JSDoc comments in source code | **This PR** adds the remaining user-facing documentation: a practical guide on how to use Otterscan for transaction inspection and debugging. ## Changes Adds a "Using Otterscan" section to the playground README covering: - How to access and use Otterscan (`http://localhost:8003`) - Inspecting transactions (overview, traces, logs, gas profiling) - Debugging failed transactions with trace analysis - Example workflow: Tracing a CoW Swap settlement ## Milestones | Milestone | Description | Status | |-----------|-------------|--------| | M1 | Otterscan Integration | [#4000](https://github.com/cowprotocol/services/pull/4000) ✅ | | M2 | Frontend Integration | [#4077](https://github.com/cowprotocol/services/pull/4077) + [cowswap#6774](https://github.com/cowprotocol/cowswap/pull/6774) ✅ | | M3 | Documentation | **This PR** | --- *Submitted by [CoBuilders](https://cobuilders.xyz) as part of the CoW Grants Program* --------- Co-authored-by: Augusto Collerone Co-authored-by: Ignacio * Add v1/orders POST endpoint to get orders in batches (#4048) # Description Aave wants to track specific orders in bulk, knowing their ids. # Changes Adds POST handler for `v1/orders/lookup` endpoint that requires a list of order uids and responds with a vector of their data. Has a hardcoded limit of 128 orders per request (to fit the MAX_JSON_BODY_PAYLOAD size). ## How to test Test on staging, query multiple orders using this API. --------- Co-authored-by: José Duarte Co-authored-by: Martin Magnus Co-authored-by: ilya * Migrate the playground config (#4214) # Description The playground was left behind amidst the config changes. This PR brings the playground back up to speed. # Changes * Remove unused variables * Migrate the orderbook migration * Migrate the autopilot migration ## How to test Run the playground and execute a trade * Compile only used packages in main Dockerfile (#4220) # Description The Dockerfile was compiling the whole workspace which is wasteful for deployment (it was compiling e2e for example). This PR only compiles the used packages. # Changes * Compiling workspace -> autopilot, driver, orderbook, refunder, solvers only ## How to test Build the dockerfile * Replace full alloy import with specific alloy crates (#4219) # Description Based on @MartinquaXD's comment on Slack, I figured that there would be places where we can import the specific alloy crate, unlocking earlier and more parallel compilation. These changes alone seem to save ~5s on a clean build in my laptop; going down from ~1m to 55s. There's more gains to take from this, but for that we need to separate shared further and then split those dependencies. https://github.com/cowprotocol/services/pull/4217 would also help a bunch. # Changes * app-data * replace alloy with alloy-primitives * since alloy uses tiny-keccak, replace the tiny-keccak with alloy's keccak * chain * manually implement thiserror (less 1 dep) * replace alloy with alloy-primitives * model * replace alloy with alloy-primives, alloy-signer, alloy-signer-local, alloy-sol-types * number * replace alloy with alloy-primitives * order-validation * replace alloy with alloy-primitives and alloy-contract * remove thiserror wrapper (less 1 dep) * serde-ext * replace alloy with alloy-primitives * testlib * replace alloy with alloy-primitives * winner-selection * replace alloy with alloy-primitives ## How to test Compiler + existing tests * Enhance error handling and reporting for app data deserialization (#4213) # Description When users provide app data with wrong partner fees, we get less than good error reporting, for example: ``` {"errorType":"AppDataInvalid","description":"app data has the wrong format: data did not match any variant of untagged enum Helper at line 1 column 232"} ``` This is, not only a pain for us to debug but also for the user: * For us because the special deserializers are all named Helper * For the user because of the bad error message This PR changes the deserialization mechanism in a "non-obvious" way to provide proper error messages, as untagged enums stand very much against them. For example floating point bps are unsupported, they returned the previous error; after this PR they return: ``` {"errorType":"AppDataInvalid","description":"app data has the wrong format: invalid type: floating point `99.0`, expected u64 at line 1 column 128"} ``` # Changes * Rename the deserializers * Refactor the deserializers into weird structs (but better for error reporting) ## How to test Unit tests to ensure nothing broke + playground to test the error ``` curl 'http://localhost:8000/api/v1/app_data \ -X 'PUT' \ -H 'accept: application/json' \ -H 'content-type: application/json' \ --data-raw '{"fullAppData":"{\"appCode\":\"YOUR_APP_CODE\",\"metadata\":{\"partnerFee\":[{\"recipient\":\"0x28c716bC23ed77CAEc27f476A366318ad5F12d58\",\"volumeBps\":99.5},{\"priceImprovementBps\":9900,\"recipient\":\"0x28c716bC23ed77CAEc27f476A366318ad5F12d58\",\"volumeBps\":100}]},\"version\":\"1.14.0\"}"}' ``` * Optimize batching for CoinGecko (#4215) # Description I have run a bunch of experiment goal of which was to make CoinGecko batches bigger (fetch price for more tokens at once) to save on costs. In a previous experiment I increased `concurrent_requests` on the `CachingNativePriceEstimator` to 500 and that totally worked and CoinGecko debounces all these requests nicely, but also means we would be spamming solvers with a lot of requests at once when we start the service. The first issue at hand is that before this PR we would create futures that would hit the cache and return immediately, which was bad, because they take up a slot in the queue when using buffered() so fewer useful futures that actually issue a native price request would get to run. I changed this to buffered_unordered() and that helped a little bit, but then the queue gets full and after that new futures get in only as previous futures finishe, so the execution is spread out in time and our CoinGecko debouncing can't gather enough tokens to form a full batch. This PR introduces a solution where we omit non-expired tokens altogether which solves the issue of futures that hit the cache taking up a spot in the queue _and_ we just run batches of 19 sequentially. We wait for each batch to finish (max ~3s, limited by QUERY_TIMEOUT) and only then issues a new batch. As a tradeoff we get a slower cache warm up/refresh, but we get to save a lot of money on CoinGecko. In addition to this change I set the refresh rate to 30s from 1s, so we can gather more expired tokens at once. Screenshot 2026-03-02 at 11 30 33 # Changes * [x] Don't try to fetch non-expired prices * [x] Run batches sequentially * [x] NATIVE_PRICE_CACHE_REFRESH set to 30s instead of 1s in service config --------- Co-authored-by: Martin Magnus * Compress `/solve` requests (#4212) # Description Our current `/solve` request can be heavy on certain chains, and given the fact that it is expected to eventually switch to a cross-chain auction, the data will be growing significantly. While it is expected to implement a delta-sync approach with event-based auction updates, this is quite involved, and as a first step, we can look into compressing the request to reduce its size and reduce network latency/delays. The tests showed that `brotli` with compression level 1 outperforms `gzip` https://github.com/cowprotocol/services/pull/4212#discussion_r2857437463 # Changes ##Autopilot - New `--compress-solve-request` boolean CLI flag threaded through `run_loop::Config` - `solve::Request` gains a `compressed()` method that br-compresses the body on a blocking task (CPU-intensive, like the existing serialization) - On compression failure, falls back to sending the uncompressed body with an error log - Compression time is tracked via the existing `auction_overhead` metric - Sets the `Content-Encoding: br` header only when the body is actually compressed - Add a `runloop_solve_request_body_size` histogram metric that records the size (in bytes) of the JSON body sent in each `/solve` request to drivers. ## Driver - Adds `RequestDecompressionLayer` to the axum middleware stack, which automatically decompresses br-encoded request bodies based on the `Content-Encoding` header ## How to test New unit and e2e tests. Deploy manually with disabled compression to collect some metrics and then with compression enabled. ## Related issues Fixes #4206 * split off new crates from `shared` (#4223) # Description `shared` is a big ball of spaghetti where dependencies are extremely unclear. This PR untangles this somewhat # Changes - moves some code out of shared to the only location where it's used - `trace_many` into `bad_token::trace_call` - `CodeFetching` into `trade_verifier` - `BlockRetrieving` into `event-indexing` - `tenderly_api` into `trade_verifier` - `subgraph` into `sources` - `RecentBlockCache` into `sources` - create separate crates for `balance-overrides`, `event-indexing`, `account-balances`, `request-sharing`, `token-info`, `liquidity-sources` - moved `BaseTokens` and `BaselineSolvable` into `liquidity-sources` as that made the most sense to me Some of the new crates are super tiny but are necessary to later move `price-estimation` into a separate crate as well. This already cut the compilation time of `shard` in half which are gains we'll often see in incremental builds Screenshot 2026-03-03 at 10 31 50 Screenshot 2026-03-03 at 10 31 55 ## How to test compiler * Allow multiple parallel settlements from one solver (#4167) # Description Resolves https://github.com/cowprotocol/services/issues/3966. Enable parallel settlement submission from a single solver by using EIP-7702 delegation. When a solver wins multiple solutions in overlapping auctions, each settlement can now be submitted concurrently through independent submission EOAs instead of waiting in a sequential queue. The solver EOA delegates its code to a CowSettlementForwarder contract via EIP-7702. Approved submission EOAs call forward(target, data) on the solver EOA, which forwards to the actual target (settlement contract, wrapper, or flashloan router) with msg.sender = solver EOA — preserving the existing whitelisting. # Changes - [x] `CowSettlementForwarder.sol` — New forwarder contract with caller whitelist. Supports forwarding to any target (settlement contract, wrappers, flashloan router) via `forward(address target, bytes data)` - [x] `SubmissionAccountPool` type: a channel-based pool that lends submission accounts for the duration of a settlement and reclaims them afterward. In EIP-7702 mode, settle requests are spawned as concurrent tasks instead of processed sequentially - [x] `submission_accounts` config that allows specifying accounts that are whitelisted to submit settlements. If omitted we fallback to non-delegated submissions directly from the solver EOA # Requirements before deployment * Deploy & verify the CowSettlementForwarder contract * Set up EIP7022 delegation to whitelisted solver EOAs to the newly deployed contract * Create new submissions accounts and fund them with eth (or other base token) & monitor their eth balance * Whitelist the submissions accounts on the solver EOA (CowSettlementForwarder code) Only then can the submissions accounts be configured and deployed. ## How to test cargo nextest run -p e2e local_node_parallel_settlement --test-threads 1 --run-ignored ignored-only * Move price estimation into separate crate (#4225) # Description Further untangles the `shared` crate. # Changes * deletes `bytes.rs` which was forgotten in an old PR * moved `trade_finding` into `price-estimation` * move `tenderly_api` arguments from `shared::Arguments` to `price_estimation::Arguments` * removed a few dependencies * downgraded a few dependencies to `dev-dependencies` new crates split off of `shared`: * `gas-price-estimation` * `price-estimation` * `bad-tokens` Unfortunately `price-estimation` is relying on a few utils which didn't make much sense to move into a new crate so I duplicated a few things in `price_estimation::utils` but I think this does not outweigh the structure and compile time gained by this PR. This PR does again not migrate the catch all `alloy` imports to the more specific sub-crates (e.g. `alloy_primitives`) ## How to test compiler * Migrate shared native price estimation config from CLI to config file (#4216) # Description Move native price cache, concurrent requests, approximation tokens, and balancer SOR URL settings from CLI arguments into the TOML config file. Introduce a shared NativePriceConfig in the shared crate that both autopilot and orderbook embed via serde flatten. Also migrate orderbook's native price estimator selection and results_required from CLI to config. # Changes * Moves shared native price settings (cache max-age, concurrent requests, approximation tokens) into a new `shared::price_estimation::config::native_price` module, used by both autopilot and orderbook via `#[serde(flatten)]` * Moves orderbook-specific settings (native price estimators, fallback estimators, results-required) from CLI args to the orderbook TOML config * Removes `--balancer-sor-url` (unused) * Adds unit tests for (de)serialization of the new config structs ## Migrated settings ### Shared (autopilot + orderbook) These settings were in `shared::price_estimation::Arguments` (CLI) and now live in the `[native-price-estimation]` section of each service's TOML config, flattened from the shared `NativePriceConfig`. | Old CLI Argument | TOML Key | |---|---| | `--native-price-cache-max-age` | `native-price-estimation.cache.max-age` | | `--native-price-cache-concurrent-requests` | `native-price-estimation.cache.concurrent-requests` | | `--native-price-approximation-tokens` | `native-price-estimation.approximation-tokens` | | `--balancer-sor-url` | **removed** | ### Orderbook-only These settings were in `orderbook::arguments::Arguments` (CLI) and now live in the `[native-price-estimation]` section of the orderbook TOML config. | Old CLI Argument | TOML Key | |---|---| | `--native-price-estimators` | `native-price-estimation.estimators` | | `--native-price-estimators-fallback` | `native-price-estimation.fallback-estimators` | | `--fast-price-estimation-results-required` | `native-price-estimation.results-required` | ## Example ### Orderbook Before: ```bash orderbook \ --native-price-cache-max-age=5m \ --native-price-cache-concurrent-requests=4 \ --native-price-approximation-tokens="0xaaa...111|0xbbb...222,0xccc...333|0xddd...444" \ --native-price-estimators="[[{type = \"CoinGecko\"}, {type = \"OneInchSpotPriceApi\"}]]" \ --native-price-estimators-fallback="[[{type = \"CoinGecko\"}]]" \ --fast-price-estimation-results-required=3 \ --config=/path/to/orderbook.toml \ --bind-address=0.0.0.0:8080 ``` After — add to `orderbook.toml`: ```toml [native-price-estimation] estimators = [[{ type = "CoinGecko" }, { type = "OneInchSpotPriceApi" }]] fallback-estimators = [[{ type = "CoinGecko" }]] results-required = 3 approximation-tokens = [ ["0xaaa...111", "0xbbb...222"], ["0xccc...333", "0xddd...444"], ] [native-price-estimation.cache] max-age = "5m" concurrent-requests = 4 ``` ### Autopilot Before: ```bash autopilot \ --native-price-cache-max-age=10m \ --native-price-cache-concurrent-requests=2 \ --native-price-approximation-tokens="0xaaa...111|0xbbb...222" \ --config=/path/to/autopilot.toml ``` After — add to `autopilot.toml` (inside the existing `[native-price-estimation]` section): ```toml [native-price-estimation] # ... existing autopilot-specific settings (estimators, etc.) ... approximation-tokens = [ ["0xaaa...111", "0xbbb...222"], ] [native-price-estimation.cache] max-age = "10m" concurrent-requests = 2 ``` ## How to test Staging --------- Co-authored-by: Claude Opus 4.6 * Accept hex params with and without 0x prefix (#4228) # Description During the Axum migration we (unknowingly) stopped supporting unprefixed hex values — this was due to Axum using the JSON deserialization (which for OrderUids for example, was rejecting non-prefixed hex) while warp used FromStr which had a more lenient implementation. This PR unifies the FromStr and serde impls and adds tests to the endpoints to ensure # Changes We notified downstream partners but this single path was forgotten in the respective migration * Refactor cancel_order handler to use Axum typed extractors (Path, Json) instead of manual parsing * Change solver_competition_v2 auction_id to u64 so negative values return BAD_REQUEST * Update OrderUid deserialization and FromStr to delegate 0x handling to const_hex (which strips the prefix internally) * Add extraction tests across all orderbook API endpoints verifying hex params (OrderUid, Address, B256, AppDataHash) accept both prefixed and non-prefixed input ## How to test Run tests Co-authored-by: Claude Opus 4.6 * Add TOML formatting support (#4226) # Description It's really annoying not to have a standard TOML formatting utility, without it we'll bikeshed on something generally not worth losing time over. This PR addresses that issue by introducing [Tombi](https://github.com/tombi-toml/tombi) in several forms: * CLI * VSCode (extension recommendation to ensure the right extension + extension config) * Zed Note: Tombi also does linting and is aware of the deprecation of package.authors in Cargo.toml, however, the book doesn't mention much of an alternative because in reality it's not just the package submitter etc, see https://github.com/rust-lang/cargo/issues/16458 for more info. # Changes * New recipe for the Justfile * Editor configs * Add tombi to CI too * Formatted all *.toml ## How to test * Test it in your respective editor * Run `just tombi-fmt` (add `--check` for check) * Extract `signature-validator` and `zeroex-api` crates (#4224) # Description While fixing my config I got Claude to address my own comments on https://github.com/cowprotocol/services/pull/4223 Move `signature_validator` and `zeroex_api` modules into their own workspace crates and update all dependents to import from the new crates. This continues the effort to reduce the `shared` crate's surface area. # Changes * Extract zeroex-api from shared — this one was a freebie and doesn't make a lot of sense being in shared as it so specific * Extract signature-validator — this one allows us to compile cow-amm without shared (see image below) * Remove thiserror in crates that had errors with a single variant or similar, in favor of manual implementation Left is after, right is before Screenshot 2026-03-03 at 14 10 09 ## How to test Compiler + tests --------- Co-authored-by: Claude Opus 4.6 Co-authored-by: MartinquaXD * Increase override amount (#4231) # Description We got a report about failing simulations on Polygon. It turns out that when we put in this change we had 1 ETH in mind which is fine on ETH chains, but it also translates to 1 POL which is worth about $0.9 and leads to simulation failures due to insufficient gas. * replace `alloy` dependencies with specific sub-dependencies (#4230) # Description While depending on `alloy` is convenient because it bundles everything inside 1 dependency it also hides what parts are actually needed and can delay the compilation of crates unnecessarily. # Changes replace `alloy` dependency with the actually used sub-crates like `alloy-primitives` and `alloy-provider` this PR mostly focuses on the smaller "leaf" dependency crates as they are manageable in size and allowing them to compile earlier is more important than optimizing crates like `driver`, and `autopilot`. So far this PR only improves the compile time very slightly by making sure `contracts` does not indirectly depend on `aws-sdk-kms` as that is insanely slow to compile. The effects of this change will be more pronounced when we change the `contracts` crate to be compiled as individual `crates`. But I think even without huge compile time gains being explicit about our dependencies still makes sense. ## How to test compiler * Migrate the database shared configuration to a file (#4218) # Description This PR moves the DB CLI arguments to a configuration file. Additionally, this PR introduces the configs/ crate to ease mix and matching parts of the config and to have one more "compilation caching" place, to reduce the size of the shared crate. # Changes * Create configs/ crate * Migrate & unify the db arguments from shared, orderbook and autopilot in configs/database # Migration Guide Database connection settings move from CLI args to the TOML config file (`[database]` section) for both autopilot and orderbook. ## CLI args -> TOML | CLI arg / env var | TOML replacement (`[database]`) | |---|---| | `--db-write-url` / `DB_WRITE_URL` | `write-url` | | `--db-read-url` / `DB_READ_URL` (orderbook) | `read-url` | | `--db-max-connections` / `DB_MAX_CONNECTIONS` | `max-connections` | | `--insert-batch-size` / `INSERT_BATCH_SIZE` (autopilot) | `insert-batch-size` | Keeping removed args in your startup command will cause an error. ## TOML example ```toml [database] write-url = "postgresql://user:password@host:5432/dbname" read-url = "postgresql://readonly:password@replica:5432/dbname" # orderbook only, optional max-connections = 10 insert-batch-size = 500 # autopilot only ``` All fields have defaults matching the old CLI defaults (`write-url = "postgresql://"`, `max-connections = 10`, `insert-batch-size = 500`, `read-url` falls back to `write-url`). Omitting `[database]` entirely preserves previous behavior. ## How to test E2E tests + staging --------- Co-authored-by: Claude Opus 4.6 * reenable https support (#4238) # Description When replacing the `alloy` dependency with its sub-depdencies I let compilation errors guide me which features need to be enabled. However, this was not enough since `https` support needs to be enabled via a feature and if that's missing we'll only encounter runtime errors but not compile time errors. `reqwest` will throw errors like `invalid URL, scheme is not http`. # Changes Enabled `https` on the workspace level to avoid feature unification causing us to miss `https` support in future edge cases Added a unit test to ensure that `https` requests succeed. ## How to test new unit test * Fix log instructions for claude (#4237) # Description Log format has changed so I updated instructions how to use it for Claude. * Dont rely on feature unification (#4240) # Description Follow up to #4238. While that PR correctly identified the issue that `https` was not enabled when is should have been it did not resolve it correctly. It incorrectly added the tls feature on the workspace toml assuming this will cause the feature to always be enabled but that's actually not correct. The driver still did not support https. I was able to reproduce that by copying the new `test_https` unit test to the driver main and running it from there. `cargo test -- test_https` passed while `cargo test -p driver -- test_https` did not. This indicates that `https` support was only enabled when the whole workspace gets compiled as one. When we only compiled the `driver` feature unification was not there to pull in the necessary features. This PR addresses this by explicitly enabling all the necessary features such that every crate can be compiled and tested individually without complaints. # Changes Add a bunch of features that were previously assumed to not be needed because feature unification pulled them in without our knowledge. Since `alloy` likes to follow the pattern of using feature flags to enable compilation of sub-crates I avoided the use of feature flags by depending on the underlying sub-crate whenever possible. For example this caused me to introduce `alloy_rpc_types_eth` and `alloy_rpc_types_trace` on the workspace level. This PR only fixes the symptom but doesn't prevent us from relying on feature unification without our knowledge again. This should be addressed in a follow up PR. ## How to test After that was done I repeated the test with `cargo -p driver -- test_https` and it passed. Indicating that the `ethrpc` crate now correctly causes https to be supported in any crate that depends on it. * Extract autopilot config groups (ethflow, cow_amm, run_loop) to TOML (#4235) # Description Move ethflow, cow_amm, and run_loop configurations from CLI arguments to dedicated TOML config file modules. Add TestDefault impls for ethflow and run_loop configs so E2E tests use appropriate values (skip_event_sync, shorter timeouts) without manual overrides in test setup code. # Changes * Move ethflow to the config * Move cow_amm to the config * Move run_loop to the config * Move other loose arguments to the config ## How to test Tested in staging # Migration Guide The following autopilot CLI arguments have moved to the TOML config file. ## Ethflow (`[ethflow]`) | CLI arg / env var | TOML key (`[ethflow]`) | |---|---| | `--ethflow-contracts` / `ETHFLOW_CONTRACTS` | `contracts` | | `--ethflow-indexing-start` / `ETHFLOW_INDEXING_START` | `indexing-start` | | `--skip-event-sync` / `SKIP_EVENT_SYNC` | `skip-event-sync` | ## CoW AMM (`[cow-amm]`) | CLI arg / env var | TOML key (`[cow-amm]`) | |---|---| | `--cow-amm-configs` / `COW_AMM_CONFIGS` | `[[cow-amm.contracts]]` (see below) | | `--archive-node-url` / `ARCHIVE_NODE_URL` | `archive-node-url` | The `--cow-amm-configs` pipe-separated format (`\|\|`) is replaced by TOML tables: ```toml [cow-amm] archive-node-url = "https://archive.example.com" [[cow-amm.contracts]] factory = "0x..." helper = "0x..." index-start = 12345678 ``` ## Run loop (`[run-loop]`) | CLI arg / env var | TOML key (`[run-loop]`) | |---|---| | `--max-run-loop-delay` / `MAX_RUN_LOOP_DELAY` | `max-delay` | | `--max-winners-per-auction` / `MAX_WINNERS_PER_AUCTION` | `max-winners-per-auction` | | `--max-solutions-per-solver` / `MAX_SOLUTIONS_PER_SOLVER` | `max-solutions-per-solver` | | `--enable-leader-lock` / `ENABLE_LEADER_LOCK` | `enable-leader-lock` | | `--compress-solve-request` / `COMPRESS_SOLVE_REQUEST` | `compress-solve-request` | | `--submission-deadline` / `SUBMISSION_DEADLINE` | `submission-deadline` | | `--max-settlement-transaction-wait` / `MAX_SETTLEMENT_TRANSACTION_WAIT` | `max-settlement-transaction-wait` | | `--solve-deadline` / `SOLVE_DEADLINE` | `solve-deadline` | ## Standalone fields | CLI arg / env var | TOML key | |---|---| | `--metrics-address` / `METRICS_ADDRESS` | `metrics-address` | | `--api-address` / `API_ADDRESS` | `api-address` | | `--shadow` / `SHADOW` | `shadow` | | `--disable-order-balance-filter` / `DISABLE_ORDER_BALANCE_FILTER` | `disable-order-balance-filter` | | `--max-maintenance-timeout` / `MAX_MAINTENANCE_TIMEOUT` | `max-maintenance-timeout` | | `--run-loop-native-price-timeout` / `RUN_LOOP_NATIVE_PRICE_TIMEOUT` | `native-price-timeout` | | `--unsupported-tokens` / `UNSUPPORTED_TOKENS` | `unsupported-tokens` | | `--min-order-validity-period` / `MIN_ORDER_VALIDITY_PERIOD` | `min-order-validity-period` | | `--max-auction-age` / `MAX_AUCTION_AGE` | `max-auction-age` | All defaults remain unchanged. * Don't consider inflight orders invalid (#4241) # Description When I was working on another feature (the debug endpoint) I noticed that there is a race condition where we sometimes insert "invalid" order event to the DB. From logs ``` 09:19:13.717 stored order events label=Executing count=1 09:19:13.733 filtered orders reason=insufficient_balance count=1 orders=[0x060a...] 09:19:13.737 stored order events label=Invalid count=1 09:19:13.756 stored order events label=Traded count=1 ``` Order is in `executing` state, then settles onchain. If the indexing runs now and we set the order to `traded` all is well. However if the in cache update runs first it's possible the wallet no longer has the necessary balance as it was traded away in the just executed swap and we consider it invalid and we insert the invalid event for an order that went through perfectly well. # Changes * [x] Don't consider in-flight orders invalid * [x] Move in-flight filtering logic from run loop to solvable_orders (move upstream) ## How to test I had an e2e test to reproduce this which failed 6/10 times. After the fix it fails 0/10. --------- Co-authored-by: Martin Magnus * Upgrade quinn-proto to 0.11.14 (#4245) # Description Cargo audit started flagging version 0.11.13, this PR upgrades it to solve the audit issue # Changes * Upgrade quinn-proto to 0.11.14 ## How to test cargo-audit * Update trivy action (#4248) # Description Updates the failing trivy action # Changes * Bump trivy to 0.35 ## How to test CI * Migrate OKX solver (#4236) # Description Migrates the OKX solver from the https://github.com/gnosis/solvers repo as per #4234. ## How to test Migrated tests. ## How to migrate 1 line config change in the infra, which basically replaces the image. * Debug endpoint (#4233) # Description Add a debug endpoint for order investigation. Given an order UID, it returns the full lifecycle: order data, events (created/ready/executing/traded), auction participation, proposed solutions, executions, trades, settlement attempts, and fee policies. Protected by a configurable auth token header. # Changes * New `GET /api/v1/debug/order/{uid}` endpoint behind `x-auth-token` header auth * `DebugReport` DB layer aggregates data from various tables (see data sources) * New `auction_orders` junction table (V106) to efficiently look up all auctions an order participated in, replacing indirect derivation through proposed_solutions/executions * `save_auction()` now populates the junction table alongside `competition_auctions` * Configurable auth tokens via `debug_route_auth_tokens` in orderbook config * E2E test covering the full happy path + auth failure cases ## Queried tables * orders (via single_order) * order_events (via get_all) * proposed_trade_executions + proposed_solutions (via find_solutions_for_order — that's a JOIN) * order_execution (via read_by_order_uid) * trades (via trades) * auction_orders (via fetch_auction_ids_by_order_uid) * competition_auctions (via fetch_multiple) * settlement_executions (via read_by_auction_ids) * fee_policies (via fetch_by_order_uid) ## How to test `cargo nextest run -p e2e debug_order --test-threads 1 --run-ignored ignored-only` Test will be flaky until we get https://github.com/cowprotocol/services/pull/4239 in. # Follow up Include a concrete reason why an order was filtered out of an auction if it was filtered out. * [TRIVIAL] Log order uids with encoding errors (#4250) # Description Currently it's quite annoying to figure out when a solver provided a solution for a given order that fails to encode. # Changes This PR adjusts the log to also mention the order uids of the solution that failed to encode. * Inline submission account config (#4242) # Description While testing a feature, I noticed that the driver's submission account is currently rendered in our infra as follows: ```toml [[drivers]] name = "okx-solve" url = "http://mainnet-driver-staging-.../" [drivers.submission-account] kms = "arn:aws:kms:eu-central-...." ``` , where the submission-account is separated, which is not really convenient when manually editing configs. This PR flattens the submission account config, so it will be rendered as: ```toml [[drivers]] name = "okx-solve" url = "http://mainnet-driver-staging-.../" kms = "arn:aws:kms:eu-central-...." ``` * Extract autopilot config to configs crate (#4246) # Description Addresses https://github.com/cowprotocol/services/pull/4235#discussion_r2910627059 # Changes * Moves autopilot configs to configs crate * Moves fee factor to configs crate ## How to test Compilation + tests * BitGet solver (#4249) This effectively replicates https://github.com/gnosis/solvers/pull/203 # Summary - Add a new solver that integrates with the https://web3.bitget.com/en/docs/swap/ API to find swap routes, following existing patterns - ~~The Bitget API uses a two-step POST flow: `quote` (to get the optimal routing market and output amount) followed by `swap` (to get the on-chain calldata)~~ The BitGet team suggested using the `request_mod="rich"` param in the `/swap` API, which makes the response contain much more data, including the `outAmount`. - ~~To mitigate the race condition between `quote` and `swap` calls, slippage is applied to the quoted output and passed as `toMinAmount` to the swap endpoint, ensuring consistency between what the calldata enforces on-chain and what the solver reports~~ Not needed anymore because of the previous point - Only sell orders are supported - the Bitget API has no `exactOut` mode # Changes - Bitget API client and DTOs with HMAC-SHA256 request signing, base64 calldata decoding, typed ChainName enum, and serialize::U256 for amount fields - TOML config loading (endpoint, chain-id, api-key, api-secret) - E2E tests: market sell order, buy order rejection, insufficient liquidity, and out-of-price limit order - `config/example.bitget.toml` - Example configuration # How to test New unit and manual e2e tests (ignored by default). * Extract orderbook config to configs crate (#4247) # Description Essentially the same as https://github.com/cowprotocol/services/pull/4246 but for the orderbook # Changes * Move orderbook configs to the configs crate * Extract SameTokensPolicy ## How to test Compilation + tests * Extract order_quoting, banned_users, http_client configs to configs crate (#4253) # Description Extract shared configuration types (`OrderQuoting`, `BannedUsersConfig`, `HttpClient`) from CLI arguments into the `configs` crate as TOML-deserialized structs. This continues the migration of runtime configuration from clap CLI args to structured TOML config files. # Changes * Move `OrderQuotingArguments` and `ExternalSolver` from `shared::arguments` (clap) to `configs::order_quoting` (serde/TOML), renaming to `OrderQuoting` * Deduplicate `BannedUsersConfig` — was duplicated in `configs::autopilot::banned_users` and `configs::orderbook::banned_users`, now a single `configs::banned_users` module shared by both * Move `HttpClient` config to `configs::http_client` * Add `TestDefault` impl for `OrderQuoting` and test helpers (`ExternalSolver::new`, `OrderQuoting::test_with_drivers`) * Update all e2e tests to pass `price-estimation-drivers` via config structs instead of the removed CLI arg * Add deserialization and roundtrip tests for `OrderQuoting` ## How to test Existing tests # Migration Guide The following CLI arguments have moved to TOML config files. This applies to **both autopilot and orderbook** services. ## Order Quoting (`[order-quoting]`) | CLI arg / env var | TOML key (`[order-quoting]`) | |---|---| | `--price-estimation-drivers` / `PRICE_ESTIMATION_DRIVERS` | `[[price-estimation-drivers]]` (see below) | | `--eip1271-onchain-quote-validity` / `EIP1271_ONCHAIN_QUOTE_VALIDITY` | `eip1271-onchain-quote-validity` | | `--presign-onchain-quote-validity` / `PRESIGN_ONCHAIN_QUOTE_VALIDITY` | `presign-onchain-quote-validity` | | `--standard-offchain-quote-validity` / `STANDARD_OFFCHAIN_QUOTE_VALIDITY` | `standard-offchain-quote-validity` | The `--price-estimation-drivers` pipe/comma-separated format (`name|url,name2|url2`) is replaced by TOML tables: ```toml [order-quoting] eip1271-onchain-quote-validity = "10m" presign-onchain-quote-validity = "10m" standard-offchain-quote-validity = "1m" [[order-quoting.price-estimation-drivers]] name = "solver1" url = "http://solver1:8080" [[order-quoting.price-estimation-drivers]] name = "solver2" url = "http://solver2:8080" ``` ## Banned Users `BannedUsersConfig` is now shared between autopilot and orderbook (previously duplicated). No config format changes — the `[banned-users]` TOML section remains the same. ## HTTP Client (`[http-client]`) | CLI arg / env var | TOML key (`[http-client]`) | |---|---| | `--http-timeout` / `HTTP_TIMEOUT` | `http-timeout` | All defaults remain unchanged. * Fix Bitget API signature when using proxy endpoint (#4256) # Description - Use the canonical Bitget API path (/bgw-pro/swapx/pro/) for HMAC signature computation instead of deriving it from the configured endpoint URL - When a proxy is configured, the request URL path differs from what Bitget expects in the signature, causing authentication failures - The HTTP request still goes to the configured (possibly proxied) URL - only the signature computation changes - I think it was in one of the original PR comments, but I forgot about it somehow ## How to test Existing tests. Staging. * Store reasons why an order was removed from auction (#4251) # Description When orders get filtered from an auction or marked invalid, we now record why it happened. This PR adds a reason column to the order_events table so we can distinguish between e.g. insufficient_balance, banned_user, missing_price etc. Currently these reasons are only visible in the DB / metrics. # Changes [x] New migration (V107): adds OrderFilterReason enum type and reason column to order_events [x] `solvable_orders` tracks the specific OrderFilterReason alongside each filtered/invalid order UID [x] Added a new filter reason when we filter an order out because it's already in-flight (= being submitted) [x] Added to order debug endopint ## How to test ``` cargo nextest run -p e2e local_node_debug_order --test-threads 1 --run-ignored ignored-only ``` * Move price-estimation config structs into configs (#4259) # Description Simulator refactor requires to move out a couple of dependencies out of shared and reuse them elsewhere. One of them is price_estimation (#4258 with http-client refactor). As both the price_estimation and http-client have their respective configs - having `price_estimation` depend on `http-client` would cause cyclic dependencies on `configs`. # Changes This removes the dependency of configs on price_estimation making it possible to refactor further common types. It also does make sense for other crates to depend on configs, instead of configs depending on other crates. ## How to test E2E tests, no code changes, just moving code around. * [TRIVIAL] Update BitGet solver chain names (#4257) Some BitGet chain names were inconsistent and don't align with [the official docs](https://web3.bitget.com/en/docs/swap/). This PR fixes it. Tested on staging on each chain. * Clean up solvers util (#4254) # Changes - Remove unused bytes.rs module and empty url.rs from `crates/solvers/src/util/` - Remove `fmt/hex.rs` module, replacing `Hex<'a>` wrapper with inline `const_hex::encode_prefixed` calls (already a dependency) - Replace `Bytes>` in `eth::Tx` with `alloy::primitives::Bytes` which provides hex debug formatting out of the box - Use `const_hex::decode` in Bitget DTO instead of manually stripping 0x prefix + `hex::decode` - Drop the `hex` crate dependency from solvers (no longer needed after switching to `const_hex`) ## How to test Existing tests * Refactor http-client to separate crate (#4258) # Description During the simulator crate refactor I had to move a couple of types out of shared to avoid cyclic dependency. It also makes sense since price_estimation essentially duplicated http-client logic. # Changes Creates a new crate `http-client` where the previously `shared::http_client` is present. Updated imports. De-duplicated `price-estimation` http client,. ## How to test E2E tests should pass. Logic is the same. --------- Co-authored-by: Martin Magnus * Increase quote verification gas limit (#4261) # Description Some insanely complicated quotes exceed the hardcoded 12M gas limit on the simulation. I'm not sure why originally picked the very big but not the highest possible value of 8M which we later bumped to 12M. # Changes This PR now makes this value configurable and sets the default to ethereum's maximum amount of gas a single tx may consume. Any quote that needs more gas than this can definitely not be included in an ethereum block. * Use write DB for latency sensitive queries (#4262) # Description Whenever the circuit breaker sees a new settlement onchain it asks the API for the competition data on it to determine whether the tx was allowed to be executed. Because the solver is supposed to be runnable by anyone it fetches the necessary data from the public API instead of a DB directly. Yesterday a solver got wrongly deny-listed because the API did not return the competition data for a given auction id. This was very strange because we have logs confirming that the necessary data was fully written to the DB at `2026-03-13 03:12:19` but the request coming in at `2026-03-13 03:12:21` still returned no data. In order to relieve strain on the primary DB we migrated all queries that only read data to use the read replica instead. Since the query is dirt simple and does not apply any complicated filters or joins which could cause us to not return any data the most likely culprit is the delay introduced by the replication process. # Changes Adjust the API to fetch latency sensitive competition data from the main DB instead of the read replica. In order to not comment multiple times why we use the write DB when only read data I introduced private helper functions which return the appropriate DB and commented why this is necessary. * Allow full balance and allowance checking during order creation (#4229) # Description Most recently We have had an order that has passed our balance and allowance checks, but ultimately the account did not have enough allowance (up to the sell token amount) for it to be settled. There are various balance checks in place (testing 1, 10, 100 transfer amounts). Always checking the full sell amount would be a breaking change, so it is introduced as additional parameter to the OrderCreation request. # Changes Adds additional parameter to the OrderCreation request: fullBalanceCheck (`full_balance_check`) which defaults to false. If set to true, the order validation will attempt transfer of full sell token amount in order to verify the account has enough balance and proper allowance. This is an opt-in behavior for our partners to use for additional robustness when placing simple orders (Flashloan orders or these using wrappers still follow the old logic, of disabling the balance/allowance checks since they can come from the additional interactions). * Added `full_balance_check` to `OrderCreation` struct * Sets the transfer amount to `sell_token_amount` for balance checks * Added e2e test case covering the new behavior * Updated the OpenAPI spec for order creation ## How to test Test order creation when the account has not enough allowance or balance, while enabling the fullBalanceCheck (it needs to have at least 1 atom of balance and allowance) - the order should not get created. --------- Co-authored-by: Martin Magnus * Move common eth domain types to separate crate (#4260) # Description Many crates duplicate eth specific domain types. The incoming simulator crate would also have to have its own, with tedious conversions between them. Instead of doing that, the eth domain types are moved into a new crate `eth-domain-types`. This PR migrates autopilot to use it. There will be follow-up PRs migrating rest of the services. # Changes Moved autopilot's eth domain types into separate crate, changed the imports. ## How to test E2E tests should pass, it's a drop-in replacement. --------- Co-authored-by: Martin Magnus * Extract price estimation config to configs crate (#4255) # Description Extract `price_estimation::Arguments` (CLI/clap) into the `configs` crate as TOML-deserialized structs. This continues the migration of runtime configuration from clap CLI args to structured TOML config files. # Changes * Remove `price_estimation::Arguments` struct and all its nested CLI argument structs (`CoinGecko`, `CoinGeckoBuffered`, `QuoteVerificationMode`, `tenderly_api::Arguments`, `balance_overrides::Arguments`) * Create `configs::price_estimation::PriceEstimation` with TOML deserialization for: Tenderly, rate limiter, 1Inch, CoinGecko (including optional buffered config), quote settings, balance overrides, and token verification bypass list * Extract `configs::native_price::NativePriceConfig` and `configs::native_price_estimators::NativePriceEstimators` into their own modules (previously lived in `configs::price_estimation`) * Add `configs::deserialize_env` module with `deserialize_optional_string_from_env()` to support `%ENV_VAR_NAME` references in TOML files for secrets * Add `serde::Deserialize` / `serde::Serialize` derives to `rate_limit::Strategy` (replacing `FromStr` comma-separated parsing with structured TOML table) * Add `serde::Deserialize` / `serde::Serialize` impls to `balance_overrides::TokenConfiguration` for TOML round-tripping * Add `PriceEstimation` field to both autopilot and orderbook `Configuration` structs * Update all affected e2e tests to pass price estimation config via TOML structs instead of removed CLI args ## How to test Existing tests — unit tests cover deserialization defaults, full deserialization, roundtrip serialization, and secret redaction in debug output. # Migration Guide The following CLI arguments have moved to TOML config files. This applies to **both autopilot and orderbook** services. ## Price Estimation (`[price-estimation]`) ### Tenderly (`[price-estimation.tenderly]`) | CLI arg / env var | TOML key | |---|---| | `--tenderly-user` / `TENDERLY_USER` | `[price-estimation.tenderly]` → `user` | | `--tenderly-project` / `TENDERLY_PROJECT` | `[price-estimation.tenderly]` → `project` | | `--tenderly-api-key` / `TENDERLY_API_KEY` | `[price-estimation.tenderly]` → `api-key` | ```toml [price-estimation.tenderly] user = "my-user" project = "my-project" api-key = "my-tenderly-key" ``` ### Rate Limiter (`[price-estimation.price-estimation-rate-limiter]`) | CLI arg / env var | TOML key | |---|---| | `--price-estimation-rate-limiter` / `PRICE_ESTIMATION_RATE_LIMITER` | `[price-estimation.price-estimation-rate-limiter]` (see below) | The old comma-separated format (`"2.0,1s,30s"`) is replaced by a TOML table: ```toml [price-estimation.price-estimation-rate-limiter] back-off-growth-factor = 2.0 min-back-off = "1s" max-back-off = "30s" ``` ### Quote Settings | CLI arg / env var | TOML key (`[price-estimation]`) | Default | |---|---|---| | `--amount-to-estimate-prices-with` / `AMOUNT_TO_ESTIMATE_PRICES_WITH` | `amount-to-estimate-prices-with` | *(network-dependent)* | | `--quote-inaccuracy-limit` / `QUOTE_INACCURACY_LIMIT` | `quote-inaccuracy-limit` | `1` | | `--quote-verification` / `QUOTE_VERIFICATION` | `quote-verification` | `"unverified"` | | `--quote-timeout` / `QUOTE_TIMEOUT` | `quote-timeout` | `"5s"` | | `--tokens-without-verification` / `TOKENS_WITHOUT_VERIFICATION` | `tokens-without-verification` | `[]` | | `--max-gas-per-tx` / `MAX_GAS_PER_TX` | `max-gas-per-tx` | `16777215` | The `--tokens-without-verification` comma-separated format is replaced by a TOML array: ```toml [price-estimation] quote-inaccuracy-limit = "0.01" quote-verification = "enforce-when-possible" quote-timeout = "10s" amount-to-estimate-prices-with = "1000000000000000000" max-gas-per-tx = 16777215 tokens-without-verification = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"] ``` ### 1Inch API (`[price-estimation.one-inch]`) | CLI arg / env var | TOML key | Default | |---|---|---| | `--one-inch-api-key` / `ONE_INCH_API_KEY` | `[price-estimation.one-inch]` → `api-key` | *(none)* | | `--one-inch-url` / `ONE_INCH_URL` | `[price-estimation.one-inch]` → `url` | `"https://api.1inch.dev/"` | ```toml [price-estimation.one-inch] api-key = "my-key" url = "https://api.1inch.dev/" ``` ### CoinGecko (`[price-estimation.coin-gecko]`) | CLI arg / env var | TOML key | Default | |---|---|---| | `--coin-gecko-api-key` / `COIN_GECKO_API_KEY` | `[price-estimation.coin-gecko]` → `api-key` | *(none)* | | `--coin-gecko-url` / `COIN_GECKO_URL` | `[price-estimation.coin-gecko]` → `url` | `"https://api.coingecko.com/api/v3/simple/token_price"` | | `--coin-gecko-debouncing-time` / `COIN_GECKO_DEBOUNCING_TIME` | `[price-estimation.coin-gecko.buffered]` → `debouncing-time` | *(none)* | | `--coin-gecko-broadcast-channel-capacity` / `COIN_GECKO_BROADCAST_CHANNEL_CAPACITY` | `[price-estimation.coin-gecko.buffered]` → `broadcast-channel-capacity` | *(none)* | The two buffered flags (which had to be provided together) are now an optional nested table: ```toml [price-estimation.coin-gecko] api-key = "my-cg-key" url = "https://pro-api.coingecko.com/api/v3/simple/token_price" [price-estimation.coin-gecko.buffered] debouncing-time = "500ms" broadcast-channel-capacity = 100 ``` ### Balance Overrides (`[price-estimation.balance-overrides]`) | CLI arg / env var | TOML key (`[price-estimation.balance-overrides]`) | Default | |---|---|---| | `--quote-token-balance-overrides` / `QUOTE_TOKEN_BALANCE_OVERRIDES` | `token-overrides` | `""` | | `--quote-autodetect-token-balance-overrides` / `QUOTE_AUTODETECT_TOKEN_BALANCE_OVERRIDES` | `autodetect` | `false` | | `--quote-autodetect-token-balance-overrides-probing-depth` / `QUOTE_AUTODETECT_TOKEN_BALANCE_OVERRIDES_PROBING_DEPTH` | `probing-depth` | `60` | | `--quote-autodetect-token-balance-overrides-cache-size` / `QUOTE_AUTODETECT_TOKEN_BALANCE_OVERRIDES_CACHE_SIZE` | `cache-size` | `1000` | The `token-overrides` field uses the same `ADDR@SLOT` comma-separated format as before: ```toml [price-estimation.balance-overrides] token-overrides = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2@3" autodetect = true probing-depth = 30 cache-size = 500 ``` All defaults remain unchanged. --------- Co-authored-by: Marcin Szymczak * Eth domain types driver (#4263) # Description Based on #4260 continues the effort of unifying eth domain types in driver. # Changes - Use eth-domain-types in driver - Hides the internals of ContractAddress and TokenAddress and derives Deref on them. ## How to test E2E tests * Add custom solver errors (#4268) # Description Takes over from #4232 by @ashleychandy. My changes over the @ashleychandy's PR: * make enum not tagged for backwards compatibility * merge in main * lint Closes #4232 --- Desc of the original PR: # Description Fixes #4222 Previously, when solvers couldn't compute quotes for RWA tokens, they returned a generic `QuotingFailed` error. This made it difficult for the frontend to provide helpful feedback. This change adds support for custom solver errors that can be returned when quoting fails. Solvers can now indicate specific reasons like: - `TradingOutsideAllowedWindow` - `TokenTemporarilySuspended` - `InsufficientLiquidity` - `UnauthorizedTrader` - `Other` When a solver returns a custom error, the driver propagates it through the API response so the frontend can display an appropriate error message. # Changes - Added `SolverError` enum with 5 variants in `solvers-dto` - Added optional `error` field to `Solutions` DTO - Updated driver to detect and propagate custom solver errors - Added 5 new error kinds to the API error handler - Updated observability to track `SolverCustomError` metric - Updated mock solver in e2e tests --------- Co-authored-by: ashleychandy Co-authored-by: Ashley Chandy <76095880+ashleychandy@users.noreply.github.com> Co-authored-by: José Duarte * Remove lens from the servies (#4269) # Description Remove lens (Chain ID 232) from the codebase since it is no longer supported by the organization. # Changes Remove lens from the configs and the chain lists across the services. ## How to test * Playground config update * configs/local update ## How to test Run the playground * Extract shared CLI arguments to config file-based SharedConfig (#4266) # Description Continues the migration from CLI-based configuration to file-based configuration by moving shared arguments (node URL, contract addresses, gas estimators, logging, tracing, ethrpc, current block) from `clap`-based `shared::arguments::Arguments` to a serde-based `SharedConfig` in the `configs` crate. This is the final step in eliminating `CliArguments` from the autopilot and orderbook `run()` functions, making both services fully driven by their TOML configuration files. # Changes * Add `crates/configs/src/shared.rs` with `SharedConfig` and sub-configs (`EthRpcConfig`, `CurrentBlockConfig`, `LoggingConfig`, `TracingConfig`, `ContractAddresses`, `GasEstimatorType`, `TokenBucketFeeOverride`) — all with serde deserialization, defaults, and tests. * Remove the `Arguments` struct and related `Display` impl from `crates/shared/src/arguments.rs`, keeping only types still used elsewhere. * Update `autopilot::run()` and `orderbook::run()` to read shared settings from `config.shared` instead of the `CliArguments` parameter, removing `CliArguments` from their signatures entirely. * Add `SharedConfig` field to both `autopilot::Configuration` and `orderbook::Configuration`. * Update `configs/local/autopilot.toml`, `configs/local/orderbook.toml`, and `playground/*.toml` with the new shared config sections, removing corresponding CLI flags from `playground/docker-compose*.yml`. * Simplify e2e test setup: replace CLI argument string building and `ignore_overwritten_cli_params` hack with direct `SharedConfig` struct construction. Remove `extra_args` / `ExtraServiceArgs` parameters from `start_autopilot`, `start_api`, `start_protocol`, and related methods. ## How to test 1. `cargo nextest run -p configs` — verifies `SharedConfig` deserialization (default and full round-trip). 2. `cargo nextest run -p autopilot -p orderbook` — unit tests for both services. 3. E2E tests: `cargo nextest run -p e2e local_node --test-threads 1 --run-ignored ignored-only` — confirms the simplified test harness works end-to-end. 4. Playground: `docker compose -f playground/docker-compose.fork.yml up -d` — verifies updated TOML configs and removed CLI flags work in the local dev environment. * Add knob to configure gas ceiling for unverified quotes (#4277) # Description We have a situation where tsolver, our main solver for RWA tokens, has a bug where they estimate extreme gas prices. We are introducing this knob as a hack to set a ceiling before they fix this on their end. # Changes * [x] New config option to set a ceiling on gas for unverified quotes ## How to test Unit tests. * Simulator crate (#4265) # Description Transaction simulation machinery is spread throughout our codebase. Due to how domain-specific and tied to internal types (driver, price-estimation) they are, it's hard to reuse it for new purposes, such as simulation endpoint or full transaction gas simulation for solvers. # Changes Introduces `simulator` crate which contains all tools related to simulating a real or fake settlement: - `Simulator` struct as previously was found in `driver` - `SwapSimulator` struct which helps to construct fake settlements - Tenderly API collected and unified into `simulator` crate. ## How to test - E2E test - Manual staging deployment for testing. --------- Co-authored-by: José Duarte * [TRIVIAL] Speed up analytics DB replication job (#4279) # Description Every 10 minutes after the hour we run a job copying some data from the main DB to an analytics DB. The heaviest workload is copying the `order_quotes` based on the `creation_timestamp`. # Changes In order to greatly speed things up this PR introduces an index on the `creation_timestamp`. With those queries now also being very fast we can significantly reduce the global DB query limit to further protect it against long running queries avoiding DoS issues in the future. ## How to test I manually created the indexes on the prod DB and one sync job already ran using it. The graph confirms that the CPU load now barely changes when the job runs. Screenshot 2026-03-20 at 08 45 46 Screenshot 2026-03-20 at 08 32 54 * fix: Don't quote unsupported tokens (#4280) # Description We got a report from the team that gnosis solvers were still quoting RWA tokens even though they were configured as unsupported. The reason for this was the only the /solve path was taking into account whether the tokens were supported or not, so the gnosis continued quoting even though they wouldn't solve the orders. I went with reusing `filter_unsupported_orders_in_auction` even though it's slightly awkward with the fake auctions to get to use the logging and metrics from the already existing code. * Add serde tag to SimulatorKind (#4281) # Description Simulator's kind config can not be deserialized without a #[serde(tag)] # Changes Adds #[serde(tag)] to SimulatorKind in configs crate and unit tests. * Clamp gas value before declaring a winner (#4282) # Description Follow up to https://github.com/cowprotocol/services/pull/4277 and https://github.com/cowprotocol/services/pull/4276. Those PRs clamped the gas estimate AFTER we already picked the best quote but since clamping the gas is also supposed to influence which quote we pick it has to happen BEFORE picking a winner. # Changes This PR moves the clamping logic into the trade verifier. * Fix cargo audit vulnerabilities (aws-lc-sys, rustls-webpki) (#4286) ## Summary Resolves all 7 `cargo audit` advisories by updating dependencies. **aws-lc-sys** 0.35.0 → 0.39.0 — fixes 5 crypto-failure advisories (RUSTSEC-2026-{0044,0045,0046,0047,0048}) including PKCS7 verification bypasses (CVSS 7.5) and X.509 name constraint bypass. Updated via `aws-lc-rs` 1.15.2 → 1.16.2. **rustls-webpki** 0.103.8 → 0.103.10 — fixes CRL distribution point matching (RUSTSEC-2026-0049). Direct `cargo update`. **rustls-webpki 0.101.7** (same RUSTSEC-2026-0049) — this old version was dragged in by `aws-sdk-s3`'s `rustls` feature, which activates the legacy TLS path (`aws-smithy-runtime/tls-rustls` → `legacy-rustls-ring` → `rustls 0.21` → `rustls-webpki 0.101.7`). The 0.101.x line has no patch. Fixed by switching `aws-sdk-s3` from `features = ["rustls"]` to `features = ["default-https-client"]`, which uses the modern `rustls-aws-lc` backend instead. This removes `rustls 0.21` from the tree entirely. **recursion_limit** — the AWS SDK update introduced slightly deeper type nesting that overflows the default compiler recursion limit (needs 130, default is 128) when building autopilot tests. Added `#![recursion_limit = "160"]` to `autopilot`. ## Test plan - [x] `cargo check --workspace` and `cargo check -p autopilot --tests` pass - [x] `cargo audit` reports 0 vulnerabilities * Map Bitget API error codes to internal solver errors (#4285) ## Description - Parse `error_code` and `message` fields from Bitget API responses (previously only `status` and `data` were parsed) - Map Bitget error codes (80000–80010) to appropriate internal error variants: - **NotFound**: 80001 (insufficient balance), 80002 (below min), 80003 (above max), 80004 (expired), 80005 (no liquidity), 80008 (reverse quote), 80009 (token info), 80010 (price info) - **BadRequest**: 80006 (illegal request) - **Api** (logged): 80000 (internal), 80007 (partner invalid), unknown codes - Handle HTTP 429 explicitly as `RateLimited` before the generic status check Previously all non-zero `status` values except hardcoded 404/429 fell through to a generic `Api` error, which led to a lot of alert noise. The old 404/429 mapping in `handle_api_error` didn't match the actual Bitget API format (which uses `status: 0/1` + `error_code: 80xxx`). Error code reference: https://web3.bitget.com/en/docs/swap-order#error-code-list ## How to test - [x] All 5 existing bitget tests pass (`cargo nextest run -p solvers bitget`) - [x] Updated `not_found` test to use the actual Bitget error response format (`status: 1, error_code: 80005`) * [EASY] Improve BitGet test (#4264) Improves the ignored by default BitGet API test that covers all the chain, making it much easier to quickly test any changes in the request/response structs. * [EASY] Clean up remaining cargo audit advisories (#4287) ## Summary Follow-up to #4286. Resolves additional `cargo audit` warnings and cleans up stale ignore entries. - **Remove `idna` and `protobuf` from ignore list** — RUSTSEC-2024-0421 and RUSTSEC-2024-0437 are no longer triggered by current dependency versions - **Update keccak 0.1.5 → 0.1.6** — fixes RUSTSEC-2026-0012 (unsound ARMv8 assembly backend) and resolves yanked crate warning - **Update aws-sdk-s3 1.119.0 → 1.127.0** — removes `lru 0.12.5` from the tree, fixing RUSTSEC-2026-0002 (unsound `IterMut`) After this PR, `cargo audit` reports zero vulnerabilities and zero warnings. The remaining ignore entries are genuinely unfixable: - `rsa` — no patch available - `derivative`/`instant`/`paste` — unmaintained, pinned by transitive deps - `model` — false positive matching our local crate name ## Test plan - [x] `cargo check --workspace` passes - [x] `cargo audit` reports 0 vulnerabilities, 0 warnings * set docker image tag to git-sha aswell (#4289) We currently only set branch names and git-tags as additional docker image tags. it would make it easier to find the offending git commit breaking staging if we had also a docker tag of the git short-sha on the image * Map remaining Bitget error codes (80011-80015) (#4290) ## Summary Follow-up to #4285. The initial error code mapping only covered 80000–80010 but the Bitget API also returns 80011–80015. Unmapped codes fell through to `Error::Api` causing unnecessary warn-level logs (e.g. `api { code: 80012, message: "" }`). New mappings: - **80011** (failed to generate calldata) → `NotFound` - **80012** (quote failed, no quote available) → `NotFound` - **80013** (unsupported chain) → `BadRequest` - **80014** (order not found) → `NotFound` - 80015 (order already submitted) → left as `Api` (shouldn't occur in our flow) Ref: https://web3.bitget.com/en/docs/swap-order#error-code-list ## Test plan - [x] All 5 bitget tests pass * Map OKX error code 51005 (honeypot token) to NotFound (#4293) ## Description Map undocumented OKX error code 51005 to `NotFound` instead of the generic `Api` error. OKX returns this code for tokens flagged as honeypots or leveraged tokens (e.g. *"0x.. cannot be traded as it may be a honeypot or leveraged token"*). This is functionally identical to 82104 (token not supported) - the token simply can't be traded. Previously, it fell through to `Error::Api` and produced unnecessary warn-level logs. We've encountered false-positive alerts for the [Tornado.Cash](https://etherscan.io/address/0x77777feddddffc19ff86db637967013e6c6a116c#code) token, which doesn't seem to be a honeypot at all. Code 51005 is not in the [OKX error code docs](https://web3.okx.com/build/dev-docs/wallet-api/dex-error-code). ## How to test Existing tests * Fix simulator using raw gas estimator without tip adjustments (#4298) ## Summary Fixes a regression introduced in #4265 (Simulator crate) that caused frequent "max fee per gas less than block base fee" errors during settlement encoding, particularly on Arbitrum. ### Root cause `Ethereum::gas_estimator()` returns `self.inner.gas.gas` — the **raw** `dyn GasPriceEstimating` without tip adjustments. Before #4265, `Ethereum::simulation_gas_price()` called `GasPriceEstimator::estimate()` which adds a configurable tip buffer (default 3 Gwei or 5%) on top of the raw estimate. After #4265, the simulator receives the raw estimator, so simulation gas prices land right at the base fee with no buffer. On Arbitrum (~250ms blocks), the base fee changes constantly. The tiny gap between estimation and `eth_call` execution is enough for the base fee to tick up past the estimated price. ### Fix Implement `GasPriceEstimating` for `GasPriceEstimator` and return the wrapped estimator from `gas_estimator()`, so the simulator gets the same tip-adjusted estimates the driver used before #4265. ## Test plan - [x] `cargo check -p driver` passes - [ ] Deploy to staging and verify "max fee per gas less than block base fee" errors stop on Arbitrum * Remove Enso's simulator support (#4294) # Description Remove the Enso Trade Simulator backend. It's unused and adds unnecessary code to maintain. # Changes * Remove the unused Enso simulator backend — deleted the `enso/` module and cleaned up all references in configs, driver, and simulator crates. ## How to test ``` cargo nextest run -p simulator -p configs -p driver ``` * Add Claude Code GitHub Workflow (#4292) ## Summary Adds Claude Code GitHub integration with two workflows, review guidelines, and Rust tooling. ### Workflows 1. **`claude.yml`** — Responds to `@claude` mentions in PR/issue comments. Claude analyzes the context and executes the request. 2. **`claude-code-review.yml`** — Automatic code review when a PR is marked ready for review. Uses the official `code-review` plugin with multi-agent verification. Does not run on drafts, pushes, or reopens. Includes `rust-analyzer-lsp` plugin for real Rust code intelligence (type checking, diagnostics, go-to-definition) during reviews. ### Review guidelines (`REVIEW.md`) Controls what Claude flags during reviews: - Prioritizes correctness, safety, security over style - Domain-specific checks: token amount conversions, error code mapping, settlement compatibility - Explicit false-positive exclusion list (no style nits, no pre-existing issues, no theoretical concerns) - Severity levels aligned with the code-review plugin's standard categories (Normal, Nit) - Strict output format: file/line, what's wrong, why it matters, suggested fix ### Security - API key stored as GitHub Actions secret - Only repo write-access users can trigger workflows - All runs visible in GitHub Actions history ## Test plan - [ ] Merge and verify `@claude` mention triggers in a test PR comment - [ ] Verify auto-review triggers when a PR is marked ready for review - [ ] Verify auto-review does NOT trigger on draft PRs or pushes * Log original request ID when reusing shared in-flight quote requests (#4300) ## Background Fast and optimal quoters share the same `ExternalTradeFinder` instance (via `Arc`), so `RequestSharing` already deduplicates identical concurrent requests. However, when a hitchhiker reused an in-flight request, there was no trace linking it back to the original HTTP call, making it hard to debug why the optimal quoter failed to provide solutions, while the fast one succeeded for basically the same quote competition or vice versa. ## Changes - Extended `RequestSharing::shared_or_else` to return a `SharedResult` that indicates whether an existing in-flight request was reused (`is_shared` flag), improving observability of request deduplication. - In `ExternalTradeFinder`, the HTTP request ID (`X-REQUEST-ID`) is now embedded in the shared future's result. When a caller (e.g. the fast or optimal quoter) reuses an in-flight request instead of sending a new one, a debug log is emitted with the `original_request_id` — making it possible to trace which HTTP request produced a given quote result. - All other `shared_or_else` callers updated to use the new `.future` field (no behavioral change). ## How to test Using staging, use the cowswap UI to get some quotes. Search for the new log. * Allow solvers to set custom gas fees in their solution (#4299) ## Summary Adds optional `maxFeePerGas` and `maxPriorityFeePerGas` fields to the solver Solution response. When provided, these override the driver's default gas price estimation during settlement submission. ### Behavior - Both fields must be provided together or both absent - Solvers can set fees **higher or lower** than the driver default, since the solver pays from their own balance, so they self-regulate - When a replacement transaction is pending (same nonce), the solver's values are raised to at least the replacement minimum (node requirement to accept the replacement) - Merged solutions discard gas fee overrides (no meaningful way to merge per-solution gas preferences) ### Changes - Add `max_fee_per_gas` and `max_priority_fee_per_gas` to `Solution` - Add `GasFeeOverride` type, carry through `Solution` → `Settlement` - Apply override to `final_gas_price` before submission ## How to test New e2e test ## Related issues Fixes #4297 * Log custom solver errors on quoting (#4301) # Description Adds a log for the custom error that the solver returned Context: https://nomevlabs.slack.com/archives/C0375NV72SC/p1774966168341339?thread_ts=1774881240.020949&cid=C0375NV72SC # Changes * Log custom error ## How to test NA * Simulation endpoint (#4275) # Description Based on #4265 Adds a /api/v1/debug/simulation/{uid} endpoint to the orderbook that lets you simulate an order's execution via Tenderly. # Changes - debug_simulation.rs — new API handler that accepts an order UID and returns the Tenderly simulation request + any error - Orderbook::simulate_order() — fetches the order, encodes it as a swap, simulates it, and returns the Tenderly request payload - OrderSimulator struct — wraps SwapSimulator with chain ID context; handles encoding orders into EncodedSwap and running simulations ### Notice **Simulation runs against the current block (not a historical replay at the block the order was filled), and that it uses the original order amounts — so for partially filled orders, the full amount is simulated against the trader's current balance.** ### Follow-up PR - [ ] Add support to simulate on specific block number https://github.com/cowprotocol/services/pull/4305 - [ ] Add support for partially filled orders. - [ ] Custom order simulation (not an existing one) https://github.com/cowprotocol/services/pull/4304 Encoding improvements in simulator: - InteractionEncoding trait extracted so both Interaction and InteractionData (from model crate) can be encoded — this allows order pre/post interactions to be encoded directly without manual conversion - WrapperCall struct + encode_wrapper_settlement() function added to handle settlements that go through wrapper contracts - SwapSimulator::fake_swap() extended to support wrapper contracts in the query Supporting changes: - app-data: added wrappers() accessor to expose wrapper calls from app data - configs/orderbook: added config for the new simulator (optional — order_simulation_gas_limit: None keeps existing behavior) - price-estimation: refactored to use the new InteractionEncoding trait - driver/encoding: cleanup — moved some encoding logic to the simulator crate The OpenAPI spec has been updated with: Path `/api/v1/debug/simulation/{uid}` — GET endpoint that takes an order UID and returns 200 (OrderSimulation), 404 (not found), 501 (not enabled), or 500 (internal error). New schemas: - OrderSimulation — top-level response with tenderly_request and optional error - TenderlyRequest — the full Tenderly simulation request structure - SimulationType — enum full | quick | abi: - full (default): Detailed decoded output — call trace, function, inputs/outputs, state diffs, and logs with Solidity types. - quick: Raw, minimal output only. Fastest option; no decoding. - abi: Decoded function inputs/outputs and logs, but no state diffs. Middle ground between quick and full. AccessListItem — address + storage_keys StateObject — balance/code/storage overrides ## How to test Create order in local playground, query for its simulation. Verify on tenderly. Example output: ```json { "tenderly_request": { "network_id": "1", "from": "0xfcc789354262dd9c2f2ff1b0a5f9067b55af1bfa", "to": "0xa513e6e4b8f2a923d98304ec87f64353c4d5c853", "input": "0x13d79a0b000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000068b1d87f95878fe05b998f19b66f4baba5de1aed00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000001bc16d674ec8000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000009315fc8ffae493123b9b6b1c93d50c9b9eef03440000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000ffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000410000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000149315fc8ffae493123b9b6b1c93d50c9b9eef03440000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "gas": 16777215, "simulation_type": "full", "save": true, "save_if_fails": true, "state_objects": { "0x68b1d87f95878fe05b998f19b66f4baba5de1aed": { "storage": { "0xd7a1f0b46140686e5b4c405dcbec93c326c8c0e52cb615e05dcf53ad4119c720": "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" } }, "0x9315fc8ffae493123b9b6b1c93d50c9b9eef0344": { "code": "0x608060405260043610610037575f3560e01c80631626ba7e1461008d578063542eb77d14610104578063eb5625d9146101255761003e565b3661003e57005b5f6100835f368080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525062010000939250506101449050565b9050805160208201f35b348015610098575f5ffd5b506100cf6100a7366004610b01565b7f1626ba7e000000000000000000000000000000000000000000000000000000009392505050565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200160405180910390f35b34801561010f575f5ffd5b5061012361011e366004610b9c565b6101c2565b005b348015610130575f5ffd5b5061012361013f366004610c00565b610916565b60605f8373ffffffffffffffffffffffffffffffffffffffff168360405161016c9190610c3e565b5f60405180830381855af49150503d805f81146101a4576040519150601f19603f3d011682016040523d82523d5f602084013e6101a9565b606091505b5092509050806101bb57815160208301fd5b5092915050565b7f02565dba7d68dcbed629110024b7b5e785bfc1a484602045eea513de8a2dcf99805460017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0082161790915560ff16156102a3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f70726570617265537761702063616e206f6e6c792062652063616c6c6564206f60448201527f6e6365000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036103e4576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8616906370a0823190602401602060405180830381865afa158015610340573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906103649190610c6a565b9050838110156103e2575f6103798286610c81565b90508047106103e0578373ffffffffffffffffffffffffffffffffffffffff1663d0e30db0826040518263ffffffff1660e01b81526004015f604051808303818588803b1580156103c8575f5ffd5b505af11580156103da573d5f5f3e3d5ffd5b50505050505b505b505b5f8573ffffffffffffffffffffffffffffffffffffffff16639b552cc26040518163ffffffff1660e01b8152600401602060405180830381865afa15801561042e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104529190610cb9565b6040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff80831660248301529192505f9187169063dd62ed3e90604401602060405180830381865afa1580156104c7573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104eb9190610c6a565b905084811015610748576040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201525f6044820152309063eb5625d9906064015f604051808303815f87803b158015610567575f5ffd5b505af1925050508015610578575060015b506040517feb5625d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8088166004830152831660248201527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6044820152309063eb5625d9906064015f604051808303815f87803b15801561060b575f5ffd5b505af192505050801561061c575060015b506040517fdd62ed3e00000000000000000000000000000000000000000000000000000000815230600482015273ffffffffffffffffffffffffffffffffffffffff83811660248301525f919088169063dd62ed3e90604401602060405180830381865afa158015610690573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906106b49190610c6a565b905085811015610746576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f74726164657220646964206e6f7420676976652074686520726571756972656460448201527f20617070726f76616c7300000000000000000000000000000000000000000000606482015260840161029a565b505b6040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201525f9073ffffffffffffffffffffffffffffffffffffffff8816906370a0823190602401602060405180830381865afa1580156107b2573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906107d69190610c6a565b90508581101561090c5773ffffffffffffffffffffffffffffffffffffffff841663494666b688610807848a610c81565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e085901b16815273ffffffffffffffffffffffffffffffffffffffff909216600483015260248201526044015f604051808303815f87803b15801561086f575f5ffd5b505af1925050508015610880575060015b61090c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f74726164657220646f6573206e6f74206861766520656e6f7567682073656c6c60448201527f20746f6b656e0000000000000000000000000000000000000000000000000000606482015260840161029a565b5050505050505050565b61093773ffffffffffffffffffffffffffffffffffffffff8416838361093c565b505050565b6040805173ffffffffffffffffffffffffffffffffffffffff848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f095ea7b300000000000000000000000000000000000000000000000000000000179052905f906109ce90861683610a46565b90506109d981610a5a565b610a3f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f5361666545524332303a20617070726f76616c206661696c6564000000000000604482015260640161029a565b5050505050565b6060610a53835f84610a7f565b9392505050565b5f81515f1480610a79575081806020019051810190610a799190610cd4565b92915050565b60605f8473ffffffffffffffffffffffffffffffffffffffff168484604051610aa89190610c3e565b5f6040518083038185875af1925050503d805f8114610ae2576040519150601f19603f3d011682016040523d82523d5f602084013e610ae7565b606091505b509250905080610af957815160208301fd5b509392505050565b5f5f5f60408486031215610b13575f5ffd5b83359250602084013567ffffffffffffffff811115610b30575f5ffd5b8401601f81018613610b40575f5ffd5b803567ffffffffffffffff811115610b56575f5ffd5b866020828401011115610b67575f5ffd5b939660209190910195509293505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610b99575f5ffd5b50565b5f5f5f5f5f60a08688031215610bb0575f5ffd5b8535610bbb81610b78565b94506020860135610bcb81610b78565b9350604086013592506060860135610be281610b78565b91506080860135610bf281610b78565b809150509295509295909350565b5f5f5f60608486031215610c12575f5ffd5b8335610c1d81610b78565b92506020840135610c2d81610b78565b929592945050506040919091013590565b5f82515f5b81811015610c5d5760208186018101518583015201610c43565b505f920191825250919050565b5f60208284031215610c7a575f5ffd5b5051919050565b81810381811115610a79577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60208284031215610cc9575f5ffd5b8151610a5381610b78565b5f60208284031215610ce4575f5ffd5b81518015158114610a53575f5ffdfea164736f6c634300081e000a" }, "0xfcc789354262dd9c2f2ff1b0a5f9067b55af1bfa": { "balance": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" }, "0x5fc8d32690cc91d4c39d9d3abcbd16989f875707": { "code": "0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c806302cc250d14602a575b5f5ffd5b603b6035366004604f565b50600190565b604051901515815260200160405180910390f35b5f60208284031215605e575f5ffd5b813573ffffffffffffffffffffffffffffffffffffffff811681146080575f5ffd5b939250505056fea164736f6c634300081e000a" } } }, "error": null } ``` # Migration Guide: simulation-endpoint branch New field: order-simulation-gas-limit A new optional top-level field has been added to the orderbook TOML configuration. Type: string (decimal integer, interpreted as U256) Default: omitted / disabled ## Enable the order simulation debug endpoint `order-simulation-gas-limit = "16777215"` Effect: When absent (default): the GET `/api/v1/debug/simulation/{uid}` endpoint is disabled. Requests return a 501 Not Implemented response. When present: the endpoint is enabled. The value sets the gas limit used when simulating the order's settlement call via Tenderly (through the price estimation driver's balance-override infrastructure). Recommended value: "16777215" (0xFFFFFF — same as the E2E test default). Adjust downward if you want to cap simulation gas usage. No action required if you do not want to expose the simulation endpoint — omitting the field is the safe default. --------- Co-authored-by: José Duarte Co-authored-by: ilya * Add block number to order simulation (#4305) # Description The order simulation endpoint would always run against the latest block. It is limiting for investigating already filled or partially filled orders. # Changes Add query parameter to the order simulation endpoint to allow specifying a block number ## How to test E2E test creates an order, withdraws trader's sell tokens on block A and funds the account on block B. The order simulation fails for block A and succeeds for block B. --------- Co-authored-by: José Duarte Co-authored-by: ilya * Update GH actions to use pinned commit SHA (#4312) # Description Most recently we have started to enforce GH actions with pinned commits to full SHA. This caused our CI jobs to fail since not all of the actions are pinned. # Changes Fixed extractions/setup-just in pull-request.yaml — The old SHA (e33e0265...) pointed to a version whose action.yaml internally called extractions/setup-crate@v1 (unpinned). Updated to 53165ef7... (v4.0.0) which internally pins setup-crate@0551596.... Fixed yu-ichiro/spin-up-docker-compose-action in pull-request.yaml — This composite action internally used actions/cache@v3 and docker/setup-buildx-action@v1 (both unpinned), even though those steps were gated behind bake/registry flags we never set. Replaced all 4 usages with direct docker compose run steps since the action was only ever used as a thin wrapper around docker compose up. ## How to test PR checks should pass now. * Custom order simulation (#4304) # Description The order simulation endpoint allows one to simulate a specific, existing order. It is useful to be able to simulate any, custom order. # Changes Implements a POST endpoint `/api/v1/debug/simulation` that allows a request specifying custom order details to be simulated: ``` pub struct SimulationRequest { pub sell_token: Address, pub buy_token: Address, #[serde_as(as = "HexOrDecimalU256")] pub sell_amount: alloy::primitives::U256, #[serde_as(as = "HexOrDecimalU256")] pub buy_amount: alloy::primitives::U256, pub kind: OrderKind, pub owner: Address, #[serde(default)] pub receiver: Option
, #[serde(default)] pub sell_token_balance: SellTokenSource, #[serde(default)] pub buy_token_balance: BuyTokenDestination, /// Full app data JSON. Defaults to `"{}"` if omitted. #[serde(default)] pub app_data: Option, #[serde(default)] pub interactions: Interactions, } ``` - [ ] Add exhaustive order details in a follow-up PR. ## How to test E2E test that runs a custom order simulation on a trader with no funds (it reverts), then account is funded and the same request passes. --------- Co-authored-by: José Duarte Co-authored-by: ilya * Skip balance check for flashloan orders (#4278) ## Summary - Flashloan orders get their sell tokens from the flashloan at settlement time, so they should not be filtered out due to insufficient user balance - Removes the restriction that flashloan orders must have the settlement contract as receiver - Flashloan orders now bypass balance allocation entirely, similar to CoW AMM orders --------- Co-authored-by: José Duarte <15343819+jmg-duarte@users.noreply.github.com> * Partially filled orders support in simulation (#4306) # Description The order simulation endpoint so far always tried to fill the order with the original 100% fill amount. This leads to reverts if an order was already partially filled. # Changes Fetches the order's fill amount using an RPC call at the given block and deducts that from the full order amount. This will cause the simulation to always fill the remaining executable amount. ## How to test E2E test simulating a partially filled order. --------- Co-authored-by: José Duarte Co-authored-by: ilya Co-authored-by: MartinquaXD * Add global query timeout to SELECT queries (#4272) # Description Following the issues we faced with RDS, and the fact that [SQLx does not provide global timeouts](https://github.com/launchbadge/sqlx/issues/3060), this PR uses PostgreSQL's native `statement_timeout` to limit the duration of read queries on the **orderbook's read replica**. Instead of wrapping every query on the client side, the timeout is set once per connection via the pool's `after_connect` hook. This means every query issued through that pool is automatically subject to the configured timeout — no per-call changes needed. The timeout is only applied to the **orderbook read replica** pool (created with `Postgres::try_new_with_timeout`). Write pools and other services are unaffected. # Changes * **`crates/orderbook/src/database/mod.rs`** — Added `statement_timeout` to `Config` and a new `Postgres::try_new_with_timeout` constructor that sets `statement_timeout` on every connection via `after_connect`. Includes a postgres integration test using `pg_sleep`. * **`crates/orderbook/src/run.rs`** — The read replica pool now uses `try_new_with_timeout`. * **`crates/configs/src/database.rs`** — Added `statement_timeout` field to `DatabasePoolConfig` (defaults to `30s`, deserialized via `humantime_serde`). * **`crates/shared/src/arguments.rs`** — Added `--statement-timeout` CLI flag (used by refunder). * **`crates/database/src/trades.rs`** — Changed `trades()` from returning a `BoxStream` to `fetch_all`, since streaming doesn't play well with connection-level timeouts. * Minor whitespace cleanups in other `crates/database/src/*.rs` files and removal of an unrelated stale config section in the playground autopilot config. ## How to test Run the integration test against a local postgres (`docker compose up -d` first): ``` cargo nextest run -p orderbook statement_timeout --test-threads 1 --run-ignored ignored-only ``` --------- Co-authored-by: Martin Magnus * Fix BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory Sepolia block number (#4316) # Description Corrects the block number for the given contract on Sepolia # Changes * Change the block number * Advise Claude to use order debug endpoint (#4303) # Description We have a new debug endpoint which will cut Claude's investigation time a lot. * Refactor contracts crate out of the main folder for faster compilation 🚀 (#4217) # Description Replace the `crates/contracts` build.rs-based compile-time code generation with a standalone code-generation tool that produces pre-generated per-contract crates. This eliminates the slow build.rs step from the main workspace build and makes contract bindings explicit, version-controllable, and independently compilable. # Changes * Move `crates/contracts` to top-level `contracts/` directory as a standalone binary * Replace build.rs code generation with pre-generated Alloy contract binding crates under `contracts/generated/` * Add `contracts-facade` crate that re-exports all generated bindings for easy consumption * Update all workspace crates to import from the new generated crates * Add missing contract artifacts (Balancer factory V3-V6, weighted pool factory V4, NoProtocolFee LBP factory, DEX router variants) ## How to test 1. `cargo check --workspace --all-targets` — workspace compiles against the new generated bindings 2. `cargo nextest run` — unit tests pass 3. `cd contracts && cargo run -- --help` — codegen tool runs independently 4. Verify `contracts/generated/` output matches checked-in code by re-running the codegen ## Charts Problematic time blocker: Screenshot 2026-04-08 at 11 32 04 CPU Graph (notice the 60s): Screenshot 2026-04-08 at 11 32 16 No more time blocker (doesn't fully fit vertically in my screen): Screenshot 2026-04-08 at 11 38 15 New CPU Graph (notice the 45s and much better (higher) CPU usage): Screenshot 2026-04-08 at 11 35 33 --------- Co-authored-by: Claude Opus 4.6 (1M context) * docs: add onboarding diagrams and entrypoints (#4311) # Description Addresses review feedback on #4296. New contributors need a single place to see how orderbook, autopilot, driver, solvers, Postgres, and the chain fit together, plus where to open the codebase for each area. This PR adds `docs/ONBOARDING.md` with Mermaid diagrams and practical code entrypoints, and links it from `README.md`. # Changes * Add `docs/ONBOARDING.md`: system context diagram, order→auction→settle sequence diagram, service responsibilities diagram, DB mental model diagram * Add "Where to start reading code" with entrypoints pointing to `run.rs` for each service (orderbook, autopilot, driver, solvers, contracts, database) * Add solver types section explaining colocated vs non-colocated distinction * Add local development quick-start (playground with `ETH_RPC_URL` note, cargo check/nextest, formatting) * Add debugging section linking to `COW_ORDER_DEBUG_SKILL.md` * Update `README.md` with a link to `docs/ONBOARDING.md` ## How to test 1. Open `docs/ONBOARDING.md` on GitHub and confirm all Mermaid blocks render (context flowchart, sequence diagram, responsibilities flowchart, DB flowchart) 2. Click links to `playground/README.md`, `database/README.md`, and `COW_ORDER_DEBUG_SKILL.md` and confirm they resolve 3. Open `README.md` and confirm the new onboarding link points to `./docs/ONBOARDING.md` * Add a timeout to balance override detection (#4317) # Description Balance override detection can stall the quote pipeline when probing certain tokens. Reflection tokens (e.g. LuckyBlock) implement `balanceOf` by iterating over a storage array in `_getReflectionRate()`. During strategy verification, we override storage slots with a test value — if that value lands on an array-length slot, the EVM loops until the node's execution timeout. This adds a configurable timeout around the full `detect()` call in `cached_detection` to prevent slow tokens from blocking quote processing. The default is 1 second. # Changes * Add `detection_timeout` field to `BalanceOverrides` with a default of 1s * Wrap `detector.detect()` in `tokio::time::timeout` inside `cached_detection`, treating timeouts as `DetectionError::NotFound` (which gets cached to avoid retrying) * Add `detection-timeout-secs` config field to `BalanceOverridesConfig` so the timeout is tunable per deployment ## How to test Staging ---
Before applying this

``` jmgduarte@unknown5e8ed84873b2 ~/D/c/infrastructure (staging)> time curl --location 'https://barn.api.cow.fi/bnb/api/v1/quote' \ --header 'Content-Type: application/json' \ --data '{ "sellToken": "0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb", "buyToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "from": "0x4EF880525383ab4E3d94b7689e3146bF899A296e", "receiver": "0x4EF880525383ab4E3d94b7689e3146bF899A296e", "sellAmountBeforeFee": "243443381571330", "kind": "sell", "priceQuality": "optimal", "validFor": 300, "appData": "{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}", "appDataHash": "0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8", "timeout":6000 }' ERROR: The request could not be satisfied

504 Gateway Timeout ERROR

The request could not be satisfied.


We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.

Generated by cloudfront (CloudFront) HTTP3 Server
Request ID: R7hFhywd1QB3QiA7xso829L_FErAgjEvZXCpY3fNa_SGX19Flxfq4g==
________________________________________________________ Executed in 30.20 secs fish external usr time 16.68 millis 0.41 millis 16.28 millis sys time 15.25 millis 1.99 millis 13.27 millis ```

After applying this

``` jmgduarte@unknown5e8ed84873b2 ~/D/c/infrastructure (staging)> time curl --location 'https://barn.api.cow.fi/bnb/api/v1/quote' \ --header 'Content-Type: application/json' \ --data '{ "sellToken": "0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb", "buyToken": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "from": "0x4EF880525383ab4E3d94b7689e3146bF899A296e", "receiver": "0x4EF880525383ab4E3d94b7689e3146bF899A296e", "sellAmountBeforeFee": "243443381571330", "kind": "sell", "priceQuality": "optimal", "validFor": 300, "appData": "{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}", "appDataHash": "0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8", "timeout":6000 }' {"quote":{"sellToken":"0x2cd96e8c3ff6b5e01169f6e3b61d28204e7810bb","buyToken":"0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","receiver":"0x4ef880525383ab4e3d94b7689e3146bf899a296e","sellAmount":"101408548709609","buyAmount":"19632333521561","validTo":1775660168,"appData":"{\"appCode\":\"OneKey CowSwap\",\"environment\":\"production\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"partnerFee\":{\"recipient\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\",\"volumeBps\":30},\"quote\":{\"slippageBips\":62,\"smartSlippage\":false},\"referrer\":{\"address\":\"0x09DDb1031A31Ac82Ef276AB8Ea67c0941ef27c02\"}},\"version\":\"1.6.0\"}","appDataHash":"0x8bc9701f53009567e798a5b879ca06aa849e4b10ada621b7f2d147f9e411d4d8","feeAmount":"142034832861721","gasAmount":"235614","gasPrice":"52500001","sellTokenPrice":"0.0870894483162219235072853962265071459114551544189453125","kind":"sell","partiallyFillable":false,"sellTokenBalance":"erc20","buyTokenBalance":"erc20","signingScheme":"eip712"},"from":"0x4ef880525383ab4e3d94b7689e3146bf899a296e","expiration":"2026-04-08T14:53:08.775181109Z","id":24317,"verified":false,"protocolFeeBps":"2"} ________________________________________________________ Executed in 1.51 secs fish external usr time 14.70 millis 1.25 millis 13.45 millis sys time 12.12 millis 3.78 millis 8.34 millis ```

* Generate tendery link on simulation (#4320) # Description The debug simulation endpoint (/api/v1/debug/simulation/{uid}) currently returns a raw Tenderly API request object that you have to manually POST to Tenderly to debug. This adds automatic submission + sharing so the response includes a clickable tenderlyUrl link. # Changes - Add simulate_and_share to the Tenderly Api trait - submits a simulation, shares it, and returns the public dashboard URL - Refactor TenderlyApi to store api_base URL instead of the full /simulate endpoint, appending paths as needed - Add optional tenderly config to OrderSimulationConfig - when set, simulations are auto-submitted - Return tenderlyUrl field in OrderSimulationResult (omitted from JSON when Tenderly is not configured) ## How to test I ran the orderbook locally with tenderly config, hit the API and got a tenderly link. * [TRIVIAL] Pin Claude workflow actions to full commit SHAs (#4323) ## Summary Pin `actions/checkout` and `anthropics/claude-code-action` to full commit SHAs in Claude workflow files, as required by the repo's action policy. - `actions/checkout@v4` → `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683` (v4.2.2) - `anthropics/claude-code-action@v1` → `anthropics/claude-code-action@e0f2d99545298b87c2f984ab534af3a6534142ae` (v1) * [TRIVIAL] Redact IPFS auth_token in debug (#4325) # Description Fixes https://github.com/cowprotocol/services/issues/4310 This fixes the leakage by redacting the IPFS secret in the Debug implementation. # Changes * Manually implement debug to redact IPFS token ## How to test Added a test to ensure it doesn't break without us noticing * Surface transfer simulation revert reasons in order creation API (#4321) ## Summary - When order creation fails with `TransferSimulationFailed`, the API now includes the actual revert reason (e.g. `0x5b263df7` = `LtvValidationFailed()`) instead of the generic "sell token cannot be transferred" - Changed `Balances.sol` helper contract to capture revert bytes from the `transferFromAccounts` try/catch block - Threaded revert bytes through `Simulation` → `TransferSimulationError::TransferFailed(Vec)` → `ValidationError::TransferSimulationFailed(Vec)` → API error description - Deployed new Balances contract at `0x88b4B74082BffB2976C306CB3f7E9093AE48B94F` on all chains (except Lens which keeps the old deployment) - Updated e2e forked test block numbers to include the new deployment ## Motivation Aave aTokens (e.g. aBasUSDC) revert with `LtvValidationFailed()` when a user has zero-LTV collateral blocking transfers. The current generic error gives no indication of why the transfer fails, making it very difficult to debug. ## API Change Before: ```json {"errorType": "TransferSimulationFailed", "description": "sell token cannot be transferred"} ``` After (when revert data available): ```json {"errorType": "TransferSimulationFailed", "description": "sell token cannot be transferred, token reverted with: 0x5b263df7"} ``` The 4-byte selector can be decoded client-side (e.g. `cast 4byte 0x5b263df7` → `LtvValidationFailed()`). ## Changes - **`contracts/solidity/Balances.sol`** — `catch` → `catch (bytes memory reason)`, added `transferRevertReason` return value - **`contracts/artifacts/Balances.json`** — Rebuilt with solc - **`crates/account-balances/src/lib.rs`** — Added `transfer_revert_reason` to `Simulation`, changed `TransferFailed` to carry `Vec`, switched to `abi_decode_params` (required for dynamic return types) - **`crates/account-balances/src/simulation.rs`** — Pass revert bytes into `TransferFailed` - **`crates/shared/src/order_validation.rs`** — Thread `Vec` through `TransferSimulationFailed` - **`crates/orderbook/src/api/post_order.rs`** — Format revert selector in error description - **`contracts/src/main.rs`** — Updated Balances addresses to new deployment `0x88b4B74082BffB2976C306CB3f7E9093AE48B94F` - **`Justfile`** — Added formatting steps to `generate-contracts` - **e2e forked tests** — Updated fork block numbers and whale addresses for new deployment ## New Balances Deployment Address `0x88b4B74082BffB2976C306CB3f7E9093AE48B94F` on: Mainnet, Arbitrum, Base, Avalanche, BNB, Optimism, Polygon, Gnosis, Linea, Ink, Sepolia, Plasma. And also Optimism, since we deployed everything there already and in case we will add support for this chain sometime later. Lens keeps the old address (`0x3e8C6De9510e7ECad902D005DE3Ab52f35cF4f1b`), since it will be dropped from the codebase in a follow-up PR. ## Test plan - [x] Existing unit tests pass (account-balances, shared, orderbook) - [x] Clippy clean - [x] E2E local node tests pass - [x] E2E forked node tests pass - [ ] Manual test: submit order with aToken that has LTV-blocked transfer, verify revert selector appears in error * Don't compile contract bindindgs we don't use (#4324) # Description Currently many contracts we use have many functions we don't use. Those result in a lot of code that gets compiled unnecessarily which is slow. # Changes I moved the unused functions in the artifact ABI files into under a new key (`_disabled`) which does not get parsed by `alloy` for code generation. I decided to do that instead of deleting the functions entirely in case we need to use some of them in the future. This resulted in ~180K lines of net reduction. On my machine this reduced compile times of a clean rebuild from 69s to 63.7s. The effects on CI are 10s faster compilation of tests and likely significantly more savings when building the full image. ## How to test all tests should still pass * Remove uniform clearing prices from winner selection (#4313) # Description Uniform clearing prices (UCP) are no longer part of the core CoW Protocol scoring mechanism since CIP-67. In winner selection, the UCP variables were already dead code (`_uniform_sell_price` / `_uniform_buy_price`). All scoring uses `calculate_custom_prices_from_executed()` from per-trade executed amounts instead. This is the first PR in a series to remove UCP from the entire backend. It targets only the winner-selection crate and its integration points in the autopilot, which is low-risk since the code was already unused. # Changes * Remove `prices: HashMap` field and `prices()` accessor from `winner_selection::Solution` * Remove dead `_uniform_sell_price` / `_uniform_buy_price` lookups in `compute_order_score()` * Stop passing clearing prices when constructing `winsel::Solution` in the autopilot * Set `clearing_prices` to `Default::default()` in the run loop (kept to avoid breaking the solver competition API) * Update test in `settlement/mod.rs` to match new `Solution::new()` signature ## How to test 1. `cargo nextest run -p winner-selection` 2. `cargo nextest run -p autopilot` 3. `cargo clippy --locked -p winner-selection -p autopilot --all-features --all-targets -- -D warnings` ## Staging test | Value | Before | After | | --- | --- | --- | | 0.1 | [0x749d2](https://dev.explorer.cow.fi/orders/0x749d2c5ebf346a47be288f20d96ffab2dcafc2ff48eebccbe8f053f8d9a3832909fbad1ea29c36dfe4f8f7baa87c5edf85e0d9f369d78f53) | [0x13873](https://dev.explorer.cow.fi/orders/0x138734c29ac9fff883f5f6784e74a3ed3cbdf82c14b5f7e4a54b08fd04a6f78809fbad1ea29c36dfe4f8f7baa87c5edf85e0d9f369d78ee4)| * Update claude's log instructions (#4319) # Description Instruct Claude to use MPC to access logs instead of using helper script. * Add E2E test to ensure flashloan filtering is performed correctly (#4329) # Description While reviewing #4309 I noticed a super small bug that would have been a pain in the ass to debug. The implemented parallelization strategy changed the order of the flashloan filter, it essentially was filtering flashloans without full information on them, meaning some flashloan orders would be sent to solvers that explicitly don't support them. This PR adds a test to ensure it doesn't break unexpectedly # Changes * Add flashloan support configuration to driver tests * Add test to ensure flashloans are correctly filtered ## How to test Run test as is and merge this PR's branch into https://github.com/cowprotocol/services/pull/4309 and check it fails * Support reading TenderlyConfig api_key from environment variable (#4332) # Description The `TenderlyConfig.api_key` field currently deserializes as a plain string from TOML config. This means it cannot use the `%ENV_VAR` pattern to read secrets from environment variables at runtime, unlike `CoinGeckoConfig.api_key` which already supports this. Since PR #4225 moved price estimation into a separate crate and removed the old CLI `--tenderly-*` arguments, the only way to configure Tenderly for the trade verifier is via the TOML `[price-estimation.tenderly]` section. Without env var support on the `api-key` field, the API key would need to be hardcoded in the ConfigMap, which is a security concern. # Changes - Added `deserialize_string_from_env` to `TenderlyConfig.api_key` so it supports the `%ENV_VAR` pattern (same as `CoinGeckoConfig.api_key`) # How to test Existing tests. * Hide competition until deadline passes (#4330) # Description Hides solver competition API data until the auction's submission deadline block has passed. Feature-gated via hide-competition-before-deadline (default off) to avoid breaking external solver monitoring that depends on these endpoints. # Changes - Added hide-competition-before-deadline bool to orderbook config (default false) - Wired CurrentBlockWatcher into AppState so handlers can check the current block - Return 404 while deadline hasn't passed - E2E test updated to exercise both the 404 and the post-deadline visibility ## How to test ``` cargo test -p e2e 'solver_competition::local_node_solver_competition' -- --exact --ignored ``` --------- Co-authored-by: Martin Magnus * Fix RUSTSEC issues (#4334) # Description Fixed (vulnerabilities): - RUSTSEC-2026-0098 & RUSTSEC-2026-0099: rustls-webpki 0.103.10 → 0.103.12 (lockfile update only) Fixed (unsound warning): - RUSTSEC-2026-0097: rand 0.9.2 → 0.9.4 (lockfile update only) - RUSTSEC-2026-0097: Workspace rand dependency bumped from 0.8.5 → 0.9.4 in Cargo.toml, with code updates to use the new API (thread_rng() → rng(), gen_range() → random_range()) * Bump alloy crates from 1.7.3 to 1.8.3 (#4333) Checked latest versions available for all alloy and sub dependencies. Bumps all alloy workspace dependencies to their latest 1.x versions: alloy, alloy-consensus, alloy-contract, alloy-eips, alloy-json-rpc, alloy-network, alloy-provider, alloy-rpc-client, alloy-rpc-types, alloy-rpc-types-eth, alloy-rpc-types-trace, alloy-signer, alloy-signer-local, alloy-transport, alloy-transport-ws: 1.7.3 → 1.8.3 alloy-primitives: 1.5.2 → 1.5.7 alloy-sol-types: 1.5.2 → 1.5.7 alloy-dyn-abi, alloy-json-abi were already at latest (1.5.7) Separated from #4205 (pod network integration) PR to isolate the dependency upgrade. * Respect settle queue size (#4338) # Description The reference driver rejects new solutions when there is already a backlog of solutions that still need to be submitted because they will most likely not be mined in time. This is intended to protect very competitive solvers from penalties when they win too much but can't submit fast enough. https://github.com/cowprotocol/services/pull/4167 introduced a bug where the check whether to reject the `/solve` request only looks at the available tx submission slots but not the settle queue. This has the consequence that a solver with only a single submission EOA that won an auction will reject `/solve` requests until the previous solution was submitted. # Changes Add a semaphore with capacity equal to queue size to mimic missing queue behavior. --------- Co-authored-by: Martin Magnus * Use a common prefix for internal endpoints (#4339) # Description We now have a few endpoints that we don't want to expose to the public and every single one of them has a) a rule in WAF b) a rule on the infra level that exposes it to partners with an API key. Instead of setting these every time we create a new internal endpoint we can use the same rules for all endpoints with the `/api/internal` prefix. # How to test Existing e2e tests. * Workflow to test contract generated code (#4336) # Description Adds a CI job to ensure that generated contract code was *just* generated # Changes * CI job to detect manual tampering of generated code ## How to test There are commits associated with this PR that should launch jobs that will fail or pass, will add them here as they're done "Successful failure" — i.e. detected changes: https://github.com/cowprotocol/services/actions/runs/24463810679/job/71485172786?pr=4336 Successful success — i.e. no changes = nothing detected: https://github.com/cowprotocol/services/actions/runs/24464073112/job/71486114618?pr=4336 --------- Co-authored-by: Jan [Yann] <4518474+fafk@users.noreply.github.com> * Add internal solver competition endpoint (#4335) # Description In https://github.com/cowprotocol/services/pull/4330 we started hiding auctions until their deadline passed. Our solver team however needs to have access to the unfiltered data, so this PR adds and endpoint that returns it and we're going to restrict access to it on the infra level. --------- Co-authored-by: Aryan Godara <65490434+AryanGodara@users.noreply.github.com> Co-authored-by: Martin Magnus * Add EIP-4626 native price estimator (#4243) # Description Adds a native price estimator for EIP-4626 vault tokens (e.g. sDAI, wrapped yield vaults). Many vault tokens lack direct DEX liquidity, causing native price estimation to fail. This estimator unwraps the vault by querying the on-chain `asset()` and `convertToAssets()` functions, then delegates pricing of the underlying token to the next estimator in the stage. # Changes * **New `Eip4626` native price estimator** (`crates/price-estimation/src/native/eip4626.rs`): calls `asset()` + `decimals()` in parallel, then `convertToAssets(10^decimals)` to compute the shares-to-assets conversion rate, and multiplies by the inner estimator's price for the underlying token. * **Negative cache for non-vault tokens**: tokens whose `asset()` call reverts are remembered in a `Mutex>` so subsequent requests skip the RPC entirely. Cleared on process restart. * **Timeout budget forwarding**: each vault RPC call is individually bounded by `tokio::time::timeout`, and whatever time remains is forwarded to the inner estimator. This keeps the total wall-clock time within the caller's original timeout, which matters for recursive vault chains. * **Configurable recursion depth**: the `Eip4626` config variant accepts a `depth` parameter (default: 1) controlling how many nested vault layers to unwrap. In the factory, `depth` layers of `Eip4626` wrap the next estimator in the stage. * **Config validation**: `NativePriceEstimators` deserialization rejects stages where `Eip4626` is the last entry (it must be followed by another estimator to price the underlying asset). * **Contract bindings**: added `IERC4626` interface and `MockERC4626Wrapper` test contract for e2e tests. * **Factory wiring** (`crates/price-estimation/src/factory.rs`): `create_native_estimator` now consumes the next estimator from the stage iterator when it encounters `Eip4626`, wrapping it in `depth` layers of instrumented `Eip4626` estimators. * **Re-added config deserialization tests** to `crates/configs/src/native_price_estimators.rs` that were lost during the extraction from `price-estimation` to `configs`. ## How to test 1. Unit tests: `cargo nextest run -p price-estimation eip4626` and `cargo nextest run -p configs native_price_estimators` 2. Live mainnet smoke test (requires `NODE_URL`): `NODE_URL=... cargo nextest run -p price-estimation -- eip4626 --run-ignored ignored-only` 3. E2e forked tests (requires `FORK_URL_MAINNET`): - Single vault: `cargo nextest run -p e2e forked_node_mainnet_eip4626_native_price --test-threads 1 --run-ignored ignored-only` - Recursive vaults: `cargo nextest run -p e2e forked_node_mainnet_eip4626_recursive_native_price --test-threads 1 --run-ignored ignored-only` --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: José Duarte Co-authored-by: José Duarte <15343819+jmg-duarte@users.noreply.github.com> * [Driver] Add SettlementStarted solver notification (#4327) # Description ## Context Some PMMs benefit from knowing that their quotes will be used in settlement as early as possible because they can hedge earlier, manage their risks better, and as a result provide better prices. The example would be #3452 feature request from Liquorice PMM - they solved their problem in PR #3451 by implementing a notification system that can notify PMM directly from driver. However, this approach requires PMMs to be involved in pushing a PR to cowprotocol, which is not always possible/feasible/desirable. ## Solution This PR introduces a new solver notification called SettlementStarted, that fires off whenever a solver has won and the settlement of its solution is about to start. # Changes `crates/solvers-dto` * `notification::Kind::SettlementStarted` was added. `crates/driver` * `infra/notify/notification.rs` - domain notification kind `Kind::SettlementStarted` was added. * `infra/solver/dto/notification.rs` - a mapping from `driver` domain to `solvers-dto` notification kind was added. * `infra/notify/mod.rs` - function `settlement_started` was added, that fires-and-forgets the named notification. * `domain/competition/mod.rs` - a `notify::settlement_started()` call was added right before trying to settle the solution. ## How to test I found no existing notification testing system, so I decided I am not the one to include that. However, it's possible to do in e2e tests by setting up a driver, a solver, calling driver's `settle` with the solver's name on it and look for the notification to be received on the solver's side. ## Related Issues Implements #4326 Co-authored-by: Haris Angelidakis <64154020+harisang@users.noreply.github.com> * Correctly encode approval interactions for USDT-like tokens (#4354) # Description Alternative to https://github.com/cowprotocol/services/pull/4340 USDT is a bit special because it is one of very few tokens where the `approve()` function requires you to set the allowance back to 0 before you can alter it again to avoid a vulnerability (see [here](https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)). # Changes This PR adjusts the driver's approval encoding logic such that it checks whether the regular `approve()` call would revert. In case that happens it encodes an `approve(0)` and `approve(desired_value)` to be compatible with this security measure / quirk. That way we support all those tokens on all chains without needing any extra configuration parameters or hardcoded values. The downside is 1 extra RPC call every time we have to increase an approval which is extremely rare so it's fine. ## How to test Converted an existing `USDC -> USDT` order fork test to be a `USDT -> USDC` test. This test fails without the PR's change. * Use inline PR review comments from Claude code review (#4356) # Description The Claude code-review action currently posts one big summary as an issue comment (example: https://github.com/cowprotocol/services/pull/4355#issuecomment-4291257773). The `code-review` plugin has a `--comment` flag that switches it from the summary-text mode to posting one PR review comment per issue anchored to the exact line in the diff, matching how `gemini-code-assist[bot]` already behaves. Per the plugin spec (step 9), this flag triggers `mcp__github_inline_comment__create_inline_comment` instead of a single `gh pr comment`. # Changes - Append `--comment` to the `/code-review:code-review` prompt in `.github/workflows/claude-code-review.yml`. # How to test Run the `claude /review` command. * Bump rustls-webpki to 0.103.13 (RUSTSEC-2026-0104) (#4357) ## Summary - `cargo audit` is currently failing on `main` due to [RUSTSEC-2026-0104](https://rustsec.org/advisories/RUSTSEC-2026-0104) — a reachable panic in `rustls-webpki`'s CRL parsing (affected: `0.103.0..0.103.13`). - `rustls-webpki` is a transitive dep (via `rustls-platform-verifier` → `reqwest`); no direct manifest bump is needed. Ran `cargo update -p rustls-webpki --precise 0.103.13` to move the lockfile from `0.103.12` → `0.103.13`. - While re-resolving, cargo also deduped a few consumers onto `windows-sys 0.60.2` and `syn 1.0.109`, both of which already existed in the lockfile on `main` — no new crate versions were introduced. ## Test plan - [ ] CI green * Support Aave v3 aToken balance overrides (#4345) # Description Quote verification was silently failing for any Aave v3 aToken as the sell token (e.g. aEthWETH → WETH) because the balance-override mechanism assumes the value at the balance storage slot is what `balanceOf` returns. Aave v3 aTokens break both assumptions: `balanceOf` applies a `rayMul(scaled, liquidityIndex)` scaling, and storage is packed `UserState { uint128 balance; uint128 additionalData }`. The auto-detector's verify step therefore never matched, returned `NotFound`, and the trade verifier silently skipped the override, producing the production revert `execution reverted: trader does not have enough sell token` visible in barn logs for aEthWETH quotes today (reproduced on Tenderly: https://dashboard.tenderly.co/cow-protocol/barn/simulator/1e69fa91-9496-44d1-9aec-a0e34166f9df). # Changes - New `Strategy::AaveV3AToken { target_contract, pool, underlying, map_slot }` variant in `configs::balance_overrides::Strategy` and a corresponding async resolver on `BalanceOverrides` that fetches the current `getReserveNormalizedIncome` from the Aave v3 Pool, rayDivs the requested amount (round-half-up, bit-for-bit compatible with `WadRayMath.rayDiv`), and writes it into the low 128 bits of the packed `_userState` slot. - Auto-detector is now Aave-aware: the detector probes the token with `UNDERLYING_ASSET_ADDRESS()` + `POOL()` and then calls `pool.getReserveData(underlying)` and verifies the returned `aTokenAddress` equals the probed token — an identity check that only passes for tokens the pool itself has registered as an aToken for their underlying, preventing rogue contracts that merely implement the aToken selectors from being accepted. When the probe succeeds, mapping-style slots are also offered as `AaveV3AToken` candidates and verified with a 1-wei round-trip tolerance instead of strict equality. No hardcoded per-token list is needed. aEthWETH and every other Aave v3 aToken on every chain (mainnet, Arbitrum, Base, Gnosis, Polygon, BNB, Linea, Plasma, Ink, Avalanche, etc.) are picked up automatically the first time they're quoted. - Shared Aave math lives in a new `balance-overrides::aave` module so the production override builder and the detector probe/verify use identical arithmetic. - `trade_verifier::prepare_state_overrides` now emits `tracing::warn!` when the spardose balance override for the sell token can't be resolved — mirroring the existing warn on the buy-side path so future missing overrides surface in logs instead of only showing up as downstream reverts. # Aave v4 note Aave v4's user-facing deposit receipt is `TokenizationSpoke`, an ERC-4626 vault built on OpenZeppelin's `ERC20Upgradeable` — not a scaled aToken. `balanceOf` is the plain OZ `_balances[user]` mapping (no `rayMul` scaling), and the contract doesn't expose `UNDERLYING_ASSET_ADDRESS()` or `POOL()`. Our probe therefore correctly rejects v4 Spokes and the detector falls through to the existing `SolidityMapping` / `DirectSlot` heuristics, which handle them as standard ERC-20s. No v4-specific work is needed here. Source: # How to test Unit tests: - `balance-overrides::tests::a_token_balance_override_bug_reproduction` — pins the arithmetic: writing the raw amount (what the old strategies do) makes `balanceOf` return `rayMul(amount, index)` (off by ~6e16 wei at aEthWETH's current index); writing `rayDiv(amount, index)` round-trips within 1 wei. - `balance-overrides::aave::tests` — mock-provider tests for the probe: accepts a valid aToken, rejects when either selector reverts, rejects when the claimed pool doesn't look like Aave v3, rejects when the pool registers a *different* aToken for the declared underlying. - `balance-overrides::tests::aave_v3_a_token_override_scales_amount_and_writes_low_128` — mock-provider integration test for the override builder. e2e local + forked node tests: - `balance-overrides::detector::tests::detects_aave_v3_a_token_mainnet` — asserts the detector returns `AaveV3AToken { pool=0x87870bca…4fa4e2, underlying=WETH, map_slot=52 }` for aEthWETH. - `balance-overrides::tests::aave_v3_a_token_override_mainnet_roundtrip` — applies the override via `eth_call` against real aEthWETH and asserts `balanceOf(holder) ≈ amount`. - `e2e::quote_verification::forked_node_mainnet_aave_atoken_quote` — same round-trip against an anvil mainnet fork, exercising the full `BalanceOverrides::state_override` path. # Follow-up items - **Registry-based detection.** The current probe costs 3 `eth_call`s per cold-cache token. An alternative is to enumerate all reserves once per chain from Aave's `AaveProtocolDataProvider` (or equivalent), cache the full list of aTokens, and do a pure `HashMap` lookup on each quote. That would make detection amortised O(1) and provide the strongest identity guarantee (taken from Aave's own registry), at the cost of introducing a per-chain constant for the DataProvider address, a refresh cadence for new markets, and handling multiple Aave v3 markets per chain (e.g. mainnet has Ethereum + Prime). Probably worth doing once aToken quote volume warrants the optimisation. - **Cache the accrued liquidity index for ~1 block.** Each aToken quote currently re-fetches `getReserveNormalizedIncome`. The index drifts slowly (fractions of a wei per block), so caching for 6–12 s would drop the per-quote cost to zero without meaningful accuracy loss. - **Aave v2 support.** Same scaling + packed-storage shape; different `LendingPool` interface. v2 is deprecated but still has markets; worth revisiting if any start to attract volume. * Include block number in logged Tenderly simulation command (#4359) # Description The Tenderly simulation command logged during quote verification cannot be replayed as-is: Tenderly rejects it with `Transaction index is not allowed when block number is pending`. The verifier passed `block: None` to `log_simulation_command`, which leaves `block_number` null in the JSON body (Tenderly treats this as pending) while `prepare_request` still set `transaction_index: -1`, and that combination is invalid. On top of that, `simulate_swap_with_solver` did not pin `.call()` to a specific block, so the node picked "latest" at request time and the logged block could diverge from what was actually simulated. # Changes - `simulate_swap_with_solver` now takes the block as a parameter and pins `.call()` to it. The caller (`trade_verifier`) snapshots `current_block` once and passes the same value to the simulator and to `log_simulation_command`, so the gas-price computation, the simulation, and the logged curl all reference identical chain state. - `log_simulation_command` and `prepare_request` now require a `BlockNo` (no longer `Option`), reflecting that every caller already supplies one and eliminating the pending-block edge case at the type level. # How to test Existing tests. After deploy, copy the curl command from the quote-verification log and run it — Tenderly should return a simulation instead of the validation error, and the result should match the driver's simulation for that block. * Fix EIP-4626 contract revert classification (#4360) # Description Testing the Euler vault tokens I found that the revert condition is a bit too strict for the Eip4626 purposes and it would think that the underlying asset was *not* valid because `asset()` failed; even though that's the expected behavior. This PR adds a new condition for a revert caused by a lack of selector, specifically for this case. Tested in staging, results can be seen in — https://victorialogs.dev.cow.fi/goto/ffjuyxx1tk6psc?orgId=1 native_price requests were done for `0x53afe3343f322c4189ab69e0d048efd154259419` # Changes * More logs, helps tracing through the layer peeling * Less strict variant of is_contract_error, including empty reverts as contract errors ## How to test Call the native_price endpoint with a Euler vault address (ensure that staging has them enabled) * Add uni v3 defaults on gnosis chain (#4350) # Description By now there is an official uniswap v3 deployment on gnosis chain so we can now add all the default values. Values were taken from [here](https://gov.uniswap.org/t/official-uniswap-v3-deployments-list/24323/3#p-53385-gnosis-7). Note, that uniswap v3 so far was manually configured in the driver but having defaults for all those contracts is just nicer. This issue was surfaced by @daveai on slack. # Changes - added addresses for router, quoter, and factory - also re-generated the rust code - added uni v3 to list of default liquidity sources (this is actually not used anymore and should be removed but since it makes more sense to delete it in a follow up PR it seemed proper to still add the value for gnosis to not leave the code base in an inconsistent state - it's just 1 line anyway) * Overshoot Spardose fake balance by 1% (#4362) # Problem Quote verification for small aToken sell amounts intermittently failed in prod with `execution reverted: trader does not have enough sell token`: [0.1 aEthWETH quote](https://victorialogs.dev.cow.fi/explore?schemaVersion=1&panes=%7B%22vdl%22%3A%7B%22datasource%22%3A%22vm-auth-prod%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22datasource%22%3A%7B%22type%22%3A%22victoriametrics-logs-datasource%22%2C%22uid%22%3A%22vm-auth-prod%22%7D%2C%22editorMode%22%3A%22code%22%2C%22expr%22%3A%22container%3A%21controller%20AND%20network%3Amainnet%20AND%20all%3A40880b4ceb2f4dbe7a25aa1877279ddd%22%2C%22queryType%22%3A%22range%22%7D%5D%2C%22range%22%3A%7B%22from%22%3A%221776965400000%22%2C%22to%22%3A%221776965700000%22%7D%2C%22panelsState%22%3A%7B%22logs%22%3A%7B%22visualisationType%22%3A%22logs%22%7D%7D%2C%22compact%22%3Afalse%7D%7D&orgId=1) failed, [1 aEthWETH quote](https://victorialogs.dev.cow.fi/explore?schemaVersion=1&panes=%7B%22vdl%22%3A%7B%22datasource%22%3A%22vm-auth-prod%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22datasource%22%3A%7B%22type%22%3A%22victoriametrics-logs-datasource%22%2C%22uid%22%3A%22vm-auth-prod%22%7D%2C%22editorMode%22%3A%22code%22%2C%22expr%22%3A%22container%3A%21controller%20AND%20network%3Amainnet%20AND%20all%3A98c568a44d1b6ce7ea068648fdfc7c94%22%2C%22queryType%22%3A%22range%22%7D%5D%2C%22range%22%3A%7B%22from%22%3A%221776965400000%22%2C%22to%22%3A%221776965700000%22%7D%2C%22panelsState%22%3A%7B%22logs%22%3A%7B%22visualisationType%22%3A%22logs%22%7D%7D%2C%22compact%22%3Afalse%7D%7D&orgId=1) minutes earlier passed. aToken does not store a balance directly. It stores a deposit and a growth factor, where `balanceOf() = deposit * growth_factor`. The factor ticks up every second as interest accrues. Quote verification writes a fake deposit into the Spardose (our donor contract) so the sim can pretend the trader has funds. The old code sized the fake deposit from a pre-sim `getReserveNormalizedIncome` RPC read. The sim then re-read the growth factor inside the EVM at its own pinned block. Those two reads can disagree by one block, leaving the Spardose's `balanceOf` one wei below `amount` when it tries to fund the trader. Aave's internal scaled-balance subtraction underflows, revert. The same kind of boundary issue could in principle hit other tokens (rebasing, tiny fee-on-transfer, future weird ones). # Fix Bump the Spardose's fake balance by 1% before passing it to the override: ``` spardose_amount = needed + needed / 100 ``` Spardose ends up slightly richer than the sim will ever transfer. Any rounding, accrual, or per-block drift is absorbed by the buffer. The Spardose is a throwaway donor, overshoot costs nothing: no gas difference, no side effects, only the trader-requested `amount` actually moves. Generic by construction. Fixes aToken and covers any future token with similar near-boundary math. # Context Supersedes #4361, which took an Aave-specific approach (scale against the stored `liquidityIndex` instead of the accrued one). Replaced with this generic overshoot per [Martin's review](https://github.com/cowprotocol/services/pull/4361#pullrequestreview-4168763147). # Changes - `price-estimation/trade_verifier`: 1% overshoot on the amount passed to the Spardose balance override. - Unit test covering the overshoot helper. # How to test `cargo test -p price-estimation trade_verifier`. Existing tests pass plus the new `spardose_amount_applies_1pct_overshoot`. * Propose multiple winning solutions in the driver (#4267) # Description The driver currently proposes only the single highest-scoring solution to the autopilot. With EIP-7702 parallel submission in place, the autopilot's combinatorial auction can now benefit from receiving all valid solutions from a driver to find the optimal set of winners. # Changes [x] Driver's solve() now returns Vec instead of Option with all valid solutions sorted best-first [x] Block re-simulation loop now monitors all proposed solutions individually, voiding only those that revert [x] New per-solver config flag propose-all-solutions (default: false) keeps existing behavior until EIP-7702 infrastructure is ready ## How to test ``` cargo nextest run -p driver --test-threads 1 --run-ignored ignored-only -E 'test(multiple_solutions)' ``` To enable in production, add to the solver config: `propose-all-solutions = true` (requires submission-accounts to also be configured along with the forwarder contract). * fix(playground): repair local compose config; prettier `test_playground.sh` output (#4352) # Description Fix the local playground setup so the Docker stack starts cleanly and `playground/test_playground.sh` works end to end again. This was needed because several local-only config mismatches had accumulated: - `tempo` was crashing on startup because `playground/tempo.yaml` used old config fields that the current `grafana/tempo:latest` image no longer accepts - `orderbook` was not reachable correctly because wrong config was being loaded - `autopilot` was missing the local database and EthFlow config needed to pick up and process the onchain test order - `driver` was trying to reach `orderbook` on the wrong internal port Less importantly, `test_playground.sh` had rough edges in its output and did not print the settlement transaction hash after a successful run. ## Error details seen locally These are the main errors that showed up while debugging: ### 1. Tempo / tracing startup failure `driver` was logging OpenTelemetry export failures like: ```text BatchSpanProcessor.ExportError ... error="Operation failed: code: 'The service is currently unavailable', message: \"dns error\", source: tonic::transport::Error(Transport, ConnectError(ConnectError(\"dns error\", Custom { kind: Uncategorized, error: \"failed to lookup address information: Name or service not known\" })))" ``` This looked like DNS, but the real issue was that `tempo` never came up because its config was invalid for the current image. ### 2. Early readiness / connection reset failures Running the playground test initially failed during readiness checks with: ```text curl: (56) Recv failure: Connection reset by peer ``` This came from `orderbook` not starting correctly and from an incorrect config being passed to it. ### 3. Autopilot startup crash Before the local config was fixed, `autopilot` crashed during startup with a panic around: ```text thread 'main' panicked at crates/autopilot/src/run.rs:185:6 ``` This was caused by missing database configuration in the local playground config. ### 4. Driver could not talk to orderbook internally When orders started reaching the solver path, `driver` still failed to fetch app data because it was calling `orderbook` on the wrong internal port due to the wrong config: ```text failed to fetch app data ... err=Http("error sending request for url (http://orderbook/api/v1/app_data/...)") ``` ### 5. Solve request timing mismatch `autopilot` would submit a solve request, `driver` would compute and score a valid settlement, but `autopilot` would still time out waiting for the response. The visible symptom was: ```text solver didn't provide solutions err=Failure(send 0: error sending request for url (http://driver/baseline/solve) ``` The root cause was that the default local solve deadline was too long for this playground test flow, so `driver` kept the solve request open much longer than the script expected. # Changes * Docker * Update `playground/tempo.yaml` to the config format accepted by the current Tempo image * Remove obsolete Tempo config sections and add the expected local trace storage/WAL paths * Add shared node configuration to `playground/configs/orderbook.toml` * Fix the `orderbook` config being passed to it * Add database configuration to `playground/configs/autopilot.toml` * Add local EthFlow contract configuration to `playground/configs/autopilot.toml` * Set a short local `solve-deadline` in `playground/configs/autopilot.toml` so the playground test does not wait on production-style timing * `test_playground.sh` * Extend `playground/test_playground.sh` to print the settlement transaction hash after success * Extend `playground/test_playground.sh` to print a ready-to-run `cast receipt` command and an Otterscan transaction URL * Clean up `playground/test_playground.sh` status output so expected pre-indexing `404` responses are shown as `indexing` Screenshot 2026-04-24 at 16 12 03 ## How to test 1. Start the local playground stack with the fork compose file: `docker compose -f playground/docker-compose.fork.yml up -d` 2. Run the smoke test: `cd playground && ./test_playground.sh` 3. Confirm the status progresses through local indexing / execution states and ends in `traded` 4. Confirm the script prints: - the settlement transaction hash - a `cast receipt ... --rpc-url http://localhost:8545` command - an Otterscan URL for the transaction 5. Optionally verify the settlement manually with the printed `cast receipt` command or by opening the printed Otterscan URL * Fix remaining sell error reporting for order simulation (#4364) # Description Simulating fully filled order (the remaining amount equals 0) errors out when creating NonZeroU256 from said amount. The error message is incorrect, as it uses order.data.sell_amount instead of the remaining_sell which is actually used. # Changes Change the error message to properly reflect which value is being used. ## How to test Query any filled order and observe correct error message. * Log driver API request deserialization errors (#4358) # Description Axum returns a terse 400 when a request body or query string fails to deserialize, which makes malformed caller payloads invisible in production. # Changes - Added `LoggingJson` and `LoggingQuery` extractors that emit a `tracing::warn!` with the serde error and the target type, then delegate to axum's stock rejection (HTTP response shape unchanged). - Wired the new extractors into `/settle`, `/reveal`, and `/quote`. - Added a `tracing::warn!` on `/solve` body parse failures in `pre_processing.rs`. # How to test Existing tests. * Make driver access list fetch optional (#4353) # Description The driver currently aborts the whole settlement whenever `simulator.access_list` fails. The access list is only functionally required for trades that send ETH to a smart-contract receiver (the EVM hard gas limit bypass described at `settlement.rs` line 140). For every other settlement it is just a gas optimization, so a failed RPC should not cost the solver the auction. # Changes - Access list is fetched best-effort when the partial access list is empty (no ETH->contract trade). On a non-revert failure (transport, deserialization, etc.) the driver now logs a warn and falls back to the empty partial list instead of aborting the settlement. - Revert errors are always propagated, even with an empty partial list, since they signal a real problem with the settlement. # How to test Existing tests. * Classify EVM InvalidFEOpcode halts as contract reverts (#4365) # Description `is_contract_revert` failed to recognize EVM halts (like the `INVALID`/0xFE opcode older Solidity contracts emit on a missing selector) as contract-level rejections. anvil/revm surface these as `EVM error ` JSON-RPC errors, with no revert data and no "revert" in the message — so calls that deterministically fail at the bytecode level (e.g. `asset()` on Bancor BNT) were misclassified as transient transport failures. In the EIP-4626 native price estimator this meant such tokens were never pinned as non-vault, so every native-price request triggered a fresh RPC round-trip that was guaranteed to fail. Extra context: I got a list of the top 150 tokens in Ethereum through Dune, ran the asset call against them and basically summed up the responses here. Older contracts fail with this InvalidFEOpcode, which is what currently blocks GNO from being quoted in staging (i.e. when eip4626 is enabled) # Changes * Extend `is_contract_revert` to match the `InvalidFEOpcode` error — every halt is a deterministic contract-level rejection, not a transport issue to retry. * Add a unit test (`contract_revert_accepts_evm_halt_errors`) covering `InvalidFEOpcode`, `OpcodeNotFound`, and `InvalidJump`. * Add a forked-mainnet test that calls `asset()` across a real EIP-4626 vault (sUSDe) and several non-vault shapes — USDC (empty revert), WETH / stETH (plain reverts), and GNO / BNT (INVALID opcode) — asserting each non-vault is correctly classified as a contract revert. * Add `BNT`, `stETH`, and `sUSDe` to `testlib::tokens` alongside the existing mainnet addresses; the new test consumes them from there. ## How to test 1. Unit tests: `cargo nextest run -p ethrpc contract_revert` 2. Forked test (requires mainnet fork URL): ``` FORK_URL_MAINNET= cargo nextest run -p e2e forked_node_mainnet_asset_call_is_contract_revert --test-threads 1 --run-ignored ignored-only --failure-output final 3. Staging Note, this is reproducible in staging, try trading a GNO token for example and you'll see liquidity errors. * Buy orders with same sell buy token (#4274) # Description Enable sell=buy handling for native-equivalent trades by treating native wrapped token to ETH marker as same-token during order validation. This is needed because the conversion path already exists downstream, but validation previously rejected these orders early. # Changes * Updated same-token validation logic to consider native wrapped token sell + ETH marker buy as same-token for policy evaluation. * Preserved existing policy behavior: allow-sell permits Sell orders, while Buy orders remain rejected. * Added unit test coverage for native-equivalent same-token cases under both disallow and allow-sell behavior. * Added e2e coverage for: * native-equivalent Sell quote/order success * native-equivalent Buy quote rejection under allow-sell policy ## How to test 1. `cargo nextest run -p shared pre_validate_err pre_validate_same_tokens_allow_sell` 2. `cargo nextest run -p e2e local_node_native_same_token_sell_with_eth_buy_marker local_node_native_same_token_buy_with_eth_buy_marker_rejected --test-threads 1 --run-ignored ignored-only --failure-output final` ## Related Issues Fixes #4053 --------- Co-authored-by: Marcin Szymczak * [TRIVIAL] Drop Claude Code workflows (#4381) The currently configured Claude code review workflow burns a lot of tokens. This requires investigating the usage and adjusting the jobs. Meanwhile, the job is dropped to avoid failing CI noise, since the API key balance remains 0 until this is sorted out. * Add Bitget buy-order support via reverse-quote endpoint (#4378) # Description Bitget's `/swapr` endpoint supports buy quotes (`requestMode = "minAmountOut"`) by running a recursion server-side on top of sell quotes. Wiring this up unlocks Bitget as a native-price source for the long tail of tokens that other estimators cover poorly. Today the integration only supports sell orders. The reverse-quote on real auction buy orders is correct end-to-end (positive overshoot accrues to the settlement buffer, the user receives exactly their signed buy amount, GPv2Settlement does no expected-vs-actual interaction-output check), but it can cost the user up to the configured slippage in surplus, so it's gated behind a config flag and off by default. # Changes - New `Side::Buy` code path in the Bitget integration, calling `/bgw-pro/swapx/pro/swapr` with `requestMode = "minAmountOut"` - Caps the reported swap output to `order.buy.amount` so CoW exact-out fulfillment passes - New `enable-buy-orders` config flag, off by default, gating the buy path - `decimal_to_wei` now rounds to the token's precision (Bitget can return more decimal digits than the token has) - Rate-limit pause between iterations in the live multi-chain test # How to test Existing tests plus new mock + ignored live tests. Run the live one with credentials: ``` BITGET_API_KEY=... BITGET_API_SECRET=... \ cargo nextest run -p solvers --run-ignored only swap_buy_all_chains ``` * Add parameter to configure `max-quote-timeout` (#4382) # Description Originally we introduced the ability to **reduce** the quote timeout below our default quote timeout as integrators felt our default timeout was too slow. Since then the default was lowered further and new tokens were added which require proprietary integrations which may require a longer timeout. # Changes Added optional `max-quote-timeout` config parameter which defaults to 10s. User provided `timeout` values in `/quote` requests get capped at that value now instead of our default quote timeout. ## How to test extended existing unit test for serializing the config files extended existing e2e test to check that quoting timeouts get enforced correctly * Restore Balancer functions used by the gnosis/solvers repo (#4383) # Description #4324 moved unused contract functions from `abi` to `_disabled` in the artifact JSONs to cut compile times. The check was scoped to this repo and missed the downstream solvers repo ([gnosis/solvers](https://github.com/gnosis/solvers)), which calls five of the disabled functions: - `BalancerQueries.queryBatchSwap` - `BalancerV3BatchRouter.swapExactIn` / `swapExactOut` - `BalancerV3BatchRouter.querySwapExactIn` / `querySwapExactOut` After the v2.359.0 bump in solvers, those functions disappeared from the generated `contracts` crate and the solver stopped compiling. Solvers worked around it locally with hand-rolled `alloy::sol!` bindings, but the right home for these is here. # Changes Move the five functions from `_disabled` back into `abi` in: - `contracts/artifacts/BalancerQueries.json` - `contracts/artifacts/BalancerV3BatchRouter.json` The hook variants (`swapExactInHook`, `querySwapExactOutHook`, ...) and other unused entries stay under `_disabled`. No changes elsewhere. # How to test - `cargo check --workspace` is green locally. - After this lands, the local bindings in cowprotocol/solvers can be removed on the next dependency bump. * [TRIVIAL] Log original quote when a new one gets computed (#4386) # Description Today we debugged an order which expired because it was placed right before the price changed a lot. # Changes Debugging this was quite tedious. To make this simpler we now log the original and the new quote id when we have to compute a new one. * Add CoW Protocol quote-verification debug skill (#4369) # Description Documents the end-to-end procedure for debugging quote competition # Changes - Adds an auto-generated but manually reviewed claude skill that explains how to debug quote competition given logs and simulation capabilities. ## How to test 1. Configure [CoW MCP](https://www.notion.so/cownation/Connecting-to-our-MCP-Gateway-3258da5f04ca8016b709f084e5f6c112) for claude 2. Tell claude your tenderly api key and rpc url to use (and if not already, install foundry) 3. Run a few different debug cases and chains (for a given request id, quote id or token pair for which their was no liquidity) I also have the services as well as the infrastructure checked out so that claude can look at the implementation directly (which may help to do furhter root cause). --------- Co-authored-by: Claude Opus 4.7 (1M context) * fix: use checked_* for `random_updated_at` (#4363) # Description Fix underflow when subtracting large `Durations` from `Instant` in `random_updated_at`. # Changes - Compute `age` as a percentage of `max_age` - Use `checked_mul` to avoid overflow when calculating age - Clamp Instant subtraction underflow to now via `checked_sub(...).unwrap_or(now)` - Adds coverage tests ## How to test cargo nextest run -p price-estimation --------- Co-authored-by: José Duarte <15343819+jmg-duarte@users.noreply.github.com> * Enforce EIP-7825 per-tx gas cap on settlement (#4371) # Description The driver caps a settlement's gas estimate at half the block gas limit (e.g. 60M on a 120M block). Fusaka introduced the EIP-7825 per-transaction gas cap of `2^24 - 1 = 16,777,215` on Mainnet; any tx exceeding this is rejected by the mempool. Without this check a solution above the cap can still be returned by `/solve` and forwarded to `/settle`, where it could never be mined. The cap is already enforced on the quote-verification path (#4261) but was never ported to settlement submission. Note: EIP-7825 is not strictly followed by all chains, some chains picked different per-tx limits (e.g. Arbitrum One uses 32M), so the driver reuses the existing `tx_gas_limit` config knob (#3780) rather than hardcoding a single value. Operators set the per-chain ceiling — `16,777,215` on Mainnet Fusaka, higher elsewhere — and `Gas::new` enforces `min(block_limit / 2, tx_gas_limit)`. # Changes * Plumb the `tx_gas_limit` config value through `infra::blockchain::Ethereum` (new `tx_gas_limit()` accessor). * In `Gas::new`, cap the per-settlement maximum at `min(block_limit / 2, tx_gas_limit)` so over-sized solutions fail fast through the existing `GasLimitExceeded` error. * Chains with a block gas limit below `2 * tx_gas_limit` keep the tighter half-block behaviour. * Add unit tests covering the configured-cap path, the boundary case (asserting both `estimate` and the clamped `limit`), the small-block-limit path, and the non-Fusaka case where the half-block cap binds. ## How to test ``` cargo nextest run -p driver settlement::tests ``` All four new tests pass; existing driver tests are unchanged. Fixes #4368 * Pre-cache BUY_ETH_ADDRESS as non-vault (#4390) # Description Pre-caches BUY_ETH_ADDRESS as a non-vault token at the EIP-4626 estimator level. Given that BUY_ETH_ADDRESS does not have code deployed to it, this makes it a "valid-enough" token to be ignored by EIP-4626 when enabled and passed on to the rest of the estimation pipeline. # Changes * Pre-caches BUY_ETH_ADDRESS as a non-vault token at the EIP-4626 estimator level. * Adds tests for said usage ## How to test Regular tests * Builder based API for `simulator` crate (#4372) # Description This new API for the `simulator` crate approaches the problem from the other side. Instead of starting by making it work first and foremost with the most complicated use case (i.e. trade verification) and building the simpler, more common use cases around that this API starts with the simple stuff and aims to be flexible enough to also support the more complicated things. Not only does this lead to a cleaner API (IMO) but also actual feature improvements. For example the trade verification logic replaces the user with a helper contract that we puppeteer to set up the necessary pre-conditions for quote verification. That helper contract makes the approach fundamentally incompatible with use cases where a pre-interaction deploys a smart contract that is actually the owner of the order (we did not have a way to know the state and bytecode of this helper contract to fall back to). The new approach now allows us to easily opt-in to each "faking machinery" individually. So the simulation endpoint will only use balance overrides and allow listing a random address while the trade verification can pull out the big guns (many balance overrides, multiple fake contracts). Additionally this PR adds the ability to correctly encode flashloans which was previously not supported by the `simulator` crate. # Changes - new builder based API to construct simulations - replaced all existing use cases of the old API with the new one - removed the old API (2 files) - updated the custom order simulation API to provide all the necessary data to not require the fake EIP 1271 user contract - this allows simulating orders of counterfactual owners (contracts that have not been deployed yet) - replaced `AnyoneAuthenticator` approach with exactly overriding only the storage slot to allow list a specific address as a solver > [!IMPORTANT] > This PR breaks the API for debugging custom orders. Due to supporting more features the request now also has to contain the `signature`, `signingScheme`, `feeAmount`, `validTo`, `appData`, and `partiallyFillable`. ## Things to improve Ultimately I would like the `simulator` crate also to be the authority when it comes to encoding the calldata of actual settlements (which is why it already handles encoding the auction id). However, this is quite difficult to do nicely because now we have to think about correctly encoding many prices and managing wrappers and hooks from multiple orders. For simplicity sake the builder currently does not concern itself with that. That's why you can only add wrappers and interactions from 1 appdata string directly and there is no support for encoding prices such that orders get surplus. # How to test updated existing e2e tests (populating the new fields in the custom order simulation request) --------- Co-authored-by: squadgazzz * [TRIVIAL] Configure flashloan router address for all networks (#4397) # Changes Configures the flashloan router address which on all networks so that we don't have to manually configure it in any configuration files for our real deployments. * [TRIVIAL] Remove `enforce-when-possible` quote verification mode (#4393) # Description `enforce-when-possible` is a quote verification mode that is not used in practice. In preparation for a larger refactor of the underlying quote verification logic it makes sense to remove this to reduce the surface area of code we need to fit the new logic into. # Changes - dropped `enforce-when-possible` mode that completely rejected unverified quotes when the user had the required balance Co-authored-by: Claude Sonnet 4.6 * Drop tx.origin = 0x0000 bypass for ZeroEx RFQ quotes (#4394) # Description We needed to introduce a loop hole to consider some quotes verified even though the simulation failed. However, for a long time now no solver actually used this edge case anymore. # Changes In order to simplify the quote verification wherever possible this PR drops this edge case and its test. --------- Co-authored-by: Claude Sonnet 4.6 * re-generate contracts (#4403) # Description Follow up to https://github.com/cowprotocol/services/pull/4397 as that got merged without having re-generated the contract bindings. To prevent these issues going forward I added another branch protection rule to make the contract bindings check required. # Changes - generated contract bindings and now the chain id entries show up * Rename BalanceOverriding trait to StateOverriding (#4395) # Description In order to make quote verification compatible with more use cases we soon need to start computing approval overrides as well. The plan is to expose both functions from the `balance-overrides` crate (will eventually be renamed to `state-overrides`). # Changes This PR only renames the traits and some functions to disambiguate the code in preparation for introducing the new functionality. Unfortunately I messed up the merge order of the stacked PRs so now this PR also includes https://github.com/cowprotocol/services/pull/4399. --------- Co-authored-by: Claude Sonnet 4.6 * Collapse settlement metrics into single-pass `summarize()` (#4388) ## Description Previously, computing settlement metrics required four separate iterations over the trades collection — one each for `surplus_in_ether()`, `fee_in_ether()`, `fee_breakdown()`, and `jit_orders()`: ```rust let gas = settlement.gas(); let gas_price = settlement.gas_price(); let surplus = settlement.surplus_in_ether(); let fee = settlement.fee_in_ether(); let fee_breakdown = settlement.fee_breakdown(); let jit_orders = settlement.jit_orders(); ``` This PR introduces a `summarize()` method on `Settlement` that performs a single pass over `self.trades`, accumulating all four values simultaneously and returning them as a `SettlementMetrics<'_>` struct. The call site in `infra/persistence` is updated to destructure directly from the returned struct: ```rust let domain::settlement::SettlementMetrics { gas, gas_price, surplus, fee, fee_breakdown, jit_orders, } = settlement.summarize(); ``` The returned struct is defined as: ```rust #[derive(Debug)] pub struct SettlementMetrics<'a> { pub gas: eth::Gas, pub gas_price: eth::EffectiveGasPrice, pub surplus: eth::Ether, pub fee: eth::Ether, pub fee_breakdown: HashMap, pub jit_orders: Vec<&'a trade::Jit>, } ``` ## Changes - **`domain/settlement/mod.rs`** - Added `SettlementMetrics<'a>` struct - Added `Settlement::summarize()` — single-pass computation replacing the four individual iteration methods - Preserves existing `tracing::warn!` fallback behaviour on per-trade errors - **`infra/persistence/mod.rs`** - Replaced six individual `settlement.*()` calls with a single `settlement.summarize()`, destructuring all fields directly from `SettlementMetrics` via pattern matching Fixes #4346 * Fetch and log (and cache) buy/sell quote token symbols (#4396) # Description Logs quote token symbols, keeps them in a cache to avoid the network # Changes * Add the token info fetcher into the QuoteHandler * The fetcher provides us with generalized caching ## How to test N/A --------- Co-authored-by: Martin Magnus --------- Co-authored-by: Jan [Yann] <4518474+fafk@users.noreply.github.com> Co-authored-by: Martin Magnus Co-authored-by: ilya Co-authored-by: Marcin Szymczak Co-authored-by: Claude Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: MarcusWentz <52706599+MarcusWentz@users.noreply.github.com> Co-authored-by: tilacog Co-authored-by: Anuj Bansal Co-authored-by: Augusto Collerone Co-authored-by: Ignacio Co-authored-by: Aryan Godara <65490434+AryanGodara@users.noreply.github.com> Co-authored-by: extrawurst <776816+extrawurst@users.noreply.github.com> Co-authored-by: openjarvis2026 Co-authored-by: ashleychandy Co-authored-by: Ashley Chandy <76095880+ashleychandy@users.noreply.github.com> Co-authored-by: filius Co-authored-by: Kaze <230549489+kaze-cow@users.noreply.github.com> Co-authored-by: Nikita Kirsanov Co-authored-by: Haris Angelidakis <64154020+harisang@users.noreply.github.com> Co-authored-by: Igor Rončević <57319163+igorroncevic@users.noreply.github.com> Co-authored-by: Felix Leupold <1200333+fleupold@users.noreply.github.com> Co-authored-by: metalurgical <97008724+metalurgical@users.noreply.github.com> Co-authored-by: DevNinja <102245100+0xDevNinja@users.noreply.github.com> --- .cargo/audit.toml | 8 - .claude/commands/debug-order.md | 20 +- CLAUDE.md | 56 +- Cargo.lock | 1616 ++- Cargo.toml | 48 +- Justfile | 25 + LICENSE-APACHE | 0 LICENSE-GPL | 0 LICENSE-MIT | 0 README.md | 2 + REVIEW.md | 78 + contracts/.gitignore | 4 + contracts/Cargo.lock | 2302 ++++ contracts/Cargo.toml | 19 + .../contracts => contracts}/LICENSE-APACHE | 0 {crates/contracts => contracts}/LICENSE-MIT | 0 contracts/README.md | 60 + .../artifacts/AnyoneAuthenticator.json | 0 .../artifacts/BalancerQueries.json | 32 +- .../artifacts/BalancerV2Authorizer.json | 170 +- .../artifacts/BalancerV2BasePool.json | 298 +- .../artifacts/BalancerV2BasePoolFactory.json | 4 +- .../BalancerV2ComposableStablePool.json | 526 +- ...BalancerV2ComposableStablePoolFactory.json | 30 +- ...lancerV2ComposableStablePoolFactoryV3.json | 282 + ...lancerV2ComposableStablePoolFactoryV4.json | 282 + ...lancerV2ComposableStablePoolFactoryV5.json | 282 + ...lancerV2ComposableStablePoolFactoryV6.json | 282 + .../BalancerV2LiquidityBootstrappingPool.json | 404 +- ...erV2LiquidityBootstrappingPoolFactory.json | 6 +- ...lFeeLiquidityBootstrappingPoolFactory.json | 160 + .../artifacts/BalancerV2StablePool.json | 370 +- .../BalancerV2StablePoolFactoryV2.json | 6 +- .../artifacts/BalancerV2Vault.json | 634 +- .../artifacts/BalancerV2WeightedPool.json | 366 +- .../BalancerV2WeightedPool2TokensFactory.json | 10 +- .../BalancerV2WeightedPoolFactory.json | 10 +- .../BalancerV2WeightedPoolFactoryV3.json | 28 +- .../BalancerV2WeightedPoolFactoryV4.json | 272 + .../artifacts/BalancerV3BatchRouter.json | 648 +- contracts/artifacts/Balances.json | 110 + contracts/artifacts/BaoswapRouter.json | 957 ++ .../artifacts/ChainalysisOracle.json | 66 +- .../artifacts/CoWSwapEthFlow.json | 38 +- .../artifacts/CoWSwapOnchainOrders.json | 0 .../artifacts/Counter.json | 0 .../artifacts/CowAmm.json | 662 +- .../CowAmmConstantProductFactory.json | 610 +- .../artifacts/CowAmmFactoryGetter.json | 0 .../artifacts/CowAmmLegacyHelper.json | 190 +- .../artifacts/CowAmmUniswapV2PriceOracle.json | 75 +- .../artifacts/CowProtocolToken.json | 278 +- .../artifacts/CowSettlementForwarder.json | 0 .../artifacts/ERC1271SignatureValidator.json | 0 .../artifacts/ERC20.json | 114 +- .../artifacts/ERC20Mintable.json | 142 +- .../artifacts/FlashLoanRouter.json | 56 +- .../GPv2AllowListAuthentication.json | 128 +- .../artifacts/GPv2Settlement.json | 238 +- .../artifacts/GasHog.json | 0 .../artifacts/GnosisSafe.json | 292 +- ...nosisSafeCompatibilityFallbackHandler.json | 124 +- .../artifacts/GnosisSafeProxy.json | 0 .../artifacts/GnosisSafeProxyFactory.json | 32 +- contracts/artifacts/HoneyswapRouter.json | 957 ++ .../artifacts/HooksTrampoline.json | 0 .../artifacts/ICowWrapper.json | 30 +- contracts/artifacts/IERC4626.json | 36 + .../artifacts/ISwaprPair.json | 244 +- .../artifacts/IUniswapLikePair.json | 186 +- .../artifacts/IUniswapLikeRouter.json | 166 +- .../artifacts/IUniswapV3Factory.json | 78 +- .../artifacts/IZeroex.json | 10362 ++++++++-------- contracts/artifacts/LiquoriceSettlement.json | 4193 +++++++ contracts/artifacts/MockERC4626Wrapper.json | 240 + .../artifacts/NonStandardERC20Balances.json | 0 contracts/artifacts/PancakeRouter.json | 957 ++ .../artifacts/Permit2.json | 200 +- .../artifacts/RemoteERC20Balances.json | 0 .../artifacts/Signatures.json | 0 .../artifacts/Solver.json | 0 .../artifacts/Spardose.json | 0 contracts/artifacts/SushiSwapRouter.json | 957 ++ .../artifacts/Swapper.json | 0 contracts/artifacts/SwaprRouter.json | 957 ++ .../artifacts/TestnetUniswapV2Router02.json | 957 ++ .../artifacts/Trader.json | 0 .../artifacts/UniswapV2Factory.json | 88 +- .../artifacts/UniswapV2Router02.json | 178 +- .../artifacts/UniswapV3Pool.json | 398 +- .../artifacts/UniswapV3QuoterV2.json | 4 +- .../artifacts/UniswapV3SwapRouterV2.json | 0 .../artifacts/WETH9.json | 196 +- {crates/contracts => contracts}/foundry.toml | 0 contracts/generated/.gitignore | 2 + contracts/generated/Cargo.lock | 4531 +++++++ contracts/generated/Cargo.toml | 17 + .../generated/contracts-facade/Cargo.toml | 90 + .../generated/contracts-facade/src/lib.rs | 99 + .../anyoneauthenticator/Cargo.toml | 19 + .../anyoneauthenticator/src/lib.rs | 527 + .../balancerqueries/Cargo.toml | 19 + .../balancerqueries/src/lib.rs | 1749 +++ .../balancerv2authorizer/Cargo.toml | 19 + .../balancerv2authorizer/src/lib.rs | 1455 +++ .../balancerv2basepool/Cargo.toml | 19 + .../balancerv2basepool/src/lib.rs | 3947 ++++++ .../balancerv2basepoolfactory/Cargo.toml | 19 + .../balancerv2basepoolfactory/src/lib.rs | 374 + .../balancerv2composablestablepool/Cargo.toml | 19 + .../balancerv2composablestablepool/src/lib.rs | 6664 ++++++++++ .../Cargo.toml | 19 + .../src/lib.rs | 1540 +++ .../Cargo.toml | 19 + .../src/lib.rs | 1544 +++ .../Cargo.toml | 19 + .../src/lib.rs | 1552 +++ .../Cargo.toml | 19 + .../src/lib.rs | 1556 +++ .../Cargo.toml | 19 + .../src/lib.rs | 1556 +++ .../Cargo.toml | 19 + .../src/lib.rs | 5185 ++++++++ .../Cargo.toml | 19 + .../src/lib.rs | 934 ++ .../Cargo.toml | 19 + .../src/lib.rs | 974 ++ .../balancerv2stablepool/Cargo.toml | 19 + .../balancerv2stablepool/src/lib.rs | 4740 +++++++ .../balancerv2stablepoolfactoryv2/Cargo.toml | 19 + .../balancerv2stablepoolfactoryv2/src/lib.rs | 1232 ++ .../balancerv2vault/Cargo.toml | 19 + .../balancerv2vault/src/lib.rs | 7268 +++++++++++ .../balancerv2weightedpool/Cargo.toml | 19 + .../balancerv2weightedpool/src/lib.rs | 4380 +++++++ .../Cargo.toml | 19 + .../src/lib.rs | 1012 ++ .../balancerv2weightedpoolfactory/Cargo.toml | 19 + .../balancerv2weightedpoolfactory/src/lib.rs | 976 ++ .../Cargo.toml | 19 + .../src/lib.rs | 1499 +++ .../Cargo.toml | 19 + .../src/lib.rs | 1511 +++ .../balancerv3batchrouter/Cargo.toml | 19 + .../balancerv3batchrouter/src/lib.rs | 4736 +++++++ .../contracts-generated/balances/Cargo.toml | 19 + .../contracts-generated/balances/src/lib.rs | 1278 ++ .../baoswaprouter/Cargo.toml | 19 + .../baoswaprouter/src/lib.rs | 1578 +++ .../chainalysisoracle/Cargo.toml | 19 + .../chainalysisoracle/src/lib.rs | 1800 +++ .../contracts-generated/counter/Cargo.toml | 19 + .../contracts-generated/counter/src/lib.rs | 952 ++ .../contracts-generated/cowamm/Cargo.toml | 19 + .../contracts-generated/cowamm/src/lib.rs | 4430 +++++++ .../cowammconstantproductfactory/Cargo.toml | 19 + .../cowammconstantproductfactory/src/lib.rs | 3081 +++++ .../cowammfactorygetter/Cargo.toml | 19 + .../cowammfactorygetter/src/lib.rs | 441 + .../cowammlegacyhelper/Cargo.toml | 19 + .../cowammlegacyhelper/src/lib.rs | 3196 +++++ .../cowammuniswapv2priceoracle/Cargo.toml | 19 + .../cowammuniswapv2priceoracle/src/lib.rs | 230 + .../cowprotocoltoken/Cargo.toml | 19 + .../cowprotocoltoken/src/lib.rs | 4183 +++++++ .../cowsettlementforwarder/Cargo.toml | 19 + .../cowsettlementforwarder/src/lib.rs | 1419 +++ .../cowswapethflow/Cargo.toml | 19 + .../cowswapethflow/src/lib.rs | 5329 ++++++++ .../cowswaponchainorders/Cargo.toml | 19 + .../cowswaponchainorders/src/lib.rs | 1815 +++ .../erc1271signaturevalidator/Cargo.toml | 19 + .../erc1271signaturevalidator/src/lib.rs | 497 + .../contracts-generated/erc20/Cargo.toml | 19 + .../contracts-generated/erc20/src/lib.rs | 2427 ++++ .../erc20mintable/Cargo.toml | 19 + .../erc20mintable/src/lib.rs | 2367 ++++ .../flashloanrouter/Cargo.toml | 19 + .../flashloanrouter/src/lib.rs | 1352 ++ .../contracts-generated/gashog/Cargo.toml | 19 + .../contracts-generated/gashog/src/lib.rs | 784 ++ .../contracts-generated/gnosissafe/Cargo.toml | 19 + .../contracts-generated/gnosissafe/src/lib.rs | 4298 +++++++ .../Cargo.toml | 19 + .../src/lib.rs | 1415 +++ .../gnosissafeproxy/Cargo.toml | 19 + .../gnosissafeproxy/src/lib.rs | 317 + .../gnosissafeproxyfactory/Cargo.toml | 19 + .../gnosissafeproxyfactory/src/lib.rs | 780 ++ .../gpv2allowlistauthentication/Cargo.toml | 19 + .../gpv2allowlistauthentication/src/lib.rs | 2150 ++++ .../gpv2settlement/Cargo.toml | 19 + .../gpv2settlement/src/lib.rs | 5150 ++++++++ .../honeyswaprouter/Cargo.toml | 19 + .../honeyswaprouter/src/lib.rs | 1578 +++ .../hookstrampoline/Cargo.toml | 19 + .../hookstrampoline/src/lib.rs | 1329 ++ .../icowwrapper/Cargo.toml | 19 + .../icowwrapper/src/lib.rs | 469 + .../contracts-generated/ierc4626/Cargo.toml | 19 + .../contracts-generated/ierc4626/src/lib.rs | 648 + .../contracts-generated/iswaprpair/Cargo.toml | 19 + .../contracts-generated/iswaprpair/src/lib.rs | 5733 +++++++++ .../iuniswaplikepair/Cargo.toml | 19 + .../iuniswaplikepair/src/lib.rs | 5554 +++++++++ .../iuniswaplikerouter/Cargo.toml | 19 + .../iuniswaplikerouter/src/lib.rs | 1546 +++ .../iuniswapv3factory/Cargo.toml | 19 + .../iuniswapv3factory/src/lib.rs | 1379 ++ .../contracts-generated/izeroex/Cargo.toml | 19 + .../contracts-generated/izeroex/src/lib.rs | 8342 +++++++++++++ .../liquoricesettlement/Cargo.toml | 19 + .../liquoricesettlement/src/lib.rs | 9607 ++++++++++++++ .../mockerc4626wrapper/Cargo.toml | 19 + .../mockerc4626wrapper/src/lib.rs | 2768 +++++ .../nonstandarderc20balances/Cargo.toml | 19 + .../nonstandarderc20balances/src/lib.rs | 1643 +++ .../pancakerouter/Cargo.toml | 19 + .../pancakerouter/src/lib.rs | 1590 +++ .../contracts-generated/permit2/Cargo.toml | 19 + .../contracts-generated/permit2/src/lib.rs | 4706 +++++++ .../remoteerc20balances/Cargo.toml | 19 + .../remoteerc20balances/src/lib.rs | 1909 +++ .../contracts-generated/signatures/Cargo.toml | 19 + .../contracts-generated/signatures/src/lib.rs | 1171 ++ .../contracts-generated/solver/Cargo.toml | 19 + .../contracts-generated/solver/src/lib.rs | 1146 ++ .../contracts-generated/spardose/Cargo.toml | 19 + .../contracts-generated/spardose/src/lib.rs | 534 + .../sushiswaprouter/Cargo.toml | 19 + .../sushiswaprouter/src/lib.rs | 1606 +++ .../contracts-generated/swapper/Cargo.toml | 19 + .../contracts-generated/swapper/src/lib.rs | 1553 +++ .../swaprrouter/Cargo.toml | 19 + .../swaprrouter/src/lib.rs | 1586 +++ .../testnetuniswapv2router02/Cargo.toml | 19 + .../testnetuniswapv2router02/src/lib.rs | 1603 +++ .../contracts-generated/trader/Cargo.toml | 19 + .../contracts-generated/trader/src/lib.rs | 1082 ++ .../uniswapv2factory/Cargo.toml | 19 + .../uniswapv2factory/src/lib.rs | 1191 ++ .../uniswapv2router02/Cargo.toml | 19 + .../uniswapv2router02/src/lib.rs | 1804 +++ .../uniswapv3pool/Cargo.toml | 19 + .../uniswapv3pool/src/lib.rs | 5990 +++++++++ .../uniswapv3quoterv2/Cargo.toml | 19 + .../uniswapv3quoterv2/src/lib.rs | 2716 ++++ .../uniswapv3swaprouterv2/Cargo.toml | 19 + .../uniswapv3swaprouterv2/src/lib.rs | 1073 ++ .../contracts-generated/weth9/Cargo.toml | 19 + .../contracts-generated/weth9/src/lib.rs | 3114 +++++ contracts/generated/rustfmt.toml | 12 + .../script/DeployBalances.s.sol | 0 .../script/DeploySignatures.s.sol | 0 .../solidity/AnyoneAuthenticator.sol | 0 .../solidity/Balances.sol | 8 +- .../solidity/CowSettlementForwarder.sol | 0 .../contracts => contracts}/solidity/Makefile | 2 +- .../solidity/README.md | 0 .../solidity/Signatures.sol | 0 .../solidity/Solver.sol | 0 .../solidity/Spardose.sol | 0 .../solidity/Swapper.sol | 0 .../solidity/Trader.sol | 0 .../solidity/interfaces/IERC1271.sol | 0 .../solidity/interfaces/IERC20.sol | 0 contracts/solidity/interfaces/IERC4626.sol | 8 + .../solidity/interfaces/ISettlement.sol | 0 .../interfaces/IStorageAccessible.sol | 0 .../solidity/interfaces/IVault.sol | 0 .../solidity/interfaces/IVaultRelayer.sol | 0 .../solidity/libraries/Caller.sol | 0 .../solidity/libraries/Math.sol | 0 .../solidity/libraries/SafeERC20.sol | 0 .../solidity/tests/Counter.sol | 0 .../solidity/tests/GasHog.sol | 0 .../solidity/tests/MockERC4626Wrapper.sol | 54 + .../tests/NonStandardERC20Balances.sol | 0 .../solidity/tests/RemoteERC20Balances.sol | 0 contracts/src/codegen.rs | 487 + contracts/src/main.rs | 602 + contracts/src/networks.rs | 13 + .../src/bin => contracts/src}/vendor.rs | 55 +- crates/account-balances/src/lib.rs | 19 +- crates/account-balances/src/simulation.rs | 14 +- crates/app-data/src/app_data.rs | 7 + crates/autopilot/Cargo.toml | 3 + crates/autopilot/src/arguments.rs | 272 +- .../src/boundary/events/settlement.rs | 2 +- crates/autopilot/src/boundary/order.rs | 5 +- .../ethflow_events/event_retriever.rs | 2 +- .../database/ethflow_events/event_storing.rs | 2 +- crates/autopilot/src/database/events.rs | 2 +- .../onchain_order_events/ethflow_events.rs | 4 +- .../onchain_order_events/event_retriever.rs | 2 +- .../src/database/onchain_order_events/mod.rs | 37 +- crates/autopilot/src/database/order_events.rs | 5 +- crates/autopilot/src/domain/auction/mod.rs | 5 +- crates/autopilot/src/domain/auction/order.rs | 3 +- crates/autopilot/src/domain/blockchain.rs | 47 + .../autopilot/src/domain/competition/mod.rs | 3 +- .../domain/competition/winner_selection.rs | 21 +- crates/autopilot/src/domain/eth/mod.rs | 366 - crates/autopilot/src/domain/fee/mod.rs | 22 +- crates/autopilot/src/domain/fee/policy.rs | 4 +- crates/autopilot/src/domain/mod.rs | 2 +- crates/autopilot/src/domain/quote/mod.rs | 3 +- .../src/domain/settlement/auction.rs | 7 +- crates/autopilot/src/domain/settlement/mod.rs | 186 +- .../src/domain/settlement/observer.rs | 12 +- .../src/domain/settlement/trade/math.rs | 4 +- .../src/domain/settlement/trade/mod.rs | 10 +- .../src/domain/settlement/transaction/mod.rs | 13 +- .../settlement/transaction/tokenized.rs | 5 +- crates/autopilot/src/infra/api.rs | 55 + .../src/infra/blockchain/contracts.rs | 33 +- crates/autopilot/src/infra/blockchain/mod.rs | 23 +- .../src/infra/persistence/dto/auction.rs | 7 +- .../src/infra/persistence/dto/order.rs | 5 +- crates/autopilot/src/infra/persistence/mod.rs | 76 +- .../autopilot/src/infra/solvers/dto/solve.rs | 7 +- crates/autopilot/src/infra/solvers/mod.rs | 4 +- crates/autopilot/src/lib.rs | 3 +- crates/autopilot/src/periodic_db_cleanup.rs | 3 + crates/autopilot/src/run.rs | 213 +- crates/autopilot/src/run_loop.rs | 63 +- crates/autopilot/src/shadow.rs | 2 +- crates/autopilot/src/solvable_orders.rs | 187 +- crates/bad-tokens/src/trace_call.rs | 2 +- crates/balance-overrides/Cargo.toml | 5 + crates/balance-overrides/src/aave.rs | 295 + crates/balance-overrides/src/detector.rs | 140 +- crates/balance-overrides/src/lib.rs | 447 +- crates/chain/src/lib.rs | 6 +- crates/configs/Cargo.toml | 13 +- crates/configs/src/autopilot/cow_amm.rs | 70 + crates/configs/src/autopilot/ethflow.rs | 58 + .../src/autopilot}/fee_policy.rs | 8 +- .../config => configs/src/autopilot}/mod.rs | 210 +- .../src/autopilot}/native_price.rs | 22 +- .../src/autopilot}/order_events_cleanup.rs | 1 + crates/configs/src/autopilot/run_loop.rs | 120 + .../config => configs/src/autopilot}/s3.rs | 1 + .../src/autopilot}/solver.rs | 21 +- .../src/autopilot}/trusted_tokens.rs | 1 + crates/configs/src/balance_overrides.rs | 252 + .../config => configs/src}/banned_users.rs | 3 + crates/configs/src/database.rs | 14 +- crates/configs/src/deserialize_env.rs | 17 + crates/configs/src/fee_factor.rs | 146 + crates/configs/src/gas_price_estimation.rs | 15 + crates/configs/src/http_client.rs | 23 + crates/configs/src/lib.rs | 14 + crates/configs/src/native_price.rs | 157 + crates/configs/src/native_price_estimators.rs | 120 + crates/configs/src/order_quoting.rs | 196 + .../config => configs/src/orderbook}/ipfs.rs | 27 +- .../config => configs/src/orderbook}/mod.rs | 160 +- .../src/orderbook}/native_price.rs | 22 +- .../src/orderbook}/order_validation.rs | 16 +- crates/configs/src/price_estimation.rs | 451 + crates/configs/src/rate_limit.rs | 73 + crates/configs/src/shared.rs | 434 + crates/configs/src/simulator.rs | 210 + crates/contracts/.gitignore | 2 - crates/contracts/Cargo.toml | 42 - crates/contracts/README.md | 14 - ...lancerV2ComposableStablePoolFactoryV3.json | 1 - ...lancerV2ComposableStablePoolFactoryV4.json | 1 - ...lancerV2ComposableStablePoolFactoryV5.json | 1 - ...lancerV2ComposableStablePoolFactoryV6.json | 1 - ...lFeeLiquidityBootstrappingPoolFactory.json | 1 - .../BalancerV2WeightedPoolFactoryV4.json | 1 - crates/contracts/artifacts/Balances.json | 105 - crates/contracts/artifacts/BaoswapRouter.json | 1 - .../contracts/artifacts/HoneyswapRouter.json | 1 - .../artifacts/LiquoriceSettlement.json | 4191 ------- crates/contracts/artifacts/PancakeRouter.json | 1 - .../contracts/artifacts/SushiSwapRouter.json | 1 - crates/contracts/artifacts/SwaprRouter.json | 1 - .../artifacts/TestnetUniswapV2Router02.json | 1 - crates/contracts/build.rs | 1262 -- crates/contracts/rustfmt.toml | 2 - crates/contracts/src/lib.rs | 5 - crates/cow-amm/src/amm.rs | 5 +- crates/cow-amm/src/cache.rs | 2 +- crates/cow-amm/src/factory.rs | 2 +- crates/cow-amm/src/lib.rs | 4 +- crates/cow-amm/src/maintainers.rs | 2 +- crates/cow-amm/src/registry.rs | 2 +- crates/database/src/auction.rs | 36 + crates/database/src/byte_array.rs | 8 +- crates/database/src/ethflow_orders.rs | 4 +- crates/database/src/fee_policies.rs | 9 + crates/database/src/jit_orders.rs | 4 +- crates/database/src/lib.rs | 1 + crates/database/src/onchain_invalidations.rs | 1 + crates/database/src/order_events.rs | 108 +- crates/database/src/order_execution.rs | 27 + crates/database/src/orders.rs | 5 +- crates/database/src/settlement_executions.rs | 55 +- crates/database/src/solver_competition.rs | 54 +- crates/database/src/solver_competition_v2.rs | 68 +- crates/database/src/trades.rs | 294 +- crates/driver/Cargo.toml | 4 + crates/driver/example.toml | 4 - .../src/boundary/liquidity/balancer/v2/mod.rs | 17 +- .../boundary/liquidity/balancer/v2/stable.rs | 5 +- .../liquidity/balancer/v2/weighted.rs | 5 +- crates/driver/src/boundary/liquidity/mod.rs | 7 +- .../src/boundary/liquidity/uniswap/v2.rs | 17 +- .../src/boundary/liquidity/uniswap/v3.rs | 11 +- .../driver/src/boundary/liquidity/zeroex.rs | 8 +- crates/driver/src/domain/blockchain.rs | 83 + .../driver/src/domain/competition/auction.rs | 6 +- crates/driver/src/domain/competition/mod.rs | 454 +- .../src/domain/competition/order/mod.rs | 7 +- .../src/domain/competition/order/signature.rs | 2 +- .../src/domain/competition/pre_processing.rs | 18 +- .../risk_detector/bad_tokens/cache.rs | 3 +- .../risk_detector/bad_tokens/simulation.rs | 2 +- .../domain/competition/risk_detector/mod.rs | 6 +- .../domain/competition/solution/encoding.rs | 105 +- .../src/domain/competition/solution/fee.rs | 12 +- .../competition/solution/interaction.rs | 3 +- .../src/domain/competition/solution/mod.rs | 101 +- .../domain/competition/solution/scoring.rs | 18 +- .../domain/competition/solution/settlement.rs | 196 +- .../domain/competition/solution/slippage.rs | 4 +- .../src/domain/competition/solution/trade.rs | 12 +- .../driver/src/domain/competition/sorting.rs | 6 +- crates/driver/src/domain/cow_amm.rs | 13 +- crates/driver/src/domain/eth/mod.rs | 459 - crates/driver/src/domain/flashloan.rs | 51 + crates/driver/src/domain/interaction.rs | 32 + .../src/domain/liquidity/balancer/v2/mod.rs | 2 +- .../domain/liquidity/balancer/v2/stable.rs | 5 +- .../domain/liquidity/balancer/v2/weighted.rs | 5 +- crates/driver/src/domain/liquidity/mod.rs | 2 +- crates/driver/src/domain/liquidity/swapr.rs | 11 +- .../driver/src/domain/liquidity/uniswap/v2.rs | 5 +- .../driver/src/domain/liquidity/uniswap/v3.rs | 5 +- crates/driver/src/domain/liquidity/zeroex.rs | 11 +- crates/driver/src/domain/mempools.rs | 76 +- crates/driver/src/domain/mod.rs | 8 +- crates/driver/src/domain/quote.rs | 33 +- crates/driver/src/infra/api/error.rs | 114 +- crates/driver/src/infra/api/extract.rs | 69 + crates/driver/src/infra/api/mod.rs | 5 +- .../src/infra/api/routes/quote/dto/order.rs | 3 +- .../src/infra/api/routes/quote/dto/quote.rs | 7 +- .../driver/src/infra/api/routes/quote/mod.rs | 7 +- .../driver/src/infra/api/routes/reveal/mod.rs | 4 +- .../driver/src/infra/api/routes/settle/mod.rs | 6 +- .../api/routes/solve/dto/solve_request.rs | 7 +- .../api/routes/solve/dto/solve_response.rs | 5 +- .../driver/src/infra/blockchain/contracts.rs | 26 +- crates/driver/src/infra/blockchain/gas.rs | 23 +- crates/driver/src/infra/blockchain/mod.rs | 120 +- crates/driver/src/infra/blockchain/token.rs | 6 +- crates/driver/src/infra/config/file/load.rs | 27 +- crates/driver/src/infra/config/file/mod.rs | 65 +- crates/driver/src/infra/config/mod.rs | 22 +- crates/driver/src/infra/liquidity/config.rs | 58 +- crates/driver/src/infra/mempool/mod.rs | 3 +- crates/driver/src/infra/mod.rs | 10 +- .../liquidity_sources/liquorice/notifier.rs | 18 +- crates/driver/src/infra/notify/mod.rs | 17 +- .../driver/src/infra/notify/notification.rs | 8 +- crates/driver/src/infra/observe/mod.rs | 32 +- crates/driver/src/infra/simulator/enso/dto.rs | 39 - crates/driver/src/infra/simulator/enso/mod.rs | 103 - .../src/infra/simulator/tenderly/dto.rs | 80 - .../src/infra/simulator/tenderly/mod.rs | 144 - crates/driver/src/infra/solver/dto/auction.rs | 24 +- .../src/infra/solver/dto/notification.rs | 5 +- .../driver/src/infra/solver/dto/solution.rs | 42 +- crates/driver/src/infra/solver/eip7702.rs | 35 +- crates/driver/src/infra/solver/mod.rs | 55 +- crates/driver/src/infra/tokens.rs | 6 +- crates/driver/src/run.rs | 62 +- crates/driver/src/tests/cases/fees.rs | 15 +- .../driver/src/tests/cases/flashloan_hints.rs | 49 + .../src/tests/cases/gas_fee_override.rs | 60 + crates/driver/src/tests/cases/haircut.rs | 3 +- crates/driver/src/tests/cases/jit_orders.rs | 32 +- crates/driver/src/tests/cases/mod.rs | 3 +- .../src/tests/cases/multiple_solutions.rs | 102 +- .../driver/src/tests/cases/protocol_fees.rs | 33 +- crates/driver/src/tests/cases/quote.rs | 3 +- crates/driver/src/tests/cases/settle.rs | 167 +- .../driver/src/tests/cases/solver_balance.rs | 6 +- crates/driver/src/tests/setup/blockchain.rs | 22 +- crates/driver/src/tests/setup/driver.rs | 16 +- crates/driver/src/tests/setup/fee.rs | 2 +- crates/driver/src/tests/setup/mod.rs | 54 +- crates/driver/src/tests/setup/solver.rs | 10 +- crates/e2e/Cargo.toml | 4 + crates/e2e/src/setup/deploy.rs | 7 +- crates/e2e/src/setup/mod.rs | 1 + .../e2e/src/setup/onchain_components/mod.rs | 4 +- .../e2e/src/setup/onchain_components/safe.rs | 2 +- crates/e2e/src/setup/services.rs | 360 +- crates/e2e/src/setup/solver/mock.rs | 57 +- crates/e2e/tests/e2e/autopilot_leader.rs | 106 +- crates/e2e/tests/e2e/banned_users.rs | 6 +- crates/e2e/tests/e2e/buffers.rs | 33 +- crates/e2e/tests/e2e/contract_revert.rs | 61 + crates/e2e/tests/e2e/cors.rs | 44 +- crates/e2e/tests/e2e/cow_amm.rs | 144 +- crates/e2e/tests/e2e/debug_order.rs | 277 + crates/e2e/tests/e2e/eip4626.rs | 293 + crates/e2e/tests/e2e/ethflow.rs | 2 +- crates/e2e/tests/e2e/hooks.rs | 6 +- crates/e2e/tests/e2e/jit_orders.rs | 34 +- crates/e2e/tests/e2e/limit_orders.rs | 195 +- crates/e2e/tests/e2e/liquidity.rs | 37 +- .../e2e/liquidity_source_notification.rs | 38 +- crates/e2e/tests/e2e/main.rs | 4 + crates/e2e/tests/e2e/malformed_requests.rs | 270 +- crates/e2e/tests/e2e/order_cancellation.rs | 33 +- crates/e2e/tests/e2e/order_simulation.rs | 425 + crates/e2e/tests/e2e/parallel_settlement.rs | 2 +- .../e2e/tests/e2e/place_order_with_quote.rs | 103 +- crates/e2e/tests/e2e/protocol_fee.rs | 85 +- crates/e2e/tests/e2e/quote_verification.rs | 353 +- crates/e2e/tests/e2e/quoting.rs | 488 +- crates/e2e/tests/e2e/refunder.rs | 2 +- crates/e2e/tests/e2e/replace_order.rs | 21 +- crates/e2e/tests/e2e/smart_contract_orders.rs | 12 +- crates/e2e/tests/e2e/solver_competition.rs | 136 +- crates/e2e/tests/e2e/submission.rs | 264 +- crates/e2e/tests/e2e/uncovered_order.rs | 118 + crates/e2e/tests/e2e/univ2.rs | 2 +- crates/e2e/tests/e2e/wrapper.rs | 4 +- crates/eth-domain-types/Cargo.toml | 14 + crates/eth-domain-types/src/access_list.rs | 94 + .../eth => eth-domain-types/src}/allowance.rs | 2 +- .../eth => eth-domain-types/src}/eip712.rs | 0 crates/eth-domain-types/src/ether.rs | 66 + .../eth => eth-domain-types/src}/gas.rs | 20 +- crates/eth-domain-types/src/lib.rs | 154 + crates/eth-domain-types/src/token_amount.rs | 125 + crates/ethrpc/src/alloy/errors.rs | 103 +- crates/event-indexing/src/event_handler.rs | 2 +- crates/gas-price-estimation/Cargo.toml | 1 + .../src/configurable_alloy.rs | 17 +- crates/gas-price-estimation/src/lib.rs | 27 +- crates/http-client/Cargo.toml | 12 + .../http_client.rs => http-client/src/lib.rs} | 33 +- .../src/balancer_v2/pool_fetching/mod.rs | 2 +- .../balancer_v2/pool_fetching/pool_storage.rs | 2 +- .../src/balancer_v2/pool_fetching/registry.rs | 2 +- .../src/balancer_v2/pools/common.rs | 4 +- .../balancer_v2/pools/composable_stable.rs | 2 +- .../pools/liquidity_bootstrapping.rs | 2 +- .../src/balancer_v2/pools/stable.rs | 2 +- .../src/balancer_v2/pools/weighted.rs | 2 +- crates/liquidity-sources/src/lib.rs | 2 +- .../src/recent_block_cache.rs | 4 +- crates/liquidity-sources/src/swapr.rs | 2 +- .../liquidity-sources/src/uniswap_v2/mod.rs | 16 +- .../src/uniswap_v2/pool_fetching.rs | 2 +- .../src/uniswap_v3/event_fetching.rs | 2 +- .../src/uniswap_v3/pool_fetching.rs | 4 +- crates/model/Cargo.toml | 1 + crates/model/src/debug_report.rs | 148 + crates/model/src/lib.rs | 1 + crates/model/src/order.rs | 8 + crates/order-validation/src/banned.rs | 2 +- crates/orderbook/Cargo.toml | 4 + crates/orderbook/openapi.yml | 534 +- crates/orderbook/src/api.rs | 111 +- crates/orderbook/src/api/debug_order.rs | 28 + crates/orderbook/src/api/debug_simulation.rs | 74 + .../src/api/get_solver_competition.rs | 46 +- .../src/api/get_solver_competition_v2.rs | 45 +- crates/orderbook/src/api/post_order.rs | 23 +- crates/orderbook/src/arguments.rs | 35 +- crates/orderbook/src/config/banned_users.rs | 28 - crates/orderbook/src/database/debug_report.rs | 238 + crates/orderbook/src/database/mod.rs | 57 +- crates/orderbook/src/database/orders.rs | 2 + .../src/database/solver_competition.rs | 37 +- .../src/database/solver_competition_v2.rs | 25 +- crates/orderbook/src/database/trades.rs | 11 +- crates/orderbook/src/dto/mod.rs | 71 +- crates/orderbook/src/lib.rs | 1 - crates/orderbook/src/orderbook.rs | 161 +- crates/orderbook/src/quoter.rs | 24 +- crates/orderbook/src/run.rs | 204 +- crates/orderbook/src/solver_competition.rs | 6 + crates/price-estimation/Cargo.toml | 5 + .../price-estimation/src/competition/mod.rs | 41 + .../price-estimation/src/competition/quote.rs | 12 - crates/price-estimation/src/config/mod.rs | 1 + .../src/config/native_price.rs | 156 +- .../src/config/price_estimation.rs | 28 + crates/price-estimation/src/factory.rs | 109 +- crates/price-estimation/src/instrumented.rs | 26 +- crates/price-estimation/src/lib.rs | 523 +- .../price-estimation/src/native/coingecko.rs | 1 - crates/price-estimation/src/native/eip4626.rs | 321 + crates/price-estimation/src/native/mod.rs | 2 + .../src/native_price_cache.rs | 113 +- crates/price-estimation/src/sanitized.rs | 12 +- .../src/trade_finding/external.rs | 183 +- .../price-estimation/src/trade_finding/mod.rs | 156 +- .../src/trade_finding/trade_estimator.rs | 76 + .../src/trade_verifier/mod.rs | 940 +- .../src/trade_verifier/tenderly_api.rs | 463 - crates/price-estimation/src/utils/encoding.rs | 88 - crates/price-estimation/src/utils/mod.rs | 1 - crates/rate-limit/Cargo.toml | 10 +- crates/rate-limit/src/lib.rs | 112 +- crates/refunder/Cargo.toml | 1 + crates/refunder/src/arguments.rs | 31 +- crates/refunder/src/infra/chain.rs | 2 +- crates/refunder/src/infra/database.rs | 2 +- crates/refunder/src/refund_service.rs | 2 +- crates/refunder/src/submitter.rs | 2 +- crates/refunder/src/traits.rs | 2 +- crates/request-sharing/src/lib.rs | 60 +- crates/s3/Cargo.toml | 2 +- crates/shared/Cargo.toml | 1 + crates/shared/src/arguments.rs | 431 +- crates/shared/src/current_block.rs | 9 + crates/shared/src/encoded_settlement.rs | 218 - crates/shared/src/fee.rs | 3 +- crates/shared/src/lib.rs | 3 - crates/shared/src/order_quoting.rs | 125 +- crates/shared/src/order_validation.rs | 261 +- crates/shared/src/web3.rs | 10 + crates/signature-validator/src/lib.rs | 8 +- crates/signature-validator/src/simulation.rs | 10 +- crates/simulator/Cargo.toml | 53 + crates/simulator/src/encoding.rs | 712 ++ crates/simulator/src/ethereum/contracts.rs | 33 + crates/simulator/src/ethereum/mod.rs | 159 + .../simulator/mod.rs => simulator/src/lib.rs} | 108 +- crates/simulator/src/simulation_builder.rs | 578 + crates/simulator/src/tenderly/dto.rs | 205 + crates/simulator/src/tenderly/mod.rs | 351 + crates/simulator/src/utils/mod.rs | 14 + crates/solver/Cargo.toml | 1 + crates/solver/src/interactions/balancer_v2.rs | 5 +- crates/solver/src/interactions/erc20.rs | 2 +- crates/solver/src/interactions/uniswap_v2.rs | 2 +- crates/solver/src/interactions/uniswap_v3.rs | 2 +- crates/solver/src/interactions/weth.rs | 2 +- crates/solver/src/interactions/zeroex.rs | 2 +- crates/solver/src/liquidity/balancer_v2.rs | 5 +- crates/solver/src/liquidity/uniswap_v3.rs | 2 +- crates/solver/src/liquidity/zeroex.rs | 5 +- crates/solver/src/settlement/mod.rs | 6 +- .../src/settlement/settlement_encoder.rs | 33 +- crates/solvers-dto/Cargo.toml | 1 + crates/solvers-dto/src/notification.rs | 1 + crates/solvers-dto/src/solution.rs | 113 +- crates/solvers/Cargo.toml | 11 + crates/solvers/config/example.bitget.toml | 19 + crates/solvers/config/example.okx.toml | 51 + crates/solvers/openapi.yml | 18 + .../solvers/src/api/routes/solve/dto/mod.rs | 2 +- .../src/api/routes/solve/dto/solution.rs | 5 +- crates/solvers/src/api/routes/solve/mod.rs | 2 +- crates/solvers/src/boundary/baseline.rs | 4 +- .../src/boundary/liquidity/concentrated.rs | 4 +- .../solvers/src/domain/dex/minimum_surplus.rs | 141 + crates/solvers/src/domain/dex/mod.rs | 204 + crates/solvers/src/domain/dex/shared.rs | 54 + crates/solvers/src/domain/dex/slippage.rs | 268 + crates/solvers/src/domain/eth/chain.rs | 197 + crates/solvers/src/domain/eth/mod.rs | 19 +- crates/solvers/src/domain/mod.rs | 1 + crates/solvers/src/domain/order.rs | 17 +- crates/solvers/src/domain/solution.rs | 103 +- .../domain/{solver.rs => solver/baseline.rs} | 4 +- crates/solvers/src/domain/solver/dex/fills.rs | 180 + crates/solvers/src/domain/solver/dex/mod.rs | 231 + crates/solvers/src/domain/solver/mod.rs | 22 + crates/solvers/src/infra/blockchain.rs | 4 + crates/solvers/src/infra/cli.rs | 10 + .../infra/{config.rs => config/baseline.rs} | 22 +- .../src/infra/config/dex/bitget/file.rs | 85 + .../src/infra/config/dex/bitget/mod.rs | 6 + crates/solvers/src/infra/config/dex/file.rs | 206 + crates/solvers/src/infra/config/dex/mod.rs | 33 + .../solvers/src/infra/config/dex/okx/file.rs | 115 + .../solvers/src/infra/config/dex/okx/mod.rs | 6 + crates/solvers/src/infra/config/mod.rs | 22 + crates/solvers/src/infra/contracts.rs | 30 +- crates/solvers/src/infra/dex/bitget/dto.rs | 271 + crates/solvers/src/infra/dex/bitget/mod.rs | 468 + crates/solvers/src/infra/dex/mod.rs | 121 + crates/solvers/src/infra/dex/okx/dto.rs | 312 + crates/solvers/src/infra/dex/okx/mod.rs | 516 + crates/solvers/src/infra/dex/simulator.rs | 120 + crates/solvers/src/infra/metrics.rs | 11 + crates/solvers/src/infra/mod.rs | 2 + crates/solvers/src/run.rs | 27 +- crates/solvers/src/tests/bitget/api_calls.rs | 282 + .../solvers/src/tests/bitget/market_order.rs | 382 + crates/solvers/src/tests/bitget/mod.rs | 38 + crates/solvers/src/tests/bitget/not_found.rs | 80 + .../solvers/src/tests/bitget/out_of_price.rs | 96 + crates/solvers/src/tests/mock/http.rs | 278 + crates/solvers/src/tests/mock/mod.rs | 1 + crates/solvers/src/tests/mod.rs | 3 + crates/solvers/src/tests/okx/api_calls.rs | 262 + crates/solvers/src/tests/okx/market_order.rs | 1067 ++ crates/solvers/src/tests/okx/mod.rs | 22 + crates/solvers/src/tests/okx/not_found.rs | 287 + crates/solvers/src/tests/okx/out_of_price.rs | 205 + crates/solvers/src/util/bytes.rs | 12 - crates/solvers/src/util/conv.rs | 40 +- crates/solvers/src/util/fmt/hex.rs | 15 - crates/solvers/src/util/fmt/mod.rs | 3 - crates/solvers/src/util/http.rs | 152 + crates/solvers/src/util/mod.rs | 3 +- crates/solvers/src/util/url.rs | 0 crates/testlib/src/tokens.rs | 9 + crates/token-info/src/lib.rs | 2 +- crates/winner-selection/src/arbitrator.rs | 18 +- crates/winner-selection/src/solution.rs | 22 +- database/README.md | 24 +- ...proposed_trade_executions_by_order_uid.sql | 2 + database/sql/V106__create_auction_orders.sql | 8 + .../sql/V107__add_reason_to_order_events.sql | 11 + .../V108__trade_owner_expression_index.sql | 13 + ..._order_quotes_creation_timestamp_index.sql | 3 + docs/COW_ORDER_DEBUG_SKILL.md | 95 +- docs/ONBOARDING.md | 205 + docs/QUOTE_VERIFICATION_DEBUG_SKILL.md | 464 + playground/autopilot.toml | 30 +- playground/baseline.toml | 14 - playground/configs/autopilot.toml | 41 + .../configs}/baseline.toml | 0 .../local => playground/configs}/driver.toml | 0 playground/configs/orderbook.toml | 19 + playground/docker-compose.fork.yml | 27 +- playground/docker-compose.non-interactive.yml | 27 +- playground/driver.toml | 29 - playground/tempo.yaml | 12 +- playground/test_playground.sh | 55 +- 745 files changed, 239652 insertions(+), 23229 deletions(-) mode change 100644 => 100755 LICENSE-APACHE mode change 100644 => 100755 LICENSE-GPL mode change 100644 => 100755 LICENSE-MIT create mode 100644 REVIEW.md create mode 100644 contracts/.gitignore create mode 100644 contracts/Cargo.lock create mode 100644 contracts/Cargo.toml rename {crates/contracts => contracts}/LICENSE-APACHE (100%) rename {crates/contracts => contracts}/LICENSE-MIT (100%) create mode 100644 contracts/README.md rename {crates/contracts => contracts}/artifacts/AnyoneAuthenticator.json (100%) rename {crates/contracts => contracts}/artifacts/BalancerQueries.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2Authorizer.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2BasePool.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2BasePoolFactory.json (96%) rename {crates/contracts => contracts}/artifacts/BalancerV2ComposableStablePool.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2ComposableStablePoolFactory.json (99%) create mode 100644 contracts/artifacts/BalancerV2ComposableStablePoolFactoryV3.json create mode 100644 contracts/artifacts/BalancerV2ComposableStablePoolFactoryV4.json create mode 100644 contracts/artifacts/BalancerV2ComposableStablePoolFactoryV5.json create mode 100644 contracts/artifacts/BalancerV2ComposableStablePoolFactoryV6.json rename {crates/contracts => contracts}/artifacts/BalancerV2LiquidityBootstrappingPool.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2LiquidityBootstrappingPoolFactory.json (99%) create mode 100644 contracts/artifacts/BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory.json rename {crates/contracts => contracts}/artifacts/BalancerV2StablePool.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2StablePoolFactoryV2.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2Vault.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2WeightedPool.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2WeightedPool2TokensFactory.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2WeightedPoolFactory.json (99%) rename {crates/contracts => contracts}/artifacts/BalancerV2WeightedPoolFactoryV3.json (99%) create mode 100644 contracts/artifacts/BalancerV2WeightedPoolFactoryV4.json rename {crates/contracts => contracts}/artifacts/BalancerV3BatchRouter.json (99%) create mode 100644 contracts/artifacts/Balances.json create mode 100644 contracts/artifacts/BaoswapRouter.json rename {crates/contracts => contracts}/artifacts/ChainalysisOracle.json (99%) rename {crates/contracts => contracts}/artifacts/CoWSwapEthFlow.json (99%) rename {crates/contracts => contracts}/artifacts/CoWSwapOnchainOrders.json (100%) rename {crates/contracts => contracts}/artifacts/Counter.json (100%) rename {crates/contracts => contracts}/artifacts/CowAmm.json (99%) rename {crates/contracts => contracts}/artifacts/CowAmmConstantProductFactory.json (99%) rename {crates/contracts => contracts}/artifacts/CowAmmFactoryGetter.json (100%) rename {crates/contracts => contracts}/artifacts/CowAmmLegacyHelper.json (99%) rename {crates/contracts => contracts}/artifacts/CowAmmUniswapV2PriceOracle.json (99%) rename {crates/contracts => contracts}/artifacts/CowProtocolToken.json (99%) rename {crates/contracts => contracts}/artifacts/CowSettlementForwarder.json (100%) rename {crates/contracts => contracts}/artifacts/ERC1271SignatureValidator.json (100%) rename {crates/contracts => contracts}/artifacts/ERC20.json (99%) rename {crates/contracts => contracts}/artifacts/ERC20Mintable.json (99%) rename {crates/contracts => contracts}/artifacts/FlashLoanRouter.json (99%) rename {crates/contracts => contracts}/artifacts/GPv2AllowListAuthentication.json (99%) rename {crates/contracts => contracts}/artifacts/GPv2Settlement.json (99%) rename {crates/contracts => contracts}/artifacts/GasHog.json (100%) rename {crates/contracts => contracts}/artifacts/GnosisSafe.json (99%) rename {crates/contracts => contracts}/artifacts/GnosisSafeCompatibilityFallbackHandler.json (99%) rename {crates/contracts => contracts}/artifacts/GnosisSafeProxy.json (100%) rename {crates/contracts => contracts}/artifacts/GnosisSafeProxyFactory.json (99%) create mode 100644 contracts/artifacts/HoneyswapRouter.json rename {crates/contracts => contracts}/artifacts/HooksTrampoline.json (100%) rename {crates/contracts => contracts}/artifacts/ICowWrapper.json (96%) create mode 100644 contracts/artifacts/IERC4626.json rename {crates/contracts => contracts}/artifacts/ISwaprPair.json (99%) rename {crates/contracts => contracts}/artifacts/IUniswapLikePair.json (99%) rename {crates/contracts => contracts}/artifacts/IUniswapLikeRouter.json (99%) rename {crates/contracts => contracts}/artifacts/IUniswapV3Factory.json (99%) rename {crates/contracts => contracts}/artifacts/IZeroex.json (99%) create mode 100644 contracts/artifacts/LiquoriceSettlement.json create mode 100644 contracts/artifacts/MockERC4626Wrapper.json rename {crates/contracts => contracts}/artifacts/NonStandardERC20Balances.json (100%) create mode 100644 contracts/artifacts/PancakeRouter.json rename {crates/contracts => contracts}/artifacts/Permit2.json (99%) rename {crates/contracts => contracts}/artifacts/RemoteERC20Balances.json (100%) rename {crates/contracts => contracts}/artifacts/Signatures.json (100%) rename {crates/contracts => contracts}/artifacts/Solver.json (100%) rename {crates/contracts => contracts}/artifacts/Spardose.json (100%) create mode 100644 contracts/artifacts/SushiSwapRouter.json rename {crates/contracts => contracts}/artifacts/Swapper.json (100%) create mode 100644 contracts/artifacts/SwaprRouter.json create mode 100644 contracts/artifacts/TestnetUniswapV2Router02.json rename {crates/contracts => contracts}/artifacts/Trader.json (100%) rename {crates/contracts => contracts}/artifacts/UniswapV2Factory.json (99%) rename {crates/contracts => contracts}/artifacts/UniswapV2Router02.json (99%) rename {crates/contracts => contracts}/artifacts/UniswapV3Pool.json (99%) rename {crates/contracts => contracts}/artifacts/UniswapV3QuoterV2.json (99%) rename {crates/contracts => contracts}/artifacts/UniswapV3SwapRouterV2.json (100%) rename {crates/contracts => contracts}/artifacts/WETH9.json (99%) rename {crates/contracts => contracts}/foundry.toml (100%) create mode 100644 contracts/generated/.gitignore create mode 100644 contracts/generated/Cargo.lock create mode 100644 contracts/generated/Cargo.toml create mode 100644 contracts/generated/contracts-facade/Cargo.toml create mode 100644 contracts/generated/contracts-facade/src/lib.rs create mode 100644 contracts/generated/contracts-generated/anyoneauthenticator/Cargo.toml create mode 100644 contracts/generated/contracts-generated/anyoneauthenticator/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerqueries/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerqueries/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2authorizer/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2authorizer/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2basepool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2basepool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2basepoolfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2basepoolfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv3/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv3/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv4/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv4/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv5/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv5/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv6/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2composablestablepoolfactoryv6/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2liquiditybootstrappingpool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2liquiditybootstrappingpool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2liquiditybootstrappingpoolfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2liquiditybootstrappingpoolfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2noprotocolfeeliquiditybootstrappingpoolfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2noprotocolfeeliquiditybootstrappingpoolfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2stablepool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2stablepool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2stablepoolfactoryv2/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2stablepoolfactoryv2/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2vault/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2vault/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpool2tokensfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpool2tokensfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactoryv3/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactoryv3/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactoryv4/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv2weightedpoolfactoryv4/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balancerv3batchrouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balancerv3batchrouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/balances/Cargo.toml create mode 100644 contracts/generated/contracts-generated/balances/src/lib.rs create mode 100644 contracts/generated/contracts-generated/baoswaprouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/baoswaprouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/chainalysisoracle/Cargo.toml create mode 100644 contracts/generated/contracts-generated/chainalysisoracle/src/lib.rs create mode 100644 contracts/generated/contracts-generated/counter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/counter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowamm/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowamm/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowammconstantproductfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowammconstantproductfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowammfactorygetter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowammfactorygetter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowammlegacyhelper/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowammlegacyhelper/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowammuniswapv2priceoracle/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowammuniswapv2priceoracle/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowprotocoltoken/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowprotocoltoken/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowsettlementforwarder/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowsettlementforwarder/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowswapethflow/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowswapethflow/src/lib.rs create mode 100644 contracts/generated/contracts-generated/cowswaponchainorders/Cargo.toml create mode 100644 contracts/generated/contracts-generated/cowswaponchainorders/src/lib.rs create mode 100644 contracts/generated/contracts-generated/erc1271signaturevalidator/Cargo.toml create mode 100644 contracts/generated/contracts-generated/erc1271signaturevalidator/src/lib.rs create mode 100644 contracts/generated/contracts-generated/erc20/Cargo.toml create mode 100644 contracts/generated/contracts-generated/erc20/src/lib.rs create mode 100644 contracts/generated/contracts-generated/erc20mintable/Cargo.toml create mode 100644 contracts/generated/contracts-generated/erc20mintable/src/lib.rs create mode 100644 contracts/generated/contracts-generated/flashloanrouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/flashloanrouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gashog/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gashog/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gnosissafe/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gnosissafe/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gnosissafecompatibilityfallbackhandler/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gnosissafecompatibilityfallbackhandler/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gnosissafeproxy/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gnosissafeproxy/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gnosissafeproxyfactory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gnosissafeproxyfactory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gpv2allowlistauthentication/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gpv2allowlistauthentication/src/lib.rs create mode 100644 contracts/generated/contracts-generated/gpv2settlement/Cargo.toml create mode 100644 contracts/generated/contracts-generated/gpv2settlement/src/lib.rs create mode 100644 contracts/generated/contracts-generated/honeyswaprouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/honeyswaprouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/hookstrampoline/Cargo.toml create mode 100644 contracts/generated/contracts-generated/hookstrampoline/src/lib.rs create mode 100644 contracts/generated/contracts-generated/icowwrapper/Cargo.toml create mode 100644 contracts/generated/contracts-generated/icowwrapper/src/lib.rs create mode 100644 contracts/generated/contracts-generated/ierc4626/Cargo.toml create mode 100644 contracts/generated/contracts-generated/ierc4626/src/lib.rs create mode 100644 contracts/generated/contracts-generated/iswaprpair/Cargo.toml create mode 100644 contracts/generated/contracts-generated/iswaprpair/src/lib.rs create mode 100644 contracts/generated/contracts-generated/iuniswaplikepair/Cargo.toml create mode 100644 contracts/generated/contracts-generated/iuniswaplikepair/src/lib.rs create mode 100644 contracts/generated/contracts-generated/iuniswaplikerouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/iuniswaplikerouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/iuniswapv3factory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/iuniswapv3factory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/izeroex/Cargo.toml create mode 100644 contracts/generated/contracts-generated/izeroex/src/lib.rs create mode 100644 contracts/generated/contracts-generated/liquoricesettlement/Cargo.toml create mode 100644 contracts/generated/contracts-generated/liquoricesettlement/src/lib.rs create mode 100644 contracts/generated/contracts-generated/mockerc4626wrapper/Cargo.toml create mode 100644 contracts/generated/contracts-generated/mockerc4626wrapper/src/lib.rs create mode 100644 contracts/generated/contracts-generated/nonstandarderc20balances/Cargo.toml create mode 100644 contracts/generated/contracts-generated/nonstandarderc20balances/src/lib.rs create mode 100644 contracts/generated/contracts-generated/pancakerouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/pancakerouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/permit2/Cargo.toml create mode 100644 contracts/generated/contracts-generated/permit2/src/lib.rs create mode 100644 contracts/generated/contracts-generated/remoteerc20balances/Cargo.toml create mode 100644 contracts/generated/contracts-generated/remoteerc20balances/src/lib.rs create mode 100644 contracts/generated/contracts-generated/signatures/Cargo.toml create mode 100644 contracts/generated/contracts-generated/signatures/src/lib.rs create mode 100644 contracts/generated/contracts-generated/solver/Cargo.toml create mode 100644 contracts/generated/contracts-generated/solver/src/lib.rs create mode 100644 contracts/generated/contracts-generated/spardose/Cargo.toml create mode 100644 contracts/generated/contracts-generated/spardose/src/lib.rs create mode 100644 contracts/generated/contracts-generated/sushiswaprouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/sushiswaprouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/swapper/Cargo.toml create mode 100644 contracts/generated/contracts-generated/swapper/src/lib.rs create mode 100644 contracts/generated/contracts-generated/swaprrouter/Cargo.toml create mode 100644 contracts/generated/contracts-generated/swaprrouter/src/lib.rs create mode 100644 contracts/generated/contracts-generated/testnetuniswapv2router02/Cargo.toml create mode 100644 contracts/generated/contracts-generated/testnetuniswapv2router02/src/lib.rs create mode 100644 contracts/generated/contracts-generated/trader/Cargo.toml create mode 100644 contracts/generated/contracts-generated/trader/src/lib.rs create mode 100644 contracts/generated/contracts-generated/uniswapv2factory/Cargo.toml create mode 100644 contracts/generated/contracts-generated/uniswapv2factory/src/lib.rs create mode 100644 contracts/generated/contracts-generated/uniswapv2router02/Cargo.toml create mode 100644 contracts/generated/contracts-generated/uniswapv2router02/src/lib.rs create mode 100644 contracts/generated/contracts-generated/uniswapv3pool/Cargo.toml create mode 100644 contracts/generated/contracts-generated/uniswapv3pool/src/lib.rs create mode 100644 contracts/generated/contracts-generated/uniswapv3quoterv2/Cargo.toml create mode 100644 contracts/generated/contracts-generated/uniswapv3quoterv2/src/lib.rs create mode 100644 contracts/generated/contracts-generated/uniswapv3swaprouterv2/Cargo.toml create mode 100644 contracts/generated/contracts-generated/uniswapv3swaprouterv2/src/lib.rs create mode 100644 contracts/generated/contracts-generated/weth9/Cargo.toml create mode 100644 contracts/generated/contracts-generated/weth9/src/lib.rs create mode 100644 contracts/generated/rustfmt.toml rename {crates/contracts => contracts}/script/DeployBalances.s.sol (100%) rename {crates/contracts => contracts}/script/DeploySignatures.s.sol (100%) rename {crates/contracts => contracts}/solidity/AnyoneAuthenticator.sol (100%) rename {crates/contracts => contracts}/solidity/Balances.sol (95%) rename {crates/contracts => contracts}/solidity/CowSettlementForwarder.sol (100%) rename {crates/contracts => contracts}/solidity/Makefile (97%) rename {crates/contracts => contracts}/solidity/README.md (100%) rename {crates/contracts => contracts}/solidity/Signatures.sol (100%) rename {crates/contracts => contracts}/solidity/Solver.sol (100%) rename {crates/contracts => contracts}/solidity/Spardose.sol (100%) rename {crates/contracts => contracts}/solidity/Swapper.sol (100%) rename {crates/contracts => contracts}/solidity/Trader.sol (100%) rename {crates/contracts => contracts}/solidity/interfaces/IERC1271.sol (100%) rename {crates/contracts => contracts}/solidity/interfaces/IERC20.sol (100%) create mode 100644 contracts/solidity/interfaces/IERC4626.sol rename {crates/contracts => contracts}/solidity/interfaces/ISettlement.sol (100%) rename {crates/contracts => contracts}/solidity/interfaces/IStorageAccessible.sol (100%) rename {crates/contracts => contracts}/solidity/interfaces/IVault.sol (100%) rename {crates/contracts => contracts}/solidity/interfaces/IVaultRelayer.sol (100%) rename {crates/contracts => contracts}/solidity/libraries/Caller.sol (100%) rename {crates/contracts => contracts}/solidity/libraries/Math.sol (100%) rename {crates/contracts => contracts}/solidity/libraries/SafeERC20.sol (100%) rename {crates/contracts => contracts}/solidity/tests/Counter.sol (100%) rename {crates/contracts => contracts}/solidity/tests/GasHog.sol (100%) create mode 100644 contracts/solidity/tests/MockERC4626Wrapper.sol rename {crates/contracts => contracts}/solidity/tests/NonStandardERC20Balances.sol (100%) rename {crates/contracts => contracts}/solidity/tests/RemoteERC20Balances.sol (100%) create mode 100644 contracts/src/codegen.rs create mode 100644 contracts/src/main.rs create mode 100644 contracts/src/networks.rs rename {crates/contracts/src/bin => contracts/src}/vendor.rs (87%) create mode 100644 crates/autopilot/src/domain/blockchain.rs delete mode 100644 crates/autopilot/src/domain/eth/mod.rs create mode 100644 crates/balance-overrides/src/aave.rs create mode 100644 crates/configs/src/autopilot/cow_amm.rs create mode 100644 crates/configs/src/autopilot/ethflow.rs rename crates/{autopilot/src/config => configs/src/autopilot}/fee_policy.rs (93%) rename crates/{autopilot/src/config => configs/src/autopilot}/mod.rs (61%) rename crates/{autopilot/src/config => configs/src/autopilot}/native_price.rs (81%) rename crates/{autopilot/src/config => configs/src/autopilot}/order_events_cleanup.rs (96%) create mode 100644 crates/configs/src/autopilot/run_loop.rs rename crates/{autopilot/src/config => configs/src/autopilot}/s3.rs (96%) rename crates/{autopilot/src/config => configs/src/autopilot}/solver.rs (89%) rename crates/{autopilot/src/config => configs/src/autopilot}/trusted_tokens.rs (96%) create mode 100644 crates/configs/src/balance_overrides.rs rename crates/{autopilot/src/config => configs/src}/banned_users.rs (88%) create mode 100644 crates/configs/src/fee_factor.rs create mode 100644 crates/configs/src/gas_price_estimation.rs create mode 100644 crates/configs/src/http_client.rs create mode 100644 crates/configs/src/native_price.rs create mode 100644 crates/configs/src/native_price_estimators.rs create mode 100644 crates/configs/src/order_quoting.rs rename crates/{orderbook/src/config => configs/src/orderbook}/ipfs.rs (78%) rename crates/{orderbook/src/config => configs/src/orderbook}/mod.rs (67%) rename crates/{orderbook/src/config => configs/src/orderbook}/native_price.rs (82%) rename crates/{orderbook/src/config => configs/src/orderbook}/order_validation.rs (89%) create mode 100644 crates/configs/src/price_estimation.rs create mode 100644 crates/configs/src/rate_limit.rs create mode 100644 crates/configs/src/shared.rs create mode 100644 crates/configs/src/simulator.rs delete mode 100644 crates/contracts/.gitignore delete mode 100644 crates/contracts/Cargo.toml delete mode 100644 crates/contracts/README.md delete mode 120000 crates/contracts/artifacts/BalancerV2ComposableStablePoolFactoryV3.json delete mode 120000 crates/contracts/artifacts/BalancerV2ComposableStablePoolFactoryV4.json delete mode 120000 crates/contracts/artifacts/BalancerV2ComposableStablePoolFactoryV5.json delete mode 120000 crates/contracts/artifacts/BalancerV2ComposableStablePoolFactoryV6.json delete mode 120000 crates/contracts/artifacts/BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory.json delete mode 120000 crates/contracts/artifacts/BalancerV2WeightedPoolFactoryV4.json delete mode 100644 crates/contracts/artifacts/Balances.json delete mode 120000 crates/contracts/artifacts/BaoswapRouter.json delete mode 120000 crates/contracts/artifacts/HoneyswapRouter.json delete mode 100644 crates/contracts/artifacts/LiquoriceSettlement.json delete mode 120000 crates/contracts/artifacts/PancakeRouter.json delete mode 120000 crates/contracts/artifacts/SushiSwapRouter.json delete mode 120000 crates/contracts/artifacts/SwaprRouter.json delete mode 120000 crates/contracts/artifacts/TestnetUniswapV2Router02.json delete mode 100644 crates/contracts/build.rs delete mode 100644 crates/contracts/rustfmt.toml delete mode 100644 crates/contracts/src/lib.rs create mode 100644 crates/driver/src/domain/blockchain.rs delete mode 100644 crates/driver/src/domain/eth/mod.rs create mode 100644 crates/driver/src/domain/flashloan.rs create mode 100644 crates/driver/src/domain/interaction.rs create mode 100644 crates/driver/src/infra/api/extract.rs delete mode 100644 crates/driver/src/infra/simulator/enso/dto.rs delete mode 100644 crates/driver/src/infra/simulator/enso/mod.rs delete mode 100644 crates/driver/src/infra/simulator/tenderly/dto.rs delete mode 100644 crates/driver/src/infra/simulator/tenderly/mod.rs create mode 100644 crates/driver/src/tests/cases/gas_fee_override.rs create mode 100644 crates/e2e/tests/e2e/contract_revert.rs create mode 100644 crates/e2e/tests/e2e/debug_order.rs create mode 100644 crates/e2e/tests/e2e/eip4626.rs create mode 100644 crates/e2e/tests/e2e/order_simulation.rs create mode 100644 crates/eth-domain-types/Cargo.toml create mode 100644 crates/eth-domain-types/src/access_list.rs rename crates/{driver/src/domain/eth => eth-domain-types/src}/allowance.rs (98%) rename crates/{driver/src/domain/eth => eth-domain-types/src}/eip712.rs (100%) create mode 100644 crates/eth-domain-types/src/ether.rs rename crates/{driver/src/domain/eth => eth-domain-types/src}/gas.rs (90%) create mode 100644 crates/eth-domain-types/src/lib.rs create mode 100644 crates/eth-domain-types/src/token_amount.rs create mode 100644 crates/http-client/Cargo.toml rename crates/{shared/src/http_client.rs => http-client/src/lib.rs} (65%) create mode 100644 crates/model/src/debug_report.rs create mode 100644 crates/orderbook/src/api/debug_order.rs create mode 100644 crates/orderbook/src/api/debug_simulation.rs delete mode 100644 crates/orderbook/src/config/banned_users.rs create mode 100644 crates/orderbook/src/database/debug_report.rs create mode 100644 crates/price-estimation/src/config/price_estimation.rs create mode 100644 crates/price-estimation/src/native/eip4626.rs delete mode 100644 crates/price-estimation/src/trade_verifier/tenderly_api.rs delete mode 100644 crates/shared/src/encoded_settlement.rs create mode 100644 crates/simulator/Cargo.toml create mode 100644 crates/simulator/src/encoding.rs create mode 100644 crates/simulator/src/ethereum/contracts.rs create mode 100644 crates/simulator/src/ethereum/mod.rs rename crates/{driver/src/infra/simulator/mod.rs => simulator/src/lib.rs} (61%) create mode 100644 crates/simulator/src/simulation_builder.rs create mode 100644 crates/simulator/src/tenderly/dto.rs create mode 100644 crates/simulator/src/tenderly/mod.rs create mode 100644 crates/simulator/src/utils/mod.rs create mode 100644 crates/solvers/config/example.bitget.toml create mode 100644 crates/solvers/config/example.okx.toml create mode 100644 crates/solvers/src/domain/dex/minimum_surplus.rs create mode 100644 crates/solvers/src/domain/dex/mod.rs create mode 100644 crates/solvers/src/domain/dex/shared.rs create mode 100644 crates/solvers/src/domain/dex/slippage.rs create mode 100644 crates/solvers/src/domain/eth/chain.rs rename crates/solvers/src/domain/{solver.rs => solver/baseline.rs} (98%) create mode 100644 crates/solvers/src/domain/solver/dex/fills.rs create mode 100644 crates/solvers/src/domain/solver/dex/mod.rs create mode 100644 crates/solvers/src/domain/solver/mod.rs create mode 100644 crates/solvers/src/infra/blockchain.rs rename crates/solvers/src/infra/{config.rs => config/baseline.rs} (83%) create mode 100644 crates/solvers/src/infra/config/dex/bitget/file.rs create mode 100644 crates/solvers/src/infra/config/dex/bitget/mod.rs create mode 100644 crates/solvers/src/infra/config/dex/file.rs create mode 100644 crates/solvers/src/infra/config/dex/mod.rs create mode 100644 crates/solvers/src/infra/config/dex/okx/file.rs create mode 100644 crates/solvers/src/infra/config/dex/okx/mod.rs create mode 100644 crates/solvers/src/infra/config/mod.rs create mode 100644 crates/solvers/src/infra/dex/bitget/dto.rs create mode 100644 crates/solvers/src/infra/dex/bitget/mod.rs create mode 100644 crates/solvers/src/infra/dex/mod.rs create mode 100644 crates/solvers/src/infra/dex/okx/dto.rs create mode 100644 crates/solvers/src/infra/dex/okx/mod.rs create mode 100644 crates/solvers/src/infra/dex/simulator.rs create mode 100644 crates/solvers/src/tests/bitget/api_calls.rs create mode 100644 crates/solvers/src/tests/bitget/market_order.rs create mode 100644 crates/solvers/src/tests/bitget/mod.rs create mode 100644 crates/solvers/src/tests/bitget/not_found.rs create mode 100644 crates/solvers/src/tests/bitget/out_of_price.rs create mode 100644 crates/solvers/src/tests/mock/http.rs create mode 100644 crates/solvers/src/tests/mock/mod.rs create mode 100644 crates/solvers/src/tests/okx/api_calls.rs create mode 100644 crates/solvers/src/tests/okx/market_order.rs create mode 100644 crates/solvers/src/tests/okx/mod.rs create mode 100644 crates/solvers/src/tests/okx/not_found.rs create mode 100644 crates/solvers/src/tests/okx/out_of_price.rs delete mode 100644 crates/solvers/src/util/bytes.rs delete mode 100644 crates/solvers/src/util/fmt/hex.rs delete mode 100644 crates/solvers/src/util/fmt/mod.rs create mode 100644 crates/solvers/src/util/http.rs delete mode 100644 crates/solvers/src/util/url.rs create mode 100644 database/sql/V105__index_proposed_trade_executions_by_order_uid.sql create mode 100644 database/sql/V106__create_auction_orders.sql create mode 100644 database/sql/V107__add_reason_to_order_events.sql create mode 100644 database/sql/V108__trade_owner_expression_index.sql create mode 100644 database/sql/V109__order_quotes_creation_timestamp_index.sql create mode 100644 docs/ONBOARDING.md create mode 100644 docs/QUOTE_VERIFICATION_DEBUG_SKILL.md delete mode 100644 playground/baseline.toml create mode 100644 playground/configs/autopilot.toml rename {configs/local => playground/configs}/baseline.toml (100%) rename {configs/local => playground/configs}/driver.toml (100%) create mode 100644 playground/configs/orderbook.toml delete mode 100644 playground/driver.toml diff --git a/.cargo/audit.toml b/.cargo/audit.toml index 76d2d8df83..8690df36b1 100644 --- a/.cargo/audit.toml +++ b/.cargo/audit.toml @@ -5,14 +5,6 @@ [advisories] # Known vulnerabilities that are tracked in https://github.com/cowprotocol/services/issues/3338 ignore = [ - # idna - Punycode label vulnerability (RUSTSEC-2024-0421) - # Needs upgrade to 1.0.0+ (transitive dependency) - "RUSTSEC-2024-0421", - - # protobuf - Uncontrolled recursion (RUSTSEC-2024-0437) - # Needs upgrade to 3.7.2+ (transitive dependency) - "RUSTSEC-2024-0437", - # rsa - Marvin Attack timing sidechannel (RUSTSEC-2023-0071) # No patch available yet (cryptography vulnerability) "RUSTSEC-2023-0071", diff --git a/.claude/commands/debug-order.md b/.claude/commands/debug-order.md index 92708aadc9..359648cf0a 100644 --- a/.claude/commands/debug-order.md +++ b/.claude/commands/debug-order.md @@ -8,11 +8,21 @@ Read and follow the instructions in ./docs/COW_ORDER_DEBUG_SKILL.md to investiga Key steps: 1. Parse the order UID and network from arguments (default: mainnet) -2. Fetch order data from API to get status and details -3. Check order_events in DB for lifecycle events -4. Search Victoria Logs for the order UID +2. **Start with the debug endpoint** — fetch the comprehensive debug report first: + ```bash + source .env.claude && curl -s -H "X-API-Key: $COW_DEBUG_API_KEY" "https://partners.cow.fi/$NETWORK/restricted/api/v1/debug/order/$ORDER_UID" | jq . + ``` + This returns order details, lifecycle events, auction participation, proposed solutions, executions, trades, and settlement attempts — all in one call. +3. Analyze the debug report — key event meanings: + - `ready` = order made it into an auction (was sent to solvers) + - `considered` = a solver included this order in a solution but that solution didn't win + - `executing` = order is in the winning solution, being submitted on-chain + - `traded` = order was settled on-chain + - `filtered` / `invalid` = order was excluded (check the `reason` field) +4. Search Victoria Logs for additional context (filter reasons, error details, solver logs) - For finding discarded solutions where the order UID appears in calldata, use regex: `.*ORDER_UID_WITHOUT_0X.*` plus `discarded` -5. Identify root cause and report findings with evidence -6. If you haven't found anything go wild and try all SQL / log searches / codebase searches you can think of +5. Use DB queries or API calls only if the debug report is missing info or you need deeper investigation +6. Identify root cause and report findings with evidence +7. If you haven't found anything go wild and try all SQL / log searches / codebase searches you can think of Always show your evidence (log lines, DB results, API responses) when presenting findings. diff --git a/CLAUDE.md b/CLAUDE.md index a03abd0c06..45269b7fd2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,7 @@ User signs order → Orderbook validates → Autopilot includes in auction - Spot format: `cargo +nightly fmt -- ` (never call stable `cargo fmt`) - Lint: `cargo clippy --locked --workspace --all-features --all-targets -- -D warnings` - Check format: `cargo +nightly fmt --all -- --check` +- Only format **after** as a final step! — i.e. after checking compilation, running tests, etc. ### Local Development Environment - Start local PostgreSQL: `docker compose up -d` @@ -181,26 +182,55 @@ Databases: `mainnet`, `arbitrum-one`, `base`, `linea`, `polygon`, `xdai`, `sepol Use `$ETH_MAINNET_RPC` from `.env.claude` for mainnet. Use `cast` or whatever tools you want freely. -## Grafana Logs Access +## Victoria Logs Access -Use the `scripts/vlogs` wrapper to query Victoria Logs. +Use the `CoW-Prod` MCP tools to query Victoria Logs directly. -**IMPORTANT**: When running `scripts/vlogs`, do NOT use bash comments before the command (e.g., `# comment\nscripts/vlogs ...`) as this causes unnecessary permission prompts. Just run the command directly. +**Timestamps**: MCP requires RFC3339 format (e.g., `2026-04-09T00:00:00Z`). Compute absolute timestamps from the current date rather than using relative time. -**IMPORTANT**: Order UIDs and other structured fields (like `quote_id`, `auction_id`) live inside the `all` field in Victoria Logs. You MUST prefix them with `all:` to match. Plain text terms (like `order created`, `filtered`) match the log message directly and don't need the prefix. +**IMPORTANT**: Order UIDs and other structured fields (like `quote_id`, `auction_id`) live inside the `all` field in Victoria Logs. You MUST prefix them with `all:` to match. Plain text terms (like `order created`, `filtered`) match the log message directly and don't need the prefix. You can also use parsed fields directly (e.g., `parsed.fields.order_uid:0x...`) for more precise matching. -```bash -scripts/vlogs "" [--from