From 8cbe5ecc239d542cf295fa0421fccca46d687ccc Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 10 Oct 2024 10:52:30 +0200 Subject: [PATCH 01/13] Driver: Include non-surplus-capturing-jit-orders in the driver /solve --- crates/driver/src/domain/competition/mod.rs | 2 +- .../src/domain/competition/solution/mod.rs | 24 ++++-- .../domain/competition/solution/settlement.rs | 73 +++++++++++------ .../src/domain/competition/solution/trade.rs | 19 ++++- crates/driver/src/domain/quote.rs | 2 +- crates/driver/src/infra/observe/mod.rs | 12 +-- crates/driver/src/infra/solver/mod.rs | 2 +- crates/driver/src/tests/cases/jit_orders.rs | 81 ++++++++++++++----- crates/driver/src/tests/setup/mod.rs | 36 +++++++++ crates/driver/src/tests/setup/solver.rs | 30 ++++--- 10 files changed, 204 insertions(+), 77 deletions(-) diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 168c0d14cc..0d3434adad 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -96,7 +96,7 @@ impl Competition { // Discard empty solutions. let solutions = solutions.filter(|solution| { - if solution.is_empty(auction.surplus_capturing_jit_order_owners()) { + if solution.is_empty() { observe::empty_solution(self.solver.name(), solution.id()); notify::empty_solution(&self.solver, auction.id(), solution.id().clone()); false diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index 8004e1668f..7f7b1c9c38 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -267,11 +267,14 @@ impl Solution { /// An empty solution has no trades which is allowed to capture surplus and /// a score of 0. - pub fn is_empty(&self, surplus_capturing_jit_order_owners: &HashSet) -> bool { - !self - .trades - .iter() - .any(|trade| self.trade_count_for_scorable(trade, surplus_capturing_jit_order_owners)) + pub fn is_empty(&self) -> bool { + !self.trades.iter().any(|trade| match trade { + Trade::Fulfillment(fulfillment) => match fulfillment.order().kind { + order::Kind::Market | order::Kind::Limit { .. } => true, + order::Kind::Liquidity => false, + }, + Trade::Jit(_) => true, + }) } pub fn merge(&self, other: &Self) -> Result { @@ -358,6 +361,17 @@ impl Solution { }) } + /// Return the trades which fulfill non-liquidity auction orders. + fn market_trades(&self) -> impl Iterator { + self.trades.iter().filter(|trade| match trade { + Trade::Fulfillment(fulfillment) => match fulfillment.order().kind { + order::Kind::Market | order::Kind::Limit { .. } => true, + order::Kind::Liquidity => false, + }, + Trade::Jit(_) => true, + }) + } + /// Return the allowances in a normalized form, where there is only one /// allowance per [`eth::allowance::Spender`], and they're ordered /// deterministically. diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index f6f899605b..b2bedf2ed9 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -2,7 +2,12 @@ use { super::{encoding, trade::ClearingPrices, Error, Solution}, crate::{ domain::{ - competition::{self, auction, order, solution}, + competition::{ + self, + auction, + order::{self, Side}, + solution::{self, Trade}, + }, eth, }, infra::{blockchain::Ethereum, observe, solver::ManageNativeToken, Simulator}, @@ -255,29 +260,49 @@ impl Settlement { /// The settled user orders with their in/out amounts. pub fn orders(&self) -> HashMap { let mut acc: HashMap = HashMap::new(); - for trade in self.solution.user_trades() { - let prices = ClearingPrices { - sell: self.solution.prices[&trade.order().sell.token.wrap(self.solution.weth)], - buy: self.solution.prices[&trade.order().buy.token.wrap(self.solution.weth)], - }; - let order = competition::Amounts { - side: trade.order().side, - sell: trade.order().sell, - buy: trade.order().buy, - executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount"); - 0.into() - }), - executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount"); - 0.into() - }), - }; - acc.insert(trade.order().uid, order); + for trade in self.solution.market_trades() { + match trade { + Trade::Fulfillment(_) => { + let prices = ClearingPrices { + sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], + buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], + }; + let order = competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| { + // This should never happen, returning 0 is better than panicking, but we + // should still alert. + tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount"); + 0.into() + }), + executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| { + // This should never happen, returning 0 is better than panicking, but we + // should still alert. + tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount"); + 0.into() + }), + }; + acc.insert(trade.uid(), order); + } + Trade::Jit(_) => { + let order = competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: match trade.side() { + Side::Buy => trade.buy().amount, + Side::Sell => trade.executed().into(), + }, + executed_buy: match trade.side() { + Side::Buy => trade.executed().into(), + Side::Sell => trade.sell().amount, + }, + }; + acc.insert(trade.uid(), order); + } + } } acc } diff --git a/crates/driver/src/domain/competition/solution/trade.rs b/crates/driver/src/domain/competition/solution/trade.rs index c0c5d950d8..c89fbf863b 100644 --- a/crates/driver/src/domain/competition/solution/trade.rs +++ b/crates/driver/src/domain/competition/solution/trade.rs @@ -2,7 +2,7 @@ use crate::{ domain::{ competition::{ self, - order::{self, FeePolicy, SellAmount, Side, TargetAmount}, + order::{self, FeePolicy, SellAmount, Side, TargetAmount, Uid}, solution::error::{self, Math}, }, eth::{self, Asset}, @@ -19,6 +19,13 @@ pub enum Trade { } impl Trade { + pub fn uid(&self) -> Uid { + match self { + Trade::Fulfillment(fulfillment) => fulfillment.order().uid, + Trade::Jit(jit) => jit.order().uid, + } + } + pub fn side(&self) -> Side { match self { Trade::Fulfillment(fulfillment) => fulfillment.order().side, @@ -62,7 +69,10 @@ impl Trade { } /// The effective amount that left the user's wallet including all fees. - fn sell_amount(&self, prices: &ClearingPrices) -> Result { + pub(crate) fn sell_amount( + &self, + prices: &ClearingPrices, + ) -> Result { let before_fee = match self.side() { order::Side::Sell => self.executed().0, order::Side::Buy => self @@ -81,7 +91,10 @@ impl Trade { /// The effective amount the user received after all fees. /// /// Settlement contract uses `ceil` division for buy amount calculation. - fn buy_amount(&self, prices: &ClearingPrices) -> Result { + pub(crate) fn buy_amount( + &self, + prices: &ClearingPrices, + ) -> Result { let amount = match self.side() { order::Side::Buy => self.executed().0, order::Side::Sell => self diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index e63b8e0040..d2b6f592a4 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -115,7 +115,7 @@ impl Order { // first solution solutions .into_iter() - .find(|solution| !solution.is_empty(auction.surplus_capturing_jit_order_owners())) + .find(|solution| !solution.is_empty()) .ok_or(QuotingFailed::NoSolutions)?, ) } diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index 02ec5f3438..f3bcdf85ac 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -24,7 +24,7 @@ use { util::http, }, ethrpc::block_stream::BlockInfo, - std::collections::{HashMap, HashSet}, + std::collections::HashMap, url::Url, }; @@ -70,14 +70,8 @@ pub fn duplicated_solution_id(solver: &solver::Name, id: &solution::Id) { } /// Observe the solutions returned by the solver. -pub fn solutions( - solutions: &[Solution], - surplus_capturing_jit_order_owners: &HashSet, -) { - if solutions - .iter() - .any(|s| !s.is_empty(surplus_capturing_jit_order_owners)) - { +pub fn solutions(solutions: &[Solution]) { + if solutions.iter().any(|s| !s.is_empty()) { tracing::info!(?solutions, "computed solutions"); } else { tracing::debug!("no solutions"); diff --git a/crates/driver/src/infra/solver/mod.rs b/crates/driver/src/infra/solver/mod.rs index cb1fb126e0..b62fb85ab0 100644 --- a/crates/driver/src/infra/solver/mod.rs +++ b/crates/driver/src/infra/solver/mod.rs @@ -241,7 +241,7 @@ impl Solver { .tap_err(|err| tracing::warn!(res, ?err, "failed to parse solver response"))?; let solutions = res.into_domain(auction, liquidity, weth, self.clone(), &self.config)?; - super::observe::solutions(&solutions, auction.surplus_capturing_jit_order_owners()); + super::observe::solutions(&solutions); Ok(solutions) } diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 05983cc885..e04287d732 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -46,7 +46,7 @@ struct Solution { } struct TestCase { - order: Order, + order: Option, execution: Execution, is_surplus_capturing_jit_order: bool, solution: Solution, @@ -54,7 +54,7 @@ struct TestCase { #[cfg(test)] async fn protocol_fee_test_case(test_case: TestCase) { - let test_name = format!("JIT Order: {:?}", test_case.order.side); + let test_name = format!("JIT Order: {:?}", test_case.solution.jit_order.order.side); // Adjust liquidity pools so that the order is executable at the amounts // expected from the solver. let quote = ab_liquidity_quote() @@ -73,48 +73,52 @@ async fn protocol_fee_test_case(test_case: TestCase) { .no_surplus(), }; - let order = ab_order() - .kind(order::Kind::Limit) - .sell_amount(test_case.order.sell_amount) - .buy_amount(test_case.order.buy_amount) - .solver_fee(Some(solver_fee)) - .side(test_case.order.side) - .partial(0.into()) - .no_surplus(); - let solver = test_solver(); - let test: Test = tests::setup() + + let mut setup = tests::setup() .name(test_name) .pool(pool) .jit_order(jit_order.clone()) - .order(order.clone()) - .solution(ab_solution()) .surplus_capturing_jit_order_owners( test_case .is_surplus_capturing_jit_order .then(|| vec![solver.address()]) .unwrap_or_default(), ) - .solvers(vec![solver]) - .done() - .await; + .solvers(vec![solver]); + + if let Some(order) = test_case.order { + let order = ab_order() + .kind(order::Kind::Limit) + .sell_amount(order.sell_amount) + .buy_amount(order.buy_amount) + .solver_fee(Some(solver_fee)) + .side(order.side) + .partial(0.into()) + .no_surplus(); + + setup = setup.order(order.clone()); + } + + let test: Test = setup.solution(ab_solution()).done().await; let result = test.solve().await.ok(); assert!(is_approximately_equal( result.score(), test_case.solution.expected_score, )); + result.jit_orders(&[jit_order]); } #[tokio::test] #[ignore] async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped() { let test_case = TestCase { - order: Order { + order: Some(Order { sell_amount: 50.ether().into_wei(), buy_amount: 40.ether().into_wei(), side: Side::Buy, - }, + }), execution: Execution { // 20 ETH surplus in sell token (after network fee), half of which is kept by the // protocol @@ -148,11 +152,11 @@ async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped( #[ignore] async fn surplus_protocol_fee_jit_order_not_capped() { let test_case = TestCase { - order: Order { + order: Some(Order { sell_amount: 50.ether().into_wei(), buy_amount: 40.ether().into_wei(), side: Side::Buy, - }, + }), execution: Execution { // 20 ETH surplus in sell token (after network fee), half of which is kept by the // protocol @@ -181,3 +185,38 @@ async fn surplus_protocol_fee_jit_order_not_capped() { protocol_fee_test_case(test_case).await; } + +#[tokio::test] +#[ignore] +async fn solution_containing_only_jit_orders() { + let test_case = TestCase { + order: None, + execution: Execution { + // 20 ETH surplus in sell token (after network fee), half of which is kept by the + // protocol + solver: Amounts { + sell: 30.ether().into_wei(), + buy: 40.ether().into_wei(), + }, + driver: Amounts { + sell: 40.ether().into_wei(), + buy: 40.ether().into_wei(), + }, + }, + is_surplus_capturing_jit_order: false, + solution: Solution { + jit_order: JitOrder { + order: Order { + sell_amount: 50.ether().into_wei(), + buy_amount: 40.ether().into_wei(), + side: Side::Buy, + }, + }, + // Score is 0 since the JIT order is not from a surplus capturing owner and there is no + // normal order + expected_score: 0.ether().into_wei(), + }, + }; + + protocol_fee_test_case(test_case).await; +} diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index aa54d08ef7..ec5920954f 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1224,6 +1224,42 @@ impl<'a> SolveOk<'a> { assert!(self.solutions().is_empty()); } + /// Check that the solution contains the expected JIT orders. + pub fn jit_orders(self, jit_orders: &[JitOrder]) -> Self { + let solution = self.solution(); + assert!(solution.get("orders").is_some()); + let trades = serde_json::from_value::>( + solution.get("orders").unwrap().clone(), + ) + .unwrap(); + + // Since JIT orders don't have UID at creation time, we need to search for + // matching token pair + for expected in jit_orders.iter() { + for trade in trades.values() { + let u256 = |value: &serde_json::Value| { + eth::U256::from_dec_str(value.as_str().unwrap()).unwrap() + }; + let token = |value: &serde_json::Value| value.as_str().unwrap().to_string(); + let sell_token = token(trade.get("sellToken").unwrap()); + let buy_token = token(trade.get("buyToken").unwrap()); + + if sell_token == expected.order.sell_token && buy_token == expected.order.buy_token + { + assert!(is_approximately_equal( + expected.order.sell_amount, + u256(trade.get("executedSell").unwrap()) + )); + assert!(is_approximately_equal( + expected.order.buy_amount.unwrap(), + u256(trade.get("executedBuy").unwrap()) + )); + } + } + } + self + } + /// Check that the solution contains the expected orders. pub fn orders(self, orders: &[Order]) -> Self { let solution = self.solution(); diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index c1fb13b4f7..b8717410bc 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -272,18 +272,24 @@ impl Solver { }).collect_vec(), }) })); - prices_json.insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.sell_token), - jit.execution.buy.to_string(), - ); - prices_json.insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.buy_token), - (jit.execution.sell - jit.quoted_order.order.surplus_fee()).to_string(), - ); + if config + .expected_surplus_capturing_jit_order_owners + .contains(&jit.quoted_order.order.owner) + { + prices_json.insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.sell_token), + jit.execution.buy.to_string(), + ); + prices_json.insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.buy_token), + (jit.execution.sell - jit.quoted_order.order.surplus_fee()) + .to_string(), + ); + } { let executed_amount = match jit.quoted_order.order.executed { Some(executed) => executed.to_string(), From ad47f0a9616f2d6f2781e7d12d1cd40b0eef5214 Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 11 Oct 2024 12:08:23 +0200 Subject: [PATCH 02/13] rework some comments --- crates/driver/src/tests/cases/jit_orders.rs | 77 ++++++--------------- crates/driver/src/tests/setup/solver.rs | 1 + 2 files changed, 21 insertions(+), 57 deletions(-) diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index e04287d732..93c23a168a 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -46,7 +46,7 @@ struct Solution { } struct TestCase { - order: Option, + order: Order, execution: Execution, is_surplus_capturing_jit_order: bool, solution: Solution, @@ -73,9 +73,18 @@ async fn protocol_fee_test_case(test_case: TestCase) { .no_surplus(), }; + let order = ab_order() + .kind(order::Kind::Limit) + .sell_amount(test_case.order.sell_amount) + .buy_amount(test_case.order.buy_amount) + .solver_fee(Some(solver_fee)) + .side(test_case.order.side) + .partial(0.into()) + .no_surplus(); + let solver = test_solver(); - let mut setup = tests::setup() + let test: Test = tests::setup() .name(test_name) .pool(pool) .jit_order(jit_order.clone()) @@ -85,22 +94,11 @@ async fn protocol_fee_test_case(test_case: TestCase) { .then(|| vec![solver.address()]) .unwrap_or_default(), ) - .solvers(vec![solver]); - - if let Some(order) = test_case.order { - let order = ab_order() - .kind(order::Kind::Limit) - .sell_amount(order.sell_amount) - .buy_amount(order.buy_amount) - .solver_fee(Some(solver_fee)) - .side(order.side) - .partial(0.into()) - .no_surplus(); - - setup = setup.order(order.clone()); - } - - let test: Test = setup.solution(ab_solution()).done().await; + .solvers(vec![solver]) + .order(order.clone()) + .solution(ab_solution()) + .done() + .await; let result = test.solve().await.ok(); assert!(is_approximately_equal( @@ -114,11 +112,11 @@ async fn protocol_fee_test_case(test_case: TestCase) { #[ignore] async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped() { let test_case = TestCase { - order: Some(Order { + order: Order { sell_amount: 50.ether().into_wei(), buy_amount: 40.ether().into_wei(), side: Side::Buy, - }), + }, execution: Execution { // 20 ETH surplus in sell token (after network fee), half of which is kept by the // protocol @@ -152,11 +150,11 @@ async fn surplus_protocol_fee_jit_order_from_surplus_capturing_owner_not_capped( #[ignore] async fn surplus_protocol_fee_jit_order_not_capped() { let test_case = TestCase { - order: Some(Order { + order: Order { sell_amount: 50.ether().into_wei(), buy_amount: 40.ether().into_wei(), side: Side::Buy, - }), + }, execution: Execution { // 20 ETH surplus in sell token (after network fee), half of which is kept by the // protocol @@ -185,38 +183,3 @@ async fn surplus_protocol_fee_jit_order_not_capped() { protocol_fee_test_case(test_case).await; } - -#[tokio::test] -#[ignore] -async fn solution_containing_only_jit_orders() { - let test_case = TestCase { - order: None, - execution: Execution { - // 20 ETH surplus in sell token (after network fee), half of which is kept by the - // protocol - solver: Amounts { - sell: 30.ether().into_wei(), - buy: 40.ether().into_wei(), - }, - driver: Amounts { - sell: 40.ether().into_wei(), - buy: 40.ether().into_wei(), - }, - }, - is_surplus_capturing_jit_order: false, - solution: Solution { - jit_order: JitOrder { - order: Order { - sell_amount: 50.ether().into_wei(), - buy_amount: 40.ether().into_wei(), - side: Side::Buy, - }, - }, - // Score is 0 since the JIT order is not from a surplus capturing owner and there is no - // normal order - expected_score: 0.ether().into_wei(), - }, - }; - - protocol_fee_test_case(test_case).await; -} diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index b8717410bc..1b2e8507d5 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -272,6 +272,7 @@ impl Solver { }).collect_vec(), }) })); + // Skipping the prices for JIT orders (non-surplus-capturing) if config .expected_surplus_capturing_jit_order_owners .contains(&jit.quoted_order.order.owner) From ecd868e2d5a17561f9940835ec1efed5868f8f54 Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 11 Oct 2024 12:15:30 +0200 Subject: [PATCH 03/13] rework more comments --- crates/driver/src/domain/competition/mod.rs | 2 +- .../driver/src/domain/competition/solution/mod.rs | 13 +++++-------- crates/driver/src/domain/quote.rs | 2 +- crates/driver/src/infra/observe/mod.rs | 12 +++++++++--- crates/driver/src/infra/solver/mod.rs | 2 +- crates/driver/src/tests/cases/jit_orders.rs | 4 ++-- 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 0d3434adad..168c0d14cc 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -96,7 +96,7 @@ impl Competition { // Discard empty solutions. let solutions = solutions.filter(|solution| { - if solution.is_empty() { + if solution.is_empty(auction.surplus_capturing_jit_order_owners()) { observe::empty_solution(self.solver.name(), solution.id()); notify::empty_solution(&self.solver, auction.id(), solution.id().clone()); false diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index 7f7b1c9c38..0785c7118d 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -267,14 +267,11 @@ impl Solution { /// An empty solution has no trades which is allowed to capture surplus and /// a score of 0. - pub fn is_empty(&self) -> bool { - !self.trades.iter().any(|trade| match trade { - Trade::Fulfillment(fulfillment) => match fulfillment.order().kind { - order::Kind::Market | order::Kind::Limit { .. } => true, - order::Kind::Liquidity => false, - }, - Trade::Jit(_) => true, - }) + pub fn is_empty(&self, surplus_capturing_jit_order_owners: &HashSet) -> bool { + !self + .trades + .iter() + .any(|trade| self.trade_count_for_scorable(trade, surplus_capturing_jit_order_owners)) } pub fn merge(&self, other: &Self) -> Result { diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index d2b6f592a4..e63b8e0040 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -115,7 +115,7 @@ impl Order { // first solution solutions .into_iter() - .find(|solution| !solution.is_empty()) + .find(|solution| !solution.is_empty(auction.surplus_capturing_jit_order_owners())) .ok_or(QuotingFailed::NoSolutions)?, ) } diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index f3bcdf85ac..02ec5f3438 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -24,7 +24,7 @@ use { util::http, }, ethrpc::block_stream::BlockInfo, - std::collections::HashMap, + std::collections::{HashMap, HashSet}, url::Url, }; @@ -70,8 +70,14 @@ pub fn duplicated_solution_id(solver: &solver::Name, id: &solution::Id) { } /// Observe the solutions returned by the solver. -pub fn solutions(solutions: &[Solution]) { - if solutions.iter().any(|s| !s.is_empty()) { +pub fn solutions( + solutions: &[Solution], + surplus_capturing_jit_order_owners: &HashSet, +) { + if solutions + .iter() + .any(|s| !s.is_empty(surplus_capturing_jit_order_owners)) + { tracing::info!(?solutions, "computed solutions"); } else { tracing::debug!("no solutions"); diff --git a/crates/driver/src/infra/solver/mod.rs b/crates/driver/src/infra/solver/mod.rs index b62fb85ab0..cb1fb126e0 100644 --- a/crates/driver/src/infra/solver/mod.rs +++ b/crates/driver/src/infra/solver/mod.rs @@ -241,7 +241,7 @@ impl Solver { .tap_err(|err| tracing::warn!(res, ?err, "failed to parse solver response"))?; let solutions = res.into_domain(auction, liquidity, weth, self.clone(), &self.config)?; - super::observe::solutions(&solutions); + super::observe::solutions(&solutions, auction.surplus_capturing_jit_order_owners()); Ok(solutions) } diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 93c23a168a..09ae7bb3e7 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -88,6 +88,8 @@ async fn protocol_fee_test_case(test_case: TestCase) { .name(test_name) .pool(pool) .jit_order(jit_order.clone()) + .order(order.clone()) + .solution(ab_solution()) .surplus_capturing_jit_order_owners( test_case .is_surplus_capturing_jit_order @@ -95,8 +97,6 @@ async fn protocol_fee_test_case(test_case: TestCase) { .unwrap_or_default(), ) .solvers(vec![solver]) - .order(order.clone()) - .solution(ab_solution()) .done() .await; From 087c0e159d670c127d5487922037ec453d7f6b03 Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 11 Oct 2024 12:37:49 +0200 Subject: [PATCH 04/13] rework comments --- .../domain/competition/solution/settlement.rs | 72 ++++++++----------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index b2bedf2ed9..e5b63838b7 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -5,7 +5,7 @@ use { competition::{ self, auction, - order::{self, Side}, + order::{self}, solution::{self, Trade}, }, eth, @@ -261,48 +261,34 @@ impl Settlement { pub fn orders(&self) -> HashMap { let mut acc: HashMap = HashMap::new(); for trade in self.solution.market_trades() { - match trade { - Trade::Fulfillment(_) => { - let prices = ClearingPrices { - sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], - buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], - }; - let order = competition::Amounts { - side: trade.side(), - sell: trade.sell(), - buy: trade.buy(), - executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount"); - 0.into() - }), - executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount"); - 0.into() - }), - }; - acc.insert(trade.uid(), order); - } - Trade::Jit(_) => { - let order = competition::Amounts { - side: trade.side(), - sell: trade.sell(), - buy: trade.buy(), - executed_sell: match trade.side() { - Side::Buy => trade.buy().amount, - Side::Sell => trade.executed().into(), - }, - executed_buy: match trade.side() { - Side::Buy => trade.executed().into(), - Side::Sell => trade.sell().amount, - }, - }; - acc.insert(trade.uid(), order); - } - } + let prices = match trade { + Trade::Fulfillment(_) => ClearingPrices { + sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], + buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], + }, + Trade::Jit(_) => ClearingPrices { + sell: trade.buy().amount.into(), + buy: trade.sell().amount.into(), + }, + }; + let order = competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| { + // This should never happen, returning 0 is better than panicking, but we + // should still alert. + tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount"); + 0.into() + }), + executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| { + // This should never happen, returning 0 is better than panicking, but we + // should still alert. + tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount"); + 0.into() + }), + }; + acc.insert(trade.uid(), order); } acc } From fc59ea6078a23e424ae49f611cab566bed2e68a7 Mon Sep 17 00:00:00 2001 From: Mateo Date: Fri, 11 Oct 2024 14:10:27 +0200 Subject: [PATCH 05/13] Rework comments --- .../domain/competition/solution/settlement.rs | 106 +++++++++++++----- 1 file changed, 76 insertions(+), 30 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index e5b63838b7..c4ebb98100 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -5,8 +5,8 @@ use { competition::{ self, auction, - order::{self}, - solution::{self, Trade}, + order::{self, Side}, + solution::{self, error, Trade}, }, eth, }, @@ -259,40 +259,86 @@ impl Settlement { /// The settled user orders with their in/out amounts. pub fn orders(&self) -> HashMap { + let log_err = |trade: &Trade, err: error::Math, kind: &str| -> eth::TokenAmount { + // This should never happen, returning 0 is better than panicking, but we + // should still alert. + let msg = format!("could not compute {kind}"); + tracing::error!(?trade, prices=?self.solution.prices, ?err, msg); + 0.into() + }; let mut acc: HashMap = HashMap::new(); for trade in self.solution.market_trades() { - let prices = match trade { - Trade::Fulfillment(_) => ClearingPrices { - sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], - buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], - }, - Trade::Jit(_) => ClearingPrices { - sell: trade.buy().amount.into(), - buy: trade.sell().amount.into(), - }, - }; - let order = competition::Amounts { - side: trade.side(), - sell: trade.sell(), - buy: trade.buy(), - executed_sell: trade.sell_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute sell_amount"); - 0.into() - }), - executed_buy: trade.buy_amount(&prices).unwrap_or_else(|err| { - // This should never happen, returning 0 is better than panicking, but we - // should still alert. - tracing::error!(?trade, prices=?self.solution.prices, ?err, "could not compute buy_amount"); - 0.into() - }), - }; - acc.insert(trade.uid(), order); + match trade { + Trade::Fulfillment(_) => { + let prices = ClearingPrices { + sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], + buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], + }; + let order = competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: trade + .sell_amount(&prices) + .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), + executed_buy: trade + .buy_amount(&prices) + .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), + }; + acc.insert(trade.uid(), order); + } + Trade::Jit(_) => { + let order = competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: Self::jit_order_executed_sell(trade) + .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), + executed_buy: Self::jit_order_executed_buy(trade) + .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), + }; + acc.insert(trade.uid(), order); + } + } } acc } + fn jit_order_executed_buy(trade: &Trade) -> Result { + Ok(match trade.side() { + Side::Buy => trade.executed().into(), + Side::Sell => (trade + .executed() + .0 + .checked_add(trade.fee().0) + .ok_or(error::Math::Overflow)?) + .checked_mul(trade.buy().amount.0) + .ok_or(error::Math::Overflow)? + .checked_div(trade.sell().amount.0) + .ok_or(error::Math::DivisionByZero)? + .into(), + }) + } + + fn jit_order_executed_sell(trade: &Trade) -> Result { + Ok(match trade.side() { + Side::Buy => trade + .executed() + .0 + .checked_mul(trade.sell().amount.0) + .ok_or(error::Math::Overflow)? + .checked_div(trade.buy().amount.0) + .ok_or(error::Math::DivisionByZero)? + .into(), + Side::Sell => trade + .executed() + .0 + .checked_add(trade.fee().0) + .ok_or(error::Math::Overflow)? + .into(), + }) + } + /// The uniform price vector this settlement proposes pub fn prices(&self) -> HashMap { self.solution From bd368fe294713a7e96f389d2b6ea38c05bf41012 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 09:51:17 +0200 Subject: [PATCH 06/13] improve methods --- .../domain/competition/solution/settlement.rs | 45 +++---------------- .../src/domain/competition/solution/trade.rs | 35 +++++++++++++++ 2 files changed, 41 insertions(+), 39 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index c4ebb98100..f52155e5c9 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -5,7 +5,7 @@ use { competition::{ self, auction, - order::{self, Side}, + order::{self}, solution::{self, error, Trade}, }, eth, @@ -287,14 +287,16 @@ impl Settlement { }; acc.insert(trade.uid(), order); } - Trade::Jit(_) => { + Trade::Jit(jit) => { let order = competition::Amounts { side: trade.side(), sell: trade.sell(), buy: trade.buy(), - executed_sell: Self::jit_order_executed_sell(trade) + executed_sell: jit + .executed_sell() .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), - executed_buy: Self::jit_order_executed_buy(trade) + executed_buy: jit + .executed_buy() .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), }; acc.insert(trade.uid(), order); @@ -304,41 +306,6 @@ impl Settlement { acc } - fn jit_order_executed_buy(trade: &Trade) -> Result { - Ok(match trade.side() { - Side::Buy => trade.executed().into(), - Side::Sell => (trade - .executed() - .0 - .checked_add(trade.fee().0) - .ok_or(error::Math::Overflow)?) - .checked_mul(trade.buy().amount.0) - .ok_or(error::Math::Overflow)? - .checked_div(trade.sell().amount.0) - .ok_or(error::Math::DivisionByZero)? - .into(), - }) - } - - fn jit_order_executed_sell(trade: &Trade) -> Result { - Ok(match trade.side() { - Side::Buy => trade - .executed() - .0 - .checked_mul(trade.sell().amount.0) - .ok_or(error::Math::Overflow)? - .checked_div(trade.buy().amount.0) - .ok_or(error::Math::DivisionByZero)? - .into(), - Side::Sell => trade - .executed() - .0 - .checked_add(trade.fee().0) - .ok_or(error::Math::Overflow)? - .into(), - }) - } - /// The uniform price vector this settlement proposes pub fn prices(&self) -> HashMap { self.solution diff --git a/crates/driver/src/domain/competition/solution/trade.rs b/crates/driver/src/domain/competition/solution/trade.rs index c89fbf863b..da9d4b2e4b 100644 --- a/crates/driver/src/domain/competition/solution/trade.rs +++ b/crates/driver/src/domain/competition/solution/trade.rs @@ -422,6 +422,41 @@ impl Jit { self.executed } + pub fn executed_buy(&self) -> Result { + Ok(match self.order().side { + Side::Buy => self.executed().into(), + Side::Sell => (self + .executed() + .0 + .checked_add(self.fee().0) + .ok_or(Math::Overflow)?) + .checked_mul(self.order.buy.amount.0) + .ok_or(Math::Overflow)? + .checked_div(self.order.sell.amount.0) + .ok_or(Math::DivisionByZero)? + .into(), + }) + } + + pub fn executed_sell(&self) -> Result { + Ok(match self.order().side { + Side::Buy => self + .executed() + .0 + .checked_mul(self.order.sell.amount.0) + .ok_or(Math::Overflow)? + .checked_div(self.order.buy.amount.0) + .ok_or(Math::DivisionByZero)? + .into(), + Side::Sell => self + .executed() + .0 + .checked_add(self.fee().0) + .ok_or(Math::Overflow)? + .into(), + }) + } + pub fn fee(&self) -> order::SellAmount { self.fee } From 5243ad2a58e0d77c7ce90390c44ce007bff6bf50 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 09:55:53 +0200 Subject: [PATCH 07/13] remove closure --- crates/driver/src/tests/setup/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index ec5920954f..afe7e6037a 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1240,9 +1240,8 @@ impl<'a> SolveOk<'a> { let u256 = |value: &serde_json::Value| { eth::U256::from_dec_str(value.as_str().unwrap()).unwrap() }; - let token = |value: &serde_json::Value| value.as_str().unwrap().to_string(); - let sell_token = token(trade.get("sellToken").unwrap()); - let buy_token = token(trade.get("buyToken").unwrap()); + let sell_token = trade.get("sellToken").unwrap().to_string(); + let buy_token = trade.get("buyToken").unwrap().to_string(); if sell_token == expected.order.sell_token && buy_token == expected.order.buy_token { From 302d53f350c92e638e81e20d2c6718baf3f9c9b1 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 13:36:33 +0200 Subject: [PATCH 08/13] checked_ceil_div --- crates/driver/src/domain/competition/solution/trade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/driver/src/domain/competition/solution/trade.rs b/crates/driver/src/domain/competition/solution/trade.rs index da9d4b2e4b..299b483502 100644 --- a/crates/driver/src/domain/competition/solution/trade.rs +++ b/crates/driver/src/domain/competition/solution/trade.rs @@ -432,7 +432,7 @@ impl Jit { .ok_or(Math::Overflow)?) .checked_mul(self.order.buy.amount.0) .ok_or(Math::Overflow)? - .checked_div(self.order.sell.amount.0) + .checked_ceil_div(&self.order.sell.amount.0) .ok_or(Math::DivisionByZero)? .into(), }) From d553360aaface9e475b44544331e0cc99719fcf1 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 13:49:55 +0200 Subject: [PATCH 09/13] remove market trades --- crates/driver/src/domain/competition/solution/mod.rs | 11 ----------- .../src/domain/competition/solution/settlement.rs | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index 0785c7118d..8004e1668f 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -358,17 +358,6 @@ impl Solution { }) } - /// Return the trades which fulfill non-liquidity auction orders. - fn market_trades(&self) -> impl Iterator { - self.trades.iter().filter(|trade| match trade { - Trade::Fulfillment(fulfillment) => match fulfillment.order().kind { - order::Kind::Market | order::Kind::Limit { .. } => true, - order::Kind::Liquidity => false, - }, - Trade::Jit(_) => true, - }) - } - /// Return the allowances in a normalized form, where there is only one /// allowance per [`eth::allowance::Spender`], and they're ordered /// deterministically. diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index f52155e5c9..f0c3b949e7 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -267,7 +267,7 @@ impl Settlement { 0.into() }; let mut acc: HashMap = HashMap::new(); - for trade in self.solution.market_trades() { + for trade in &self.solution.trades { match trade { Trade::Fulfillment(_) => { let prices = ClearingPrices { From 72638451e28afac2fa9d84e9999a6f9387e68310 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 15:38:01 +0200 Subject: [PATCH 10/13] Update openAPI --- crates/driver/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/driver/openapi.yml b/crates/driver/openapi.yml index f70dd131b0..27a2f7d665 100644 --- a/crates/driver/openapi.yml +++ b/crates/driver/openapi.yml @@ -237,7 +237,7 @@ components: enum: ["erc20", "internal"] class: type: string - enum: ["market", "limit", "liquidity"] + enum: ["market", "limit"] appData: description: 32 bytes encoded as hex with `0x` prefix. type: string From b364db7dd7faf9616b2d581426aba4f6bbf555e6 Mon Sep 17 00:00:00 2001 From: Mateo Date: Mon, 14 Oct 2024 16:17:13 +0200 Subject: [PATCH 11/13] nit --- .../domain/competition/solution/settlement.rs | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index f0c3b949e7..4f52311307 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -268,13 +268,13 @@ impl Settlement { }; let mut acc: HashMap = HashMap::new(); for trade in &self.solution.trades { - match trade { + let order = match trade { Trade::Fulfillment(_) => { let prices = ClearingPrices { sell: self.solution.prices[&trade.sell().token.wrap(self.solution.weth)], buy: self.solution.prices[&trade.buy().token.wrap(self.solution.weth)], }; - let order = competition::Amounts { + competition::Amounts { side: trade.side(), sell: trade.sell(), buy: trade.buy(), @@ -284,24 +284,21 @@ impl Settlement { executed_buy: trade .buy_amount(&prices) .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), - }; - acc.insert(trade.uid(), order); - } - Trade::Jit(jit) => { - let order = competition::Amounts { - side: trade.side(), - sell: trade.sell(), - buy: trade.buy(), - executed_sell: jit - .executed_sell() - .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), - executed_buy: jit - .executed_buy() - .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), - }; - acc.insert(trade.uid(), order); + } } - } + Trade::Jit(jit) => competition::Amounts { + side: trade.side(), + sell: trade.sell(), + buy: trade.buy(), + executed_sell: jit + .executed_sell() + .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), + executed_buy: jit + .executed_buy() + .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), + }, + }; + acc.insert(trade.uid(), order); } acc } From b31da69132e9586ba37a217ff514f9985bda666b Mon Sep 17 00:00:00 2001 From: Mateo Date: Tue, 15 Oct 2024 11:47:55 +0200 Subject: [PATCH 12/13] rework comments --- .../domain/competition/solution/settlement.rs | 8 +-- crates/driver/src/tests/cases/jit_orders.rs | 16 ++++- crates/driver/src/tests/setup/mod.rs | 39 +++++++----- crates/driver/src/tests/setup/solver.rs | 62 +++++++++++-------- 4 files changed, 76 insertions(+), 49 deletions(-) diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index 4f52311307..8fd3111928 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -280,10 +280,10 @@ impl Settlement { buy: trade.buy(), executed_sell: trade .sell_amount(&prices) - .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), + .unwrap_or_else(|err| log_err(trade, err, "executed_sell")), executed_buy: trade .buy_amount(&prices) - .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), + .unwrap_or_else(|err| log_err(trade, err, "executed_buy")), } } Trade::Jit(jit) => competition::Amounts { @@ -292,10 +292,10 @@ impl Settlement { buy: trade.buy(), executed_sell: jit .executed_sell() - .unwrap_or_else(|err| log_err(trade, err, "sell_amount")), + .unwrap_or_else(|err| log_err(trade, err, "executed_sell")), executed_buy: jit .executed_buy() - .unwrap_or_else(|err| log_err(trade, err, "buy_amount")), + .unwrap_or_else(|err| log_err(trade, err, "executed_buy")), }, }; acc.insert(trade.uid(), order); diff --git a/crates/driver/src/tests/cases/jit_orders.rs b/crates/driver/src/tests/cases/jit_orders.rs index 09ae7bb3e7..243b709b83 100644 --- a/crates/driver/src/tests/cases/jit_orders.rs +++ b/crates/driver/src/tests/cases/jit_orders.rs @@ -13,6 +13,7 @@ use crate::{ ab_order, ab_solution, test_solver, + ExpectedOrderAmounts, Test, }, }, @@ -62,6 +63,18 @@ async fn protocol_fee_test_case(test_case: TestCase) { .buy_amount(test_case.execution.solver.buy); let pool = ab_adjusted_pool(quote); let solver_fee = test_case.execution.driver.sell / 100; + // Amounts expected to be returned by the driver after fee processing + let jit_order_expected_amounts = if test_case.is_surplus_capturing_jit_order { + ExpectedOrderAmounts { + sell: test_case.execution.solver.sell, + buy: test_case.execution.solver.buy, + } + } else { + ExpectedOrderAmounts { + sell: test_case.solution.jit_order.order.sell_amount, + buy: test_case.solution.jit_order.order.buy_amount, + } + }; let jit_order = setup::JitOrder { order: ab_order() @@ -70,7 +83,8 @@ async fn protocol_fee_test_case(test_case: TestCase) { .buy_amount(test_case.solution.jit_order.order.buy_amount) .solver_fee(Some(solver_fee)) .side(test_case.solution.jit_order.order.side) - .no_surplus(), + .no_surplus() + .expected_amounts(jit_order_expected_amounts), }; let order = ab_order() diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index afe7e6037a..875780e982 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -27,6 +27,7 @@ use { DEFAULT_SURPLUS_FACTOR, ETH_ORDER_AMOUNT, }, + hex_address, setup::blockchain::{Blockchain, Trade}, }, }, @@ -1236,29 +1237,33 @@ impl<'a> SolveOk<'a> { // Since JIT orders don't have UID at creation time, we need to search for // matching token pair for expected in jit_orders.iter() { + let mut exist = false; for trade in trades.values() { - let u256 = |value: &serde_json::Value| { - eth::U256::from_dec_str(value.as_str().unwrap()).unwrap() - }; - let sell_token = trade.get("sellToken").unwrap().to_string(); - let buy_token = trade.get("buyToken").unwrap().to_string(); - - if sell_token == expected.order.sell_token && buy_token == expected.order.buy_token - { - assert!(is_approximately_equal( - expected.order.sell_amount, - u256(trade.get("executedSell").unwrap()) - )); - assert!(is_approximately_equal( - expected.order.buy_amount.unwrap(), - u256(trade.get("executedBuy").unwrap()) - )); - } + exist |= self.exist_jit_order(trade, expected) } + assert!(exist, "JIT order {expected:?} not found"); } self } + /// Find for a JIT order, given specific token pair and buy/sell amount, + /// return true if the JIT order was found + fn exist_jit_order(&self, trade: &serde_json::Value, expected: &JitOrder) -> bool { + let u256 = + |value: &serde_json::Value| eth::U256::from_dec_str(value.as_str().unwrap()).unwrap(); + let sell_token = trade.get("sellToken").unwrap().to_string(); + let sell_token = sell_token.trim_matches('"'); + let buy_token = trade.get("buyToken").unwrap().to_string(); + let buy_token = buy_token.trim_matches('"'); + let sell_amount = u256(trade.get("executedSell").unwrap()); + let buy_amount = u256(trade.get("executedBuy").unwrap()); + + sell_token == hex_address(self.blockchain.get_token(expected.order.sell_token)) + && buy_token == hex_address(self.blockchain.get_token(expected.order.buy_token)) + && expected.order.expected_amounts.clone().unwrap().sell == sell_amount + && expected.order.expected_amounts.clone().unwrap().buy == buy_amount + } + /// Check that the solution contains the expected orders. pub fn orders(self, orders: &[Order]) -> Self { let solution = self.solution(); diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index 0939f734c6..c4e0e00fbd 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -197,20 +197,24 @@ impl Solver { }) }, )); - prices_json.insert( - config - .blockchain - .get_token_wrapped(fulfillment.quoted_order.order.sell_token), - fulfillment.execution.buy.to_string(), - ); - prices_json.insert( - config - .blockchain - .get_token_wrapped(fulfillment.quoted_order.order.buy_token), - (fulfillment.execution.sell - - fulfillment.quoted_order.order.surplus_fee()) - .to_string(), - ); + assert!(prices_json + .insert( + config + .blockchain + .get_token_wrapped(fulfillment.quoted_order.order.sell_token), + fulfillment.execution.buy.to_string(), + ) + .is_none()); + assert!(prices_json + .insert( + config + .blockchain + .get_token_wrapped(fulfillment.quoted_order.order.buy_token), + (fulfillment.execution.sell + - fulfillment.quoted_order.order.surplus_fee()) + .to_string(), + ) + .is_none()); { // trades have optional field `fee` let order = if config.quote { @@ -275,19 +279,23 @@ impl Solver { .expected_surplus_capturing_jit_order_owners .contains(&jit.quoted_order.order.owner) { - prices_json.insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.sell_token), - jit.execution.buy.to_string(), - ); - prices_json.insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.buy_token), - (jit.execution.sell - jit.quoted_order.order.surplus_fee()) - .to_string(), - ); + assert!(prices_json + .insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.sell_token), + jit.execution.buy.to_string(), + ) + .is_none()); + assert!(prices_json + .insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.buy_token), + (jit.execution.sell - jit.quoted_order.order.surplus_fee()) + .to_string(), + ) + .is_none()); } { let executed_amount = match jit.quoted_order.order.executed { From 53f648745e80396de19aaf5efeb885034d1da607 Mon Sep 17 00:00:00 2001 From: Mateo Date: Tue, 15 Oct 2024 16:21:00 +0200 Subject: [PATCH 13/13] rework comments --- crates/driver/src/tests/setup/mod.rs | 9 ++-- crates/driver/src/tests/setup/solver.rs | 66 ++++++++++++------------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index 875780e982..9f1526c33b 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -1237,10 +1237,9 @@ impl<'a> SolveOk<'a> { // Since JIT orders don't have UID at creation time, we need to search for // matching token pair for expected in jit_orders.iter() { - let mut exist = false; - for trade in trades.values() { - exist |= self.exist_jit_order(trade, expected) - } + let exist = trades + .values() + .any(|trade| self.trade_matches(trade, expected)); assert!(exist, "JIT order {expected:?} not found"); } self @@ -1248,7 +1247,7 @@ impl<'a> SolveOk<'a> { /// Find for a JIT order, given specific token pair and buy/sell amount, /// return true if the JIT order was found - fn exist_jit_order(&self, trade: &serde_json::Value, expected: &JitOrder) -> bool { + fn trade_matches(&self, trade: &serde_json::Value, expected: &JitOrder) -> bool { let u256 = |value: &serde_json::Value| eth::U256::from_dec_str(value.as_str().unwrap()).unwrap(); let sell_token = trade.get("sellToken").unwrap().to_string(); diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index 4c2ff71e0d..2f9cbe28cc 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -197,24 +197,22 @@ impl Solver { }) }, )); - assert!(prices_json - .insert( - config - .blockchain - .get_token_wrapped(fulfillment.quoted_order.order.sell_token), - fulfillment.execution.buy.to_string(), - ) - .is_none()); - assert!(prices_json - .insert( - config - .blockchain - .get_token_wrapped(fulfillment.quoted_order.order.buy_token), - (fulfillment.execution.sell - - fulfillment.quoted_order.order.surplus_fee()) - .to_string(), - ) - .is_none()); + let previous_value = prices_json.insert( + config + .blockchain + .get_token_wrapped(fulfillment.quoted_order.order.sell_token), + fulfillment.execution.buy.to_string(), + ); + assert_eq!(previous_value, None, "existing price overwritten"); + let previous_value = prices_json.insert( + config + .blockchain + .get_token_wrapped(fulfillment.quoted_order.order.buy_token), + (fulfillment.execution.sell + - fulfillment.quoted_order.order.surplus_fee()) + .to_string(), + ); + assert_eq!(previous_value, None, "existing price overwritten"); { // trades have optional field `fee` let order = if config.quote { @@ -279,23 +277,21 @@ impl Solver { .expected_surplus_capturing_jit_order_owners .contains(&jit.quoted_order.order.owner) { - assert!(prices_json - .insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.sell_token), - jit.execution.buy.to_string(), - ) - .is_none()); - assert!(prices_json - .insert( - config - .blockchain - .get_token_wrapped(jit.quoted_order.order.buy_token), - (jit.execution.sell - jit.quoted_order.order.surplus_fee()) - .to_string(), - ) - .is_none()); + let previous_value = prices_json.insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.sell_token), + jit.execution.buy.to_string(), + ); + assert_eq!(previous_value, None, "existing price overwritten"); + let previous_value = prices_json.insert( + config + .blockchain + .get_token_wrapped(jit.quoted_order.order.buy_token), + (jit.execution.sell - jit.quoted_order.order.surplus_fee()) + .to_string(), + ); + assert_eq!(previous_value, None, "existing price overwritten"); } { let executed_amount = match jit.quoted_order.order.executed {