From 986613cfeded6ff5ef4ecbb30910feb00535684d Mon Sep 17 00:00:00 2001 From: Dusan Stanivukovic Date: Fri, 29 Sep 2023 09:50:09 +0200 Subject: [PATCH] Optimal bidding colocation (#1856) Tackles risk estimation and score calculation for colocated driver. Colocated Driver expects to receive from `solvers` either: 1. `Score` as absolute value 2. `success_probability` value that is used to calculate (1) using `ScoreSolve` RiskParameters loading is moved to `solvers` but not used for calculating the `success_probability` yet, instead, value `1.0` is returned for Naive, Baseline and Dex solvers. For legacy solvers, whatever they sent to us is forwarded to `driver`. Release notes: Currently missing the default values for risk parameters... I'm fine with defining them explicitly in the infrastructure repo. --- crates/autopilot/src/arguments.rs | 7 +- crates/autopilot/src/driver_model.rs | 2 + crates/autopilot/src/run.rs | 3 +- crates/autopilot/src/run_loop.rs | 12 ++- crates/autopilot/src/shadow.rs | 6 +- crates/driver/src/boundary/mempool.rs | 4 + crates/driver/src/boundary/settlement.rs | 92 ++++++++++++------- .../driver/src/domain/competition/auction.rs | 7 ++ crates/driver/src/domain/competition/mod.rs | 26 +++++- .../src/domain/competition/solution/mod.rs | 61 +++--------- .../domain/competition/solution/settlement.rs | 10 +- crates/driver/src/domain/mempools.rs | 23 +++++ crates/driver/src/domain/mod.rs | 6 +- crates/driver/src/domain/quote.rs | 1 + .../src/infra/api/routes/solve/dto/auction.rs | 3 + crates/driver/src/infra/config/file/load.rs | 1 + crates/driver/src/infra/config/file/mod.rs | 7 ++ crates/driver/src/infra/config/mod.rs | 10 +- crates/driver/src/infra/observe/mod.rs | 4 +- .../driver/src/infra/solver/dto/solution.rs | 18 +++- crates/driver/src/tests/cases/mod.rs | 5 +- .../driver/src/tests/cases/negative_scores.rs | 1 - crates/driver/src/tests/cases/risk.rs | 28 ------ .../src/tests/cases/score_competition.rs | 36 ++++++++ crates/driver/src/tests/setup/blockchain.rs | 6 +- crates/driver/src/tests/setup/driver.rs | 3 +- crates/driver/src/tests/setup/mod.rs | 48 +++++++--- crates/driver/src/tests/setup/solver.rs | 2 +- crates/solver/src/run.rs | 6 +- .../src/settlement_post_processing/mod.rs | 8 +- crates/solver/src/settlement_rater.rs | 89 ++++++------------ crates/solver/src/solver/risk_computation.rs | 3 - crates/solvers/src/api/dto/solution.rs | 13 +++ crates/solvers/src/boundary/legacy.rs | 11 +++ crates/solvers/src/boundary/naive.rs | 1 + crates/solvers/src/domain/solution.rs | 29 ++++++ .../src/tests/balancer/market_order.rs | 10 +- .../src/tests/baseline/bal_liquidity.rs | 25 ++++- .../src/tests/baseline/buy_order_rounding.rs | 30 ++++-- .../solvers/src/tests/baseline/direct_swap.rs | 5 +- .../src/tests/baseline/partial_fill.rs | 5 +- crates/solvers/src/tests/dex/partial_fill.rs | 15 ++- .../src/tests/legacy/attaching_approvals.rs | 5 +- .../tests/legacy/concentrated_liquidity.rs | 5 +- crates/solvers/src/tests/legacy/jit_order.rs | 3 + .../solvers/src/tests/legacy/market_order.rs | 10 +- .../src/tests/naive/extract_deepest_pool.rs | 5 +- .../naive/filters_out_of_price_orders.rs | 3 + .../solvers/src/tests/naive/matches_orders.rs | 12 +++ .../src/tests/naive/reserves_too_small.rs | 3 + .../rounds_prices_in_favour_of_traders.rs | 3 + .../solvers/src/tests/naive/without_pool.rs | 3 + .../solvers/src/tests/oneinch/market_order.rs | 5 +- .../src/tests/paraswap/market_order.rs | 10 +- .../solvers/src/tests/zeroex/market_order.rs | 6 ++ crates/solvers/src/tests/zeroex/options.rs | 3 + 56 files changed, 508 insertions(+), 250 deletions(-) delete mode 100644 crates/driver/src/tests/cases/risk.rs create mode 100644 crates/driver/src/tests/cases/score_competition.rs diff --git a/crates/autopilot/src/arguments.rs b/crates/autopilot/src/arguments.rs index 4f3fcb4580..9864999d0c 100644 --- a/crates/autopilot/src/arguments.rs +++ b/crates/autopilot/src/arguments.rs @@ -1,5 +1,5 @@ use { - primitive_types::H160, + primitive_types::{H160, U256}, shared::{ arguments::{display_list, display_option}, bad_token::token_owner_finder, @@ -169,6 +169,10 @@ pub struct Arguments { #[clap(long, env, default_value = "5")] pub additional_deadline_for_rewards: usize, + /// Cap used for CIP20 score calculation. Defaults to 0.01 ETH. + #[clap(long, env, default_value = "10000000000000000")] + pub score_cap: U256, + /// Run the autopilot in a shadow mode by specifying an upstream CoW /// protocol deployment to pull auctions from. This will cause the autopilot /// to start a run loop where it performs solver competition on driver, @@ -236,6 +240,7 @@ impl std::fmt::Display for Arguments { "additional_deadline_for_rewards: {}", self.additional_deadline_for_rewards )?; + writeln!(f, "score_cap: {}", self.score_cap)?; display_option(f, "shadow", &self.shadow)?; Ok(()) } diff --git a/crates/autopilot/src/driver_model.rs b/crates/autopilot/src/driver_model.rs index 36374f2a60..1428a8f4df 100644 --- a/crates/autopilot/src/driver_model.rs +++ b/crates/autopilot/src/driver_model.rs @@ -68,6 +68,8 @@ pub mod solve { pub tokens: Vec, pub orders: Vec, pub deadline: DateTime, + #[serde_as(as = "DecimalU256")] + pub score_cap: U256, } #[serde_as] diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 5e59e8c5ea..0c85d3d324 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -596,6 +596,7 @@ pub async fn run(args: Arguments) { market_makable_token_list, submission_deadline: args.submission_deadline as u64, additional_deadline_for_rewards: args.additional_deadline_for_rewards as u64, + score_cap: args.score_cap, }; run.run_forever().await; unreachable!("run loop exited"); @@ -649,7 +650,7 @@ async fn shadow_mode(args: Arguments) -> ! { .await }; - let shadow = shadow::RunLoop::new(orderbook, drivers, trusted_tokens); + let shadow = shadow::RunLoop::new(orderbook, drivers, trusted_tokens, args.score_cap); shadow.run_forever().await; unreachable!("shadow run loop exited"); diff --git a/crates/autopilot/src/run_loop.rs b/crates/autopilot/src/run_loop.rs index 4e8ee4e3ce..dfa7a5cf2f 100644 --- a/crates/autopilot/src/run_loop.rs +++ b/crates/autopilot/src/run_loop.rs @@ -28,7 +28,7 @@ use { SolverSettlement, }, }, - primitive_types::{H160, H256}, + primitive_types::{H160, H256, U256}, rand::seq::SliceRandom, shared::{ event_handling::MAX_REORG_BLOCK_COUNT, @@ -56,6 +56,7 @@ pub struct RunLoop { pub market_makable_token_list: AutoUpdatingTokenList, pub submission_deadline: u64, pub additional_deadline_for_rewards: u64, + pub score_cap: U256, } impl RunLoop { @@ -269,7 +270,12 @@ impl RunLoop { return Default::default(); } - let request = solve_request(id, auction, &self.market_makable_token_list.all()); + let request = solve_request( + id, + auction, + &self.market_makable_token_list.all(), + self.score_cap, + ); let request = &request; self.database @@ -435,6 +441,7 @@ pub fn solve_request( id: AuctionId, auction: &Auction, trusted_tokens: &HashSet, + score_cap: U256, ) -> solve::Request { solve::Request { id, @@ -499,5 +506,6 @@ pub fn solve_request( .unique_by(|token| token.address) .collect(), deadline: Utc::now() + chrono::Duration::from_std(SOLVE_TIME_LIMIT).unwrap(), + score_cap, } } diff --git a/crates/autopilot/src/shadow.rs b/crates/autopilot/src/shadow.rs index 62e327e4d8..f07b950b71 100644 --- a/crates/autopilot/src/shadow.rs +++ b/crates/autopilot/src/shadow.rs @@ -32,6 +32,7 @@ pub struct RunLoop { trusted_tokens: AutoUpdatingTokenList, auction: AuctionId, block: u64, + score_cap: U256, } impl RunLoop { @@ -39,6 +40,7 @@ impl RunLoop { orderbook: protocol::Orderbook, drivers: Vec, trusted_tokens: AutoUpdatingTokenList, + score_cap: U256, ) -> Self { Self { orderbook, @@ -46,6 +48,7 @@ impl RunLoop { trusted_tokens, auction: 0, block: 0, + score_cap, } } @@ -170,7 +173,8 @@ impl RunLoop { /// Runs the solver competition, making all configured drivers participate. async fn competition(&self, id: AuctionId, auction: &Auction) -> Vec> { - let request = run_loop::solve_request(id, auction, &self.trusted_tokens.all()); + let request = + run_loop::solve_request(id, auction, &self.trusted_tokens.all(), self.score_cap); let request = &request; futures::future::join_all(self.drivers.iter().map(|driver| async move { diff --git a/crates/driver/src/boundary/mempool.rs b/crates/driver/src/boundary/mempool.rs index 868dddf962..82d8e7ad34 100644 --- a/crates/driver/src/boundary/mempool.rs +++ b/crates/driver/src/boundary/mempool.rs @@ -166,6 +166,10 @@ impl Mempool { .await?; Ok(receipt.transaction_hash.into()) } + + pub fn config(&self) -> &Config { + &self.config + } } struct AccessListEstimator(eth::AccessList); diff --git a/crates/driver/src/boundary/settlement.rs b/crates/driver/src/boundary/settlement.rs index 7f986e8292..7bb886e877 100644 --- a/crates/driver/src/boundary/settlement.rs +++ b/crates/driver/src/boundary/settlement.rs @@ -1,14 +1,12 @@ use { crate::{ + domain, domain::{ competition::{ self, auction, order, - solution::{ - self, - settlement::{self, Internalization}, - }, + solution::settlement::{self, Internalization}, }, eth, liquidity, @@ -16,8 +14,7 @@ use { infra::Ethereum, util::conv::u256::U256Ext, }, - anyhow::{anyhow, ensure, Context, Result}, - bigdecimal::Signed, + anyhow::{anyhow, Context, Result}, model::{ app_data::AppDataHash, interaction::InteractionData, @@ -37,7 +34,10 @@ use { }, shared::{ external_prices::ExternalPrices, - http_solver::model::{InternalizationStrategy, TokenAmount}, + http_solver::{ + self, + model::{InternalizationStrategy, TokenAmount}, + }, }, solver::{ interactions::Erc20ApproveInteraction, @@ -56,7 +56,6 @@ use { pub struct Settlement { pub(super) inner: solver::settlement::Settlement, pub solver: eth::Address, - risk: solution::Risk, } impl Settlement { @@ -158,10 +157,19 @@ impl Settlement { ); } + settlement.score = match solution.score().clone() { + competition::SolverScore::Solver(score) => http_solver::model::Score::Solver { score }, + competition::SolverScore::RiskAdjusted(success_probability) => { + http_solver::model::Score::RiskAdjusted { + success_probability, + gas_amount: None, + } + } + }; + Ok(Self { inner: settlement, solver: solution.solver().address(), - risk: solution.risk(), }) } @@ -199,36 +207,54 @@ impl Settlement { eth: &Ethereum, auction: &competition::Auction, gas: eth::Gas, - ) -> Result { - let prices = ExternalPrices::try_from_auction_prices( - eth.contracts().weth().address(), - auction - .tokens() - .iter() - .filter_map(|token| { - token - .price - .map(|price| (token.address.into(), price.into())) - }) - .collect(), - )?; - let gas_price = eth::U256::from(auction.gas_price().effective()).to_big_rational(); - let inputs = solver::objective_value::Inputs::from_settlement( - &self.inner, - &prices, - gas_price, - &gas.into(), - ); - ensure!(!inputs.objective_value().is_negative(), "negative score"); - let objective_value = eth::U256::from_big_rational(&inputs.objective_value())?; - Ok((objective_value - self.risk.0).into()) + revert_protection: &domain::RevertProtection, + ) -> Result { + let score = match self.inner.score { + http_solver::model::Score::Solver { score } => score, + http_solver::model::Score::Discount { .. } => { + unreachable!("discounted score no longer supported") + } + http_solver::model::Score::RiskAdjusted { + success_probability, + gas_amount, + } => { + let prices = ExternalPrices::try_from_auction_prices( + eth.contracts().weth().address(), + auction + .tokens() + .iter() + .filter_map(|token| { + token + .price + .map(|price| (token.address.into(), price.into())) + }) + .collect(), + )?; + let gas_price = eth::U256::from(auction.gas_price().effective()).to_big_rational(); + let inputs = solver::objective_value::Inputs::from_settlement( + &self.inner, + &prices, + gas_price.clone(), + &gas_amount.unwrap_or(gas.into()), + ); + solver::settlement_rater::ScoreCalculator::new( + auction.score_cap().to_big_rational(), + matches!(revert_protection, domain::RevertProtection::Disabled), + ) + .compute_score( + &inputs.objective_value(), + &inputs.gas_cost(), + success_probability, + )? + } + }; + Ok(score.into()) } pub fn merge(self, other: Self) -> Result { self.inner.merge(other.inner).map(|inner| Self { inner, solver: self.solver, - risk: self.risk.merge(other.risk), }) } } diff --git a/crates/driver/src/domain/competition/auction.rs b/crates/driver/src/domain/competition/auction.rs index 4ca0705ae1..3d8f249890 100644 --- a/crates/driver/src/domain/competition/auction.rs +++ b/crates/driver/src/domain/competition/auction.rs @@ -27,6 +27,7 @@ pub struct Auction { tokens: Tokens, gas_price: eth::GasPrice, deadline: Deadline, + score_cap: eth::U256, } impl Auction { @@ -36,6 +37,7 @@ impl Auction { tokens: impl Iterator, deadline: Deadline, eth: &Ethereum, + score_cap: eth::U256, ) -> Result { let tokens = Tokens(tokens.map(|token| (token.address, token)).collect()); @@ -59,6 +61,7 @@ impl Auction { tokens, gas_price: eth.gas_price().await?, deadline, + score_cap, }) } @@ -231,6 +234,10 @@ impl Auction { pub fn deadline(&self) -> Deadline { self.deadline } + + pub fn score_cap(&self) -> eth::U256 { + self.score_cap + } } /// The tokens that are used in an auction. diff --git a/crates/driver/src/domain/competition/mod.rs b/crates/driver/src/domain/competition/mod.rs index 39f3acd13c..5646cceac1 100644 --- a/crates/driver/src/domain/competition/mod.rs +++ b/crates/driver/src/domain/competition/mod.rs @@ -1,6 +1,6 @@ use { self::solution::settlement, - super::Mempools, + super::{eth, Mempools}, crate::{ domain::{competition::solution::Settlement, liquidity}, infra::{ @@ -26,7 +26,7 @@ pub mod solution; pub use { auction::Auction, order::Order, - solution::{Score, Solution, SolverTimeout}, + solution::{Solution, SolverScore, SolverTimeout}, }; /// An ongoing competition. There is one competition going on per solver at any @@ -142,7 +142,10 @@ impl Competition { .into_iter() .map(|settlement| { observe::scoring(&settlement); - (settlement.score(&self.eth, auction), settlement) + ( + settlement.score(&self.eth, auction, &self.mempools.revert_protection()), + settlement, + ) }) .collect_vec(); @@ -234,6 +237,23 @@ impl Competition { } } +/// Represents a single value suitable for comparing/ranking solutions. +/// This is a final score that is observed by the autopilot. +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] +pub struct Score(pub eth::U256); + +impl From for eth::U256 { + fn from(value: Score) -> Self { + value.0 + } +} + +impl From for Score { + fn from(value: eth::U256) -> Self { + Self(value) + } +} + /// Solution information sent to the protocol by the driver before the solution /// ranking happens. #[derive(Debug)] diff --git a/crates/driver/src/domain/competition/solution/mod.rs b/crates/driver/src/domain/competition/solution/mod.rs index c89f8110dd..4d6f4fccb3 100644 --- a/crates/driver/src/domain/competition/solution/mod.rs +++ b/crates/driver/src/domain/competition/solution/mod.rs @@ -37,7 +37,7 @@ pub struct Solution { prices: HashMap, interactions: Vec, solver: Solver, - risk: Risk, + score: SolverScore, weth: eth::WethAddress, } @@ -48,7 +48,7 @@ impl Solution { prices: HashMap, interactions: Vec, solver: Solver, - risk: Risk, + score: SolverScore, weth: eth::WethAddress, ) -> Result { let solution = Self { @@ -57,7 +57,7 @@ impl Solution { prices, interactions, solver, - risk, + score, weth, }; @@ -92,9 +92,8 @@ impl Solution { &self.solver } - /// The risk of this solution. - pub fn risk(&self) -> Risk { - self.risk + pub fn score(&self) -> &SolverScore { + &self.score } /// Approval interactions necessary for encoding the settlement. @@ -243,7 +242,7 @@ impl std::fmt::Debug for Solution { .field("prices", &self.prices) .field("interactions", &self.interactions) .field("solver", &self.solver.name()) - .field("risk", &self.risk) + .field("score", &self.score) .finish() } } @@ -284,49 +283,15 @@ impl SolverTimeout { } } -/// The solution score. This is often referred to as the "objective value". -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct Score(pub eth::U256); +/// Represents the probability that a solution will be successfully settled. +type SuccessProbability = f64; -impl From for eth::U256 { - fn from(value: Score) -> Self { - value.0 - } -} - -impl From for Score { - fn from(value: eth::U256) -> Self { - Self(value) - } +/// Carries information how the score should be calculated. +#[derive(Debug, Clone)] +pub enum SolverScore { + Solver(eth::U256), + RiskAdjusted(SuccessProbability), } - -/// Solver-estimated risk that the settlement might revert. This value is -/// subtracted from the final score of the solution. -#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)] -pub struct Risk(pub eth::U256); - -impl From for eth::U256 { - fn from(value: Risk) -> Self { - value.0 - } -} - -impl From for Risk { - fn from(value: eth::U256) -> Self { - Self(value) - } -} - -impl Risk { - // TODO(#1533) Improve the risk merging formula. For now it's OK to simply add - // the risks, since it causes the solvers to under-bid which is less - // dangerous than over-bidding. - /// Combine two risk values. - pub fn merge(self, other: Risk) -> Self { - Self(self.0 + other.0) - } -} - /// A unique solution ID. This ID is generated by the solver and only needs to /// be unique within a single round of competition. This ID is only important in /// the communication between the driver and the solver, and it is not used by diff --git a/crates/driver/src/domain/competition/solution/settlement.rs b/crates/driver/src/domain/competition/solution/settlement.rs index a586516409..71ed25e88d 100644 --- a/crates/driver/src/domain/competition/solution/settlement.rs +++ b/crates/driver/src/domain/competition/solution/settlement.rs @@ -3,9 +3,9 @@ use { crate::{ boundary, domain::{ - competition, - competition::{auction, order, solution}, + competition::{self, auction, order, solution}, eth, + mempools, }, infra::{blockchain::Ethereum, Simulator}, util::conv::u256::U256Ext, @@ -264,8 +264,10 @@ impl Settlement { &self, eth: &Ethereum, auction: &competition::Auction, - ) -> Result { - self.boundary.score(eth, auction, self.gas.estimate) + revert_protection: &mempools::RevertProtection, + ) -> Result { + self.boundary + .score(eth, auction, self.gas.estimate, revert_protection) } // TODO(#1478): merge() should be defined on Solution rather than Settlement. diff --git a/crates/driver/src/domain/mempools.rs b/crates/driver/src/domain/mempools.rs index 5df794e578..0b808f0079 100644 --- a/crates/driver/src/domain/mempools.rs +++ b/crates/driver/src/domain/mempools.rs @@ -34,8 +34,31 @@ impl Mempools { .boxed() }))); } + + /// Defines if the mempools are configured in a way that guarantees that + /// /settle'd solution will not revert. + pub fn revert_protection(&self) -> RevertProtection { + if self.0.iter().any(|mempool| { + matches!( + mempool.config().kind, + infra::mempool::Kind::Public(infra::mempool::HighRisk::Enabled) + ) + }) { + RevertProtection::Disabled + } else { + RevertProtection::Enabled + } + } } #[derive(Debug, Error)] #[error("no mempools configured, cannot execute settlements")] pub struct NoMempools; + +/// Defines if the mempools are configured in a way that guarantees that +/// /settle'd solution will not revert. +#[derive(Debug, Clone, Copy)] +pub enum RevertProtection { + Enabled, + Disabled, +} diff --git a/crates/driver/src/domain/mod.rs b/crates/driver/src/domain/mod.rs index 03eea6b452..a66681440f 100644 --- a/crates/driver/src/domain/mod.rs +++ b/crates/driver/src/domain/mod.rs @@ -4,4 +4,8 @@ pub mod liquidity; mod mempools; pub mod quote; -pub use {competition::Competition, liquidity::Liquidity, mempools::Mempools}; +pub use { + competition::Competition, + liquidity::Liquidity, + mempools::{Mempools, RevertProtection}, +}; diff --git a/crates/driver/src/domain/quote.rs b/crates/driver/src/domain/quote.rs index 7bf54f6cef..dddc868819 100644 --- a/crates/driver/src/domain/quote.rs +++ b/crates/driver/src/domain/quote.rs @@ -145,6 +145,7 @@ impl Order { .into_iter(), Default::default(), eth, + Default::default(), ) .await .map_err(|err| match err { diff --git a/crates/driver/src/infra/api/routes/solve/dto/auction.rs b/crates/driver/src/infra/api/routes/solve/dto/auction.rs index 77c6d5782e..56b714862a 100644 --- a/crates/driver/src/infra/api/routes/solve/dto/auction.rs +++ b/crates/driver/src/infra/api/routes/solve/dto/auction.rs @@ -129,6 +129,7 @@ impl Auction { }), self.deadline.into(), eth, + self.score_cap, ) .await .map_err(Into::into) @@ -174,6 +175,8 @@ pub struct Auction { tokens: Vec, orders: Vec, deadline: chrono::DateTime, + #[serde_as(as = "serialize::U256")] + score_cap: eth::U256, } impl Auction { diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index 953a39c3ac..c04856dff7 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -37,6 +37,7 @@ pub async fn load(network: &blockchain::Network, path: &Path) -> infra::Config { ); infra::Config { + score_cap: config.score_cap, solvers: join_all(config.solvers.into_iter().map(|config| async move { let account = match config.account { file::Account::PrivateKey(private_key) => ethcontract::Account::Offline( diff --git a/crates/driver/src/infra/config/file/mod.rs b/crates/driver/src/infra/config/file/mod.rs index 86b873f7df..0f0171e0e4 100644 --- a/crates/driver/src/infra/config/file/mod.rs +++ b/crates/driver/src/infra/config/file/mod.rs @@ -47,6 +47,9 @@ struct Config { #[serde(default)] liquidity: LiquidityConfig, + + #[serde(default = "default_score_cap")] + score_cap: eth::U256, } #[derive(Debug, Default, Deserialize)] @@ -141,6 +144,10 @@ fn default_soft_cancellations_flag() -> bool { false } +fn default_score_cap() -> eth::U256 { + 10_000_000_000_000_000u128.into() // 0.01 ETH +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case", deny_unknown_fields)] diff --git a/crates/driver/src/infra/config/mod.rs b/crates/driver/src/infra/config/mod.rs index c3b08f6ba6..b639463958 100644 --- a/crates/driver/src/infra/config/mod.rs +++ b/crates/driver/src/infra/config/mod.rs @@ -1,6 +1,9 @@ -use crate::{ - domain::eth, - infra::{blockchain, liquidity, mempool, simulator, solver}, +use { + crate::{ + domain::eth, + infra::{blockchain, liquidity, mempool, simulator, solver}, + }, + primitive_types::U256, }; pub mod file; @@ -8,6 +11,7 @@ pub mod file; /// Configuration of infrastructural components. #[derive(Debug)] pub struct Config { + pub score_cap: U256, pub disable_access_list_simulation: bool, pub disable_gas_simulation: Option, pub solvers: Vec, diff --git a/crates/driver/src/infra/observe/mod.rs b/crates/driver/src/infra/observe/mod.rs index f17865b7a3..4022bcead7 100644 --- a/crates/driver/src/infra/observe/mod.rs +++ b/crates/driver/src/infra/observe/mod.rs @@ -124,10 +124,10 @@ pub fn scoring_failed(solver: &solver::Name, err: &boundary::Error) { } /// Observe the settlement score. -pub fn score(settlement: &Settlement, score: &solution::Score) { +pub fn score(settlement: &Settlement, score: &competition::Score) { tracing::info!( solutions = ?settlement.solutions(), - score = score.0.to_f64_lossy(), + score = ?score, "scored settlement" ); } diff --git a/crates/driver/src/infra/solver/dto/solution.rs b/crates/driver/src/infra/solver/dto/solution.rs index 9ce2f90861..3d344780c0 100644 --- a/crates/driver/src/infra/solver/dto/solution.rs +++ b/crates/driver/src/infra/solver/dto/solution.rs @@ -196,7 +196,12 @@ impl Solutions { }) .try_collect()?, solver.clone(), - solution.risk.into(), + match solution.score { + Score::Solver(score) => competition::solution::SolverScore::Solver(score), + Score::RiskAdjusted(success_probability) => { + competition::solution::SolverScore::RiskAdjusted(success_probability) + } + }, weth, ) .map_err(|competition::solution::InvalidClearingPrices| { @@ -223,9 +228,7 @@ pub struct Solution { prices: HashMap, trades: Vec, interactions: Vec, - #[serde_as(as = "serialize::U256")] - #[serde(default)] - risk: eth::U256, + score: Score, } #[derive(Debug, Deserialize)] @@ -369,3 +372,10 @@ enum SigningScheme { PreSign, Eip1271, } + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase", deny_unknown_fields)] +pub enum Score { + Solver(eth::U256), + RiskAdjusted(f64), +} diff --git a/crates/driver/src/tests/cases/mod.rs b/crates/driver/src/tests/cases/mod.rs index 7f25c7c223..ecd33e8214 100644 --- a/crates/driver/src/tests/cases/mod.rs +++ b/crates/driver/src/tests/cases/mod.rs @@ -9,7 +9,7 @@ pub mod multiple_solutions; pub mod negative_scores; pub mod order_prioritization; pub mod quote; -pub mod risk; +pub mod score_competition; pub mod settle; pub mod solver_balance; @@ -41,3 +41,6 @@ pub const DEFAULT_SCORE_MAX: u128 = 500000000000000000000000000000u128; /// The default solver fee for limit orders. pub const DEFAULT_SOLVER_FEE: u128 = 100u128; + +/// The default maximum value to be payout out to solver per solution +pub const DEFAULT_SCORE_CAP: u128 = 10000000000000000u128; diff --git a/crates/driver/src/tests/cases/negative_scores.rs b/crates/driver/src/tests/cases/negative_scores.rs index b6ed42ddf9..d62a25c39a 100644 --- a/crates/driver/src/tests/cases/negative_scores.rs +++ b/crates/driver/src/tests/cases/negative_scores.rs @@ -36,7 +36,6 @@ async fn one_valid_solution() { }) .done() .await; - test.solve().await.ok().default_score(); test.reveal().await.ok().orders(&[ab_order().name]); } diff --git a/crates/driver/src/tests/cases/risk.rs b/crates/driver/src/tests/cases/risk.rs deleted file mode 100644 index b7760c9f2a..0000000000 --- a/crates/driver/src/tests/cases/risk.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::tests::{ - self, - cases::{DEFAULT_SCORE_MAX, DEFAULT_SCORE_MIN}, - setup::{ab_order, ab_pool, ab_solution}, -}; - -pub const RISK: u128 = 100000000000000000u128; - -/// Test that the solution risk affects the score. -#[tokio::test] -#[ignore] -async fn test() { - let test = tests::setup() - .pool(ab_pool()) - .order(ab_order()) - .solution(ab_solution().risk(RISK.into())) - .done() - .await; - - let solve = test.solve().await; - - solve.ok().score( - (DEFAULT_SCORE_MIN - RISK).into(), - (DEFAULT_SCORE_MAX - RISK).into(), - ); - - test.reveal().await.ok().orders(&[ab_order().name]); -} diff --git a/crates/driver/src/tests/cases/score_competition.rs b/crates/driver/src/tests/cases/score_competition.rs new file mode 100644 index 0000000000..6305b4e5dc --- /dev/null +++ b/crates/driver/src/tests/cases/score_competition.rs @@ -0,0 +1,36 @@ +//! Test that driver properly does competition. + +use crate::tests::{ + cases::{DEFAULT_SCORE_MAX, DEFAULT_SCORE_MIN}, + setup::{ab_order, ab_pool, ab_solution, setup, Score}, +}; + +#[tokio::test] +#[ignore] +async fn solver_score_winner() { + let test = setup() + .pool(ab_pool()) + .order(ab_order()) + .solution(ab_solution().score(Score::Solver(DEFAULT_SCORE_MAX.into()))) + .solution(ab_solution().score(Score::RiskAdjusted(0.6))) + .done() + .await; + + assert_eq!(test.solve().await.ok().score(), DEFAULT_SCORE_MAX.into()); + test.reveal().await.ok().orders(&[ab_order().name]); +} + +#[tokio::test] +#[ignore] +async fn risk_adjusted_score_winner() { + let test = setup() + .pool(ab_pool()) + .order(ab_order()) + .solution(ab_solution().score(Score::Solver(DEFAULT_SCORE_MIN.into()))) + .solution(ab_solution().score(Score::RiskAdjusted(0.9))) + .done() + .await; + + assert!(test.solve().await.ok().score() != DEFAULT_SCORE_MIN.into()); + test.reveal().await.ok().orders(&[ab_order().name]); +} diff --git a/crates/driver/src/tests/setup/blockchain.rs b/crates/driver/src/tests/setup/blockchain.rs index fab24f64c7..881859fc1a 100644 --- a/crates/driver/src/tests/setup/blockchain.rs +++ b/crates/driver/src/tests/setup/blockchain.rs @@ -1,5 +1,5 @@ use { - super::{Asset, Order, Partial}, + super::{Asset, Order, Partial, Score}, crate::{ domain::{ competition::order, @@ -77,7 +77,7 @@ impl Pool { #[derive(Debug, Clone)] pub struct Solution { pub fulfillments: Vec, - pub risk: eth::U256, + pub score: Score, } #[derive(Debug, Clone)] @@ -676,7 +676,7 @@ impl Blockchain { } Solution { fulfillments, - risk: solution.risk, + score: solution.score.clone(), } } diff --git a/crates/driver/src/tests/setup/driver.rs b/crates/driver/src/tests/setup/driver.rs index f99a6ce3d9..73edf285b4 100644 --- a/crates/driver/src/tests/setup/driver.rs +++ b/crates/driver/src/tests/setup/driver.rs @@ -8,7 +8,7 @@ use { crate::{ domain::{competition::order, eth}, infra::time, - tests::hex_address, + tests::{cases, hex_address}, }, rand::seq::SliceRandom, secp256k1::SecretKey, @@ -119,6 +119,7 @@ pub fn solve_req(test: &Test) -> serde_json::Value { "tokens": tokens_json, "orders": orders_json, "deadline": test.deadline, + "scoreCap": cases::DEFAULT_SCORE_CAP.to_string(), }) } diff --git a/crates/driver/src/tests/setup/mod.rs b/crates/driver/src/tests/setup/mod.rs index acf9862d0d..d5f7cda61a 100644 --- a/crates/driver/src/tests/setup/mod.rs +++ b/crates/driver/src/tests/setup/mod.rs @@ -85,6 +85,19 @@ impl ExecutionDiff { } } +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Score { + Solver(eth::U256), + RiskAdjusted(f64), +} + +impl Default for Score { + fn default() -> Self { + Self::RiskAdjusted(1.0) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct Order { pub name: &'static str, @@ -312,7 +325,7 @@ pub enum Calldata { pub struct Solution { pub calldata: Calldata, pub orders: Vec<&'static str>, - pub risk: eth::U256, + pub score: Score, } impl Solution { @@ -337,9 +350,9 @@ impl Solution { } } - /// Set the solution risk. - pub fn risk(self, risk: eth::U256) -> Self { - Self { risk, ..self } + /// Set the solution score to the specified value. + pub fn score(self, score: Score) -> Self { + Self { score, ..self } } } @@ -350,7 +363,7 @@ impl Default for Solution { additional_bytes: 0, }, orders: Default::default(), - risk: Default::default(), + score: Default::default(), } } } @@ -383,7 +396,7 @@ pub fn ab_solution() -> Solution { additional_bytes: 0, }, orders: vec!["A-B order"], - risk: Default::default(), + score: Default::default(), } } @@ -415,7 +428,7 @@ pub fn cd_solution() -> Solution { additional_bytes: 0, }, orders: vec!["C-D order"], - risk: Default::default(), + score: Default::default(), } } @@ -446,7 +459,7 @@ pub fn eth_solution() -> Solution { additional_bytes: 0, }, orders: vec!["ETH order"], - risk: Default::default(), + score: Default::default(), } } @@ -802,17 +815,22 @@ pub struct SolveOk { } impl SolveOk { - /// Ensure that the score in the response is within a certain range. The - /// reason why this is a range is because small timing differences in - /// the test can lead to the settlement using slightly different amounts - /// of gas, which in turn leads to different scores. - pub fn score(self, min: eth::U256, max: eth::U256) -> Self { + /// Extracts the score from the response. + pub fn score(&self) -> eth::U256 { let result: serde_json::Value = serde_json::from_str(&self.body).unwrap(); assert!(result.is_object()); assert_eq!(result.as_object().unwrap().len(), 2); assert!(result.get("score").is_some()); let score = result.get("score").unwrap().as_str().unwrap(); - let score = eth::U256::from_dec_str(score).unwrap(); + eth::U256::from_dec_str(score).unwrap() + } + + /// Ensure that the score in the response is within a certain range. The + /// reason why this is a range is because small timing differences in + /// the test can lead to the settlement using slightly different amounts + /// of gas, which in turn leads to different scores. + pub fn score_in_range(self, min: eth::U256, max: eth::U256) -> Self { + let score = self.score(); assert!(score >= min, "score less than min {score} < {min}"); assert!(score <= max, "score more than max {score} > {max}"); self @@ -820,7 +838,7 @@ impl SolveOk { /// Ensure that the score is within the default expected range. pub fn default_score(self) -> Self { - self.score(DEFAULT_SCORE_MIN.into(), DEFAULT_SCORE_MAX.into()) + self.score_in_range(DEFAULT_SCORE_MIN.into(), DEFAULT_SCORE_MAX.into()) } } diff --git a/crates/driver/src/tests/setup/solver.rs b/crates/driver/src/tests/setup/solver.rs index 25d55d1661..c3cd708226 100644 --- a/crates/driver/src/tests/setup/solver.rs +++ b/crates/driver/src/tests/setup/solver.rs @@ -156,7 +156,7 @@ impl Solver { "prices": prices_json, "trades": trades_json, "interactions": interactions_json, - "risk": solution.risk.to_string(), + "score": solution.score, })); } diff --git a/crates/solver/src/run.rs b/crates/solver/src/run.rs index 053183645e..d253667af5 100644 --- a/crates/solver/src/run.rs +++ b/crates/solver/src/run.rs @@ -350,8 +350,10 @@ pub async fn run(args: Arguments) { code_fetcher: code_fetcher.clone(), score_calculator: ScoreCalculator::new( u256_to_big_rational(&args.score_cap), - args.transaction_strategy.clone(), - args.disable_high_risk_public_mempool_transactions, + args.transaction_strategy.iter().any(|s| { + matches!(s, TransactionStrategyArg::PublicMempool) + && !args.disable_high_risk_public_mempool_transactions + }), ), }); diff --git a/crates/solver/src/settlement_post_processing/mod.rs b/crates/solver/src/settlement_post_processing/mod.rs index 22e99371cd..33471ff549 100644 --- a/crates/solver/src/settlement_post_processing/mod.rs +++ b/crates/solver/src/settlement_post_processing/mod.rs @@ -1,5 +1,3 @@ -use shared::http_solver::model::Score; - mod optimize_buffer_usage; mod optimize_score; mod optimize_unwrapping; @@ -20,7 +18,7 @@ use { primitive_types::H160, shared::{ ethrpc::Web3, - http_solver::model::InternalizationStrategy, + http_solver::{self, model::InternalizationStrategy}, token_list::AutoUpdatingTokenList, }, }; @@ -138,7 +136,7 @@ impl PostProcessing for PostProcessingPipeline { // TODO: once we eliminate naive and baseline this logic should be moved to // SingleOrderSettlement::into_settlement match (optimized_solution.score, risk_calculator) { - (Score::RiskAdjusted { gas_amount, .. }, Some(risk_calculator)) => { + (http_solver::model::Score::RiskAdjusted { gas_amount, .. }, Some(risk_calculator)) => { match compute_success_probability( &optimized_solution, &simulator, @@ -149,7 +147,7 @@ impl PostProcessing for PostProcessingPipeline { .await { Ok(success_probability) => Settlement { - score: Score::RiskAdjusted { + score: http_solver::model::Score::RiskAdjusted { success_probability, gas_amount, }, diff --git a/crates/solver/src/settlement_rater.rs b/crates/solver/src/settlement_rater.rs index 547f4ff8cb..8b1ed8f3e5 100644 --- a/crates/solver/src/settlement_rater.rs +++ b/crates/solver/src/settlement_rater.rs @@ -1,6 +1,5 @@ use { crate::{ - arguments::TransactionStrategyArg, driver::solver_settlements::RatedSettlement, settlement::Settlement, settlement_access_list::{estimate_settlement_access_list, AccessListEstimating}, @@ -24,7 +23,10 @@ use { code_fetching::CodeFetching, ethrpc::Web3, external_prices::ExternalPrices, - http_solver::model::{InternalizationStrategy, SimulatedTransaction}, + http_solver::{ + self, + model::{InternalizationStrategy, SimulatedTransaction}, + }, }, std::{borrow::Borrow, cmp::min, sync::Arc}, web3::types::AccessList, @@ -242,7 +244,7 @@ impl SettlementRating for SettlementRater { let earned_fees = settlement.total_earned_fees(prices); let inputs = { let gas_amount = match settlement.score { - shared::http_solver::model::Score::RiskAdjusted { gas_amount, .. } => { + http_solver::model::Score::RiskAdjusted { gas_amount, .. } => { gas_amount.unwrap_or(gas_estimate) } _ => gas_estimate, @@ -256,19 +258,19 @@ impl SettlementRating for SettlementRater { }; let objective_value = inputs.objective_value(); - let score = match &settlement.score { - shared::http_solver::model::Score::Solver { score } => Score::Solver(*score), - shared::http_solver::model::Score::Discount { score_discount } => Score::Discounted( - big_rational_to_u256(&objective_value)?.saturating_sub(*score_discount), + let score = match settlement.score { + http_solver::model::Score::Solver { score } => Score::Solver(score), + http_solver::model::Score::Discount { score_discount } => Score::Discounted( + big_rational_to_u256(&objective_value)?.saturating_sub(score_discount), ), - shared::http_solver::model::Score::RiskAdjusted { + http_solver::model::Score::RiskAdjusted { success_probability, .. - } => self.score_calculator.compute_score( + } => Score::ProtocolWithSolverRisk(self.score_calculator.compute_score( &inputs.objective_value(), &inputs.gas_cost(), - *success_probability, - )?, + success_probability, + )?), }; let rated_settlement = RatedSettlement { @@ -320,49 +322,27 @@ impl From for anyhow::Error { } } -/// Contains a subset of the configuration options for the submission of a -/// settlement, needed for the score calculation. -pub struct SubmissionConfig { - pub strategies: Vec, - pub disable_high_risk_public_mempool_transactions: bool, -} - +#[derive(Debug, Clone)] pub struct ScoreCalculator { score_cap: BigRational, - submission_config: SubmissionConfig, + consider_cost_failure: bool, } impl ScoreCalculator { - pub fn new( - score_cap: BigRational, - strategies: Vec, - disable_high_risk_public_mempool_transactions: bool, - ) -> Self { + pub fn new(score_cap: BigRational, consider_cost_failure: bool) -> Self { Self { score_cap, - submission_config: SubmissionConfig { - strategies, - disable_high_risk_public_mempool_transactions, - }, + consider_cost_failure, } } - pub fn cost_fail(&self, gas_cost: &BigRational) -> BigRational { - if self - .submission_config - .strategies - .contains(&TransactionStrategyArg::PublicMempool) - && !self - .submission_config - .disable_high_risk_public_mempool_transactions - { - // The cost in case of a revert can deviate non-deterministically from the cost - // in case of success and it is often significantly smaller. Thus, we go with - // the full cost as a safe assumption. - gas_cost.clone() - } else { - zero() - } + fn cost_fail(&self, gas_cost: &BigRational) -> BigRational { + // The cost in case of a revert can deviate non-deterministically from the cost + // in case of success and it is often significantly smaller. Thus, we go with + // the full cost as a safe assumption. + self.consider_cost_failure + .then(|| gas_cost.clone()) + .unwrap_or_else(zero) } pub fn compute_score( @@ -370,7 +350,7 @@ impl ScoreCalculator { objective_value: &BigRational, gas_cost: &BigRational, success_probability: f64, - ) -> Result { + ) -> Result { if objective_value <= &zero() { return Err(ScoringError::ObjectiveValueNonPositive( objective_value.clone(), @@ -394,7 +374,7 @@ impl ScoreCalculator { return Err(ScoringError::ScoreHigherThanObjective(optimal_score)); } let score = big_rational_to_u256(&optimal_score).context("Bad conversion")?; - Ok(Score::ProtocolWithSolverRisk(score)) + Ok(score) } } @@ -543,12 +523,7 @@ fn profit( #[cfg(test)] mod tests { - use { - crate::arguments::TransactionStrategyArg, - num::BigRational, - primitive_types::U256, - shared::conversions::U256Ext, - }; + use {num::BigRational, primitive_types::U256, shared::conversions::U256Ext}; fn calculate_score( objective_value: &BigRational, @@ -556,18 +531,10 @@ mod tests { success_probability: f64, ) -> U256 { let score_cap = BigRational::from_float(1e16).unwrap(); - let score_calculator = super::ScoreCalculator::new( - score_cap, - vec![ - TransactionStrategyArg::Flashbots, - TransactionStrategyArg::PublicMempool, - ], - true, - ); + let score_calculator = super::ScoreCalculator::new(score_cap, false); score_calculator .compute_score(objective_value, gas_cost, success_probability) .unwrap() - .score() } #[test] diff --git a/crates/solver/src/solver/risk_computation.rs b/crates/solver/src/solver/risk_computation.rs index 2d996fe5fc..34bc2daee2 100644 --- a/crates/solver/src/solver/risk_computation.rs +++ b/crates/solver/src/solver/risk_computation.rs @@ -10,7 +10,6 @@ use { super::SolverType, anyhow::{Context, Result}, clap::{Parser, ValueEnum}, - num::ToPrimitive, std::{ collections::HashMap, fmt::{Display, Formatter}, @@ -29,8 +28,6 @@ pub struct RiskCalculator { impl RiskCalculator { pub fn calculate(&self, gas_amount: f64, gas_price: f64, nmb_orders: usize) -> Result { - let gas_amount = gas_amount.to_f64().context("gas_amount conversion")?; - let gas_price = gas_price.to_f64().context("gas_price conversion")?; let exponent = self.intercept.neg() - self.gas_amount_factor * gas_amount / 1_000_000. - self.gas_price_factor * gas_price / 10_000_000_000. diff --git a/crates/solvers/src/api/dto/solution.rs b/crates/solvers/src/api/dto/solution.rs index 1c36cc83b8..093e49ea65 100644 --- a/crates/solvers/src/api/dto/solution.rs +++ b/crates/solvers/src/api/dto/solution.rs @@ -133,6 +133,10 @@ impl Solutions { } }) .collect(), + score: match solution.score.clone() { + solution::Score::Solver(score) => Score::Solver(score), + solution::Score::RiskAdjusted(score) => Score::RiskAdjusted(score), + }, }) .collect(), } @@ -154,6 +158,7 @@ struct Solution { prices: HashMap, trades: Vec, interactions: Vec, + score: Score, } #[derive(Debug, Serialize)] @@ -313,3 +318,11 @@ enum SigningScheme { PreSign, Eip1271, } + +/// A score for a solution. The score is used to rank solutions. +#[derive(Debug, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum Score { + Solver(U256), + RiskAdjusted(f64), +} diff --git a/crates/solvers/src/boundary/legacy.rs b/crates/solvers/src/boundary/legacy.rs index cf4d1e5a37..2ecb1b4c9d 100644 --- a/crates/solvers/src/boundary/legacy.rs +++ b/crates/solvers/src/boundary/legacy.rs @@ -17,6 +17,7 @@ use { ConstantProductPoolParameters, MetadataModel, OrderModel, + Score, SettledBatchAuctionModel, StablePoolParameters, TokenAmount, @@ -532,6 +533,16 @@ fn to_domain_solution( .into_iter() .map(|(interaction, _)| interaction) .collect(), + score: match model.score { + Score::Solver { score } => solution::Score::Solver(score), + Score::Discount { .. } => { + return Err(anyhow::anyhow!("score_discount no longer supported")) + } + Score::RiskAdjusted { + success_probability, + .. + } => solution::Score::RiskAdjusted(success_probability), + }, }) } diff --git a/crates/solvers/src/boundary/naive.rs b/crates/solvers/src/boundary/naive.rs index ab8c26e86a..2660eff0b7 100644 --- a/crates/solvers/src/boundary/naive.rs +++ b/crates/solvers/src/boundary/naive.rs @@ -143,6 +143,7 @@ pub fn solve( }) }) .collect(), + score: Default::default(), }) } diff --git a/crates/solvers/src/domain/solution.rs b/crates/solvers/src/domain/solution.rs index fd70ffcd81..e4e2b6354e 100644 --- a/crates/solvers/src/domain/solution.rs +++ b/crates/solvers/src/domain/solution.rs @@ -13,6 +13,13 @@ pub struct Solution { pub prices: ClearingPrices, pub trades: Vec, pub interactions: Vec, + pub score: Score, +} + +impl Solution { + pub fn with_score(self, score: Score) -> Self { + Self { score, ..self } + } } /// A solution for a settling a single order. @@ -111,6 +118,7 @@ impl Single { ]), trades: vec![Trade::Fulfillment(Fulfillment::new(order, executed, fee)?)], interactions, + score: Default::default(), }) } } @@ -285,3 +293,24 @@ pub struct Allowance { pub spender: Address, pub asset: eth::Asset, } + +/// Represents the probability that a solution will be successfully settled. +type SuccessProbability = f64; + +/// A score for a solution. The score is used to rank solutions. +#[derive(Debug, Clone)] +pub enum Score { + /// The score value is provided as is from solver. + /// Success probability is not incorporated into this value. + Solver(U256), + /// This option is used to indicate that the solver did not provide a score. + /// Instead, the score should be computed by the protocol given the success + /// probability. + RiskAdjusted(SuccessProbability), +} + +impl Default for Score { + fn default() -> Self { + Self::RiskAdjusted(1.0) + } +} diff --git a/crates/solvers/src/tests/balancer/market_order.rs b/crates/solvers/src/tests/balancer/market_order.rs index b2dfd04f8f..fbad9093f4 100644 --- a/crates/solvers/src/tests/balancer/market_order.rs +++ b/crates/solvers/src/tests/balancer/market_order.rs @@ -154,7 +154,10 @@ async fn sell() { }, ], } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -308,7 +311,10 @@ async fn buy() { }, ], } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/baseline/bal_liquidity.rs b/crates/solvers/src/tests/baseline/bal_liquidity.rs index bf3be0fd86..ed2ca78bb2 100644 --- a/crates/solvers/src/tests/baseline/bal_liquidity.rs +++ b/crates/solvers/src/tests/baseline/bal_liquidity.rs @@ -114,7 +114,10 @@ async fn weighted() { "inputAmount": "1000000000000000000", "outputAmount": "1657855325872947866705" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -225,7 +228,10 @@ async fn weighted_v3plus() { "inputAmount": "1000000000000000000", "outputAmount": "1663373703594405548696" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -359,7 +365,10 @@ async fn stable() { "inputAmount": "10000000000000000000", "outputAmount": "9999475" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }, { "id": 1, @@ -386,7 +395,10 @@ async fn stable() { "inputAmount": "10000524328839166557", "outputAmount": "10000000" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }, ] }), @@ -515,7 +527,10 @@ async fn composable_stable_v4() { "inputAmount": "10000000000000000000", "outputAmount": "10029862202766050434" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }, ] }), diff --git a/crates/solvers/src/tests/baseline/buy_order_rounding.rs b/crates/solvers/src/tests/baseline/buy_order_rounding.rs index 68955a47e8..8cf594a15e 100644 --- a/crates/solvers/src/tests/baseline/buy_order_rounding.rs +++ b/crates/solvers/src/tests/baseline/buy_order_rounding.rs @@ -99,7 +99,10 @@ async fn uniswap() { "inputAmount": "1848013595", "outputAmount": "1000000000428620302" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -260,7 +263,10 @@ async fn balancer_weighted() { "inputAmount": "9056454904360584", "outputAmount": "1000000000000337213" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -371,7 +377,10 @@ async fn balancer_weighted_v3plus() { "inputAmount": "603167793526702182", "outputAmount": "1000000000000001964333" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -482,7 +491,10 @@ async fn distant_convergence() { "inputAmount": "601109440402472000", "outputAmount": "1000000000000015112015" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -628,7 +640,10 @@ async fn same_path() { "inputAmount": "15503270361045187242", "outputAmount": "9056454904357528" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -763,7 +778,10 @@ async fn balancer_stable() { "inputAmount": "9970226684231795304", "outputAmount": "10000000000000000000" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }, ] }), diff --git a/crates/solvers/src/tests/baseline/direct_swap.rs b/crates/solvers/src/tests/baseline/direct_swap.rs index b23f1421a5..6c4e5bbad8 100644 --- a/crates/solvers/src/tests/baseline/direct_swap.rs +++ b/crates/solvers/src/tests/baseline/direct_swap.rs @@ -95,7 +95,10 @@ async fn test() { "inputAmount": "133700000000000000", "outputAmount": "6043910341261930467761" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/baseline/partial_fill.rs b/crates/solvers/src/tests/baseline/partial_fill.rs index c785ff5155..df0f872c02 100644 --- a/crates/solvers/src/tests/baseline/partial_fill.rs +++ b/crates/solvers/src/tests/baseline/partial_fill.rs @@ -96,7 +96,10 @@ async fn test() { "inputAmount": "500000000000000000", "outputAmount": "20694705425542464884657" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/dex/partial_fill.rs b/crates/solvers/src/tests/dex/partial_fill.rs index 0d116f267b..7b837533f2 100644 --- a/crates/solvers/src/tests/dex/partial_fill.rs +++ b/crates/solvers/src/tests/dex/partial_fill.rs @@ -221,7 +221,10 @@ async fn tested_amounts_adjust_depending_on_response() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }) ); @@ -524,7 +527,10 @@ async fn moves_surplus_fee_to_buy_token() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }) ); @@ -788,7 +794,10 @@ async fn market() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }) ); diff --git a/crates/solvers/src/tests/legacy/attaching_approvals.rs b/crates/solvers/src/tests/legacy/attaching_approvals.rs index 8c758838cb..31b9f36e26 100644 --- a/crates/solvers/src/tests/legacy/attaching_approvals.rs +++ b/crates/solvers/src/tests/legacy/attaching_approvals.rs @@ -145,7 +145,10 @@ async fn test() { ], "kind": "custom", }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/legacy/concentrated_liquidity.rs b/crates/solvers/src/tests/legacy/concentrated_liquidity.rs index 27fc4cac9f..84d3202f20 100644 --- a/crates/solvers/src/tests/legacy/concentrated_liquidity.rs +++ b/crates/solvers/src/tests/legacy/concentrated_liquidity.rs @@ -137,7 +137,10 @@ async fn test() { "inputAmount": "6043910341261930467761", "outputAmount": "133700000000000000", } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/legacy/jit_order.rs b/crates/solvers/src/tests/legacy/jit_order.rs index c6cda70426..60955c5f25 100644 --- a/crates/solvers/src/tests/legacy/jit_order.rs +++ b/crates/solvers/src/tests/legacy/jit_order.rs @@ -127,6 +127,9 @@ async fn test() { } ], "interactions": [], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/legacy/market_order.rs b/crates/solvers/src/tests/legacy/market_order.rs index 1d587cb157..fca0a1c693 100644 --- a/crates/solvers/src/tests/legacy/market_order.rs +++ b/crates/solvers/src/tests/legacy/market_order.rs @@ -202,7 +202,10 @@ async fn quote() { "inputAmount": "6043910341261930467761", "outputAmount": "133700000000000000", } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -403,7 +406,10 @@ async fn solve() { "inputAmount": "6043910341261930467761", "outputAmount": "133700000000000000", } - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/extract_deepest_pool.rs b/crates/solvers/src/tests/naive/extract_deepest_pool.rs index 1213ad5433..82624f436f 100644 --- a/crates/solvers/src/tests/naive/extract_deepest_pool.rs +++ b/crates/solvers/src/tests/naive/extract_deepest_pool.rs @@ -114,7 +114,10 @@ async fn test() { "inputAmount": "100", "outputAmount": "99" }, - ] + ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs b/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs index 6049364d6e..bc253ea420 100644 --- a/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs +++ b/crates/solvers/src/tests/naive/filters_out_of_price_orders.rs @@ -117,6 +117,9 @@ async fn sell_orders_on_both_sides() { }, ], "interactions": [], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/matches_orders.rs b/crates/solvers/src/tests/naive/matches_orders.rs index 1bcc8bf005..aa04b1254a 100644 --- a/crates/solvers/src/tests/naive/matches_orders.rs +++ b/crates/solvers/src/tests/naive/matches_orders.rs @@ -97,6 +97,9 @@ async fn sell_orders_on_both_sides() { "outputAmount": "54287532963535509685" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -196,6 +199,9 @@ async fn sell_orders_on_one_side() { "outputAmount": "139560520142598496102" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -295,6 +301,9 @@ async fn buy_orders_on_both_sides() { "outputAmount": "61942706346833798926" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -394,6 +403,9 @@ async fn buy_and_sell_orders() { "outputAmount": "65237102608923246619" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/reserves_too_small.rs b/crates/solvers/src/tests/naive/reserves_too_small.rs index 304e4123d7..2987626af5 100644 --- a/crates/solvers/src/tests/naive/reserves_too_small.rs +++ b/crates/solvers/src/tests/naive/reserves_too_small.rs @@ -90,6 +90,9 @@ async fn test() { "outputAmount": "2500007430" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs b/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs index e920568828..6b36212174 100644 --- a/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs +++ b/crates/solvers/src/tests/naive/rounds_prices_in_favour_of_traders.rs @@ -104,6 +104,9 @@ async fn test() { "outputAmount": "997000" }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/naive/without_pool.rs b/crates/solvers/src/tests/naive/without_pool.rs index 5b5c3b90e2..07144fbf5a 100644 --- a/crates/solvers/src/tests/naive/without_pool.rs +++ b/crates/solvers/src/tests/naive/without_pool.rs @@ -87,6 +87,9 @@ async fn test() { }, ], "interactions": [], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/oneinch/market_order.rs b/crates/solvers/src/tests/oneinch/market_order.rs index ca0acb79f6..b8f6e31b75 100644 --- a/crates/solvers/src/tests/oneinch/market_order.rs +++ b/crates/solvers/src/tests/oneinch/market_order.rs @@ -205,7 +205,10 @@ async fn sell() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } } ] }), diff --git a/crates/solvers/src/tests/paraswap/market_order.rs b/crates/solvers/src/tests/paraswap/market_order.rs index 96086ada4c..101ef61a1e 100644 --- a/crates/solvers/src/tests/paraswap/market_order.rs +++ b/crates/solvers/src/tests/paraswap/market_order.rs @@ -234,7 +234,10 @@ async fn sell() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } } ] }), @@ -485,7 +488,10 @@ async fn buy() { "kind": "fulfillment", "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a" } - ] + ], + "score": { + "riskadjusted": 1.0 + } } ] }), diff --git a/crates/solvers/src/tests/zeroex/market_order.rs b/crates/solvers/src/tests/zeroex/market_order.rs index 4330371d82..e85b2e6a53 100644 --- a/crates/solvers/src/tests/zeroex/market_order.rs +++ b/crates/solvers/src/tests/zeroex/market_order.rs @@ -186,6 +186,9 @@ async fn sell() { ], }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); @@ -364,6 +367,9 @@ async fn buy() { ], }, ], + "score": { + "riskadjusted": 1.0 + } }] }), ); diff --git a/crates/solvers/src/tests/zeroex/options.rs b/crates/solvers/src/tests/zeroex/options.rs index 42f5dbc24b..c4539feafb 100644 --- a/crates/solvers/src/tests/zeroex/options.rs +++ b/crates/solvers/src/tests/zeroex/options.rs @@ -294,6 +294,9 @@ enable-slippage-protection = true ], }, ], + "score": { + "riskadjusted": 1.0 + } }] }), );