diff --git a/crates/driver/src/domain/competition/order/mod.rs b/crates/driver/src/domain/competition/order/mod.rs index 8f9daed123..55eec87ff6 100644 --- a/crates/driver/src/domain/competition/order/mod.rs +++ b/crates/driver/src/domain/competition/order/mod.rs @@ -8,6 +8,7 @@ use { derive_more::{From, Into}, eth_domain_types as eth, model::order::{BuyTokenDestination, SellTokenSource}, + std::sync::Arc, }; pub use {fees::FeePolicy, signature::Signature}; @@ -15,9 +16,10 @@ pub mod app_data; pub mod fees; pub mod signature; -/// An order in the auction. -#[derive(Debug, Clone)] -pub struct Order { +/// The immutable, auction-independent data of an order. Wrapped in [`Arc`] so +/// that per-solver copies of [`Order`] share a single allocation. +#[derive(Debug)] +pub struct OrderData { pub uid: Uid, /// The user specified a custom address to receive the output of this order. pub receiver: Option, @@ -29,8 +31,6 @@ pub struct Order { pub sell: eth::Asset, pub side: Side, pub kind: Kind, - pub app_data: app_data::AppData, - pub partial: Partial, /// The onchain calls to run before sending user funds to the settlement /// contract. /// These are set by the user and included in the settlement transaction. @@ -50,6 +50,32 @@ pub struct Order { pub quote: Option, } +/// An order in the auction. +/// +/// Designed to be cheap to clone. Most of the data is immutable and is +/// reference counted. Solvers only get individual copies of data they +/// have need to update during the pre-processing phase. +#[derive(Debug, Clone)] +pub struct Order { + /// Immutable order data shared across all per-solver copies. + pub data: Arc, + /// Per-solver mutable as the data may only become available after + /// assembling the orders. + pub app_data: app_data::AppData, + /// Per-solver mutable as the driver may allocated different amounts of + /// the user's available balance based on the solver's order prioritization + /// logic. + pub partial: Partial, +} + +impl std::ops::Deref for Order { + type Target = OrderData; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + /// An amount denominated in the sell token of an [`Order`]. #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, From, Into)] pub struct SellAmount(pub eth::U256); @@ -433,37 +459,43 @@ mod tests { amount: eth::U256::from(amount).into(), }; - let order = |sell_amount: u64, buy_amount: u64, available: Option| Order { - uid: Default::default(), - receiver: Default::default(), - created: util::Timestamp(100), - valid_to: util::Timestamp(u32::MAX), - buy: buy(buy_amount), - sell: sell(sell_amount), - side: match available { + let order = |sell_amount: u64, buy_amount: u64, available: Option| { + let side = match available { None => Side::Sell, Some(executed) if executed.token == sell(0).token => Side::Sell, Some(executed) if executed.token == buy(0).token => Side::Buy, _ => panic!(), - }, - kind: Kind::Limit, - app_data: Default::default(), - partial: available + }; + let partial = available .map(|available| Partial::Yes { available: available.amount.into(), }) - .unwrap_or(Partial::No), - pre_interactions: Default::default(), - post_interactions: Default::default(), - sell_token_balance: SellTokenBalance::Erc20, - buy_token_balance: BuyTokenBalance::Erc20, - signature: Signature { - scheme: signature::Scheme::PreSign, - data: Default::default(), - signer: Default::default(), - }, - protocol_fees: Default::default(), - quote: Default::default(), + .unwrap_or(Partial::No); + Order { + data: Arc::new(OrderData { + uid: Default::default(), + receiver: Default::default(), + created: util::Timestamp(100), + valid_to: util::Timestamp(u32::MAX), + buy: buy(buy_amount), + sell: sell(sell_amount), + side, + kind: Kind::Limit, + pre_interactions: Default::default(), + post_interactions: Default::default(), + sell_token_balance: SellTokenBalance::Erc20, + buy_token_balance: BuyTokenBalance::Erc20, + signature: Signature { + scheme: signature::Scheme::PreSign, + data: Default::default(), + signer: Default::default(), + }, + protocol_fees: Default::default(), + quote: Default::default(), + }), + app_data: Default::default(), + partial, + } }; assert_eq!( diff --git a/crates/driver/src/domain/competition/pre_processing.rs b/crates/driver/src/domain/competition/pre_processing.rs index d4ae216b8e..6a5329876b 100644 --- a/crates/driver/src/domain/competition/pre_processing.rs +++ b/crates/driver/src/domain/competition/pre_processing.rs @@ -463,28 +463,8 @@ impl Utilities { let orders: Vec<_> = results .into_iter() .filter_map(|(amm, result)| match result { - Ok(template) => Some(Order { - uid: template.order.uid(&domain_separator, amm).0.into(), - receiver: template.order.receiver, - created: u32::try_from(Utc::now().timestamp()) - .unwrap_or(u32::MIN) - .into(), - valid_to: template.order.valid_to.into(), - buy: eth::Asset { - amount: template.order.buy_amount.into(), - token: template.order.buy_token.into(), - }, - sell: eth::Asset { - amount: template.order.sell_amount.into(), - token: template.order.sell_token.into(), - }, - kind: order::Kind::Limit, - side: template.order.kind.into(), - app_data: order::app_data::AppDataHash(FixedBytes(template.order.app_data.0)) - .into(), - buy_token_balance: template.order.buy_token_balance.into(), - sell_token_balance: template.order.sell_token_balance.into(), - partial: match template.order.partially_fillable { + Ok(template) => { + let partial = match template.order.partially_fillable { true => order::Partial::Yes { available: match template.order.kind { OrderKind::Sell => order::TargetAmount(template.order.sell_amount), @@ -492,18 +472,8 @@ impl Utilities { }, }, false => order::Partial::No, - }, - pre_interactions: template - .pre_interactions - .into_iter() - .map(Into::into) - .collect(), - post_interactions: template - .post_interactions - .into_iter() - .map(Into::into) - .collect(), - signature: match template.signature { + }; + let signature = match template.signature { Signature::Eip1271(bytes) => order::Signature { scheme: order::signature::Scheme::Eip1271, data: Bytes::from(bytes), @@ -517,10 +487,48 @@ impl Utilities { ); return None; } - }, - protocol_fees: vec![], - quote: None, - }), + }; + Some(Order { + data: Arc::new(order::OrderData { + uid: template.order.uid(&domain_separator, amm).0.into(), + receiver: template.order.receiver, + created: u32::try_from(Utc::now().timestamp()) + .unwrap_or(u32::MIN) + .into(), + valid_to: template.order.valid_to.into(), + buy: eth::Asset { + amount: template.order.buy_amount.into(), + token: template.order.buy_token.into(), + }, + sell: eth::Asset { + amount: template.order.sell_amount.into(), + token: template.order.sell_token.into(), + }, + kind: order::Kind::Limit, + side: template.order.kind.into(), + buy_token_balance: template.order.buy_token_balance.into(), + sell_token_balance: template.order.sell_token_balance.into(), + pre_interactions: template + .pre_interactions + .into_iter() + .map(Into::into) + .collect(), + post_interactions: template + .post_interactions + .into_iter() + .map(Into::into) + .collect(), + signature, + protocol_fees: vec![], + quote: None, + }), + app_data: order::app_data::AppDataHash(FixedBytes( + template.order.app_data.0, + )) + .into(), + partial, + }) + } Err(err) => { tracing::warn!(?err, ?amm, "failed to generate template order for cow amm"); None diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index 11d63dfa80..f4e5ca731d 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -112,25 +112,27 @@ impl Solution { *trade = Trade::Fulfillment( Fulfillment::new( competition::Order { - uid: jit.order().uid, - kind: order::Kind::Limit, - side: jit.order().side, - sell: jit.order().sell, - buy: jit.order().buy, - signature: jit.order().signature.clone(), - receiver: Some(jit.order().receiver), - created: u32::try_from(Utc::now().timestamp()) - .unwrap_or(u32::MIN) - .into(), - valid_to: jit.order().valid_to, + data: std::sync::Arc::new(order::OrderData { + uid: jit.order().uid, + kind: order::Kind::Limit, + side: jit.order().side, + sell: jit.order().sell, + buy: jit.order().buy, + signature: jit.order().signature.clone(), + receiver: Some(jit.order().receiver), + created: u32::try_from(Utc::now().timestamp()) + .unwrap_or(u32::MIN) + .into(), + valid_to: jit.order().valid_to, + pre_interactions: vec![], + post_interactions: vec![], + sell_token_balance: jit.order().sell_token_balance, + buy_token_balance: jit.order().buy_token_balance, + protocol_fees: vec![], + quote: None, + }), app_data: jit.order().app_data.into(), partial: jit.order().partially_fillable(), - pre_interactions: vec![], - post_interactions: vec![], - sell_token_balance: jit.order().sell_token_balance, - buy_token_balance: jit.order().buy_token_balance, - protocol_fees: vec![], - quote: None, }, jit.executed(), Fee::Dynamic(jit.fee()), diff --git a/crates/driver/src/domain/competition/solution/trade.rs b/crates/driver/src/domain/competition/solution/trade.rs index c742a2dd07..b85981e671 100644 --- a/crates/driver/src/domain/competition/solution/trade.rs +++ b/crates/driver/src/domain/competition/solution/trade.rs @@ -10,7 +10,6 @@ use { /// A trade which executes an order as part of this solution. #[derive(Debug, Clone)] -#[expect(clippy::large_enum_variant)] pub enum Trade { Fulfillment(Fulfillment), Jit(Jit), diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index 4acc4946ba..efba437c20 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -177,33 +177,35 @@ impl Order { competition::Auction::new( None, vec![competition::Order { - uid: Default::default(), - receiver: None, - created: u32::try_from(Utc::now().timestamp()) - .unwrap_or(u32::MIN) - .into(), - valid_to: util::Timestamp::MAX, - buy: self.buy(), - sell: self.sell(), - side: self.side, - kind: if quote_using_limit_orders { - competition::order::Kind::Limit - } else { - competition::order::Kind::Market - }, + data: std::sync::Arc::new(competition::order::OrderData { + uid: Default::default(), + receiver: None, + created: u32::try_from(Utc::now().timestamp()) + .unwrap_or(u32::MIN) + .into(), + valid_to: util::Timestamp::MAX, + buy: self.buy(), + sell: self.sell(), + side: self.side, + kind: if quote_using_limit_orders { + competition::order::Kind::Limit + } else { + competition::order::Kind::Market + }, + pre_interactions: Default::default(), + post_interactions: Default::default(), + sell_token_balance: competition::order::SellTokenBalance::Erc20, + buy_token_balance: competition::order::BuyTokenBalance::Erc20, + signature: competition::order::Signature { + scheme: competition::order::signature::Scheme::Eip1271, + data: Default::default(), + signer: Default::default(), + }, + protocol_fees: Default::default(), + quote: Default::default(), + }), app_data: Default::default(), partial: competition::order::Partial::No, - pre_interactions: Default::default(), - post_interactions: Default::default(), - sell_token_balance: competition::order::SellTokenBalance::Erc20, - buy_token_balance: competition::order::BuyTokenBalance::Erc20, - signature: competition::order::Signature { - scheme: competition::order::signature::Scheme::Eip1271, - data: Default::default(), - signer: Default::default(), - }, - protocol_fees: Default::default(), - quote: Default::default(), }], [ auction::Token { diff --git a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs index 875e6b3e83..4b3db0a06b 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/solve_request.rs @@ -42,32 +42,12 @@ impl SolveRequest { Some(self.id.try_into()?), self.orders .into_iter() - .map(|order| competition::Order { - uid: order.uid.into(), - receiver: order.receiver, - created: order.created.into(), - valid_to: order.valid_to.into(), - buy: eth::Asset { - amount: order.buy_amount.into(), - token: order.buy_token.into(), - }, - sell: eth::Asset { - amount: order.sell_amount.into(), - token: order.sell_token.into(), - }, - side: match order.kind { - Kind::Sell => competition::order::Side::Sell, - Kind::Buy => competition::order::Side::Buy, - }, - kind: match order.class { - Class::Market => competition::order::Kind::Market, - Class::Limit => competition::order::Kind::Limit, - }, - app_data: match app_data.get(&AppDataHash::from(order.app_data)) { + .map(|order| { + let app_data = match app_data.get(&AppDataHash::from(order.app_data)) { Some(data) => AppData::Full(data.clone()), None => AppData::Hash(AppDataHash::from(order.app_data)), - }, - partial: if order.partially_fillable { + }; + let partial = if order.partially_fillable { competition::order::Partial::Yes { available: match order.kind { Kind::Sell => { @@ -78,82 +58,116 @@ impl SolveRequest { } } else { competition::order::Partial::No - }, - pre_interactions: order - .pre_interactions - .into_iter() - .map(|interaction| domain::Interaction { - target: interaction.target, - value: interaction.value.into(), - call_data: interaction.call_data.into(), - }) - .collect(), - post_interactions: order - .post_interactions - .into_iter() - .map(|interaction| domain::Interaction { - target: interaction.target, - value: interaction.value.into(), - call_data: interaction.call_data.into(), - }) - .collect(), - sell_token_balance: match order.sell_token_balance { - SellTokenBalance::Erc20 => competition::order::SellTokenBalance::Erc20, - SellTokenBalance::Internal => { - competition::order::SellTokenBalance::Internal - } - SellTokenBalance::External => { - competition::order::SellTokenBalance::External - } - }, - buy_token_balance: match order.buy_token_balance { - BuyTokenBalance::Erc20 => competition::order::BuyTokenBalance::Erc20, - BuyTokenBalance::Internal => competition::order::BuyTokenBalance::Internal, - }, - signature: competition::order::Signature { - scheme: match order.signing_scheme { - SigningScheme::Eip712 => competition::order::signature::Scheme::Eip712, - SigningScheme::EthSign => { - competition::order::signature::Scheme::EthSign - } - SigningScheme::PreSign => { - competition::order::signature::Scheme::PreSign - } - SigningScheme::Eip1271 => { - competition::order::signature::Scheme::Eip1271 - } - }, - data: order.signature.into(), - signer: order.owner, - }, - protocol_fees: order - .protocol_fees - .into_iter() - .map(|policy| match policy { - FeePolicy::Surplus { - factor, - max_volume_factor, - } => competition::order::FeePolicy::Surplus { - factor, - max_volume_factor, + }; + competition::Order { + data: Arc::new(competition::order::OrderData { + uid: order.uid.into(), + receiver: order.receiver, + created: order.created.into(), + valid_to: order.valid_to.into(), + buy: eth::Asset { + amount: order.buy_amount.into(), + token: order.buy_token.into(), + }, + sell: eth::Asset { + amount: order.sell_amount.into(), + token: order.sell_token.into(), + }, + side: match order.kind { + Kind::Sell => competition::order::Side::Sell, + Kind::Buy => competition::order::Side::Buy, + }, + kind: match order.class { + Class::Market => competition::order::Kind::Market, + Class::Limit => competition::order::Kind::Limit, + }, + pre_interactions: order + .pre_interactions + .into_iter() + .map(|interaction| domain::Interaction { + target: interaction.target, + value: interaction.value.into(), + call_data: interaction.call_data.into(), + }) + .collect(), + post_interactions: order + .post_interactions + .into_iter() + .map(|interaction| domain::Interaction { + target: interaction.target, + value: interaction.value.into(), + call_data: interaction.call_data.into(), + }) + .collect(), + sell_token_balance: match order.sell_token_balance { + SellTokenBalance::Erc20 => { + competition::order::SellTokenBalance::Erc20 + } + SellTokenBalance::Internal => { + competition::order::SellTokenBalance::Internal + } + SellTokenBalance::External => { + competition::order::SellTokenBalance::External + } + }, + buy_token_balance: match order.buy_token_balance { + BuyTokenBalance::Erc20 => { + competition::order::BuyTokenBalance::Erc20 + } + BuyTokenBalance::Internal => { + competition::order::BuyTokenBalance::Internal + } }, - FeePolicy::PriceImprovement { - factor, - max_volume_factor, - quote, - } => competition::order::FeePolicy::PriceImprovement { - factor, - max_volume_factor, - quote: quote.into_domain(order.sell_token, order.buy_token), + signature: competition::order::Signature { + scheme: match order.signing_scheme { + SigningScheme::Eip712 => { + competition::order::signature::Scheme::Eip712 + } + SigningScheme::EthSign => { + competition::order::signature::Scheme::EthSign + } + SigningScheme::PreSign => { + competition::order::signature::Scheme::PreSign + } + SigningScheme::Eip1271 => { + competition::order::signature::Scheme::Eip1271 + } + }, + data: order.signature.into(), + signer: order.owner, }, - FeePolicy::Volume { factor } => { - competition::order::FeePolicy::Volume { factor } - } - }) - .collect(), - quote: order - .quote - .map(|q| q.into_domain(order.sell_token, order.buy_token)), + protocol_fees: order + .protocol_fees + .into_iter() + .map(|policy| match policy { + FeePolicy::Surplus { + factor, + max_volume_factor, + } => competition::order::FeePolicy::Surplus { + factor, + max_volume_factor, + }, + FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote, + } => competition::order::FeePolicy::PriceImprovement { + factor, + max_volume_factor, + quote: quote.into_domain(order.sell_token, order.buy_token), + }, + FeePolicy::Volume { factor } => { + competition::order::FeePolicy::Volume { factor } + } + }) + .collect(), + quote: order + .quote + .map(|q| q.into_domain(order.sell_token, order.buy_token)), + }), + app_data, + partial, + } }) .collect(), self.tokens.into_iter().map(|token| {