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