Return error on duplicate fills - #4844
Conversation
|
Reminder: Please consider backward compatibility when modifying the API specification.
Caused by: |
|
Claude finished @jmg-duarte's task in 3m 3s —— View job PR Review: Return error on duplicate fills
Overall this is a clean, well-targeted change — the two deserialization hooks are the correct enforcement boundaries, JIT trades are correctly excluded from the fulfillment dedup, the OpenAPI docs are updated, and tests cover both the reject and accept paths. I verified the hand-written One design point to consider (posted inline on No blocking correctness bugs found. |
| /// A partially fillable order may be split across several solutions, but a | ||
| /// single solution settles each order exactly once. Collecting the entries into | ||
| /// a map would silently keep only the last of a repeated order. | ||
| fn deserialize_orders<'de, D>( |
There was a problem hiding this comment.
Because the check lives in Solution's deserializer and Response { solutions: Vec<Solution> } is parsed atomically in request_response (serde_json::from_slice), a single solution with a duplicated order UID makes the entire driver response fail — every other (valid) solution from that same driver is discarded for the auction, not just the offending one.
Previously the last-wins HashMap silently corrupted the executed amounts, which is worse for accounting, so rejecting is the right call. But dropping all of a driver's solutions is a liveness regression vs. filtering out only the bad solution. Is failing the whole response the intended granularity, or would it be preferable to skip just the offending solution (validate after deserialization, drop that one, keep the rest)?
The same all-or-nothing applies on the solvers-dto side (SolverResponse::Solutions { solutions: Vec<Solution> }), though there the blast radius is a single solver engine's response.
|
|
Description
Context: https://nomevlabs.slack.com/archives/C0375NV72SC/p1788220867855339
A single solution must settle each order at most once. Until now a solver could list the same order in several trades of one solution, and a driver could repeat an order key in one solution's
ordersmap. Nothing rejected this: the driver forwarded the duplicate trades, and the autopilot collected the map into aHashMap, which silently kept the last entry.Changes
solvers-dto: reject a solution whosetradescontain more than one fulfillment for the same order UID.solvers-dto: hand-writeDeserializeforSolverResponseso the rejection reason reaches the solver notification instead of the generic "did not match any variant" message from#[serde(untagged)].autopilot: reject a driver solution whoseordersmap repeats an order UID.No breaking API change. Only payloads that were already invalid are now rejected.
How to test
Unit tests in
crates/solvers-dto/src/solution.rsandcrates/autopilot/src/infra/solvers/dto/solve.rs: