From 178013a25cfe0e13dbfdb0d4ac794e22983b145c Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:07:44 +0000 Subject: [PATCH 1/6] Use only scores computed by the autopilot --- .../autopilot/src/domain/competition/mod.rs | 16 ++++-------- .../src/domain/competition/participant.rs | 4 +-- .../domain/competition/winner_selection.rs | 26 +++++-------------- .../autopilot/src/infra/solvers/dto/solve.rs | 3 --- .../api/routes/solve/dto/solve_response.rs | 3 --- 5 files changed, 14 insertions(+), 38 deletions(-) diff --git a/crates/autopilot/src/domain/competition/mod.rs b/crates/autopilot/src/domain/competition/mod.rs index 0e368e1ad2..b2ac1f2204 100644 --- a/crates/autopilot/src/domain/competition/mod.rs +++ b/crates/autopilot/src/domain/competition/mod.rs @@ -23,31 +23,27 @@ pub struct Solution { /// A solution ID provided by the solver. id: SolutionId, solver: Address, - /// Score reported by the solver in their response. - score: Score, orders: HashMap, prices: auction::Prices, /// Score computed by the autopilot based on the solution /// of the solver. // TODO: refactor this to compute the score in the constructor - computed_score: Option, + score: Option, } impl Solution { pub fn new( id: SolutionId, solver: Address, - score: Score, orders: HashMap, prices: auction::Prices, ) -> Self { Self { id, solver, - score, orders, prices, - computed_score: None, + score: None, } } @@ -60,11 +56,9 @@ impl Solution { } pub fn score(&self) -> Score { - self.score - } - - pub fn computed_score(&self) -> Option<&Score> { - self.computed_score.as_ref() + self.score.expect( + "this function only gets called after the winner selection populated this value", + ) } pub fn order_ids(&self) -> impl Iterator + std::fmt::Debug { diff --git a/crates/autopilot/src/domain/competition/participant.rs b/crates/autopilot/src/domain/competition/participant.rs index 0a5636c3b4..766ea9b9c4 100644 --- a/crates/autopilot/src/domain/competition/participant.rs +++ b/crates/autopilot/src/domain/competition/participant.rs @@ -24,8 +24,8 @@ impl Participant { &self.solution } - pub fn set_computed_score(&mut self, score: Score) { - self.solution.computed_score = Some(score); + pub fn set_score(&mut self, score: Score) { + self.solution.score = Some(score); } pub fn driver(&self) -> &Arc { diff --git a/crates/autopilot/src/domain/competition/winner_selection.rs b/crates/autopilot/src/domain/competition/winner_selection.rs index b94ecb6a91..44d3565535 100644 --- a/crates/autopilot/src/domain/competition/winner_selection.rs +++ b/crates/autopilot/src/domain/competition/winner_selection.rs @@ -73,7 +73,7 @@ impl Arbitrator { // winners before non-winners std::cmp::Reverse(participant.is_winner()), // high score before low score - std::cmp::Reverse(participant.solution().computed_score().cloned()), + std::cmp::Reverse(participant.solution().score()), ) }); Ranking { @@ -94,12 +94,7 @@ impl Arbitrator { participants.sort_by_key(|participant| { std::cmp::Reverse( // we use the computed score to not trust the score provided by solvers - participant - .solution() - .computed_score() - .expect("every remaining participant has a computed score") - .get() - .0, + participant.solution().score().get().0, ) }); let baseline_scores = compute_baseline_scores(&scores_by_solution); @@ -180,7 +175,7 @@ impl Arbitrator { let score = solutions_without_solver .enumerate() .filter(|(index, _)| winner_indices.contains(index)) - .filter_map(|(_, solution)| solution.computed_score) + .filter_map(|(_, solution)| solution.score) .reduce(Score::saturating_add) .unwrap_or_default(); reference_scores.insert(solver, score); @@ -267,7 +262,7 @@ fn compute_scores_by_solution( }, score, ); - p.set_computed_score(total_score); + p.set_score(total_score); true } Err(err) => { @@ -471,7 +466,7 @@ mod tests { Price, order::{self, AppDataHash}, }, - competition::{Participant, Score, Solution, TradedOrder, Unranked}, + competition::{Participant, Solution, TradedOrder, Unranked}, eth::{self, TokenAddress}, }, infra::Driver, @@ -1216,7 +1211,7 @@ mod tests { // winners before non-winners std::cmp::Reverse(a.is_winner()), // high score before low score - std::cmp::Reverse(a.solution().computed_score().unwrap()) + std::cmp::Reverse(a.solution().score()) ))); assert_eq!(winners.len(), self.expected_winners.len()); for (actual, expected) in winners.iter().zip(&self.expected_winners) { @@ -1391,14 +1386,7 @@ mod tests { let trade_order_map: HashMap = trades.into_iter().collect(); let solver_address = solver_address.into_alloy(); - let solution = Solution::new( - solution_id, - solver_address, - // provided score does not matter as it's computed automatically by the arbitrator - Score(eth::Ether(eth::U256::ZERO)), - trade_order_map, - prices, - ); + let solution = Solution::new(solution_id, solver_address, trade_order_map, prices); let driver = Driver::try_new( url::Url::parse("http://localhost").unwrap(), diff --git a/crates/autopilot/src/infra/solvers/dto/solve.rs b/crates/autopilot/src/infra/solvers/dto/solve.rs index 847d18d79d..a66b065fa8 100644 --- a/crates/autopilot/src/infra/solvers/dto/solve.rs +++ b/crates/autopilot/src/infra/solvers/dto/solve.rs @@ -110,7 +110,6 @@ impl Solution { Ok(domain::competition::Solution::new( self.solution_id, self.submission_address, - domain::competition::Score::try_new(self.score.into())?, self.orders .into_iter() .map(|(o, amounts)| (o.into(), amounts.into_domain())) @@ -186,8 +185,6 @@ pub struct Solution { /// Unique ID of the solution (per driver competition), used to identify /// it in subsequent requests (reveal, settle). pub solution_id: u64, - #[serde_as(as = "HexOrDecimalU256")] - pub score: U256, /// Address used by the driver to submit the settlement onchain. pub submission_address: Address, pub orders: HashMap, diff --git a/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs index 3c57936481..90ee9bd020 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs @@ -30,7 +30,6 @@ impl Solution { pub fn new(solution_id: u64, solved: competition::Solved, solver: &Solver) -> Self { Self { solution_id, - score: solved.score.0, submission_address: solver.address(), orders: solved .trades @@ -71,8 +70,6 @@ pub struct Solution { /// Unique ID of the solution (per driver competition), used to identify it /// in subsequent requests (reveal, settle). solution_id: u64, - #[serde_as(as = "serialize::U256")] - score: eth::U256, submission_address: eth::Address, #[serde_as(as = "HashMap")] orders: HashMap, From 230c410e103f7e11cb19c688a1890ab5d1e321f7 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:27:31 +0000 Subject: [PATCH 2/6] remove score handling from driver tests --- crates/driver/src/tests/cases/jit_orders.rs | 4 ---- crates/driver/src/tests/cases/protocol_fees.rs | 4 ---- crates/driver/src/tests/setup/mod.rs | 11 +---------- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 66a7e44782..86c88c9d33 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -114,10 +114,6 @@ async fn protocol_fee_test_case(test_case: TestCase) { .await; let result = test.solve().await.ok(); - assert!(is_approximately_equal( - result.score(), - test_case.solution.expected_score, - )); result.jit_orders(&[jit_order]); } diff --git a/crates/driver/src/tests/cases/protocol_fees.rs b/crates/driver/src/tests/cases/protocol_fees.rs index dbcbbddfb3..d7222b3097 100644 --- a/crates/driver/src/tests/cases/protocol_fees.rs +++ b/crates/driver/src/tests/cases/protocol_fees.rs @@ -94,10 +94,6 @@ async fn protocol_fee_test_case(test_case: TestCase) { .await; let result = test.solve().await.ok(); - assert!(is_approximately_equal( - result.score(), - test_case.expected_score - )); result.orders(&[order]); } diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index 391f125b30..3fcbe6c3af 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1262,19 +1262,10 @@ impl SolveOk<'_> { let solution = solutions[0].clone(); assert!(solution.is_object()); // response contains 1 optional field - assert!((5..=6).contains(&solution.as_object().unwrap().len())); + assert!((4..=5).contains(&solution.as_object().unwrap().len())); solution } - /// Extracts the score from the response. Since response can contain - /// multiple solutions, it takes the score from the first solution. - pub fn score(&self) -> eth::U256 { - let solution = self.solution(); - assert!(solution.get("score").is_some()); - let score = solution.get("score").unwrap().as_str().unwrap(); - eth::U256::from_str_radix(score, 10).unwrap() - } - /// Ensures that `/solve` returns no solutions. pub fn empty(self) { assert!(self.solutions().is_empty()); From eec4946e26fecf1b919ceacede853a6d659a9cc8 Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:47:56 +0000 Subject: [PATCH 3/6] `clippy` lints --- crates/driver/src/tests/cases/jit_orders.rs | 6 +----- crates/driver/src/tests/cases/protocol_fees.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 86c88c9d33..24cb185387 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -5,7 +5,7 @@ use crate::{ }, tests::{ self, - cases::{EtherExt, is_approximately_equal}, + cases::EtherExt, setup::{ self, ExpectedOrderAmounts, @@ -43,7 +43,6 @@ struct JitOrder { struct Solution { jit_order: JitOrder, - expected_score: eth::U256, } struct TestCase { @@ -147,9 +146,6 @@ async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped( side: Side::Buy, }, }, - // Surplus is 40 ETH worth of sell tokens, converted to buy tokens using the order's - // limit price (50 / 60 = 80%) this leaves us with a score of 32 ETH. - expected_score: 32.ether().into_wei(), }, }; diff --git a/crates/driver/src/tests/cases/protocol_fees.rs b/crates/driver/src/tests/cases/protocol_fees.rs index d7222b3097..95de2ae912 100644 --- a/crates/driver/src/tests/cases/protocol_fees.rs +++ b/crates/driver/src/tests/cases/protocol_fees.rs @@ -3,7 +3,7 @@ use crate::{ infra::config::file::FeeHandler, tests::{ self, - cases::{EtherExt, is_approximately_equal}, + cases::EtherExt, setup::{ ExpectedOrderAmounts, Test, From 220fe4f74a4462f8426221f1f1a41be58e89460f Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:49:20 +0000 Subject: [PATCH 4/6] Revert "`clippy` lints" This reverts commit eec4946e26fecf1b919ceacede853a6d659a9cc8. --- crates/driver/src/tests/cases/jit_orders.rs | 6 +++++- crates/driver/src/tests/cases/protocol_fees.rs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 24cb185387..86c88c9d33 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -5,7 +5,7 @@ use crate::{ }, tests::{ self, - cases::EtherExt, + cases::{EtherExt, is_approximately_equal}, setup::{ self, ExpectedOrderAmounts, @@ -43,6 +43,7 @@ struct JitOrder { struct Solution { jit_order: JitOrder, + expected_score: eth::U256, } struct TestCase { @@ -146,6 +147,9 @@ async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped( side: Side::Buy, }, }, + // Surplus is 40 ETH worth of sell tokens, converted to buy tokens using the order's + // limit price (50 / 60 = 80%) this leaves us with a score of 32 ETH. + expected_score: 32.ether().into_wei(), }, }; diff --git a/crates/driver/src/tests/cases/protocol_fees.rs b/crates/driver/src/tests/cases/protocol_fees.rs index 95de2ae912..d7222b3097 100644 --- a/crates/driver/src/tests/cases/protocol_fees.rs +++ b/crates/driver/src/tests/cases/protocol_fees.rs @@ -3,7 +3,7 @@ use crate::{ infra::config::file::FeeHandler, tests::{ self, - cases::EtherExt, + cases::{EtherExt, is_approximately_equal}, setup::{ ExpectedOrderAmounts, Test, From cafd5eb7a4681bebf14d70af275adb47932859ce Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:49:43 +0000 Subject: [PATCH 5/6] Revert "remove score handling from driver tests" This reverts commit 230c410e103f7e11cb19c688a1890ab5d1e321f7. --- crates/driver/src/tests/cases/jit_orders.rs | 4 ++++ crates/driver/src/tests/cases/protocol_fees.rs | 4 ++++ crates/driver/src/tests/setup/mod.rs | 11 ++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 86c88c9d33..66a7e44782 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -114,6 +114,10 @@ async fn protocol_fee_test_case(test_case: TestCase) { .await; let result = test.solve().await.ok(); + assert!(is_approximately_equal( + result.score(), + test_case.solution.expected_score, + )); result.jit_orders(&[jit_order]); } diff --git a/crates/driver/src/tests/cases/protocol_fees.rs b/crates/driver/src/tests/cases/protocol_fees.rs index d7222b3097..dbcbbddfb3 100644 --- a/crates/driver/src/tests/cases/protocol_fees.rs +++ b/crates/driver/src/tests/cases/protocol_fees.rs @@ -94,6 +94,10 @@ async fn protocol_fee_test_case(test_case: TestCase) { .await; let result = test.solve().await.ok(); + assert!(is_approximately_equal( + result.score(), + test_case.expected_score + )); result.orders(&[order]); } diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index 3fcbe6c3af..391f125b30 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1262,10 +1262,19 @@ impl SolveOk<'_> { let solution = solutions[0].clone(); assert!(solution.is_object()); // response contains 1 optional field - assert!((4..=5).contains(&solution.as_object().unwrap().len())); + assert!((5..=6).contains(&solution.as_object().unwrap().len())); solution } + /// Extracts the score from the response. Since response can contain + /// multiple solutions, it takes the score from the first solution. + pub fn score(&self) -> eth::U256 { + let solution = self.solution(); + assert!(solution.get("score").is_some()); + let score = solution.get("score").unwrap().as_str().unwrap(); + eth::U256::from_str_radix(score, 10).unwrap() + } + /// Ensures that `/solve` returns no solutions. pub fn empty(self) { assert!(self.solutions().is_empty()); From bbd45b28630c58328803c39ee4bdeaa1febdf27c Mon Sep 17 00:00:00 2001 From: MartinquaXD Date: Fri, 12 Dec 2025 14:52:24 +0000 Subject: [PATCH 6/6] put back scores into the driver --- crates/driver/src/infra/api/routes/solve/dto/solve_response.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs index 90ee9bd020..4fde32eeed 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_response.rs @@ -30,6 +30,7 @@ impl Solution { pub fn new(solution_id: u64, solved: competition::Solved, solver: &Solver) -> Self { Self { solution_id, + score: solved.score.0, submission_address: solver.address(), orders: solved .trades @@ -71,6 +72,8 @@ pub struct Solution { /// in subsequent requests (reveal, settle). solution_id: u64, submission_address: eth::Address, + #[serde_as(as = "serialize::U256")] + score: eth::U256, #[serde_as(as = "HashMap")] orders: HashMap, #[serde_as(as = "HashMap<_, serialize::U256>")]