Skip to content

v2.352.0 → v2.359.0#11

Open
m-sz wants to merge 220 commits intomainfrom
upgrade-v2.359.0
Open

v2.352.0 → v2.359.0#11
m-sz wants to merge 220 commits intomainfrom
upgrade-v2.359.0

Conversation

@m-sz
Copy link
Copy Markdown
Collaborator

@m-sz m-sz commented Apr 30, 2026

Migration Guide: v2.352.0 → v2.359.0


Changelog

v2.353.0 — March 16, 2025

  • Extract autopilot config groups (ethflow, cow_amm, run_loop) to TOML format
  • Extract autopilot configuration to a dedicated configs crate
  • Extract orderbook configuration to the configs crate
  • Extract shared CLI arguments to config file-based SharedConfig
  • Move order_quoting, banned_users, and http_client configs to the configs crate
  • Relocate price-estimation config structs to the configs module
  • Refactor http-client into a separate crate
  • Add BitGet solver support
  • Migrate OKX solver implementation
  • Inline submission account configuration
  • Fix handling of inflight orders to prevent them from being marked invalid
  • Add a new debug endpoint
  • Log order UIDs that encounter encoding errors
  • Add tracking for reasons orders are removed from auctions
  • Optimize latency-sensitive database queries to use the write database
  • Enhance order creation with comprehensive balance and allowance validation
  • Increase gas limit for quote verification
  • Upgrade quinn-proto dependency to v0.11.14
  • Update the Trivy security scanning action

v2.354.0 — March 23, 2025

  • Move common eth domain types to separate crate
  • Extract price estimation config to configs crate
  • Extract shared CLI arguments to config file-based SharedConfig
  • Add simulator crate
  • Add custom solver errors
  • Add knob to ensure gas floor for unverified quotes
  • Add knob to configure gas ceiling for unverified quotes
  • Remove lens from the services
  • More efficient account trades query
  • Configs to fix the playground
  • Speed up analytics DB replication job
  • Fix: Don't quote unsupported tokens
  • Add serde tag to SimulatorKind
  • Clamp gas value before declaring a winner

v2.355.0 — March 30, 2025

  • Fix cargo audit — address remaining advisories
  • Update docker image tagging to include git SHA
  • Add Bitget error code mappings (80011–80015)
  • Map OKX error code 51005 (honeypot token) to NotFound
  • Fix gas estimator tip adjustment behavior in simulator

v2.356.0 — April 6, 2025

  • Remove Enso's simulator support
  • Add Claude Code GitHub workflow
  • Allow solvers to set custom gas fees in their solution
  • Log original request ID when reusing shared in-flight quote requests
  • Log custom solver errors on quoting
  • Add simulation endpoint
  • Add block number to order simulation

v2.357.0 — April 13, 2025

  • Custom order simulation
  • Partially filled orders support in simulation
  • Generate Tenderly link on simulation
  • Skip balance check for flashloan orders
  • Surface transfer simulation revert reasons in order creation API
  • Refactor contracts crate out of the main folder for faster compilation
  • Add a timeout to balance override detection
  • Add global query timeout to SELECT queries
  • Fix BalancerV2NoProtocolFeeLiquidityBootstrappingPoolFactory Sepolia block
  • Redact IPFS auth_token in debug output
  • Docs: add onboarding diagrams and entrypoints
  • Advise Claude to use order debug endpoint
  • Update GH actions to use pinned commit SHA
  • Pin Claude workflow actions to full commit SHAs

v2.358.0 — April 20, 2025

  • Don't compile contract bindings we don't use
  • Remove uniform clearing prices from winner selection
  • Add E2E test to ensure flashloan filtering is performed correctly
  • Support reading TenderlyConfig api_key from environment variable
  • Hide competition until deadline passes
  • Fix RUSTSEC issues
  • Bump alloy crates from 1.7.3 to 1.8.3
  • Respect settle queue size
  • Use a common prefix for internal endpoints
  • Add internal solver competition endpoint
  • Workflow to test contract generated code
  • Update Claude log instructions

v2.359.0 — April 27, 2025

  • Support Aave v3 aToken balance overrides
  • Propose multiple winning solutions in the driver
  • Fix EIP-4626 contract revert classification
  • Add Uniswap V3 defaults on Gnosis chain
  • Overshoot Spardose fake balance by 1%
  • Include block number in logged Tenderly simulation command
  • Bump rustls-webpki to 0.103.13 (RUSTSEC-2026-0104)
  • Use inline PR review comments from Claude code review

The Core Change

Before: Services took a --config TOML file for a subset of settings, with the rest passed as CLI arguments / env vars.

After: Services take --config TOML file for all settings. The only remaining CLI arg is --config itself (plus infrastructure args for driver).


Orderbook

Remove all these CLI args / env vars

Env var (old) Was default
BIND_ADDRESS 0.0.0.0:8080
NODE_URL (required)
SIMULATION_NODE_URL (optional)
CHAIN_ID (optional)
LOG_FILTER (required)
LOG_STDERR_THRESHOLD (optional)
USE_JSON_LOGS false
TRACING_COLLECTOR_ENDPOINT (optional)
TRACING_LEVEL INFO
TRACING_EXPORTER_TIMEOUT 10s
GAS_ESTIMATORS Web3
NETWORK_BLOCK_INTERVAL (optional)
SETTLEMENT_CONTRACT_ADDRESS (optional)
BALANCES_CONTRACT_ADDRESS (optional)
SIGNATURES_CONTRACT_ADDRESS (optional)
NATIVE_TOKEN_ADDRESS (optional)
HOOKS_CONTRACT_ADDRESS (optional)
VOLUME_FEE_BUCKET_OVERRIDES (optional)
ENABLE_SELL_EQUALS_BUY_VOLUME_FEE false
PRICE_ESTIMATION_DRIVERS (required)
HTTP_CLIENT_TIMEOUT (optional)
ETHRPC_MAX_BATCH_SIZE (optional)
ETHRPC_MAX_CONCURRENT_REQUESTS (optional)

Add to the orderbook TOML file

These fields were previously CLI-only and must now live in the TOML:

bind-address = "0.0.0.0:8080"

[shared]
node-url = "http://..."
simulation-node-url = "http://..."  # optional
chain-id = 123456789                # optional, Aave fork chain ID

  [shared.logging]
  filter = "info,orderbook=debug"
  stderr-threshold = "warn"         # optional
  use-json = true

  [shared.tracing]                  # optional
  collector-endpoint = "http://tempo:4317"
  level = "INFO"
  exporter-timeout = "10s"

  [shared.ethrpc]                   # optional
  max-batch-size = 100
  max-concurrent-requests = 10

  [shared.current-block]            # optional
  poll-interval = "1s"

  [[shared.gas-estimators]]
  type = "Web3"

  [shared.contracts]                # optional overrides
  settlement = "0x..."
  native-token = "0x..."

[http-client]                       # optional
timeout = "10s"

[order-quoting]
price-estimation-drivers = [{ name = "baseline", url = "http://driver/baseline" }]

[price-estimation]                  # optional

Aave-fork specific fields

These were already in your TOML on main. Verify they are present, and note skip-domain-separator-verification is a new field added in this upgrade:

eip1271-skip-creation-validation = true
skip-domain-separator-verification = true   # NEW in v2.359.0

Autopilot

Remove all these CLI args / env vars

In addition to all [shared] env vars listed in the orderbook table above:

Env var (old) Was default
ETHFLOW_CONTRACTS []
ETHFLOW_INDEXING_START (optional)
METRICS_ADDRESS 0.0.0.0:9589
API_ADDRESS 0.0.0.0:12088
SKIP_EVENT_SYNC false
UNSUPPORTED_TOKENS []
MIN_ORDER_VALIDITY_PERIOD 1m
MAX_AUCTION_AGE 5m
SUBMISSION_DEADLINE 5 (blocks)
MAX_SETTLEMENT_TRANSACTION_WAIT 1m
SHADOW (optional)
SOLVE_DEADLINE 15s
COW_AMM_CONFIGS []
MAX_RUN_LOOP_DELAY 2s
RUN_LOOP_NATIVE_PRICE_TIMEOUT 0s
MAX_WINNERS_PER_AUCTION 20
MAX_SOLUTIONS_PER_SOLVER 3
ARCHIVE_NODE_URL (optional)
DISABLE_ORDER_BALANCE_FILTER false
ENABLE_LEADER_LOCK false
COMPRESS_SOLVE_REQUEST false
MAX_MAINTENANCE_TIMEOUT 5s

Add to the autopilot TOML file

# Shared infrastructure — same structure as orderbook
[shared]
node-url = "http://..."
simulation-node-url = "http://..."  # optional
chain-id = 123456789                # optional

  [shared.logging]
  filter = "warn,autopilot=debug,shared=info"
  use-json = true

  [shared.tracing]
  collector-endpoint = "http://tempo:4317"

  [[shared.gas-estimators]]
  type = "Web3"

  [[shared.gas-estimators]]
  type = "Alloy"

# Previously CLI-only settings
metrics-address = "0.0.0.0:9589"
api-address = "0.0.0.0:12088"
disable-order-balance-filter = false
unsupported-tokens = []
min-order-validity-period = "1m"
max-auction-age = "5m"
max-maintenance-timeout = "5s"

[run-loop]
max-delay = "2s"

[ethflow]                           # if using ethflow
contracts = ["0x..."]
indexing-start = 12345              # optional

[cow-amm]                           # if using CoW AMMs
factory-config = [{ factory = "0x...", helper = "0x...", index-start = 12345 }]

[http-client]

[order-quoting]
price-estimation-drivers = [{ name = "baseline", url = "http://..." }]

[price-estimation]

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 --config for the service TOML.


Solvers (baseline)

No change to invocation format. The baseline solver already used --config in v2.352.0.


Action Items Summary

  1. Remove all the CLI args/env vars listed above from your deployment manifests (Kubernetes, docker-compose, etc.) for orderbook and autopilot.
  2. Move those values into the respective TOML config files under the new [shared] section structure.
  3. Add skip-domain-separator-verification = true to your orderbook TOML (new Aave-fork field introduced in this upgrade).
  4. The [shared] section is the key new container — node-url, logging, tracing, ethrpc, gas-estimators, current-block, and contracts all live there now for both orderbook and autopilot.

fafk and others added 30 commits January 26, 2026 09:40
# 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
jmg-duarte and others added 27 commits April 10, 2026 09:53
# 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!(
@m-sz
Copy link
Copy Markdown
Collaborator Author

m-sz commented Apr 30, 2026

Verified the PR by running all of the available tests: unit, doc, driver, db, local.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.