From 91be0f830c415727b6df576fc1402909f8b22a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= <15343819+jmg-duarte@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:56:15 +0100 Subject: [PATCH 1/4] Return error on duplicate fills --- .../autopilot/src/infra/solvers/dto/solve.rs | 93 ++++++++++++++- crates/driver/openapi.yml | 5 +- crates/solvers-dto/src/solution.rs | 112 +++++++++++++++++- crates/solvers/openapi.yml | 5 +- 4 files changed, 209 insertions(+), 6 deletions(-) diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index 7610832d7b..c4ec2ddc9e 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -16,12 +16,13 @@ use { number::serialization::HexOrDecimalU256, observe::http_body::Measured, reqwest::{RequestBuilder, header::HeaderValue}, - serde::{Deserialize, Serialize}, + serde::{Deserialize, Deserializer, Serialize, de}, serde_with::{DisplayFromStr, serde_as}, std::{ borrow::Cow, collections::{HashMap, HashSet}, convert::Infallible, + fmt, io::Write, time::Duration, }, @@ -388,6 +389,7 @@ pub struct Solution { pub solution_id: u64, /// Address used by the driver to submit the settlement onchain. pub submission_address: Address, + #[serde(deserialize_with = "deserialize_orders")] pub orders: HashMap, /// Deprecated: uniform clearing prices are no longer used by the /// autopilot. Kept here purely so we can detect and log drivers that @@ -399,6 +401,43 @@ pub struct Solution { pub gas: Option, } +/// 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>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct Visitor; + + impl<'de> de::Visitor<'de> for Visitor { + type Value = HashMap; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map of order uid to executed amounts") + } + + fn visit_map(self, mut map: A) -> Result + where + A: de::MapAccess<'de>, + { + let mut orders = HashMap::with_capacity(map.size_hint().unwrap_or_default()); + while let Some((uid, order)) = map.next_entry::()? { + if orders.insert(uid, order).is_some() { + return Err(de::Error::custom(format!( + "multiple fills for a single order (uid: {uid})" + ))); + } + } + Ok(orders) + } + } + + deserializer.deserialize_map(Visitor) +} + #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -450,6 +489,58 @@ mod tests { } } + fn order_uid(byte: u8) -> String { + const_hex::encode_prefixed([byte; 56]) + } + + fn traded_order() -> serde_json::Value { + serde_json::json!({ + "side": "sell", + "sellToken": format!("0x{:040x}", 1), + "buyToken": format!("0x{:040x}", 2), + "limitSell": "1000", + "limitBuy": "900", + "executedSell": "500", + "executedBuy": "450", + }) + } + + fn response_with_orders(orders: &[String]) -> String { + let orders = orders + .iter() + .map(|uid| format!("\"{uid}\": {}", traded_order())) + .join(","); + format!( + r#"{{"solutions": [{{ + "solutionId": 1, + "submissionAddress": "0x{:040x}", + "orders": {{{orders}}} + }}]}}"#, + 3 + ) + } + + #[test] + fn rejects_solutions_settling_the_same_order_twice() { + let uid = order_uid(1); + let response = response_with_orders(&[uid.clone(), uid.clone()]); + + let err = serde_json::from_str::(&response).unwrap_err(); + assert!( + err.to_string() + .contains(&format!("order {uid} is settled by more than one trade")), + "unexpected error: {err}" + ); + } + + #[test] + fn accepts_one_entry_per_order() { + let response = response_with_orders(&[order_uid(1), order_uid(2)]); + + let response = serde_json::from_str::(&response).unwrap(); + assert_eq!(response.solutions[0].orders.len(), 2); + } + #[test] fn compressed_request_round_trips() { let json = make_test_json(); diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index 5681b3abb4..da085c1f14 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -542,7 +542,10 @@ components: orders: description: > Mapping of order uid to net executed amount (including all - fees). + fees). Each order may appear at most once: a partially + fillable order can be split across multiple solutions, but not + across multiple entries of the same solution. Solutions that + violate this are rejected. additionalProperties: type: object properties: diff --git a/crates/solvers-dto/src/solution.rs b/crates/solvers-dto/src/solution.rs index 816486e47b..b9ab39884f 100644 --- a/crates/solvers-dto/src/solution.rs +++ b/crates/solvers-dto/src/solution.rs @@ -1,9 +1,9 @@ use { alloy_primitives::{Address, U256}, number::serialization::HexOrDecimalU256, - serde::{Deserialize, Serialize}, + serde::{Deserialize, Deserializer, Serialize, de}, serde_with::serde_as, - std::collections::HashMap, + std::collections::{HashMap, HashSet}, }; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] @@ -27,13 +27,42 @@ pub enum SolverErrorCode { Other, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum SolverResponse { Solutions { solutions: Vec }, Error { error: SolverError }, } +/// Hand written because `#[serde(untagged)]` discards the error of every +/// variant it tries and reports only that none matched, which hides why a +/// solution was rejected from the solver we notify about it. +impl<'de> Deserialize<'de> for SolverResponse { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Raw { + solutions: Option>, + error: Option, + } + + match Raw::deserialize(deserializer)? { + Raw { + solutions: Some(solutions), + .. + } => Ok(Self::Solutions { solutions }), + Raw { + error: Some(error), .. + } => Ok(Self::Error { error }), + Raw { .. } => Err(de::Error::custom( + "expected either a `solutions` or an `error` field", + )), + } + } +} + impl Default for SolverResponse { fn default() -> Self { Self::Solutions { @@ -49,6 +78,7 @@ pub struct Solution { pub id: u64, #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub prices: HashMap, + #[serde(deserialize_with = "deserialize_trades")] pub trades: Vec, #[serde(default)] pub pre_interactions: Vec, @@ -65,6 +95,30 @@ pub struct Solution { pub wrappers: Vec, } +/// A partially fillable order may be split across several solutions, but a +/// single solution settles each order exactly once. +fn deserialize_trades<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let trades = Vec::::deserialize(deserializer)?; + + let mut settled = HashSet::with_capacity(trades.len()); + for trade in &trades { + let Trade::Fulfillment(fulfillment) = trade else { + continue; + }; + if !settled.insert(&fulfillment.order) { + return Err(de::Error::custom(format!( + "multiple fills for a single order (uid: {uid})", + const_hex::encode_prefixed(fulfillment.order.0) + ))); + } + } + + Ok(trades) +} + #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)] pub struct OrderUid(#[serde_as(as = "serde_ext::Hex")] pub [u8; 56]); @@ -300,6 +354,58 @@ mod tests { ); } + fn order_uid(byte: u8) -> String { + const_hex::encode_prefixed([byte; 56]) + } + + fn solution_with_fulfillments(orders: &[String]) -> serde_json::Value { + json!({ + "id": 0, + "prices": {}, + "trades": orders.iter().map(|order| json!({ + "kind": "fulfillment", + "order": order, + "executedAmount": "1000", + })).collect::>(), + "interactions": [], + }) + } + + #[test] + fn rejects_solutions_settling_the_same_order_twice() { + let order = order_uid(1); + let solution = solution_with_fulfillments(&[order.clone(), order.clone()]); + + let err = serde_json::from_value::(solution).unwrap_err(); + assert_eq!( + err.to_string(), + format!("order {order} is settled by more than one trade") + ); + } + + #[test] + fn accepts_one_trade_per_order() { + let solution = solution_with_fulfillments(&[order_uid(1), order_uid(2)]); + + let solution = serde_json::from_value::(solution).unwrap(); + assert_eq!(solution.trades.len(), 2); + } + + #[test] + fn duplicate_trade_error_survives_the_response_wrapper() { + let order = order_uid(1); + let response = json!({ + "solutions": [solution_with_fulfillments(&[order.clone(), order.clone()])], + }); + + let err = serde_json::from_value::(response).unwrap_err(); + assert!( + err.to_string() + .contains(&format!("order {order} is settled by more than one trade")), + "unexpected error: {err}" + ); + } + #[test] fn serializes_and_deserializes_error_responses() { let cases = vec![ diff --git a/crates/solvers/openapi.yml b/crates/solvers/openapi.yml index 2177a12881..e739e024bf 100644 --- a/crates/solvers/openapi.yml +++ b/crates/solvers/openapi.yml @@ -1009,7 +1009,10 @@ components: $ref: "#/components/schemas/U256" trades: description: | - CoW Protocol order trades included in the solution. + CoW Protocol order trades included in the solution. Each order may + appear at most once: a partially fillable order can be split across + multiple solutions, but not across multiple trades of the same + solution. Solutions that violate this are rejected. type: array items: $ref: "#/components/schemas/Trade" From d4fbaf7038a755a278f25a661147567d77ab96f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= <15343819+jmg-duarte@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:43:16 +0100 Subject: [PATCH 2/4] Fix --- crates/autopilot/src/infra/solvers/dto/solve.rs | 2 +- crates/solvers-dto/src/solution.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index c4ec2ddc9e..dd462321c9 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -427,7 +427,7 @@ where while let Some((uid, order)) = map.next_entry::()? { if orders.insert(uid, order).is_some() { return Err(de::Error::custom(format!( - "multiple fills for a single order (uid: {uid})" + "order {uid} is settled by more than one trade" ))); } } diff --git a/crates/solvers-dto/src/solution.rs b/crates/solvers-dto/src/solution.rs index b9ab39884f..9eea432145 100644 --- a/crates/solvers-dto/src/solution.rs +++ b/crates/solvers-dto/src/solution.rs @@ -109,9 +109,9 @@ where continue; }; if !settled.insert(&fulfillment.order) { + let uid = const_hex::encode_prefixed(fulfillment.order.0); return Err(de::Error::custom(format!( - "multiple fills for a single order (uid: {uid})", - const_hex::encode_prefixed(fulfillment.order.0) + "order {uid} is settled by more than one trade" ))); } } From 7488db46b33b9c38a5c5a1bac13392fa9127458a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= <15343819+jmg-duarte@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:29:38 +0100 Subject: [PATCH 3/4] Change approach --- crates/autopilot-svm/src/infra/driver/dto.rs | 4 +- .../domain/competition/winner_selection.rs | 10 ++-- .../src/infra/persistence/dto/auction.rs | 4 +- .../autopilot/src/infra/solvers/dto/solve.rs | 50 +++---------------- .../api/routes/settle/dto/settle_request.rs | 4 +- crates/driver/src/infra/config/file/mod.rs | 4 +- crates/model/src/debug_report.rs | 4 +- crates/model/src/solver_competition.rs | 6 +-- crates/model/src/solver_competition_v2.rs | 8 +-- crates/orderbook/src/dto/auction.rs | 4 +- .../src/trade_finding/external.rs | 4 +- crates/simulator/src/tenderly/dto.rs | 3 +- .../api/routes/solve/dto/solve_response.rs | 4 +- .../src/infra/solver/dto/solution.rs | 14 +++--- crates/solvers-dto/src/auction.rs | 6 ++- crates/solvers-dto/src/solution.rs | 5 +- 16 files changed, 55 insertions(+), 79 deletions(-) diff --git a/crates/autopilot-svm/src/infra/driver/dto.rs b/crates/autopilot-svm/src/infra/driver/dto.rs index 07f111a75b..59da0aa4a2 100644 --- a/crates/autopilot-svm/src/infra/driver/dto.rs +++ b/crates/autopilot-svm/src/infra/driver/dto.rs @@ -8,7 +8,7 @@ use { crate::domain::auction, chain_types::solana::{AppData, IntentHash, Pubkey, Signature}, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, serde_as}, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, std::collections::HashMap, }; @@ -107,7 +107,7 @@ pub struct Solution { #[serde_as(as = "DisplayFromStr")] pub solver: Pubkey, /// Executed amounts per filled order. - #[serde_as(as = "HashMap")] + #[serde_as(as = "MapPreventDuplicates")] pub orders: HashMap, } diff --git a/crates/autopilot/src/domain/competition/winner_selection.rs b/crates/autopilot/src/domain/competition/winner_selection.rs index e7d2376995..88e31161c0 100644 --- a/crates/autopilot/src/domain/competition/winner_selection.rs +++ b/crates/autopilot/src/domain/competition/winner_selection.rs @@ -297,7 +297,7 @@ mod tests { number::serialization::HexOrDecimalU256, serde::Deserialize, serde_json::json, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::{ collections::HashMap, hash::{DefaultHasher, Hash, Hasher}, @@ -967,10 +967,11 @@ mod tests { struct TestCase { pub tokens: Vec<(String, Address)>, pub auction: TestAuction, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub solutions: HashMap, pub expected_fair_solutions: Vec, pub expected_winners: Vec, - #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub expected_reference_scores: HashMap, } @@ -1109,9 +1110,10 @@ mod tests { #[serde_as] #[derive(Deserialize, Debug)] struct TestAuction { + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub orders: HashMap, #[serde(default)] - #[serde_as(as = "Option>")] + #[serde_as(as = "Option>")] pub prices: Option>, } @@ -1128,9 +1130,11 @@ mod tests { pub buy_amount: eth::U256, } + #[serde_as] #[derive(Deserialize, Debug)] struct TestSolution { pub solver: String, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub trades: HashMap, } diff --git a/crates/autopilot/src/infra/persistence/dto/auction.rs b/crates/autopilot/src/infra/persistence/dto/auction.rs index b3aa28fcbe..c29a6e9a80 100644 --- a/crates/autopilot/src/infra/persistence/dto/auction.rs +++ b/crates/autopilot/src/infra/persistence/dto/auction.rs @@ -5,7 +5,7 @@ use { eth_domain_types as eth, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::BTreeMap, }; @@ -36,7 +36,7 @@ pub fn from_domain(auction: &domain::RawAuctionData) -> RawAuctionData { pub struct RawAuctionData { pub block: u64, pub orders: Vec, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub prices: BTreeMap, #[serde(default)] pub surplus_capturing_jit_order_owners: Vec
, diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index dd462321c9..9dfc98f5a6 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -16,13 +16,12 @@ use { number::serialization::HexOrDecimalU256, observe::http_body::Measured, reqwest::{RequestBuilder, header::HeaderValue}, - serde::{Deserialize, Deserializer, Serialize, de}, - serde_with::{DisplayFromStr, serde_as}, + serde::{Deserialize, Serialize}, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, std::{ borrow::Cow, collections::{HashMap, HashSet}, convert::Infallible, - fmt, io::Write, time::Duration, }, @@ -389,55 +388,20 @@ pub struct Solution { pub solution_id: u64, /// Address used by the driver to submit the settlement onchain. pub submission_address: Address, - #[serde(deserialize_with = "deserialize_orders")] + /// A partially fillable order may be split across several solutions, but + /// a single solution settles each order exactly once. + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub orders: HashMap, /// Deprecated: uniform clearing prices are no longer used by the /// autopilot. Kept here purely so we can detect and log drivers that /// still send them, in order to chase them down before the field is /// removed entirely. #[serde(default)] - #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, pub gas: Option, } -/// 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>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct Visitor; - - impl<'de> de::Visitor<'de> for Visitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map of order uid to executed amounts") - } - - fn visit_map(self, mut map: A) -> Result - where - A: de::MapAccess<'de>, - { - let mut orders = HashMap::with_capacity(map.size_hint().unwrap_or_default()); - while let Some((uid, order)) = map.next_entry::()? { - if orders.insert(uid, order).is_some() { - return Err(de::Error::custom(format!( - "order {uid} is settled by more than one trade" - ))); - } - } - Ok(orders) - } - } - - deserializer.deserialize_map(Visitor) -} - #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Response { @@ -528,7 +492,7 @@ mod tests { let err = serde_json::from_str::(&response).unwrap_err(); assert!( err.to_string() - .contains(&format!("order {uid} is settled by more than one trade")), + .contains("invalid entry: found duplicate key"), "unexpected error: {err}" ); } diff --git a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index f2ea5ef340..933e7cba5f 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -2,7 +2,7 @@ use { crate::infra::api::routes::solve::dto::solve_request::Order, eth_domain_types as eth, serde::Deserialize, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::HashMap, }; @@ -32,7 +32,7 @@ pub struct FastPath { /// The sell/buy amounts defining the exact price the order must fill at. pub limit_prices: LimitPrices, /// Native prices (wei per 10**18) for the order's tokens. - #[serde_as(as = "HashMap<_, serde_ext::U256>")] + #[serde_as(as = "MapPreventDuplicates<_, serde_ext::U256>")] pub native_prices: HashMap, } diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index fdd3f9502a..9f26c2076f 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -7,7 +7,7 @@ use { number::serialization::HexOrDecimalU256, reqwest::Url, serde::{Deserialize, Deserializer, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, solver::solver::Arn, std::{collections::HashMap, num::NonZeroUsize, time::Duration}, }; @@ -269,6 +269,7 @@ struct SolverConfig { timeouts: Timeouts, #[serde(default)] + #[serde_as(as = "MapPreventDuplicates<_, _>")] request_headers: HashMap, /// Determines whether the `solver` or the `driver` handles the fees @@ -832,6 +833,7 @@ fn default_simulation_bad_token_max_age() -> Duration { pub struct BadOrderDetectionConfig { /// Which tokens are explicitly supported or unsupported by the solver. #[serde(default)] + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub token_supported: HashMap, /// Whether the solver opted into detecting unsupported diff --git a/crates/model/src/debug_report.rs b/crates/model/src/debug_report.rs index 21c9cabd61..f850a4896d 100644 --- a/crates/model/src/debug_report.rs +++ b/crates/model/src/debug_report.rs @@ -4,7 +4,7 @@ use { bigdecimal::BigDecimal, chrono::{DateTime, Utc}, serde::Serialize, - serde_with::{DisplayFromStr, serde_as}, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, std::collections::HashMap, }; @@ -37,7 +37,7 @@ pub struct Auction { pub id: i64, pub block: i64, pub deadline: i64, - #[serde_as(as = "HashMap<_, DisplayFromStr>")] + #[serde_as(as = "MapPreventDuplicates<_, DisplayFromStr>")] pub native_prices: HashMap, pub proposed_solutions: Vec, pub executions: Vec, diff --git a/crates/model/src/solver_competition.rs b/crates/model/src/solver_competition.rs index 20b9dd934b..e1cc44f538 100644 --- a/crates/model/src/solver_competition.rs +++ b/crates/model/src/solver_competition.rs @@ -3,7 +3,7 @@ use { alloy_primitives::{Address, B256, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::BTreeMap, }; @@ -34,7 +34,7 @@ pub struct SolverCompetitionAPI { #[serde(rename_all = "camelCase")] pub struct CompetitionAuction { pub orders: Vec, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub prices: BTreeMap, } @@ -49,7 +49,7 @@ pub struct SolverSettlement { pub score: Option, #[serde(default)] pub ranking: usize, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub clearing_prices: BTreeMap, pub orders: Vec, #[serde(default)] diff --git a/crates/model/src/solver_competition_v2.rs b/crates/model/src/solver_competition_v2.rs index f36447f506..475ca8ed75 100644 --- a/crates/model/src/solver_competition_v2.rs +++ b/crates/model/src/solver_competition_v2.rs @@ -3,7 +3,7 @@ use { alloy_primitives::{Address, B256, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::BTreeMap, }; @@ -15,7 +15,7 @@ pub struct Response { pub auction_start_block: i64, pub auction_deadline_block: i64, pub transaction_hashes: Vec, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub reference_scores: BTreeMap, pub auction: Auction, pub solutions: Vec, @@ -26,7 +26,7 @@ pub struct Response { #[serde(rename_all = "camelCase")] pub struct Auction { pub orders: Vec, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub prices: BTreeMap, } @@ -38,7 +38,7 @@ pub struct Solution { #[serde_as(as = "HexOrDecimalU256")] pub score: U256, pub ranking: i64, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub clearing_prices: BTreeMap, pub orders: Vec, pub is_winner: bool, diff --git a/crates/orderbook/src/dto/auction.rs b/crates/orderbook/src/dto/auction.rs index 54268c0ad9..ff21b4d3f2 100644 --- a/crates/orderbook/src/dto/auction.rs +++ b/crates/orderbook/src/dto/auction.rs @@ -3,7 +3,7 @@ use { alloy::primitives::{Address, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::BTreeMap, }; @@ -15,7 +15,7 @@ use { pub struct Auction { pub block: u64, pub orders: Vec, - #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub prices: BTreeMap, #[serde(default)] pub surplus_capturing_jit_order_owners: Vec
, diff --git a/crates/price-estimation/src/trade_finding/external.rs b/crates/price-estimation/src/trade_finding/external.rs index f160177f21..33210235c9 100644 --- a/crates/price-estimation/src/trade_finding/external.rs +++ b/crates/price-estimation/src/trade_finding/external.rs @@ -388,7 +388,7 @@ pub mod dto { }, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::HashMap, }; @@ -435,7 +435,7 @@ pub mod dto { #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Quote { - #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, #[serde(default)] pub pre_interactions: Vec, diff --git a/crates/simulator/src/tenderly/dto.rs b/crates/simulator/src/tenderly/dto.rs index 0800e57281..d696ec36af 100644 --- a/crates/simulator/src/tenderly/dto.rs +++ b/crates/simulator/src/tenderly/dto.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, B256, U256, map::B256Map}, eth_domain_types as eth, serde::{Deserialize, Serialize}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::HashMap, }; @@ -49,6 +49,7 @@ pub struct Request { pub generate_access_list: Option, /// Overrides for a given contract. #[serde(skip_serializing_if = "Option::is_none")] + #[serde_as(as = "Option>")] pub state_objects: Option>, /// EIP-2930 access list used by the transaction. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs b/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs index 4f63f5b2b3..09294c3a6b 100644 --- a/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs +++ b/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs @@ -8,7 +8,7 @@ use { crate::domain::{self, order_uid::OrderUid}, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, serde_as}, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, solana_sdk::pubkey::Pubkey, std::collections::HashMap, }; @@ -34,7 +34,7 @@ pub struct Solution { #[serde_as(as = "DisplayFromStr")] solver: Pubkey, /// Executed amounts per filled order. - #[serde_as(as = "HashMap")] + #[serde_as(as = "MapPreventDuplicates")] orders: HashMap, } diff --git a/crates/solana-driver/src/infra/solver/dto/solution.rs b/crates/solana-driver/src/infra/solver/dto/solution.rs index dd9c89a4f5..249392e535 100644 --- a/crates/solana-driver/src/infra/solver/dto/solution.rs +++ b/crates/solana-driver/src/infra/solver/dto/solution.rs @@ -9,7 +9,7 @@ use { infra::solver::dto::auction::{Auction, Order}, }, serde::Deserialize, - serde_with::serde_as, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, solana_sdk::{ instruction::{AccountMeta as SdkAccountMeta, Instruction as SdkInstruction}, pubkey::Pubkey, @@ -36,7 +36,7 @@ pub struct Solution { /// `executed_sell * price_sell == executed_buy * price_buy`. The engine /// currently only produces single-order solutions, so the pair is the /// executed swap's ratio. - #[serde_as(as = "HashMap")] + #[serde_as(as = "MapPreventDuplicates")] pub prices: HashMap>, pub trades: Vec, pub interactions: Vec, @@ -45,7 +45,7 @@ pub struct Solution { pub cu_estimate: Option, /// The address lookup tables the interactions assume. #[serde(default)] - #[serde_as(as = "Vec")] + #[serde_as(as = "Vec")] pub address_lookup_tables: Vec, } @@ -55,10 +55,10 @@ pub struct Solution { #[serde(rename_all = "camelCase")] pub struct Trade { /// The order's 32-byte intent hash. - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde_as(as = "DisplayFromStr")] pub order_uid: OrderUid, /// Sell-token units for sell orders, buy-token units for buy orders. - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde_as(as = "DisplayFromStr")] pub executed_amount: u64, } @@ -118,7 +118,7 @@ impl Trade { #[derive(Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Instruction { - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde_as(as = "DisplayFromStr")] pub program_id: Pubkey, pub accounts: Vec, #[serde_as(as = "serde_with::base64::Base64")] @@ -130,7 +130,7 @@ pub struct Instruction { #[derive(Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AccountMeta { - #[serde_as(as = "serde_with::DisplayFromStr")] + #[serde_as(as = "DisplayFromStr")] pub pubkey: Pubkey, pub is_signer: bool, pub is_writable: bool, diff --git a/crates/solvers-dto/src/auction.rs b/crates/solvers-dto/src/auction.rs index 1160c462d7..a769fd6e5a 100644 --- a/crates/solvers-dto/src/auction.rs +++ b/crates/solvers-dto/src/auction.rs @@ -4,7 +4,7 @@ use { bigdecimal::BigDecimal, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, serde_as}, + serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, std::{ collections::{BTreeMap, HashMap}, sync::Arc, @@ -17,6 +17,7 @@ use { pub struct Auction { #[serde_as(as = "Option")] pub id: Option, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub orders: Vec, pub liquidity: Vec, @@ -198,6 +199,7 @@ pub struct ConstantProductPool { pub router: Address, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub fee: BigDecimal, } @@ -219,6 +221,7 @@ pub struct WeightedProductPool { pub balancer_pool_id: B256, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub fee: BigDecimal, pub version: WeightedProductVersion, @@ -250,6 +253,7 @@ pub struct StablePool { pub balancer_pool_id: B256, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, + #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub amplification_parameter: BigDecimal, pub fee: BigDecimal, diff --git a/crates/solvers-dto/src/solution.rs b/crates/solvers-dto/src/solution.rs index 9eea432145..d0f2956c83 100644 --- a/crates/solvers-dto/src/solution.rs +++ b/crates/solvers-dto/src/solution.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Deserializer, Serialize, de}, - serde_with::serde_as, + serde_with::{MapPreventDuplicates, serde_as}, std::collections::{HashMap, HashSet}, }; @@ -76,7 +76,7 @@ impl Default for SolverResponse { #[serde(rename_all = "camelCase")] pub struct Solution { pub id: u64, - #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] + #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] pub prices: HashMap, #[serde(deserialize_with = "deserialize_trades")] pub trades: Vec, @@ -90,6 +90,7 @@ pub struct Solution { #[serde(flatten)] pub gas_fee_override: Option, #[serde(skip_serializing_if = "Option::is_none", default)] + #[serde_as(as = "Option>")] pub flashloans: Option>, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub wrappers: Vec, From cfe830a6ac00b80433e64bceb06b78149b62ef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Duarte?= <15343819+jmg-duarte@users.noreply.github.com> Date: Mon, 7 Sep 2026 10:41:46 +0100 Subject: [PATCH 4/4] semi-revert --- crates/autopilot-svm/src/infra/driver/dto.rs | 4 ++-- .../src/domain/competition/winner_selection.rs | 10 +++------- .../autopilot/src/infra/persistence/dto/auction.rs | 4 ++-- crates/autopilot/src/infra/solvers/dto/solve.rs | 2 +- .../infra/api/routes/settle/dto/settle_request.rs | 4 ++-- crates/driver/src/infra/config/file/mod.rs | 4 +--- crates/model/src/debug_report.rs | 4 ++-- crates/model/src/solver_competition.rs | 6 +++--- crates/model/src/solver_competition_v2.rs | 8 ++++---- crates/orderbook/src/dto/auction.rs | 4 ++-- .../price-estimation/src/trade_finding/external.rs | 4 ++-- crates/simulator/src/tenderly/dto.rs | 3 +-- .../infra/api/routes/solve/dto/solve_response.rs | 4 ++-- .../solana-driver/src/infra/solver/dto/solution.rs | 14 +++++++------- crates/solvers-dto/src/auction.rs | 6 +----- crates/solvers-dto/src/solution.rs | 5 ++--- 16 files changed, 37 insertions(+), 49 deletions(-) diff --git a/crates/autopilot-svm/src/infra/driver/dto.rs b/crates/autopilot-svm/src/infra/driver/dto.rs index 59da0aa4a2..07f111a75b 100644 --- a/crates/autopilot-svm/src/infra/driver/dto.rs +++ b/crates/autopilot-svm/src/infra/driver/dto.rs @@ -8,7 +8,7 @@ use { crate::domain::auction, chain_types::solana::{AppData, IntentHash, Pubkey, Signature}, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, + serde_with::{DisplayFromStr, serde_as}, std::collections::HashMap, }; @@ -107,7 +107,7 @@ pub struct Solution { #[serde_as(as = "DisplayFromStr")] pub solver: Pubkey, /// Executed amounts per filled order. - #[serde_as(as = "MapPreventDuplicates")] + #[serde_as(as = "HashMap")] pub orders: HashMap, } diff --git a/crates/autopilot/src/domain/competition/winner_selection.rs b/crates/autopilot/src/domain/competition/winner_selection.rs index 88e31161c0..e7d2376995 100644 --- a/crates/autopilot/src/domain/competition/winner_selection.rs +++ b/crates/autopilot/src/domain/competition/winner_selection.rs @@ -297,7 +297,7 @@ mod tests { number::serialization::HexOrDecimalU256, serde::Deserialize, serde_json::json, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::{ collections::HashMap, hash::{DefaultHasher, Hash, Hasher}, @@ -967,11 +967,10 @@ mod tests { struct TestCase { pub tokens: Vec<(String, Address)>, pub auction: TestAuction, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub solutions: HashMap, pub expected_fair_solutions: Vec, pub expected_winners: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub expected_reference_scores: HashMap, } @@ -1110,10 +1109,9 @@ mod tests { #[serde_as] #[derive(Deserialize, Debug)] struct TestAuction { - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub orders: HashMap, #[serde(default)] - #[serde_as(as = "Option>")] + #[serde_as(as = "Option>")] pub prices: Option>, } @@ -1130,11 +1128,9 @@ mod tests { pub buy_amount: eth::U256, } - #[serde_as] #[derive(Deserialize, Debug)] struct TestSolution { pub solver: String, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub trades: HashMap, } diff --git a/crates/autopilot/src/infra/persistence/dto/auction.rs b/crates/autopilot/src/infra/persistence/dto/auction.rs index c29a6e9a80..b3aa28fcbe 100644 --- a/crates/autopilot/src/infra/persistence/dto/auction.rs +++ b/crates/autopilot/src/infra/persistence/dto/auction.rs @@ -5,7 +5,7 @@ use { eth_domain_types as eth, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::BTreeMap, }; @@ -36,7 +36,7 @@ pub fn from_domain(auction: &domain::RawAuctionData) -> RawAuctionData { pub struct RawAuctionData { pub block: u64, pub orders: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, #[serde(default)] pub surplus_capturing_jit_order_owners: Vec
, diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index 9dfc98f5a6..674d55cb98 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -397,7 +397,7 @@ pub struct Solution { /// still send them, in order to chase them down before the field is /// removed entirely. #[serde(default)] - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, pub gas: Option, } diff --git a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs index 933e7cba5f..f2ea5ef340 100644 --- a/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs +++ b/crates/driver/src/infra/api/routes/settle/dto/settle_request.rs @@ -2,7 +2,7 @@ use { crate::infra::api::routes::solve::dto::solve_request::Order, eth_domain_types as eth, serde::Deserialize, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::HashMap, }; @@ -32,7 +32,7 @@ pub struct FastPath { /// The sell/buy amounts defining the exact price the order must fill at. pub limit_prices: LimitPrices, /// Native prices (wei per 10**18) for the order's tokens. - #[serde_as(as = "MapPreventDuplicates<_, serde_ext::U256>")] + #[serde_as(as = "HashMap<_, serde_ext::U256>")] pub native_prices: HashMap, } diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 9f26c2076f..fdd3f9502a 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -7,7 +7,7 @@ use { number::serialization::HexOrDecimalU256, reqwest::Url, serde::{Deserialize, Deserializer, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, solver::solver::Arn, std::{collections::HashMap, num::NonZeroUsize, time::Duration}, }; @@ -269,7 +269,6 @@ struct SolverConfig { timeouts: Timeouts, #[serde(default)] - #[serde_as(as = "MapPreventDuplicates<_, _>")] request_headers: HashMap, /// Determines whether the `solver` or the `driver` handles the fees @@ -833,7 +832,6 @@ fn default_simulation_bad_token_max_age() -> Duration { pub struct BadOrderDetectionConfig { /// Which tokens are explicitly supported or unsupported by the solver. #[serde(default)] - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub token_supported: HashMap, /// Whether the solver opted into detecting unsupported diff --git a/crates/model/src/debug_report.rs b/crates/model/src/debug_report.rs index f850a4896d..21c9cabd61 100644 --- a/crates/model/src/debug_report.rs +++ b/crates/model/src/debug_report.rs @@ -4,7 +4,7 @@ use { bigdecimal::BigDecimal, chrono::{DateTime, Utc}, serde::Serialize, - serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, + serde_with::{DisplayFromStr, serde_as}, std::collections::HashMap, }; @@ -37,7 +37,7 @@ pub struct Auction { pub id: i64, pub block: i64, pub deadline: i64, - #[serde_as(as = "MapPreventDuplicates<_, DisplayFromStr>")] + #[serde_as(as = "HashMap<_, DisplayFromStr>")] pub native_prices: HashMap, pub proposed_solutions: Vec, pub executions: Vec, diff --git a/crates/model/src/solver_competition.rs b/crates/model/src/solver_competition.rs index e1cc44f538..20b9dd934b 100644 --- a/crates/model/src/solver_competition.rs +++ b/crates/model/src/solver_competition.rs @@ -3,7 +3,7 @@ use { alloy_primitives::{Address, B256, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::BTreeMap, }; @@ -34,7 +34,7 @@ pub struct SolverCompetitionAPI { #[serde(rename_all = "camelCase")] pub struct CompetitionAuction { pub orders: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, } @@ -49,7 +49,7 @@ pub struct SolverSettlement { pub score: Option, #[serde(default)] pub ranking: usize, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub clearing_prices: BTreeMap, pub orders: Vec, #[serde(default)] diff --git a/crates/model/src/solver_competition_v2.rs b/crates/model/src/solver_competition_v2.rs index 475ca8ed75..f36447f506 100644 --- a/crates/model/src/solver_competition_v2.rs +++ b/crates/model/src/solver_competition_v2.rs @@ -3,7 +3,7 @@ use { alloy_primitives::{Address, B256, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::BTreeMap, }; @@ -15,7 +15,7 @@ pub struct Response { pub auction_start_block: i64, pub auction_deadline_block: i64, pub transaction_hashes: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub reference_scores: BTreeMap, pub auction: Auction, pub solutions: Vec, @@ -26,7 +26,7 @@ pub struct Response { #[serde(rename_all = "camelCase")] pub struct Auction { pub orders: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, } @@ -38,7 +38,7 @@ pub struct Solution { #[serde_as(as = "HexOrDecimalU256")] pub score: U256, pub ranking: i64, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub clearing_prices: BTreeMap, pub orders: Vec, pub is_winner: bool, diff --git a/crates/orderbook/src/dto/auction.rs b/crates/orderbook/src/dto/auction.rs index ff21b4d3f2..54268c0ad9 100644 --- a/crates/orderbook/src/dto/auction.rs +++ b/crates/orderbook/src/dto/auction.rs @@ -3,7 +3,7 @@ use { alloy::primitives::{Address, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::BTreeMap, }; @@ -15,7 +15,7 @@ use { pub struct Auction { pub block: u64, pub orders: Vec, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "BTreeMap<_, HexOrDecimalU256>")] pub prices: BTreeMap, #[serde(default)] pub surplus_capturing_jit_order_owners: Vec
, diff --git a/crates/price-estimation/src/trade_finding/external.rs b/crates/price-estimation/src/trade_finding/external.rs index 33210235c9..f160177f21 100644 --- a/crates/price-estimation/src/trade_finding/external.rs +++ b/crates/price-estimation/src/trade_finding/external.rs @@ -388,7 +388,7 @@ pub mod dto { }, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::HashMap, }; @@ -435,7 +435,7 @@ pub mod dto { #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Quote { - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub clearing_prices: HashMap, #[serde(default)] pub pre_interactions: Vec, diff --git a/crates/simulator/src/tenderly/dto.rs b/crates/simulator/src/tenderly/dto.rs index d696ec36af..0800e57281 100644 --- a/crates/simulator/src/tenderly/dto.rs +++ b/crates/simulator/src/tenderly/dto.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, B256, U256, map::B256Map}, eth_domain_types as eth, serde::{Deserialize, Serialize}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::HashMap, }; @@ -49,7 +49,6 @@ pub struct Request { pub generate_access_list: Option, /// Overrides for a given contract. #[serde(skip_serializing_if = "Option::is_none")] - #[serde_as(as = "Option>")] pub state_objects: Option>, /// EIP-2930 access list used by the transaction. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs b/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs index 09294c3a6b..4f63f5b2b3 100644 --- a/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs +++ b/crates/solana-driver/src/infra/api/routes/solve/dto/solve_response.rs @@ -8,7 +8,7 @@ use { crate::domain::{self, order_uid::OrderUid}, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, + serde_with::{DisplayFromStr, serde_as}, solana_sdk::pubkey::Pubkey, std::collections::HashMap, }; @@ -34,7 +34,7 @@ pub struct Solution { #[serde_as(as = "DisplayFromStr")] solver: Pubkey, /// Executed amounts per filled order. - #[serde_as(as = "MapPreventDuplicates")] + #[serde_as(as = "HashMap")] orders: HashMap, } diff --git a/crates/solana-driver/src/infra/solver/dto/solution.rs b/crates/solana-driver/src/infra/solver/dto/solution.rs index 249392e535..dd9c89a4f5 100644 --- a/crates/solana-driver/src/infra/solver/dto/solution.rs +++ b/crates/solana-driver/src/infra/solver/dto/solution.rs @@ -9,7 +9,7 @@ use { infra::solver::dto::auction::{Auction, Order}, }, serde::Deserialize, - serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, + serde_with::serde_as, solana_sdk::{ instruction::{AccountMeta as SdkAccountMeta, Instruction as SdkInstruction}, pubkey::Pubkey, @@ -36,7 +36,7 @@ pub struct Solution { /// `executed_sell * price_sell == executed_buy * price_buy`. The engine /// currently only produces single-order solutions, so the pair is the /// executed swap's ratio. - #[serde_as(as = "MapPreventDuplicates")] + #[serde_as(as = "HashMap")] pub prices: HashMap>, pub trades: Vec, pub interactions: Vec, @@ -45,7 +45,7 @@ pub struct Solution { pub cu_estimate: Option, /// The address lookup tables the interactions assume. #[serde(default)] - #[serde_as(as = "Vec")] + #[serde_as(as = "Vec")] pub address_lookup_tables: Vec, } @@ -55,10 +55,10 @@ pub struct Solution { #[serde(rename_all = "camelCase")] pub struct Trade { /// The order's 32-byte intent hash. - #[serde_as(as = "DisplayFromStr")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub order_uid: OrderUid, /// Sell-token units for sell orders, buy-token units for buy orders. - #[serde_as(as = "DisplayFromStr")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub executed_amount: u64, } @@ -118,7 +118,7 @@ impl Trade { #[derive(Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Instruction { - #[serde_as(as = "DisplayFromStr")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub program_id: Pubkey, pub accounts: Vec, #[serde_as(as = "serde_with::base64::Base64")] @@ -130,7 +130,7 @@ pub struct Instruction { #[derive(Debug, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AccountMeta { - #[serde_as(as = "DisplayFromStr")] + #[serde_as(as = "serde_with::DisplayFromStr")] pub pubkey: Pubkey, pub is_signer: bool, pub is_writable: bool, diff --git a/crates/solvers-dto/src/auction.rs b/crates/solvers-dto/src/auction.rs index a769fd6e5a..1160c462d7 100644 --- a/crates/solvers-dto/src/auction.rs +++ b/crates/solvers-dto/src/auction.rs @@ -4,7 +4,7 @@ use { bigdecimal::BigDecimal, number::serialization::HexOrDecimalU256, serde::{Deserialize, Serialize}, - serde_with::{DisplayFromStr, MapPreventDuplicates, serde_as}, + serde_with::{DisplayFromStr, serde_as}, std::{ collections::{BTreeMap, HashMap}, sync::Arc, @@ -17,7 +17,6 @@ use { pub struct Auction { #[serde_as(as = "Option")] pub id: Option, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub orders: Vec, pub liquidity: Vec, @@ -199,7 +198,6 @@ pub struct ConstantProductPool { pub router: Address, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub fee: BigDecimal, } @@ -221,7 +219,6 @@ pub struct WeightedProductPool { pub balancer_pool_id: B256, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub fee: BigDecimal, pub version: WeightedProductVersion, @@ -253,7 +250,6 @@ pub struct StablePool { pub balancer_pool_id: B256, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, - #[serde_as(as = "MapPreventDuplicates<_, _>")] pub tokens: HashMap, pub amplification_parameter: BigDecimal, pub fee: BigDecimal, diff --git a/crates/solvers-dto/src/solution.rs b/crates/solvers-dto/src/solution.rs index d0f2956c83..9eea432145 100644 --- a/crates/solvers-dto/src/solution.rs +++ b/crates/solvers-dto/src/solution.rs @@ -2,7 +2,7 @@ use { alloy_primitives::{Address, U256}, number::serialization::HexOrDecimalU256, serde::{Deserialize, Deserializer, Serialize, de}, - serde_with::{MapPreventDuplicates, serde_as}, + serde_with::serde_as, std::collections::{HashMap, HashSet}, }; @@ -76,7 +76,7 @@ impl Default for SolverResponse { #[serde(rename_all = "camelCase")] pub struct Solution { pub id: u64, - #[serde_as(as = "MapPreventDuplicates<_, HexOrDecimalU256>")] + #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub prices: HashMap, #[serde(deserialize_with = "deserialize_trades")] pub trades: Vec, @@ -90,7 +90,6 @@ pub struct Solution { #[serde(flatten)] pub gas_fee_override: Option, #[serde(skip_serializing_if = "Option::is_none", default)] - #[serde_as(as = "Option>")] pub flashloans: Option>, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub wrappers: Vec,