Conversation
# 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
# 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.
# 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 <ilya@cow.fi>
# 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](cowprotocol#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.
…rotocol#4055) # Description Second part of cowprotocol#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 cowprotocol#4021 --------- Co-authored-by: ilya <ilya@cow.fi> Co-authored-by: José Duarte <duarte.gmj@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Martin Magnus <martin.beckmann@protonmail.com>
…l#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 <noreply@anthropic.com>
# 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
# 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. <details> <summary>execution plan</summary> ``` "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" ``` </details> ## How to test added a new unit test for the DB query
# 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
cowprotocol#4094) # Description This reverts commit 7081b03. (PR cowprotocol#4055) The migrations will be revisited as they could not be applied to prod due to lockup and long duration.
…owprotocol#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
# 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.
# 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
# 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 <noreply@anthropic.com>
# Description Reinstates cowprotocol#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 <ilya@cow.fi> Co-authored-by: José Duarte <duarte.gmj@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Martin Magnus <martin.beckmann@protonmail.com>
> [!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<u64> 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<u64> 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 <noreply@anthropic.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Adds Plasma URLs to the orderbook's OpenAPI.
# Description Reinstates queries from cowprotocol#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).
# 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.
# 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.
# 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 <noreply@anthropic.com>
# Description Fixes Docker pnpm version error. # Changes Enable and prepare corepack version before running pnpm install. # Fixes cowprotocol#4101
…atibility (cowprotocol#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` ✅
…ol#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>
# 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 <ilya@cow.fi>
# 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](cowprotocol#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
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
### Description This PR follows up on cowprotocol#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 ```
## 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.
…ol#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
# Description Fixes cowprotocol#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
…protocol#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<u8>)` → `ValidationError::TransferSimulationFailed(Vec<u8>)` → 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<u8>`, 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<u8>` 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
# 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
# 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<Address, U256>` 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)|
# Description Instruct Claude to use MPC to access logs instead of using helper script.
…wprotocol#4329) # Description While reviewing cowprotocol#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 cowprotocol#4309 and check it fails
…protocol#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 cowprotocol#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.
# 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 <martin.beckmann@protonmail.com>
# 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())
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 cowprotocol#4205 (pod network integration) PR to isolate the dependency upgrade.
# 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. cowprotocol#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 <martin.beckmann@protonmail.com>
# 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.
# 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>
# Description In cowprotocol#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 <martin.beckmann@protonmail.com>
# 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<HashSet<Address>>` 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 <noreply@anthropic.com> Co-authored-by: José Duarte <duarte.gmj@gmail.com> Co-authored-by: José Duarte <15343819+jmg-duarte@users.noreply.github.com>
# 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 cowprotocol#3452 feature request from Liquorice PMM - they solved their problem in PR cowprotocol#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 cowprotocol#4326 Co-authored-by: Haris Angelidakis <64154020+harisang@users.noreply.github.com>
…col#4354) # Description Alternative to cowprotocol#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](ethereum/EIPs#20 (comment))). # 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.
# Description The Claude code-review action currently posts one big summary as an issue comment (example: cowprotocol#4355 (comment)). 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.
## 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
# 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:
<https://github.com/aave/aave-v4/blob/main/src/spoke/TokenizationSpoke.sol>
# 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.
…ol#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<BlockNo>`), 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.
# 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)
# 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)
# 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 cowprotocol#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](cowprotocol#4361 (review)). # 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`.
# 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<Solved> instead of Option<Solved> 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).
Merge upstream v2.359.0 into Aave fork, preserving local patches: - MAINNET_FORK (chain ID 123456789) network constant and contract addresses - skip_domain_separator_verification config option for fork environments - Flashloan bypass for balance check (Tenderly forks lacking debug_traceCall) - Extra debug logs for balance filtering Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| // Helper to fetch the debug report. | ||
| let fetch_debug_report = || async { | ||
| let response = client | ||
| .get(format!("{API_HOST}/restricted/api/v1/debug/order/{uid}")) |
| // Non-existent order -> 404. | ||
| let fake_uid = "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; | ||
| let response = client | ||
| .get(format!( |
| let has_invalid_event = || async { | ||
| onchain.mint_block().await; | ||
| let response = client | ||
| .get(format!("{API_HOST}/restricted/api/v1/debug/order/{uid}")) |
| .unwrap(); | ||
|
|
||
| let response = client | ||
| .get(format!("{API_HOST}/restricted/api/v1/debug/order/{uid}")) |
|
|
||
| // Simulation at the block where the trader had no WETH must fail. | ||
| let response = client | ||
| .get(format!( |
|
|
||
| // Simulation at the block where the trader has WETH must succeed. | ||
| let response = client | ||
| .get(format!( |
| // Simulation at the latest block (block_number parameter omitted), must | ||
| // succeed. | ||
| let response = client | ||
| .get(format!( |
|
|
||
| // filledAmount=0 on-chain; full 4 WETH needed; trader only has 1 → must fail. | ||
| let response = client | ||
| .get(format!( |
| // Without reading on-chain fill state the simulator would need the full | ||
| // 4 WETH from the trader (who only holds ~2) and revert. | ||
| let response = client | ||
| .get(format!( |
Collaborator
Author
|
Verified the PR by running all of the available tests: unit, doc, driver, db, local. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migration Guide: v2.352.0 → v2.359.0
Changelog
v2.353.0 — March 16, 2025
v2.354.0 — March 23, 2025
v2.355.0 — March 30, 2025
v2.356.0 — April 6, 2025
v2.357.0 — April 13, 2025
v2.358.0 — April 20, 2025
v2.359.0 — April 27, 2025
The Core Change
Before: Services took a
--configTOML file for a subset of settings, with the rest passed as CLI arguments / env vars.After: Services take
--configTOML file for all settings. The only remaining CLI arg is--configitself (plus infrastructure args fordriver).Orderbook
Remove all these CLI args / env vars
BIND_ADDRESS0.0.0.0:8080NODE_URLSIMULATION_NODE_URLCHAIN_IDLOG_FILTERLOG_STDERR_THRESHOLDUSE_JSON_LOGSfalseTRACING_COLLECTOR_ENDPOINTTRACING_LEVELINFOTRACING_EXPORTER_TIMEOUT10sGAS_ESTIMATORSWeb3NETWORK_BLOCK_INTERVALSETTLEMENT_CONTRACT_ADDRESSBALANCES_CONTRACT_ADDRESSSIGNATURES_CONTRACT_ADDRESSNATIVE_TOKEN_ADDRESSHOOKS_CONTRACT_ADDRESSVOLUME_FEE_BUCKET_OVERRIDESENABLE_SELL_EQUALS_BUY_VOLUME_FEEfalsePRICE_ESTIMATION_DRIVERSHTTP_CLIENT_TIMEOUTETHRPC_MAX_BATCH_SIZEETHRPC_MAX_CONCURRENT_REQUESTSAdd to the orderbook TOML file
These fields were previously CLI-only and must now live in the TOML:
Aave-fork specific fields
These were already in your TOML on
main. Verify they are present, and noteskip-domain-separator-verificationis a new field added in this upgrade:Autopilot
Remove all these CLI args / env vars
In addition to all
[shared]env vars listed in the orderbook table above:ETHFLOW_CONTRACTS[]ETHFLOW_INDEXING_STARTMETRICS_ADDRESS0.0.0.0:9589API_ADDRESS0.0.0.0:12088SKIP_EVENT_SYNCfalseUNSUPPORTED_TOKENS[]MIN_ORDER_VALIDITY_PERIOD1mMAX_AUCTION_AGE5mSUBMISSION_DEADLINE5(blocks)MAX_SETTLEMENT_TRANSACTION_WAIT1mSHADOWSOLVE_DEADLINE15sCOW_AMM_CONFIGS[]MAX_RUN_LOOP_DELAY2sRUN_LOOP_NATIVE_PRICE_TIMEOUT0sMAX_WINNERS_PER_AUCTION20MAX_SOLUTIONS_PER_SOLVER3ARCHIVE_NODE_URLDISABLE_ORDER_BALANCE_FILTERfalseENABLE_LEADER_LOCKfalseCOMPRESS_SOLVE_REQUESTfalseMAX_MAINTENANCE_TIMEOUT5sAdd to the autopilot TOML file
Driver
No change. The driver still takes the same CLI args as in v2.352.0:
--addr,--log,--ethrpc,--ethrpc-max-batch-size,--ethrpc-max-concurrent-requests,--tracing.*,--current-block.*, plus--configfor the service TOML.Solvers (baseline)
No change to invocation format. The baseline solver already used
--configin v2.352.0.Action Items Summary
orderbookandautopilot.[shared]section structure.skip-domain-separator-verification = trueto your orderbook TOML (new Aave-fork field introduced in this upgrade).[shared]section is the key new container —node-url,logging,tracing,ethrpc,gas-estimators,current-block, andcontractsall live there now for bothorderbookandautopilot.