Exploring a Rust Solver Correctness & Verification Grant for CoW Protocol

Hi CoW community,

I’m exploring whether there is a useful grant-sized problem around correctness and verification of CoW Protocol’s Rust/solver infrastructure.

My background is in Rust distributed/state-machine systems and protocol correctness. I also work with property-based testing, fuzzing, Kani, Z3, TLA+ and Rocq/Coq.

I’ve been looking at the recent work around the Rust SDK, solver infrastructure, programmable orders and pre-flight verification. Rather than proposing tooling without understanding what maintainers actually need, I’d first like to identify a concrete correctness problem worth solving.

Areas I’m interested in include:

  • order and signature invariants
  • solver input/output validation
  • serialization and boundary conditions
  • deterministic or adversarial testing
  • pre-flight verification before execution
  • property-based testing of Rust components

If there is a current pain point in one of these areas, I’d be interested in scoping it into a small initial research/engineering milestone, with open-source deliverables.

GitHub: https://github.com/ss1738

I’m especially interested in hearing from maintainers working directly on the Rust SDK or solver infrastructure before writing a formal grant proposal.

Hey @satya_98402 , thanks for your interest. In regards to your query on grants, what it is that you mention broadly may be helpful, but for guidance as to how to work out what would need to be done, I’d refer you to GitHub - cowdao-grants/cow-rs · GitHub and GitHub - cowprotocol/services: Off-chain services for CoW Protocol · GitHub. Essentially the services is the ‘source of truth’, and cow-rs would be tested against properties / invariants etc derived from services. This would be the path that I would recommend in order to arrive at a grant that is soundly scoped (and useful).

mfw.

Hi mfw78,

Thanks, this is very helpful. I will use the services repository as the behavioral source of truth and work from the corresponding cow-rs surfaces rather than trying to define the verification scope abstractly.

First, I will identify a small set of concrete properties and invariants where differential or property-based testing would provide useful assurance, along with the exact modules involved and what constitutes a counterexample.

I will follow up with a narrowly scoped milestone proposal before implementing anything substantial.

Best regards,
Satyawan Singh

Production solver operator here (kaisersolver, we run on Arbitrum and Base). I come at this from the opposite end, verifying settlements rather than solver code, but a few things we ran into might help you scope.

Settlement-level invariants are cheap to write down and they catch real problems. We check sixteen per settlement using only public data: the auction id in the calldata matches the competition record, the settling address is the auction winner and a registered solver, exactly the winning solution’s orders settled at the scored amounts, every trade respects the signed limit, delivery actually reached the recorded receiver, the deadline was met, and so on. The rule that made the tool trustworthy is that a check may only say VIOLATION when public data proves it. Anything else is UNCERTAIN.

The bugs we found in our own tool were boundary and serialization bugs, which is basically your list. A node answering null for a transaction receipt gave us a false “reverted” verdict on a perfectly valid settlement. A calldata decoder accepted a non-canonical auction-id tail (length 8 mod 32) that the reference implementation rejects. A truncated calldata read decoded as a zero limit price. All three are hermetic test cases now, and both our engines (a Python CLI and a browser JS port) agree across an 80-settlement corpus.

If you want a live differential target: the circuit-breaker validator repo has an open issue from a core team member where its score came out about 2.06e12 wei below the autopilot’s on a capped price, which was enough to deny-list a solver on mainnet. Two implementations of one formula disagreeing by rounding is exactly what a fuzzer finds in an afternoon.

Happy to share the invariant list and the fixtures if that’s useful.

Hi Kaiser,

Thank you; this is extremely useful. The circuit-breaker discrepancy is a great example of a bounded differential target, especially given its real-world production impact.

I would definitely appreciate it if you could share the invariant list and fixtures. I plan to use them to reproduce the issue and separate settlement-level invariants from implementation-specific assumptions.

I plan to start with a narrow surface, specifically the capped-price/score calculation, by building a differential harness around the two implementations. I will then generate boundary cases around rounding, integer conversion, serialisation, and cap transitions, reducing any disagreements to minimal reproducible cases.

If this yields useful results, I will formulate a focused grant milestone detailing the specific properties, source of truth, testing strategy, and deliverables.

Thanks again; the fixtures will be very helpful.

Best regards,
Satyawan

Hi mfw78,

I followed your suggestion and mapped the relevant services and cow-rs surfaces to help scope the grant proposal.

One clarification would help ensure I focus on the right equivalence boundary. The cow-rs implementation on the develop branch appears primarily focused on SDK functionality: order types/signing, app-data, API DTOs, and settlement-event decoding rather than solver competition or autopilot scoring.

When you suggested testing cow-rs against properties/invariants derived from services, did you primarily have those SDK-conformance surfaces in mind, or is there another services ↔ cow-rs behaviour you consider most valuable to verify?

Regarding the circuit-breaker score discrepancy mentioned by Kaiser, I reproduced the reported issue but found that validator-side score recomputation was removed in PR #19 in favour of treating the autopilot score as authoritative. As a result, I will not pursue that comparison as a grant target.

If you can point me toward the specific services ↔ cow-rs surface where maintaining conformance is most critical, I will focus my next investigation there and follow up with concrete properties and counterexamples.

Best regards,
Satyawan

Apologies, I’ve corrected it inline above, in that the cow-rs properties / teseting would be done against services, as that is ultimately where the data goes.

Here is the material you asked for, in a form you can run today.

Fixtures. Everything is in GitHub - KaiserSolver/cow-certify: Independent verification for CoW Protocol settlements — reproducible verdicts from public data only. CLI + browser app, 11 chains, never accuses from ambiguity. · GitHub. The 16 checks are described in the README under “What gets checked”. The corpora sit in the repo root: self_audit_corpus.csv is 80 settlements across chains with the certificates in certs_self_audit/, and certs_negative_corpus.csv holds the reverted and malformed cases with certificates in certs_negative/. A certificate is JSON with schema_version, subject, overall, a checks array of {check, verdict, detail}, not_verifiable, evidence and a reproduce block, so it doubles as an expected-output fixture for any implementation of the same properties. To regenerate one: cow-certify --network base 0x --json, or the whole corpus with python3 -m cow_certify.batch corpus.csv --out certs/. One caveat worth designing around: the self-audit corpus is PASS-heavy by construction, so a harness needs its own crafted negatives; the seven in certs_negative are a start, not a suite.

The invariants, as predicates rather than names, with the source of each value:

  1. Solution fidelity: the set of settled order uids and their executed amounts equal the winning solution recorded in the competition API. Real settlements match exactly; in 94 corpus certificates the difference was never nonzero.
  2. Limit compliance, per trade from the on-chain Trade event: buy_received × limit_sell ≥ limit_buy × (sell_paid − fee), where the signed limits come from the calldata for a direct settle() and from the public orderbook for wrapper routes; a zero limit is not a limit and is skipped.
  3. Auction binding: the auction id is read only from a canonical calldata tail of exactly 8 bytes; any other tail length is reported, never compared.
  4. Execution status: “reverted” requires an explicit status 0x0 from every RPC witness; a missing receipt is UNCERTAIN, never a violation. A successful settle() also proves the caller passed the on-chain onlySolver check.
  5. Receiver delivery: each order’s buy tokens reached the recorded receiver, from ERC-20 Transfer logs.
  6. Protocol buffer: the net ERC-20 delta of the settlement contract shows whether the settlement drew on accumulated fees.

A live differential target. The CIP-87 per-order penalty cap passed Snapshot on 25 August and is now in services (PRs #4773, #4784, #4785, merged 31 August to 1 September). The formula lives in crates/autopilot/src/domain/penalty_cap.rs: the order’s volume is its sell amount for sell orders and its buy amount for buy orders, converted with the auction’s native prices; the cap is min(factor × volume, USD bound in native), where the factor is a per-chain basis-point fraction with pair-bucket overrides (the correlated-token case) resolved as “first matching override wins”, and the USD bound is converted through a separately fetched native price of a USD reference token. If the volume cannot be determined, because of a missing price or overflow, the absolute bound applies. The result is persisted for accounting and exposed to drivers and solver engines as penaltyCapNative on each order. That is three consumers of one number and a boundary list you can enumerate from the code: the fallback path, staleness of the reference price, bucket ordering, wrapped-native mapping, and rounding at the cap transition.

If you build the harness, we are happy to run it across our daily archive of settlements on Base and Arbitrum and publish any disagreement as a certificate.

Hi mfw78,

Following up with a concrete proposal for the first milestone.

I propose focusing on the signed-order digest / EIP-712 / EthSign / owner-recovery / OrderUid conformance corpus between cow-rs and services. This represents the cleanest cow-rs ↔ services boundary: it’s the exact data services accepts, it’s hermetic, and it’s concise enough for thorough review.

Scope & Deliverables:

  • Services side (authority): model::order::OrderData::{TYPE_HASH, hash_struct, uid}, OrderUid::from_parts, model::signature::{hashed_eip712_message, EcdsaSignature::recover} for owner recovery, and the corresponding EthSign digest path (crates/model/src/{order.rs,signature.rs}).
  • cow-rs side: cowprotocol_signing::OrderData::{hash_struct, uid, signing_hash, recover_signer}, signature::{signing_message, ecdsa_recover}, cowprotocol_primitives::{OrderUid, OrderUidParts, parse_order_uid}.
  • Deliverable: A deterministic services test/utility that emits JSON vectors (covering every enum variant, receiver None/zero/nonzero, valid_to edges, U256 amount edges, EIP-712 and EthSign signing schemes, v ∈ {0,1,27,28} normalisation, and invalid-v rejection), pinned to an exact services SHA. Plus, a cow-rs test that consumes every vector and verifies hash_struct, both signing digests, recovered owner, and UID split/rebuild.
  • Counterexample: Any vector where cow-rs’s digest, recovered owner, or UID diverges from the pinned services output.

Before proceeding with implementation, please clarify the following points (to align with both repositories’ issue requirement guidelines):

  1. Should services generate the canonical fixtures in their own CI, or should cow-rs vendor-generated vectors and update parity/source-lock.toml manually?
  2. Is cow-rs/develop still the target base branch, given that PR #50 (promoting 0.2.0 to main) remains open?
  3. Which input domain should be in scope: only client-constructible orders, or every services::OrderCreation variant (including hash/full/both app-data forms and on-chain signature schemes)?

Note on CIP-87: I am excluding CIP-87 from this corpus. Since cow-rs contains no penalty-cap logic, a differential test here would not be applicable. If valuable, property/boundary tests inside services for PenaltyCapCalculator can be submitted as a separate proposal.

I will open the issue once you confirm the preferred direction above.

Best regards,
Satyawan

Hi Kaiser,

Thanks again for the corpus and the cow-certify pointer. I pulled the tool locally and reviewed the check table along with both fixture sets (the 80-settlement self-audit corpus and the negative corpus).

I want to share two quick updates:

First, apologies for the delayed update, but the circuit-breaker differential harness is off the table. I mentioned this to mfw78 on September 5th, but I should have looped you in sooner given the context of your fixtures.

Second, regarding the CIP-87 suggestion: neither cow-rs nor cow-certify currently implements the penalty-cap calculation (min(factor × volume, USD bound) from PenaltyCapCalculator). Your 16-check list also does not cover this. Since a differential harness requires two independent implementations to compare, and currently only the one in services exists, a genuine differential test is not buildable for CIP-87 on either side right now.

Instead, I propose the following:

  1. Services-internal property/boundary suite for PenaltyCapCalculator: I identified concrete gaps, including lack of tests for first-match bucket-override precedence when buckets overlap, boundary behaviour at the exact volume_cap == absolute_cap transition, and stale reference-price handling (which is currently only observed via a metric rather than rejected). This would not require a second implementation, just a maintainer-approved numeric contract to test against.
  2. Longer-term integration: If cow-certify could export the raw settlement inputs behind a penaltyCapNative value (e.g., the auction’s native prices and reference-price age/source at settlement time, rather than just the final settlement outcome), we could replay the cap calculation from public data and compare it against what services computed. Is that data reachable from your daily archive, or does it only expose final settlement state?

Please let me know if either direction sounds useful. Thanks again for the fixtures; the negative corpus in particular provides a great model for the signed-order/UID corpus I am proposing to mfw78.

Best regards,
Satyawan

The staging autopilots write penaltyCapNative into every order of the auction bodies they hand to solvers, and those bodies are in the public instance bucket (staging/{chain}/auction/{id}.json). Each body also carries the inputs the calculation consumes, the signed amounts, and the per-token native prices, so the cap can be recomputed from scratch and compared with the published number. cow-certify 0.5.0 does exactly that: cow-certify --network base --penalty-cap {auction id} --env staging recomputes every order’s cap from the body and reports MATCH or DIFFERENCE per order with the volume, factor, bound, and regime it used. Release: Client Challenge (source and corpus: GitHub - KaiserSolver/cow-certify at v0.5.0 · GitHub ). The module (cow_certify/penalty_cap.py) is mirrored against services main at d4bc794, including the branches a re-implementation gets wrong: a missing native price makes the cap the $20 bound rather than zero, an overflow converting the bound makes it U256::MAX, factors are parts-per-million with floor division, overrides are first-match. Every report pins that commit.

Result on 188 staging auctions sampled across the bucket’s retention window: all 171 order instances that carry a published cap are reproduced exactly, zero differences, across 19 distinct orders on mainnet, Base, Gnosis, BNB, Arbitrum and Avalanche. A held-out set shipped alongside it, the newest 12 auctions per chain on Base, mainnet and Gnosis with none of them in the corpus, reproduced 85 of 85 order instances across 36 auctions. All three
regimes appear: the per-chain factor, the 0.1 bps correlated factor, and the $20 bound on mainnet’s 10,000 WETH test orders, where the bound reproduces exactly through USDC’s price in the same body. The corpus ships as penalty_cap_corpus.csv with one report per auction, each embedding its inputs, so it stays a hermetic vector set after the bucket drops the bodies. Size caveat stated plainly: the staging bots repeat a handful of orders, so 19 distinct orders is the real count, re-priced across many auctions.

What the replay states rather than hides. The correlated token sets and the usd reference token are deployment config, not published, so a value that only reproduces under the 0.1 bps factor is a match marked inferred, with the factor and value that actually matched recorded next to what the stated config would have given; --strict turns those into differences and the sets can be declared in code. The per-chain defaults are confirmed by
published caps on Base, Gnosis, Arbitrum, BNB and Avalanche; mainnet’s 4 bps is not, because every mainnet cap in the corpus was correlated or bound-bound, and the report says so. Not exercised at all: a buy order below the bound, a bound-binding order off mainnet, and Polygon, Linea, Plasma and Ink, whose staging bodies carry no cap yet.

One spec-versus-code observation that may interest you more than the numbers. CIP-87’s text defines the basis as the order’s quote, buy amount net of volume fees for sell orders and sell amount plus fees for buy orders. calculate() values the order’s own sell side (sell orders) or buy side (buy orders) at the auction’s reference prices, and the published caps follow the code, which is what the replay mirrors. Likewise, the CIP’s scaling to the
executed fraction of a partial fill happens at penalty time, not in the published per-order cap. Neither is a bug, but a differential harness written from the text would disagree with the autopilot on every fee-bearing order.

On your three boundary items, all three match the code as of today: first-match precedence over the overrides vector, min() at the cap transition after the volume is floored by 1e18 and the factor applied in ppm, and the stale reference price that only moves a gauge. Two more from reading calculate() that a property suite could pin cheaply: a missing native price for the order’s volume token silently raises the cap to the absolute bound, and an overflow in absolute_cap_in_native returns U256::MAX, which is no cap at all.

Side effect worth having: the corpus brackets the staging rollout per chain (last sampled auction without a cap, first with one), if that helps track when prod follows.

Happy to add whatever vectors your services-side suite would want to share with this one.

Kaiser

Hi Kaiser,

This is genuinely excellent work. I pulled cow-certify v0.5.0’s penalty_cap.py, found the public bucket (solver-instances.s3.eu-central-1.amazonaws.com/staging//auction/.json), and independently recomputed two live caps using a fresh, separate Python calculation that did not import or reuse cow-certify code:

Mainnet auction 16354776, USDT→USDC order: published penaltyCapNative = 8,037,316,304 wei. Among the CIP-listed factors, only 0.1 bps matches exactly, checked against every other distinct factor listed in the CIP. That is consistent with the correlated-pair regime, although the deployed correlated-token sets are not public, so the classification remains inferred.
Same auction, USDC→WETH order: published cap = 8,038,375,881,682,727 wei matches the $20 bound computed through USDC’s reference price, exactly.

Both matched exactly in my independent recomputation.

I also re-checked your quote-vs-code finding against the actual penalty_cap.rs source and the Snapshot proposal text, both fetched fresh and confirmed. calculate() uses order.data.sell_amount/buy_amount (lines 99- 101 of penalty_cap.rs), the raw signed order fields; the CIP text explicitly defines the basis as “the quote,” which it defines as “the buy amount with all volume fees deducted for sell orders and the sell amount with all volume fees added for buy orders” a different quantity. Real, useful catch for anyone building a property suite from the text instead of the code.

This changes something I told you and mfw78 a few days ago: I’d said no second implementation existed within services or cow-rs. That is no longer an accurate description of the broader ecosystem: cow-certify v0.5.0 is now a public external implementation, and I independently verified two of its results.

One correction to make before I say anything else to anyone: I went looking for a gap to offer to fill and initially thought the absolute_cap_in_native overflow-to-U256::MAX branch had zero test coverage anywhere. I was wrong: test_absolute_cap_conversion_and_overflow in your tests/test_penalty_cap.py already asserts exactly that, and test_overflowing_bound_is_uncapped covers the end-to-end consequence. What’s actually still missing is narrower: services’ own Rust test suite (the 8 unit tests in penalty_cap.rs itself) has no test for this branch at all; your Python mirror has it, the real Rust source doesn’t. I’ll add that on the Rust side.

I also went further than the summary you sent. I independently aggregated all 224 shipped reports (certs_penalty_cap/ + certs_penalty_cap_heldout/) and confirmed they contain 171/171 and 85/85 MATCH verdicts with zero reported differences. The 171/85 figures independently confirm the contents and aggregate counts of the shipped reports; they do not independently recompute those caps. The two raw-data calculations above are the separate arithmetic cross-check. To be fully clear on scope: the replay uses the auction body’s embedded amounts and reference prices together with the published CIP factors and your explicitly reported configuration assumptions, not just the raw auction body alone, since the correlated-token sets and most chains’ USD reference tokens aren’t public and have to be assumed.

Would you be willing to preserve the raw USD reference-token address, decimals, and price in each certificate as a dedicated field? Right now the reports preserve order amounts, factors, and derived bounds, but not that, so the absolute-bound branch can’t always be recomputed from first principles once a source auction expires from the bucket.
On the joint proposal: given your Python suite already covers bucket-precedence and the exact cap-transition boundary too (test_override_precedence_is_first_match, test_transition_where_volume_cap_equals_bound) the same two things I drafted local, uncommitted Rust tests for independently, although I have not yet been able to run them on the authorised build runner I’d want the two layers actually connected, not just parallel: shared fixtures or an explicit parity check in CI, so a divergence between your Python mirror and the real Rust code would actually get caught by something. Two suites independently passing wouldn’t, by itself, detect drift between them.

Thanks again; this was the missing piece.

Satyawan

Hi mfw78,

Update on CIP-87: I need to walk back something I told you: I said there was no second implementation of the penalty-cap formula, so a differential target wasn’t viable. That’s no longer accurate.

Kaiser (kaisersolver) has built one cow-certify v0.5.0 replays penaltyCapNative using the auction body’s embedded amounts and reference prices together with the published CIP factors and explicitly reported configuration assumptions (the correlated-token sets and most chains’ USD reference tokens aren’t public, so those are stated assumptions, not derived from the auction body alone). I didn’t take his numbers on faith. I independently recomputed two live Mainnet caps using a fresh calculation that did not import or reuse cow-certify; one matched the 0.1bps correlated-pair regime (checked against every other distinct factor listed in the CIP; the correlated classification itself is inferred, since the deployed token sets aren’t public), and one matched the $20 bound both exactly. Separately, I independently aggregated all 224 of the tool’s own shipped reports directly from the tagged repo and confirmed they contain 171/171 and 85/85 MATCH verdicts with zero reported differences. The 171/85 figures independently confirm the contents and aggregate counts of the shipped reports; they do not independently recompute those caps. The two Mainnet calculations above are the separate arithmetic cross-check.

He also caught something worth your attention directly: CIP-87’s text defines the cap’s basis as the order’s fee-adjusted “quote,” but penalty_cap.rs’s actual calculate() (line 99-101) uses the raw signed sell_amount/buy_amount. I checked this myself against the code and the Snapshot proposal text side by side, and confirmed. I am not classifying this as a bug: the published caps follow the implementation, but only the maintainers can confirm whether the divergence from the CIP wording is intentional. Worth documenting explicitly if it isn’t already: a harness implemented literally from the CIP text can disagree on fee-bearing orders. However, rounding, correlated factors, or the absolute bound may mask the difference in individual cases.

Given this, I’d like to revise what I’m proposing for CIP-87: instead of internal services tests alone, pair them with Kaiser’s public-data replay as an external validation layer, connected through shared vectors or an explicit cross-language parity check so drift between the Python mirror and services becomes mechanically detectable rather than just two suites independently passing. I’ve drafted local, uncommitted Rust tests covering bucket precedence, the exact cap-transition point, the Mainnet governance parameters, and zero-reference-price behaviour, though I haven’t yet been able to run them on the authorised build runner. I recommend adding coverage for the absolute_cap_in_native overflow branch, which his Python mirror already tests but the actual Rust suite does not, and expanding the governance-parameter coverage if maintainers want those values pinned directly.

Separately, the signed-order/UID corpus milestone I proposed is still open and unchanged; that one doesn’t depend on any of this. Still hoping to hear which of the two you’d rather see formalised first, or if both are worth pursuing in parallel.

Best regards,
Satyawan

Hi Satyawan,

Thank you for recomputing those two from scratch; that is the check that matters, and the numbers you quote for auction 16354776 are the ones in our shipped report to the wei.

You’re right about the reference price, and it’s fixed in 0.5.1 ( Client Challenge ). Every order now carries absolute_bound_inputs: the usd reference token, its decimals, its price in that body and the $ bound, and the report’s config records the reference price too, so the bound branch recomputes from the report alone after the body expires. An inferred bound match records the token that actually reproduced it.

On connecting the layers: every matched order in a report now carries a vector, and one command collects the corpus into a single file, penalty_cap_vectors.json (schema cow-certify/penalty-cap-vectors/1, 256 vectors in the shipped one): the signed order fields, the native prices the formula consumed, the configuration that reproduces the published value (default factor in ppm, any override needed, the $ bound, the usd reference token and decimals, the wrapped native token), and expected_cap = the autopilot’s published number. Inferred regimes are made explicit in each vector’s config, so a literal implementation of the formula reproduces every vector without knowing anything about inference, and the file’s own --check recomputes each one from its fields and fails on any mismatch. If your Rust tests load that file and assert calculate() reproduces expected_cap, drift between the two implementations becomes a failing test rather than two green suites. I’ll regenerate it whenever the corpus grows; each vector id pins network, environment, auction and order.

Agreed on the Rust side of the overflow branch, and thank you for the correction on ours.

Kaiser

Hi Kaiser,

Wanted to report back on v0.5.1. I verified it properly, not just trusted the numbers.

I fetched penalty_cap_vectors.json (256 vectors, schema cow-certify/penalty-cap-vectors/1) and spot-checked three vectors across all three regimes with my own from-scratch Python recomputation before touching any Rust; all three matched.

Then I built a local Rust integration test in services that loads your vectors file and calls the real PenaltyCapCalculator::calculate() the actual production function- for every vector it can construct. 209 of the 256 are constructible, and all 209 match exactly. I confirmed the harness isn’t tautological by mutating one expected_cap by a single wei: it failed correctly, reporting the production result (3,920,386,055) against the mutated expectation (3,920,386,056); removing the mutation returned it to green.

The other 47 (all volume_factor regime) can’t be constructed at all, for a specific reason: PenaltyCapCalculator::new requires a USD reference price unconditionally, but the vector only preserves the prices that fed the winning branch’s own calculation. Real services retains its last-known reference price across auctions and refresh it opportunistically; that retained state isn’t in the corpus, so this is a vector-extraction gap, not a bug in calculate() itself. I checked what happens if you guess instead of skip: on one of the 47, a guessed price below the crossover point returns 3,000,000 even though the vector’s published expected cap is 30,000,000 a 10x wrong result, not a theoretical risk.

Would you be open to including the exact retained reference-token price in every vector, regardless of which regime wins? That would make all 256 checkable instead of 209.

Satyawan

Hi Satyawan,

Thank you for building the Rust side and for the mutation check — that is exactly the harness this file was meant for.

Done in 0.5.2 (Client Challenge). Every vector now carries usd_reference_price with a usd_reference_price_source, and the file’s own --check proves the value it states:

  • auction: the body carried the reference token’s price — 209 of the 256 vectors. That is the exact value the calculator was constructed with for that cycle.
  • retained:{auction-id}: recovered from the most recent prior body that carried one. Since the body publishes the same price map the calculator reads, the retained value is that prior price unless the autopilot restarted in between (its startup fetch is never published). Opt-in via --retained-lookback N; never used when it would contradict the published cap (retained_consistent in the report).
  • unpublished: the body lists the token with a null price and no prior body within the lookback had one. All 47 of your unconstructible vectors are this: on Gnosis staging the reference token is listed in every body with a null price — I sampled 61 bodies across the whole corpus span and none priced it — so the retained value there is the autopilot’s startup fetch and is not public data. I could not honestly give you the exact number, so those vectors carry min_usd_reference_price instead: the smallest reference price at which the absolute bound still exceeds the published cap. Constructed with any price at or above it, calculate() returns expected_cap; one wei below it the bound binds and it returns the bound. --check asserts both directions for every such vector, so the 10x failure you reproduced by guessing below the crossover is now a stated boundary rather than a trap. All 256 vectors are constructible with that rule.

Two things the bodies taught me on the way. The autopilot lists its USD reference token in the body even when no order uses it (solvable_orders.rs adds it to every cycle’s price fetch) — I saw that directly on BNB and Gnosis staging — which is public evidence of which token is deployed: that is how those two assumptions were corroborated, and the reports now record config.usd_reference.token_in_body. And the “retained” semantics are exactly what you described — set_usd_price runs only when the cycle’s price map has the token — so on any chain where the estimator fails intermittently the lookback recovers the real value, and on Gnosis staging, where it fails every cycle, nothing can.

Report schema is 0.2.0 to 0.3.0 and the vectors schema 1 to 2, additive only; the 209 you already construct are unchanged except for the new fields.

Kaiser

Hi Kaiser,

v0.5.2 confirmed and verified the same way as before, not on trust.

Updated my Rust test to schema 2. It now checks all 256 vectors: 209 against the real published price, 47 against min_usd_reference_price. All 256 pass. Re-ran the mutation test on one of the 47 (boundary-priced) vectors specifically to make sure that path isn’t just a rubber stamp; it failed correctly, reporting the exact vector and both values, then went back to green after restoring.

One thing worth telling you: for the Gnosis vector I originally flagged (gnosis/staging/180104309/…), your min_usd_reference_price came out to 1,500,000,000,000,000,000, exactly the crossover price I’d derived independently by hand before you shipped this. Same number, two different code paths. Good sign for both of us.

Nice fix genuinely closes the gap cleanly.

Satyawan

Hi mfw78,

Just a quick check-in no urgency; I know you’re likely juggling a lot. Two things are still open on my end when you get a chance: which of the two milestones (signed-order/UID corpus or CIP-87) you’d rather formalise first, and whether the CIP-87 direction is worth pursuing at all given where it’s landed with Kaiser’s tooling. Happy to wait as long as needed; just didn’t want either to fall through the cracks unintentionally.

Satyawan