From ea1389e823128bf75c01211c72a7247a2c7ce4fe Mon Sep 17 00:00:00 2001 From: ilya Date: Fri, 15 Dec 2023 10:10:05 +0000 Subject: [PATCH] Redundant files --- .idea/.gitignore | 8 - .idea/highlightedFiles.xml | 17 - .idea/modules.xml | 8 - .idea/solvers.iml | 11 - .idea/vcs.xml | 6 - src/boundary/baseline.rs | 248 ------ src/boundary/legacy.rs | 711 ---------------- src/boundary/liquidity/constant_product.rs | 25 - src/boundary/liquidity/mod.rs | 3 - src/boundary/liquidity/stable.rs | 64 -- src/boundary/liquidity/weighted_product.rs | 65 -- src/boundary/naive.rs | 248 ------ src/domain/solver/baseline.rs | 250 ------ src/domain/solver/legacy.rs | 74 -- src/domain/solver/naive.rs | 121 --- src/infra/config/baseline/file.rs | 85 -- src/infra/config/baseline/mod.rs | 11 - src/infra/config/legacy.rs | 53 -- src/infra/config/naive/file.rs | 37 - src/infra/config/naive/mod.rs | 7 - src/tests/baseline/bal_liquidity.rs | 542 ------------ src/tests/baseline/buy_order_rounding.rs | 794 ------------------ src/tests/baseline/direct_swap.rs | 105 --- src/tests/baseline/internalization.rs | 307 ------- src/tests/baseline/mod.rs | 7 - src/tests/baseline/partial_fill.rs | 106 --- src/tests/legacy/attaching_approvals.rs | 155 ---- src/tests/legacy/concentrated_liquidity.rs | 147 ---- src/tests/legacy/jit_order.rs | 136 --- src/tests/legacy/market_order.rs | 418 --------- src/tests/legacy/mod.rs | 18 - src/tests/naive/extract_deepest_pool.rs | 128 --- .../naive/filters_out_of_price_orders.rs | 130 --- src/tests/naive/limit_order_price.rs | 61 -- src/tests/naive/matches_orders.rs | 428 ---------- src/tests/naive/mod.rs | 8 - src/tests/naive/reserves_too_small.rs | 103 --- .../rounds_prices_in_favour_of_traders.rs | 117 --- src/tests/naive/swap_less_than_reserves.rs | 79 -- src/tests/naive/without_pool.rs | 100 --- 40 files changed, 5941 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/highlightedFiles.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/solvers.iml delete mode 100644 .idea/vcs.xml delete mode 100644 src/boundary/baseline.rs delete mode 100644 src/boundary/legacy.rs delete mode 100644 src/boundary/liquidity/constant_product.rs delete mode 100644 src/boundary/liquidity/mod.rs delete mode 100644 src/boundary/liquidity/stable.rs delete mode 100644 src/boundary/liquidity/weighted_product.rs delete mode 100644 src/boundary/naive.rs delete mode 100644 src/domain/solver/baseline.rs delete mode 100644 src/domain/solver/legacy.rs delete mode 100644 src/domain/solver/naive.rs delete mode 100644 src/infra/config/baseline/file.rs delete mode 100644 src/infra/config/baseline/mod.rs delete mode 100644 src/infra/config/legacy.rs delete mode 100644 src/infra/config/naive/file.rs delete mode 100644 src/infra/config/naive/mod.rs delete mode 100644 src/tests/baseline/bal_liquidity.rs delete mode 100644 src/tests/baseline/buy_order_rounding.rs delete mode 100644 src/tests/baseline/direct_swap.rs delete mode 100644 src/tests/baseline/internalization.rs delete mode 100644 src/tests/baseline/mod.rs delete mode 100644 src/tests/baseline/partial_fill.rs delete mode 100644 src/tests/legacy/attaching_approvals.rs delete mode 100644 src/tests/legacy/concentrated_liquidity.rs delete mode 100644 src/tests/legacy/jit_order.rs delete mode 100644 src/tests/legacy/market_order.rs delete mode 100644 src/tests/legacy/mod.rs delete mode 100644 src/tests/naive/extract_deepest_pool.rs delete mode 100644 src/tests/naive/filters_out_of_price_orders.rs delete mode 100644 src/tests/naive/limit_order_price.rs delete mode 100644 src/tests/naive/matches_orders.rs delete mode 100644 src/tests/naive/mod.rs delete mode 100644 src/tests/naive/reserves_too_small.rs delete mode 100644 src/tests/naive/rounds_prices_in_favour_of_traders.rs delete mode 100644 src/tests/naive/swap_less_than_reserves.rs delete mode 100644 src/tests/naive/without_pool.rs diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/highlightedFiles.xml b/.idea/highlightedFiles.xml deleted file mode 100644 index f9d3c5e..0000000 --- a/.idea/highlightedFiles.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index df3bcb3..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/solvers.iml b/.idea/solvers.iml deleted file mode 100644 index cf84ae4..0000000 --- a/.idea/solvers.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/boundary/baseline.rs b/src/boundary/baseline.rs deleted file mode 100644 index 32434a4..0000000 --- a/src/boundary/baseline.rs +++ /dev/null @@ -1,248 +0,0 @@ -//! Boundary wrappers around the [`shared`] Baseline solving logic. - -use { - crate::{ - boundary, - domain::{eth, liquidity, order, solver::baseline}, - }, - ethereum_types::{H160, U256}, - model::TokenPair, - shared::baseline_solver::{self, BaseTokens, BaselineSolvable}, - std::collections::{HashMap, HashSet}, -}; - -pub struct Solver<'a> { - base_tokens: BaseTokens, - amms: HashMap>, - liquidity: HashMap, -} - -impl<'a> Solver<'a> { - pub fn new( - weth: ð::WethAddress, - base_tokens: &HashSet, - liquidity: &'a [liquidity::Liquidity], - ) -> Self { - Self { - base_tokens: to_boundary_base_tokens(weth, base_tokens), - amms: to_boundary_amms(liquidity), - liquidity: liquidity - .iter() - .map(|liquidity| (liquidity.id.clone(), liquidity)) - .collect(), - } - } - - pub fn route( - &self, - request: baseline::Request, - max_hops: usize, - ) -> Option> { - let candidates = self.base_tokens.path_candidates_with_hops( - request.sell.token.0, - request.buy.token.0, - max_hops, - ); - - let (segments, _) = match request.side { - order::Side::Buy => candidates - .iter() - .filter_map(|path| { - let sell = baseline_solver::estimate_sell_amount( - request.buy.amount, - path, - &self.amms, - )?; - let segments = - self.traverse_path(&sell.path, request.sell.token.0, sell.value)?; - - let buy = segments.last().map(|segment| segment.output.amount); - if buy.map(|buy| buy >= request.buy.amount) != Some(true) { - tracing::warn!( - ?request, - ?segments, - "invalid buy estimate does not cover order" - ); - return None; - } - - (sell.value <= request.sell.amount).then_some((segments, sell)) - }) - .min_by_key(|(_, sell)| sell.value)?, - order::Side::Sell => candidates - .iter() - .filter_map(|path| { - let buy = baseline_solver::estimate_buy_amount( - request.sell.amount, - path, - &self.amms, - )?; - let segments = - self.traverse_path(&buy.path, request.sell.token.0, request.sell.amount)?; - - let sell = segments.first().map(|segment| segment.input.amount); - if sell.map(|sell| sell >= request.sell.amount) != Some(true) { - tracing::warn!( - ?request, - ?segments, - "invalid sell estimate does not cover order" - ); - return None; - } - - (buy.value >= request.buy.amount).then_some((segments, buy)) - }) - .max_by_key(|(_, buy)| buy.value)?, - }; - - baseline::Route::new(segments) - } - - fn traverse_path( - &self, - path: &[&Amm], - mut sell_token: H160, - mut sell_amount: U256, - ) -> Option>> { - let mut segments = Vec::new(); - for liquidity in path { - let reference_liquidity = self - .liquidity - .get(&liquidity.id) - .expect("boundary liquidity does not match ID"); - - let buy_token = liquidity - .token_pair - .other(&sell_token) - .expect("Inconsistent path"); - let buy_amount = liquidity.get_amount_out(buy_token, (sell_amount, sell_token))?; - - segments.push(baseline::Segment { - liquidity: reference_liquidity, - input: eth::Asset { - token: eth::TokenAddress(sell_token), - amount: sell_amount, - }, - output: eth::Asset { - token: eth::TokenAddress(buy_token), - amount: buy_amount, - }, - gas: eth::Gas(liquidity.gas_cost().into()), - }); - - sell_token = buy_token; - sell_amount = buy_amount; - } - Some(segments) - } -} - -fn to_boundary_amms(liquidity: &[liquidity::Liquidity]) -> HashMap> { - liquidity - .iter() - .fold(HashMap::new(), |mut amms, liquidity| { - match &liquidity.state { - liquidity::State::ConstantProduct(pool) => { - if let Some(boundary_pool) = - boundary::liquidity::constant_product::to_boundary_pool( - liquidity.address, - pool, - ) - { - amms.entry(boundary_pool.tokens).or_default().push(Amm { - id: liquidity.id.clone(), - token_pair: boundary_pool.tokens, - pool: Pool::ConstantProduct(boundary_pool), - }); - } - } - liquidity::State::WeightedProduct(pool) => { - if let Some(boundary_pool) = - boundary::liquidity::weighted_product::to_boundary_pool( - liquidity.address, - pool, - ) - { - for pair in pool.reserves.token_pairs() { - let token_pair = to_boundary_token_pair(&pair); - amms.entry(token_pair).or_default().push(Amm { - id: liquidity.id.clone(), - token_pair, - pool: Pool::WeightedProduct(boundary_pool.clone()), - }); - } - } - } - liquidity::State::Stable(pool) => { - if let Some(boundary_pool) = - boundary::liquidity::stable::to_boundary_pool(liquidity.address, pool) - { - for pair in pool.reserves.token_pairs() { - let token_pair = to_boundary_token_pair(&pair); - amms.entry(token_pair).or_default().push(Amm { - id: liquidity.id.clone(), - token_pair, - pool: Pool::Stable(boundary_pool.clone()), - }); - } - } - } - // The baseline solver does not currently support other AMMs. - _ => {} - }; - amms - }) -} - -#[derive(Debug)] -struct Amm { - id: liquidity::Id, - token_pair: TokenPair, - pool: Pool, -} - -#[derive(Debug)] -enum Pool { - ConstantProduct(boundary::liquidity::constant_product::Pool), - WeightedProduct(boundary::liquidity::weighted_product::Pool), - Stable(boundary::liquidity::stable::Pool), -} - -impl BaselineSolvable for Amm { - fn get_amount_out(&self, out_token: H160, input: (U256, H160)) -> Option { - match &self.pool { - Pool::ConstantProduct(pool) => pool.get_amount_out(out_token, input), - Pool::WeightedProduct(pool) => pool.get_amount_out(out_token, input), - Pool::Stable(pool) => pool.get_amount_out(out_token, input), - } - } - - fn get_amount_in(&self, in_token: H160, out: (U256, H160)) -> Option { - match &self.pool { - Pool::ConstantProduct(pool) => pool.get_amount_in(in_token, out), - Pool::WeightedProduct(pool) => pool.get_amount_in(in_token, out), - Pool::Stable(pool) => pool.get_amount_in(in_token, out), - } - } - - fn gas_cost(&self) -> usize { - match &self.pool { - Pool::ConstantProduct(pool) => pool.gas_cost(), - Pool::WeightedProduct(pool) => pool.gas_cost(), - Pool::Stable(pool) => pool.gas_cost(), - } - } -} - -fn to_boundary_base_tokens( - weth: ð::WethAddress, - base_tokens: &HashSet, -) -> BaseTokens { - let base_tokens = base_tokens.iter().map(|token| token.0).collect::>(); - BaseTokens::new(weth.0, &base_tokens) -} - -fn to_boundary_token_pair(pair: &liquidity::TokenPair) -> TokenPair { - let (a, b) = pair.get(); - TokenPair::new(a.0, b.0).unwrap() -} diff --git a/src/boundary/legacy.rs b/src/boundary/legacy.rs deleted file mode 100644 index a6ae7ae..0000000 --- a/src/boundary/legacy.rs +++ /dev/null @@ -1,711 +0,0 @@ -use { - crate::{ - boundary, - domain::{ - auction, - eth, - liquidity, - notification::{self, Kind, ScoreKind, Settlement}, - order, - solution, - solver::legacy::Error, - }, - }, - anyhow::{Context as _, Result}, - ethereum_types::{H160, U256}, - model::order::{OrderKind, OrderUid}, - shared::{ - http_solver::{ - gas_model::GasModel, - model::{ - AmmModel, - AmmParameters, - AuctionResult, - BatchAuctionModel, - ConcentratedPoolParameters, - ConstantProductPoolParameters, - InternalizationStrategy, - MetadataModel, - OrderModel, - Score, - SettledBatchAuctionModel, - SimulatedTransaction, - SolverRejectionReason, - SolverRunError, - StablePoolParameters, - SubmissionResult, - TokenAmount, - TokenInfoModel, - TransactionWithError, - WeightedPoolTokenData, - WeightedProductPoolParameters, - }, - DefaultHttpSolverApi, - HttpSolverApi, - SolverConfig, - }, - network::network_name, - sources::uniswap_v3::{ - graph_api::Token, - pool_fetching::{PoolInfo, PoolState, PoolStats}, - }, - }, - std::collections::BTreeMap, -}; - -pub struct Legacy { - solver: DefaultHttpSolverApi, - weth: eth::WethAddress, -} - -impl Legacy { - pub fn new(config: crate::domain::solver::legacy::Config) -> Self { - let (base, solve_path) = shared::url::split_at_path(&config.endpoint).unwrap(); - - Self { - solver: DefaultHttpSolverApi { - name: config.solver_name, - network_name: network_name( - config.chain_id.network_id(), - config.chain_id.value().as_u64(), - ) - .into(), - chain_id: config.chain_id.value().as_u64(), - base, - client: reqwest::Client::new(), - gzip_requests: config.gzip_requests, - solve_path, - config: SolverConfig { - // Note that we unconditionally set this to "true". This is - // because the auction that we are solving for already - // contains which tokens can and can't be internalized, - // and we don't need to duplicate this setting here. Ergo, - // in order to disable internalization, the driver would be - // configured to have 0 trusted tokens. - use_internal_buffers: Some(true), - ..Default::default() - }, - }, - weth: config.weth, - } - } - - pub async fn solve(&self, auction: &auction::Auction) -> Result { - let (mapping, auction_model) = - to_boundary_auction(auction, self.weth, self.solver.network_name.clone()); - let solving_time = auction.deadline.remaining().context("no time to solve")?; - let solution = self.solver.solve(&auction_model, solving_time).await?; - to_domain_solution(&solution, mapping).map_err(Into::into) - } - - pub fn notify(&self, notification: notification::Notification) { - let (auction_id, auction_result) = to_boundary_auction_result(¬ification); - self.solver - .notify_auction_result(auction_id, auction_result); - } -} - -impl From for Error { - fn from(value: shared::http_solver::Error) -> Self { - match value { - shared::http_solver::Error::DeadlineExceeded => Error::Timeout, - shared::http_solver::Error::RateLimited => { - Error::Other(anyhow::anyhow!("rate limited")) - } - shared::http_solver::Error::Other(err) => Error::Other(err), - } - } -} - -/// Mapping state used for marshalling domain auctions and solutions to and from -/// their legacy HTTP solver DTO representations. This is needed because the -/// legacy HTTP solver API uses arbitrary indices for identifying orders and -/// AMMs that need to be back-referenced to auction domain values. -#[derive(Default)] -struct Mapping<'a> { - orders: Vec>, - amms: BTreeMap, -} - -enum Order<'a> { - Protocol(&'a order::Order), - Liquidity( - &'a liquidity::Liquidity, - &'a liquidity::limit_order::LimitOrder, - ), -} - -fn to_boundary_auction( - auction: &auction::Auction, - weth: eth::WethAddress, - network: String, -) -> (Mapping, BatchAuctionModel) { - let gas = GasModel { - native_token: weth.0, - gas_price: auction.gas_price.0 .0.to_f64_lossy(), - }; - - let mut mapping = Mapping::default(); - let mut model = BatchAuctionModel { - tokens: auction - .tokens - .0 - .iter() - .map(|(address, info)| { - let is_weth = address.0 == weth.0; - ( - address.0, - TokenInfoModel { - alias: info.symbol.clone(), - external_price: info - .reference_price - .map(|price| price.0 .0.to_f64_lossy() / 1e18), - internal_buffer: Some(info.available_balance), - accepted_for_internalization: info.trusted, - // Quasimodo crashes when the reference token (i.e. WETH) doesn't - // have decimals. So use a reasonable default if we don't get one. - decimals: info.decimals.or_else(|| is_weth.then_some(18)), - normalize_priority: Some(u64::from(is_weth)), - }, - ) - }) - .collect(), - metadata: Some(MetadataModel { - environment: Some(network), - auction_id: match auction.id { - auction::Id::Solve(id) => Some(id), - auction::Id::Quote => None, - }, - run_id: None, - gas_price: Some(gas.gas_price), - native_token: Some(weth.0), - }), - ..Default::default() - }; - - // Make sure the reference token is always included in the legacy auction. - // Existing solvers (including Quasimodo) rely on this behaviour. - model - .tokens - .entry(weth.0) - .or_insert_with(|| TokenInfoModel { - decimals: Some(18), - normalize_priority: Some(1), - ..Default::default() - }); - - for order in &auction.orders { - let index = mapping.orders.len(); - mapping.orders.push(Order::Protocol(order)); - model.orders.insert( - index, - OrderModel { - id: Some(OrderUid(order.uid.0)), - sell_token: order.sell.token.0, - buy_token: order.buy.token.0, - sell_amount: order.sell.amount, - buy_amount: order.buy.amount, - allow_partial_fill: order.partially_fillable, - is_sell_order: order.side == order::Side::Sell, - fee: TokenAmount { - amount: order.fee().amount, - token: order.fee().token.0, - }, - cost: gas.gp_order_cost(), - is_liquidity_order: order.class == order::Class::Liquidity, - is_mature: true, - mandatory: false, - has_atomic_execution: false, - reward: 0., - }, - ); - } - - for liquidity in &auction.liquidity { - let cost = TokenAmount { - amount: U256::from_f64_lossy(liquidity.gas.0.to_f64_lossy() * gas.gas_price), - token: weth.0, - }; - - let (parameters, fee) = match &liquidity.state { - liquidity::State::ConstantProduct(state) => ( - AmmParameters::ConstantProduct(ConstantProductPoolParameters { - reserves: [ - ( - state.reserves.get().0.token.0, - state.reserves.get().0.amount, - ), - ( - state.reserves.get().1.token.0, - state.reserves.get().1.amount, - ), - ] - .into_iter() - .collect(), - }), - to_big_rational(&state.fee), - ), - liquidity::State::WeightedProduct(state) => ( - AmmParameters::WeightedProduct(WeightedProductPoolParameters { - reserves: state - .reserves - .iter() - .map(|reserve| { - ( - reserve.asset.token.0, - WeightedPoolTokenData { - balance: reserve.asset.amount, - weight: to_big_rational(&reserve.weight), - }, - ) - }) - .collect(), - }), - to_big_rational(&state.fee), - ), - liquidity::State::Stable(state) => { - // Composable stable pools have their own BPT tokens as a - // registered token and, when doing "regular swaps" skips the - // BPT token. Since the legacy solver API expects regular stable - // pools, we adapt composable stable pools by skipping the BPT - // token from the reserves. - // - // - let reserves_without_bpt = || { - state - .reserves - .iter() - .filter(|reserve| reserve.asset.token.0 != liquidity.address) - }; - - let Some(scaling_rates) = reserves_without_bpt() - .map(|reserve| Some((reserve.asset.token.0, to_scaling_rate(&reserve.scale)?))) - .collect() - else { - // A scaling factor that cannot be represented in the format - // expected by the legacy solver, so skip this liquidity. - continue; - }; - - ( - AmmParameters::Stable(StablePoolParameters { - reserves: reserves_without_bpt() - .map(|reserve| (reserve.asset.token.0, reserve.asset.amount)) - .collect(), - scaling_rates, - amplification_parameter: to_big_rational(&state.amplification_parameter), - }), - to_big_rational(&state.fee), - ) - } - liquidity::State::Concentrated(state) => { - let token = |address: eth::TokenAddress| { - // Uniswap V3 math doesn't care about decimals, so default - // to 18 if it isn't available. - let decimals = auction.tokens.decimals(&address).unwrap_or(18); - - Token { - id: address.0, - decimals, - } - }; - ( - AmmParameters::Concentrated(ConcentratedPoolParameters { - pool: PoolInfo { - address: liquidity.address, - tokens: vec![token(state.tokens.get().0), token(state.tokens.get().1)], - state: PoolState { - sqrt_price: state.sqrt_price.0, - liquidity: state.liquidity.0.into(), - tick: num::BigInt::from(state.tick.0), - liquidity_net: state - .liquidity_net - .iter() - .map(|(tick, amount)| { - (num::BigInt::from(tick.0), num::BigInt::from(amount.0)) - }) - .collect(), - fee: num::rational::Ratio::new( - state.fee.0.numer().as_u32(), - state.fee.0.denom().as_u32(), - ), - }, - gas_stats: PoolStats { - mean_gas: liquidity.gas.0, - }, - }, - }), - to_big_rational(&state.fee.0), - ) - } - liquidity::State::LimitOrder(state) => { - let index = mapping.orders.len(); - mapping.orders.push(Order::Liquidity(liquidity, state)); - model.orders.insert( - index, - OrderModel { - id: None, - sell_token: state.maker.token.0, - buy_token: state.taker.token.0, - sell_amount: state.maker.amount, - buy_amount: state.taker.amount, - allow_partial_fill: true, - is_sell_order: false, - fee: TokenAmount { - amount: state.fee().amount, - token: state.fee().token.0, - }, - cost, - is_liquidity_order: true, - is_mature: true, - mandatory: false, - has_atomic_execution: true, - reward: 0., - }, - ); - continue; - } - }; - - mapping.amms.insert(liquidity.address, liquidity); - model.amms.insert( - liquidity.address, - AmmModel { - parameters, - fee, - cost, - mandatory: false, - address: liquidity.address, - }, - ); - } - - (mapping, model) -} - -fn to_domain_solution( - model: &SettledBatchAuctionModel, - mapping: Mapping, -) -> boundary::Result { - let mut trades = Vec::new(); - let mut interactions = Vec::new(); - - for jit in &model.foreign_liquidity_orders { - trades.push(solution::Trade::Jit(solution::JitTrade { - order: order::JitOrder { - owner: jit.order.from, - pre_interactions: jit - .order - .interactions - .pre - .iter() - .map(|i| order::Interaction { - target: i.target, - value: eth::Ether(i.value), - calldata: i.call_data.clone(), - }) - .collect(), - signature: jit.order.signature.clone().into(), - sell: eth::Asset { - token: eth::TokenAddress(jit.order.data.sell_token), - amount: jit.order.data.sell_amount, - }, - buy: eth::Asset { - token: eth::TokenAddress(jit.order.data.buy_token), - amount: jit.order.data.buy_amount, - }, - fee: order::Fee(jit.order.data.fee_amount), - side: match jit.order.data.kind { - OrderKind::Buy => order::Side::Buy, - OrderKind::Sell => order::Side::Sell, - }, - class: order::Class::Liquidity, - partially_fillable: jit.order.data.partially_fillable, - receiver: jit.order.data.receiver.unwrap_or_default(), - app_data: order::AppData(jit.order.data.app_data.0), - valid_to: jit.order.data.valid_to, - }, - executed: match jit.order.data.kind { - model::order::OrderKind::Buy => jit.exec_buy_amount, - model::order::OrderKind::Sell => jit.exec_sell_amount, - }, - })); - } - - for (id, execution) in &model.orders { - match mapping - .orders - .get(*id) - .context("solution contains order not part of auction")? - { - Order::Protocol(order) => trades.push(solution::Trade::Fulfillment( - solution::Fulfillment::new( - (*order).clone(), - match order.side { - order::Side::Buy => execution.exec_buy_amount, - order::Side::Sell => execution.exec_sell_amount, - }, - match order.solver_determines_fee() { - true => execution - .exec_fee_amount - .map(solution::Fee::Surplus) - .context("no surplus fee")?, - false => solution::Fee::Protocol, - }, - ) - .context("invalid trade execution")?, - )), - Order::Liquidity(liquidity, state) => { - let coordinate = execution.exec_plan.as_ref().map(|e| &e.coordinates); - let interaction = - solution::Interaction::Liquidity(solution::LiquidityInteraction { - liquidity: (*liquidity).clone(), - input: eth::Asset { - token: state.taker.token, - amount: execution.exec_buy_amount, - }, - output: eth::Asset { - token: state.maker.token, - amount: execution.exec_sell_amount, - }, - internalize: execution - .exec_plan - .as_ref() - .map(|e| e.internal) - .unwrap_or_default(), - }); - interactions.push((interaction, coordinate)); - } - } - } - - for (address, amm) in &model.amms { - let liquidity = mapping - .amms - .get(address) - .context("uses unknown liquidity")?; - - for interaction in &amm.execution { - let coordinate = Some(&interaction.exec_plan.coordinates); - let interaction = solution::Interaction::Liquidity(solution::LiquidityInteraction { - liquidity: (*liquidity).clone(), - input: eth::Asset { - token: eth::TokenAddress(interaction.buy_token), - amount: interaction.exec_buy_amount, - }, - output: eth::Asset { - token: eth::TokenAddress(interaction.sell_token), - amount: interaction.exec_sell_amount, - }, - internalize: interaction.exec_plan.internal, - }); - interactions.push((interaction, coordinate)); - } - } - - for interaction in &model.interaction_data { - let coordinate = interaction.exec_plan.as_ref().map(|e| &e.coordinates); - let interaction = solution::Interaction::Custom(solution::CustomInteraction { - target: interaction.target, - value: eth::Ether(interaction.value), - calldata: interaction.call_data.clone(), - inputs: interaction - .inputs - .iter() - .map(|i| eth::Asset { - token: eth::TokenAddress(i.token), - amount: i.amount, - }) - .collect(), - outputs: interaction - .outputs - .iter() - .map(|i| eth::Asset { - token: eth::TokenAddress(i.token), - amount: i.amount, - }) - .collect(), - internalize: interaction - .exec_plan - .as_ref() - .map(|e| e.internal) - .unwrap_or_default(), - // allowances get added later - allowances: Default::default(), - }); - interactions.push((interaction, coordinate)); - } - - // sort Vec<(interaction, Option)> by coordinates (optionals first) - interactions.sort_by(|(_, a), (_, b)| a.cmp(b)); - - let allowances: Vec<_> = model - .approvals - .iter() - .map(|approval| solution::Allowance { - spender: approval.spender, - asset: eth::Asset { - token: eth::TokenAddress(approval.token), - amount: approval.amount, - }, - }) - .collect(); - - // Add all allowances to first non-internalized interaction. This is a work - // around because the legacy solvers didn't associate approvals with - // specific interactions so we have to come up with some association. - for (interaction, _) in &mut interactions { - match interaction { - solution::Interaction::Custom(custom) if !custom.internalize => { - custom.allowances = allowances; - break; - } - _ => (), - }; - } - - Ok(solution::Solution { - id: Default::default(), - prices: solution::ClearingPrices( - model - .prices - .iter() - .map(|(address, price)| (eth::TokenAddress(*address), *price)) - .collect(), - ), - trades, - interactions: interactions - .into_iter() - .map(|(interaction, _)| interaction) - .collect(), - score: match model.score { - Score::Solver { score } => solution::Score::Solver(score), - Score::RiskAdjusted { - success_probability, - .. - } => solution::Score::RiskAdjusted(solution::SuccessProbability(success_probability)), - }, - }) -} - -fn to_boundary_auction_result(notification: ¬ification::Notification) -> (i64, AuctionResult) { - let auction_id = match notification.auction_id { - auction::Id::Solve(id) => id, - auction::Id::Quote => 0, - }; - - let auction_result = match ¬ification.kind { - Kind::Timeout => { - AuctionResult::Rejected(SolverRejectionReason::RunError(SolverRunError::Timeout)) - } - Kind::EmptySolution => AuctionResult::Rejected(SolverRejectionReason::NoUserOrders), - Kind::SimulationFailed(block_number, tx, succeeded_at_least_once) => { - AuctionResult::Rejected(SolverRejectionReason::SimulationFailure( - TransactionWithError { - error: "".to_string(), - transaction: SimulatedTransaction { - from: tx.from.into(), - to: tx.to.into(), - data: tx.input.clone().into(), - internalization: InternalizationStrategy::Unknown, - block_number: *block_number, - tx_index: Default::default(), - access_list: Default::default(), - max_fee_per_gas: Default::default(), - max_priority_fee_per_gas: Default::default(), - }, - }, - *succeeded_at_least_once, - )) - } - Kind::ScoringFailed(ScoreKind::ObjectiveValueNonPositive(quality, gas_cost)) => { - AuctionResult::Rejected(SolverRejectionReason::ObjectiveValueNonPositive { - quality: quality.0, - gas_cost: gas_cost.0, - }) - } - Kind::ScoringFailed(ScoreKind::ZeroScore) => { - AuctionResult::Rejected(SolverRejectionReason::NonPositiveScore) - } - Kind::ScoringFailed(ScoreKind::ScoreHigherThanQuality(score, quality)) => { - AuctionResult::Rejected(SolverRejectionReason::ScoreHigherThanQuality { - score: score.0, - quality: quality.0, - }) - } - Kind::ScoringFailed(ScoreKind::SuccessProbabilityOutOfRange(_)) => { - AuctionResult::Rejected(SolverRejectionReason::SuccessProbabilityOutOfRange) - } - Kind::NonBufferableTokensUsed(tokens) => { - AuctionResult::Rejected(SolverRejectionReason::NonBufferableTokensUsed( - tokens.iter().map(|token| token.0).collect(), - )) - } - Kind::SolverAccountInsufficientBalance(required) => AuctionResult::Rejected( - SolverRejectionReason::SolverAccountInsufficientBalance(required.0), - ), - Kind::DuplicatedSolutionId => { - AuctionResult::Rejected(SolverRejectionReason::DuplicatedSolutionId( - notification - .solution_id - .expect("duplicated solution ID notification must have a solution ID") - .0, - )) - } - Kind::Settled(kind) => AuctionResult::SubmittedOnchain(match kind { - Settlement::Success(hash) => SubmissionResult::Success(*hash), - Settlement::Revert(hash) => SubmissionResult::Revert(*hash), - Settlement::SimulationRevert => SubmissionResult::SimulationRevert, - Settlement::Fail => SubmissionResult::Fail, - }), - }; - - (auction_id, auction_result) -} - -fn to_big_rational(r: ð::Rational) -> num::BigRational { - num::BigRational::new(to_big_int(r.numer()), to_big_int(r.denom())) -} - -fn to_big_int(i: &U256) -> num::BigInt { - let mut bytes = [0; 32]; - i.to_big_endian(&mut bytes); - num::BigInt::from_bytes_be(num::bigint::Sign::Plus, &bytes) -} - -// A "scaling rate" is used by Quasimodo for token scaling, scaling, and -// specifically represents `double` that, when dividing a token amount in atoms, -// would compute the equivalent amount in base units. From the source: -// -// ```text -// auto in = in_unscaled / m_scaling_rates.at(t_in).convert_to(); -// ``` -// -// In other words, this is the **inverse** of the scaling factor in scaled by -// 1e18. -fn to_scaling_rate(r: &liquidity::ScalingFactor) -> Option { - Some(U256::exp10(18).checked_mul(*r.get().denom())? / r.get().numer()) -} - -impl From for order::EcdsaSignature { - fn from(signature: model::signature::EcdsaSignature) -> Self { - Self { - r: signature.r, - s: signature.s, - v: signature.v, - } - } -} - -impl From for order::Signature { - fn from(signature: model::signature::Signature) -> Self { - use model::signature::Signature::*; - - match signature { - Eip712(signature) => order::Signature::Eip712(signature.into()), - EthSign(signature) => order::Signature::EthSign(signature.into()), - Eip1271(data) => order::Signature::Eip1271(data), - PreSign => order::Signature::PreSign, - } - } -} diff --git a/src/boundary/liquidity/constant_product.rs b/src/boundary/liquidity/constant_product.rs deleted file mode 100644 index b2d5d2f..0000000 --- a/src/boundary/liquidity/constant_product.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub use shared::sources::uniswap_v2::pool_fetching::Pool; -use {crate::domain::liquidity, ethereum_types::H160, model::TokenPair}; - -/// Converts a domain pool into a [`shared`] Uniswap V2 pool. Returns `None` if -/// the domain pool cannot be represented as a boundary pool. -pub fn to_boundary_pool(address: H160, pool: &liquidity::constant_product::Pool) -> Option { - let reserves = pool.reserves.get(); - let tokens = TokenPair::new(reserves.0.token.0, reserves.1.token.0) - .expect("tokens are distinct by construction"); - - // reserves are ordered by construction. - let reserves = (reserves.0.amount.as_u128(), reserves.1.amount.as_u128()); - - if *pool.fee.numer() > u32::MAX.into() || *pool.fee.denom() > u32::MAX.into() { - return None; - } - let fee = num::rational::Ratio::new(pool.fee.numer().as_u32(), pool.fee.denom().as_u32()); - - Some(Pool { - address, - tokens, - reserves, - fee, - }) -} diff --git a/src/boundary/liquidity/mod.rs b/src/boundary/liquidity/mod.rs deleted file mode 100644 index 1d9b6dc..0000000 --- a/src/boundary/liquidity/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod constant_product; -pub mod stable; -pub mod weighted_product; diff --git a/src/boundary/liquidity/stable.rs b/src/boundary/liquidity/stable.rs deleted file mode 100644 index feec338..0000000 --- a/src/boundary/liquidity/stable.rs +++ /dev/null @@ -1,64 +0,0 @@ -pub use shared::sources::balancer_v2::pool_fetching::StablePool as Pool; -use { - crate::domain::{eth, liquidity}, - ethereum_types::{H160, H256, U256}, - shared::sources::balancer_v2::{ - pool_fetching::{AmplificationParameter, CommonPoolState, TokenState}, - swap::fixed_point::Bfp, - }, -}; - -/// Converts a domain pool into a [`shared`] Balancer V2 stable pool. Returns -/// `None` if the domain pool cannot be represented as a boundary pool. -pub fn to_boundary_pool(address: H160, pool: &liquidity::stable::Pool) -> Option { - // NOTE: this is only used for encoding and not for solving, so it's OK to - // use this an approximate value for now. In fact, Balancer V2 pool IDs - // are `pool address || pool kind || pool index`, so this approximation is - // pretty good. - let id = { - let mut buf = [0_u8; 32]; - buf[..20].copy_from_slice(address.as_bytes()); - H256(buf) - }; - - let swap_fee = to_fixed_point(&pool.fee)?; - let reserves = pool - .reserves - .iter() - .map(|reserve| { - Some(( - reserve.asset.token.0, - TokenState { - balance: reserve.asset.amount, - scaling_factor: to_fixed_point(&reserve.scale.get())?, - }, - )) - }) - .collect::>()?; - let amplification_parameter = AmplificationParameter::new( - *pool.amplification_parameter.numer(), - *pool.amplification_parameter.denom(), - ) - .ok()?; - - Some(Pool { - common: CommonPoolState { - id, - address, - swap_fee, - paused: false, - }, - reserves, - amplification_parameter, - }) -} - -/// Converts a rational to a Balancer fixed point number. -fn to_fixed_point(ratio: ð::Rational) -> Option { - // Balancer "fixed point numbers" are in a weird decimal FP format (instead - // of a base 2 FP format you typically see). Just convert our ratio into - // this format. - let base = U256::exp10(18); - let wei = ratio.numer().checked_mul(base)? / ratio.denom(); - Some(Bfp::from_wei(wei)) -} diff --git a/src/boundary/liquidity/weighted_product.rs b/src/boundary/liquidity/weighted_product.rs deleted file mode 100644 index f7da600..0000000 --- a/src/boundary/liquidity/weighted_product.rs +++ /dev/null @@ -1,65 +0,0 @@ -pub use shared::sources::balancer_v2::pool_fetching::WeightedPool as Pool; -use { - crate::domain::{eth, liquidity}, - ethereum_types::{H160, H256, U256}, - shared::sources::balancer_v2::{ - pool_fetching::{CommonPoolState, TokenState, WeightedPoolVersion, WeightedTokenState}, - swap::fixed_point::Bfp, - }, -}; - -/// Converts a domain pool into a [`shared`] Balancer V2 weighted pool. Returns -/// `None` if the domain pool cannot be represented as a boundary pool. -pub fn to_boundary_pool(address: H160, pool: &liquidity::weighted_product::Pool) -> Option { - // NOTE: this is only used for encoding and not for solving, so it's OK to - // use this an approximate value for now. In fact, Balancer V2 pool IDs - // are `pool address || pool kind || pool index`, so this approximation is - // pretty good. - let id = { - let mut buf = [0_u8; 32]; - buf[..20].copy_from_slice(address.as_bytes()); - H256(buf) - }; - - let swap_fee = to_fixed_point(&pool.fee)?; - let reserves = pool - .reserves - .iter() - .map(|reserve| { - Some(( - reserve.asset.token.0, - WeightedTokenState { - common: TokenState { - balance: reserve.asset.amount, - scaling_factor: to_fixed_point(&reserve.scale.get())?, - }, - weight: to_fixed_point(&reserve.weight)?, - }, - )) - }) - .collect::>()?; - - Some(Pool { - common: CommonPoolState { - id, - address, - swap_fee, - paused: false, - }, - reserves, - version: match pool.version { - liquidity::weighted_product::Version::V0 => WeightedPoolVersion::V0, - liquidity::weighted_product::Version::V3Plus => WeightedPoolVersion::V3Plus, - }, - }) -} - -/// Converts a rational to a Balancer fixed point number. -fn to_fixed_point(ratio: ð::Rational) -> Option { - // Balancer "fixed point numbers" are in a weird decimal FP format (instead - // of a base 2 FP format you typically see). Just convert our ratio into - // this format. - let base = U256::exp10(18); - let wei = ratio.numer().checked_mul(base)? / ratio.denom(); - Some(Bfp::from_wei(wei)) -} diff --git a/src/boundary/naive.rs b/src/boundary/naive.rs deleted file mode 100644 index 34d45fd..0000000 --- a/src/boundary/naive.rs +++ /dev/null @@ -1,248 +0,0 @@ -use { - crate::{ - boundary::liquidity::constant_product::to_boundary_pool, - domain::{ - eth, - liquidity, - order, - solution::{self}, - }, - }, - ethereum_types::H160, - itertools::Itertools, - model::order::{Order, OrderClass, OrderData, OrderKind, OrderMetadata, OrderUid}, - num::{BigRational, One}, - shared::external_prices::ExternalPrices, - solver::{ - liquidity::{ - slippage::{SlippageCalculator, SlippageContext}, - AmmOrderExecution, - ConstantProductOrder, - Exchange, - LimitOrder, - LimitOrderExecution, - LimitOrderId, - LiquidityOrderId, - SettlementHandling, - }, - settlement::SettlementEncoder, - solver::naive_solver::multi_order_solver, - }, - std::sync::{Arc, Mutex}, -}; - -pub fn solve( - orders: &[&order::Order], - liquidity: &liquidity::Liquidity, -) -> Option { - let pool = match &liquidity.state { - liquidity::State::ConstantProduct(pool) => pool, - _ => return None, - }; - - // Note that the `order::Order` -> `boundary::LimitOrder` mapping here is - // not exact. Among other things, the signature and various signed order - // fields are missing from the `order::Order` data that the solver engines - // have access to. This means that the naive solver in the `solver` crate - // will encode "incorrect" settlements. This is fine, since we give it just - // enough data to compute the correct swapped orders and the swap amounts - // which is what the naive solver in the `solvers` crate cares about. The - // `driver` is then responsible for encoding the solution into a valid - // settlement transaction anyway. - let boundary_orders = orders - .iter() - // The naive solver currently doesn't support limit orders, so filter them out. - .filter(|order| !order.solver_determines_fee()) - .map(|order| LimitOrder { - id: match order.class { - order::Class::Market => LimitOrderId::Market(OrderUid(order.uid.0)), - order::Class::Limit => LimitOrderId::Limit(OrderUid(order.uid.0)), - order::Class::Liquidity => { - LimitOrderId::Liquidity(LiquidityOrderId::Protocol(OrderUid(order.uid.0))) - } - }, - sell_token: order.sell.token.0, - buy_token: order.buy.token.0, - sell_amount: order.sell.amount, - buy_amount: order.buy.amount, - kind: match order.side { - order::Side::Buy => OrderKind::Buy, - order::Side::Sell => OrderKind::Sell, - }, - partially_fillable: order.partially_fillable, - scoring_fee: order.fee().amount, - settlement_handling: Arc::new(OrderHandler { - order: Order { - metadata: OrderMetadata { - uid: OrderUid(order.uid.0), - class: match order.class { - order::Class::Market => OrderClass::Market, - order::Class::Limit => OrderClass::Limit(Default::default()), - order::Class::Liquidity => OrderClass::Liquidity, - }, - solver_fee: order.fee().amount, - ..Default::default() - }, - data: OrderData { - sell_token: order.sell.token.0, - buy_token: order.buy.token.0, - sell_amount: order.sell.amount, - buy_amount: order.buy.amount, - fee_amount: order.fee().amount, - kind: match order.side { - order::Side::Buy => OrderKind::Buy, - order::Side::Sell => OrderKind::Sell, - }, - partially_fillable: order.partially_fillable, - ..Default::default() - }, - ..Default::default() - }, - }), - exchange: Exchange::GnosisProtocol, - }) - .collect_vec(); - - let slippage = Slippage::new(pool.tokens()); - let pool_handler = Arc::new(PoolHandler::default()); - let boundary_pool = ConstantProductOrder::for_pool( - to_boundary_pool(liquidity.address, pool)?, - pool_handler.clone(), - ); - - let boundary_solution = - multi_order_solver::solve(&slippage.context(), boundary_orders, &boundary_pool)?; - - let swap = pool_handler.swap.lock().unwrap().take(); - Some(solution::Solution { - id: Default::default(), - prices: solution::ClearingPrices::new( - boundary_solution - .clearing_prices() - .iter() - .map(|(token, price)| (eth::TokenAddress(*token), *price)), - ), - trades: boundary_solution - .traded_orders() - .map(|order| { - solution::Trade::Fulfillment( - solution::Fulfillment::fill( - orders - .iter() - .copied() - .find(|o| o.uid.0 == order.metadata.uid.0) - .unwrap() - .clone(), - ) - .expect("all orders can be filled, as limit orders are filtered out"), - ) - }) - .collect(), - interactions: swap - .into_iter() - .map(|(input, output)| { - solution::Interaction::Liquidity(solution::LiquidityInteraction { - liquidity: liquidity.clone(), - input, - output, - internalize: false, - }) - }) - .collect(), - score: Default::default(), - }) -} - -// Beyond this point is... well... nameless and boundless chaos. The -// unfathomable horrors that follow are not for the faint of heart! -// -// Joking aside, the existing naive solver implementation is tightly coupled -// with the `Settlement` and `SettlementEncoder` types in the `solver` crate. -// This means that there is no convenient way to say: "please compute a solution -// given this list of orders and constant product pool" without it creating a -// full settlement for encoding. In order to adapt that API into something that -// is useful in this boundary module, we create a fake slippage context that -// applies 0 slippage (so that we can recover the exact executed amounts from -// the constant product pool) and we create capturing settlement handler -// implementations that record the swap that gets added to each settlement so -// that it can be recovered later to build a solution. - -struct Slippage { - calculator: SlippageCalculator, - prices: ExternalPrices, -} - -impl Slippage { - fn new(tokens: liquidity::TokenPair) -> Self { - // We don't actually want to include slippage yet. This is because the - // Naive solver encodes liquidity interactions and the driver is - // responsible for applying slippage to those. Create a dummy slippage - // context for use with the legacy Naive solver. - let (token0, token1) = tokens.get(); - Self { - calculator: SlippageCalculator::from_bps(0, None), - prices: ExternalPrices::new( - H160::default(), - [ - (token0.0, BigRational::one()), - (token1.0, BigRational::one()), - ] - .into_iter() - .collect(), - ) - .unwrap(), - } - } - - fn context(&self) -> SlippageContext { - self.calculator.context(&self.prices) - } -} - -struct OrderHandler { - order: Order, -} - -impl SettlementHandling for OrderHandler { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn encode( - &self, - execution: LimitOrderExecution, - encoder: &mut SettlementEncoder, - ) -> anyhow::Result<()> { - encoder.add_trade(self.order.clone(), execution.filled, execution.scoring_fee)?; - Ok(()) - } -} - -#[derive(Default)] -struct PoolHandler { - swap: Mutex>, -} - -impl SettlementHandling for PoolHandler { - fn as_any(&self) -> &dyn std::any::Any { - self - } - - fn encode( - &self, - execution: AmmOrderExecution, - _: &mut SettlementEncoder, - ) -> anyhow::Result<()> { - *self.swap.lock().unwrap() = Some(( - eth::Asset { - token: eth::TokenAddress(execution.input_max.token), - amount: execution.input_max.amount, - }, - eth::Asset { - token: eth::TokenAddress(execution.output.token), - amount: execution.output.amount, - }, - )); - Ok(()) - } -} diff --git a/src/domain/solver/baseline.rs b/src/domain/solver/baseline.rs deleted file mode 100644 index d3a0e80..0000000 --- a/src/domain/solver/baseline.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! "Baseline" solver implementation. -//! -//! The baseline solver is a simple solver implementation that finds the best -//! path of at most length `max_hops + 1` over a set of on-chain liquidity. It -//! **does not** try to split large orders into multiple parts and route them -//! over separate paths. - -use { - crate::{ - boundary, - domain::{ - self, - auction, - eth, - liquidity, - order::{self, UserOrder}, - solution, - }, - infra::config, - }, - ethereum_types::U256, - std::{cmp, collections::HashSet, sync::Arc}, -}; - -pub struct Baseline(Arc); - -struct Inner { - weth: eth::WethAddress, - - /// Set of tokens to additionally consider as intermediary hops when - /// path-finding. This allows paths of the kind `TOKEN1 -> WETH -> TOKEN2` - /// to be considered. - base_tokens: HashSet, - - /// Maximum number of hops that can be considered in a trading path. A hop - /// is an intermediary token within a trading path. For example: - /// - A value of 0 indicates that only a direct trade is allowed: `A -> B` - /// - A value of 1 indicates that a single intermediary token can appear - /// within a trading path: `A -> B -> C` - /// - A value of 2 indicates: `A -> B -> C -> D` - /// - etc. - max_hops: usize, - - /// The maximum number of attempts to solve a partially fillable order. - /// Basically we continuously halve the amount to execute until we find a - /// valid solution or exceed this count. - max_partial_attempts: usize, - - /// Parameters used to calculate the revert risk of a solution. - risk: domain::Risk, -} - -impl Baseline { - /// Creates a new baseline solver for the specified configuration. - pub fn new(config: config::baseline::Config) -> Self { - Self(Arc::new(Inner { - weth: config.weth, - base_tokens: config.base_tokens.into_iter().collect(), - max_hops: config.max_hops, - max_partial_attempts: config.max_partial_attempts, - risk: config.risk, - })) - } - - /// Solves the specified auction, returning a vector of all possible - /// solutions. - pub async fn solve(&self, auction: auction::Auction) -> Vec { - // Make sure to push the CPU-heavy code to a separate thread in order to - // not lock up the [`tokio`] runtime and cause it to slow down handling - // the real async things. For larger settlements, this can block in the - // 100s of ms. - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel(); - let deadline = auction.deadline.remaining().unwrap_or_default(); - - let inner = self.0.clone(); - let span = tracing::Span::current(); - let background_work = async move { - let _entered = span.enter(); - inner.solve(auction, sender).await; - }; - - if tokio::time::timeout(deadline, tokio::spawn(background_work)) - .await - .is_err() - { - tracing::debug!("reached timeout while solving orders"); - } - - let mut solutions = vec![]; - while let Ok(solution) = receiver.try_recv() { - solutions.push(solution); - } - solutions - } -} - -impl Inner { - async fn solve( - &self, - auction: auction::Auction, - sender: tokio::sync::mpsc::UnboundedSender, - ) { - let boundary_solver = - boundary::baseline::Solver::new(&self.weth, &self.base_tokens, &auction.liquidity); - - for (i, order) in auction.orders.into_iter().enumerate() { - let sell_token = auction.tokens.reference_price(&order.sell.token); - let Some(user_order) = UserOrder::new(&order) else { - continue; - }; - let solution = self.requests_for_order(user_order).find_map(|request| { - tracing::trace!(order =% order.uid, ?request, "finding route"); - - let route = boundary_solver.route(request, self.max_hops)?; - let interactions = route - .segments - .iter() - .map(|segment| { - solution::Interaction::Liquidity(solution::LiquidityInteraction { - liquidity: segment.liquidity.clone(), - input: segment.input, - output: segment.output, - // TODO does the baseline solver know about this optimization? - internalize: false, - }) - }) - .collect(); - - // The baseline solver generates a path with swapping - // for exact output token amounts. This leads to - // potential rounding errors for buy orders, where we - // can buy slightly more than intended. Fix this by - // capping the output amount to the order's buy amount - // for buy orders. - let mut output = route.output(); - if let order::Side::Buy = order.side { - output.amount = cmp::min(output.amount, order.buy.amount); - } - - let score = solution::Score::RiskAdjusted(solution::SuccessProbability( - self.risk - .success_probability(route.gas(), auction.gas_price, 1), - )); - - Some( - solution::Single { - order: order.clone(), - input: route.input(), - output, - interactions, - gas: route.gas(), - } - .into_solution(auction.gas_price, sell_token, score)? - .with_id(solution::Id(i as u64)) - .with_buffers_internalizations(&auction.tokens), - ) - }); - if let Some(solution) = solution { - if sender.send(solution).is_err() { - tracing::debug!("deadline hit, receiver dropped"); - break; - } - } - } - } - - fn requests_for_order(&self, order: UserOrder) -> impl Iterator { - let order::Order { - sell, buy, side, .. - } = order.get().clone(); - - let n = if order.get().partially_fillable { - self.max_partial_attempts - } else { - 1 - }; - - (0..n) - .map(move |i| { - let divisor = U256::one() << i; - Request { - sell: eth::Asset { - token: sell.token, - amount: sell.amount / divisor, - }, - buy: eth::Asset { - token: buy.token, - amount: buy.amount / divisor, - }, - side, - } - }) - .filter(|r| !r.sell.amount.is_zero() && !r.buy.amount.is_zero()) - } -} - -/// A baseline routing request. -#[derive(Debug)] -pub struct Request { - pub sell: eth::Asset, - pub buy: eth::Asset, - pub side: order::Side, -} - -/// A trading route. -#[derive(Debug)] -pub struct Route<'a> { - segments: Vec>, -} - -/// A segment in a trading route. -#[derive(Debug)] -pub struct Segment<'a> { - pub liquidity: &'a liquidity::Liquidity, - // TODO: There is no type-level guarantee here that both `input.token` and - // `output.token` are valid for the liquidity in this segment. This is - // unfortunate because this type leaks out of this module (currently into - // the `boundary::baseline` module) but should no longer need to be `pub` - // once the `boundary::baseline` module gets refactored into the domain - // logic, so I think it is fine for now. - pub input: eth::Asset, - pub output: eth::Asset, - pub gas: eth::Gas, -} - -impl<'a> Route<'a> { - pub fn new(segments: Vec>) -> Option { - if segments.is_empty() { - return None; - } - Some(Self { segments }) - } - - fn input(&self) -> eth::Asset { - self.segments[0].input - } - - fn output(&self) -> eth::Asset { - self.segments - .last() - .expect("route has at least one segment by construction") - .output - } - - fn gas(&self) -> eth::Gas { - eth::Gas(self.segments.iter().fold(U256::zero(), |acc, segment| { - acc.saturating_add(segment.gas.0) - })) - } -} diff --git a/src/domain/solver/legacy.rs b/src/domain/solver/legacy.rs deleted file mode 100644 index cfc55ed..0000000 --- a/src/domain/solver/legacy.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! Legacy HTTP solver adapter implementation. -//! -//! In order to facilitate the transition from the legacy HTTP solver API to the -//! new HTTP API, we provide a solver "wrapper" that just marshals the API -//! request types to and from the legacy format. - -use { - crate::{ - boundary, - domain::{auction, eth, notification, solution}, - }, - reqwest::Url, -}; - -pub struct Config { - pub weth: eth::WethAddress, - pub solver_name: String, - pub chain_id: eth::ChainId, - pub endpoint: Url, - pub gzip_requests: bool, -} - -pub struct Legacy(boundary::legacy::Legacy); - -impl Legacy { - pub fn new(config: Config) -> Self { - Self(boundary::legacy::Legacy::new(config)) - } - - pub async fn solve(&self, auction: auction::Auction) -> Vec { - match self.0.solve(&auction).await { - Ok(solution) => { - if solution.is_empty() { - vec![] - } else { - vec![solution] - } - } - Err(err) => { - tracing::warn!(?err, "failed to solve auction"); - if err.is_timeout() { - self.notify_timeout(auction.id) - } - vec![] - } - } - } - - pub fn notify(&self, notification: notification::Notification) { - self.0.notify(notification); - } - - fn notify_timeout(&self, auction_id: auction::Id) { - self.notify(notification::Notification { - auction_id, - solution_id: None, - kind: notification::Kind::Timeout, - }) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("timeout")] - Timeout, - #[error(transparent)] - Other(#[from] anyhow::Error), -} - -impl Error { - fn is_timeout(&self) -> bool { - matches!(self, Self::Timeout) - } -} diff --git a/src/domain/solver/naive.rs b/src/domain/solver/naive.rs deleted file mode 100644 index 1b0430a..0000000 --- a/src/domain/solver/naive.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! "Naive" solver implementation. -//! -//! The naive solver is a solver that collects all orders over a single token -//! pair, computing how many leftover tokens can't be matched peer-to-peer, and -//! matching that excess over a Uniswap V2 pool. This allows for naive -//! coincidence of wants over a single Uniswap V2 pools. - -use { - crate::{ - boundary, - domain::{self, auction, eth, liquidity, order, solution}, - infra::config, - }, - std::collections::HashMap, -}; - -pub struct Naive { - /// Parameters used to calculate the revert risk of a solution. - risk: domain::Risk, -} - -impl Naive { - /// Creates a new naive solver for the specified configuration. - pub fn new(config: config::naive::Config) -> Self { - Self { risk: config.risk } - } - - /// Solves the specified auction, returning a vector of all possible - /// solutions. - pub async fn solve(&self, auction: auction::Auction) -> Vec { - let risk = self.risk.clone(); - // Make sure to push the CPU-heavy code to a separate thread in order to - // not lock up the [`tokio`] runtime and cause it to slow down handling - // the real async things. - let span = tracing::Span::current(); - tokio::task::spawn_blocking(move || { - let _entered = span.enter(); - let groups = group_by_token_pair(&auction); - groups - .values() - .enumerate() - .filter_map(|(i, group)| { - boundary::naive::solve(&group.orders, group.liquidity).map(|solution| { - let gas = solution::INITIALIZATION_COST - + solution::SETTLEMENT - + solution::ERC20_TRANSFER * solution.trades.len() as u64 * 2 - + group.liquidity.gas.0.as_u64(); // this is pessimistic in case the pool is not used - solution - .with_risk_adjusted_score( - &risk, - eth::Gas(gas.into()), - auction.gas_price, - ) - .with_id(solution::Id(i as u64)) - }) - }) - .map(|solution| solution.with_buffers_internalizations(&auction.tokens)) - .collect() - }) - .await - .expect("naive solver unexpected panic") - } -} - -#[derive(Debug)] -struct Group<'a> { - orders: Vec<&'a order::Order>, - liquidity: &'a liquidity::Liquidity, - pool: &'a liquidity::constant_product::Pool, -} - -type Groups<'a> = HashMap>; - -/// Groups an auction by token pairs, where each group contains all orders over -/// the token pair as well as the **deepest** constant product pool (i.e. most -/// liquidity, which translates to a higher `K` value for Uniswap V2 style -/// constant product pools). -fn group_by_token_pair(auction: &auction::Auction) -> Groups { - let mut groups = Groups::new(); - - for liquidity in &auction.liquidity { - let pool = match &liquidity.state { - liquidity::State::ConstantProduct(pool) => pool, - _ => continue, - }; - - groups - .entry(pool.tokens()) - .and_modify(|group| { - if group.pool.k() < pool.k() { - group.liquidity = liquidity; - group.pool = pool; - } - }) - .or_insert_with(|| Group { - orders: Vec::new(), - liquidity, - pool, - }); - } - - for order in &auction.orders { - // The naive solver algorithm is sensitive to 0-amount orders (i.e. they - // cause panics). Make sure we don't consider them. - if order.sell.amount.is_zero() || order.buy.amount.is_zero() { - continue; - } - - let tokens = match liquidity::TokenPair::new(order.sell.token, order.buy.token) { - Some(value) => value, - None => continue, - }; - - groups - .entry(tokens) - .and_modify(|group| group.orders.push(order)); - } - - groups.retain(|_, group| !group.orders.is_empty()); - groups -} diff --git a/src/infra/config/baseline/file.rs b/src/infra/config/baseline/file.rs deleted file mode 100644 index 108b650..0000000 --- a/src/infra/config/baseline/file.rs +++ /dev/null @@ -1,85 +0,0 @@ -use { - crate::{ - domain::{eth, Risk}, - infra::{config::unwrap_or_log, contracts}, - util::serialize, - }, - ethereum_types::H160, - serde::Deserialize, - serde_with::serde_as, - std::path::Path, - tokio::fs, -}; - -#[serde_as] -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -struct Config { - /// Optional chain ID. This is used to automatically determine the address - /// of the WETH contract. - #[serde_as(as = "Option")] - chain_id: Option, - - /// Optional WETH contract address. This can be used to specify a manual - /// value **instead** of using the canonical WETH contract for the - /// configured chain. - weth: Option, - - /// List of base tokens to use when path finding. This defines the tokens - /// that can appear as intermediate "hops" within a trading route. Note that - /// WETH is always considered as a base token. - base_tokens: Vec, - - /// The maximum number of hops to consider when finding the optimal trading - /// path. - max_hops: usize, - - /// The maximum number of pieces to divide partially fillable limit orders - /// when trying to solve it against baseline liquidity. - max_partial_attempts: usize, - - /// Parameters used to calculate the revert risk of a solution. - /// (gas_amount_factor, gas_price_factor, nmb_orders_factor, intercept) - risk_parameters: (f64, f64, f64, f64), -} - -/// Load the driver configuration from a TOML file. -/// -/// # Panics -/// -/// This method panics if the config is invalid or on I/O errors. -pub async fn load(path: &Path) -> super::Config { - let data = fs::read_to_string(path) - .await - .unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}")); - // Not printing detailed error because it could potentially leak secrets. - let config = unwrap_or_log(toml::de::from_str::(&data), &path); - let weth = match (config.chain_id, config.weth) { - (Some(chain_id), None) => contracts::Contracts::for_chain(chain_id).weth, - (None, Some(weth)) => eth::WethAddress(weth), - (Some(_), Some(_)) => panic!( - "invalid configuration: cannot specify both `chain-id` and `weth` configuration \ - options", - ), - (None, None) => panic!( - "invalid configuration: must specify either `chain-id` or `weth` configuration options", - ), - }; - - super::Config { - weth, - base_tokens: config - .base_tokens - .into_iter() - .map(eth::TokenAddress) - .collect(), - max_hops: config.max_hops, - max_partial_attempts: config.max_partial_attempts, - risk: Risk { - gas_amount_factor: config.risk_parameters.0, - gas_price_factor: config.risk_parameters.1, - nmb_orders_factor: config.risk_parameters.2, - intercept: config.risk_parameters.3, - }, - } -} diff --git a/src/infra/config/baseline/mod.rs b/src/infra/config/baseline/mod.rs deleted file mode 100644 index 606b83f..0000000 --- a/src/infra/config/baseline/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::domain::{eth, Risk}; - -pub mod file; - -pub struct Config { - pub weth: eth::WethAddress, - pub base_tokens: Vec, - pub max_hops: usize, - pub max_partial_attempts: usize, - pub risk: Risk, -} diff --git a/src/infra/config/legacy.rs b/src/infra/config/legacy.rs deleted file mode 100644 index 611df54..0000000 --- a/src/infra/config/legacy.rs +++ /dev/null @@ -1,53 +0,0 @@ -use { - crate::{ - domain::{eth, solver::legacy}, - infra::{config::unwrap_or_log, contracts}, - util::serialize, - }, - reqwest::Url, - serde::Deserialize, - serde_with::serde_as, - std::path::Path, - tokio::fs, -}; - -#[serde_as] -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -struct Config { - /// Chain id used to automatically determine the address - /// of the WETH contract and for metrics. - #[serde_as(as = "serialize::ChainId")] - chain_id: eth::ChainId, - - /// The solver name used in metrics. - solver_name: String, - - /// The URL of the endpoint that responds to solve requests. - endpoint: String, - - /// Enabled requests compression - #[serde(default)] - gzip_requests: bool, -} - -/// Load the driver configuration from a TOML file. -/// -/// # Panics -/// -/// This method panics if the config is invalid or on I/O errors. -pub async fn load(path: &Path) -> legacy::Config { - let data = fs::read_to_string(path) - .await - .unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}")); - let config = unwrap_or_log(toml::de::from_str::(&data), &path); - let contracts = contracts::Contracts::for_chain(config.chain_id); - - legacy::Config { - weth: contracts.weth, - solver_name: config.solver_name, - chain_id: config.chain_id, - endpoint: Url::parse(&config.endpoint).unwrap(), - gzip_requests: config.gzip_requests, - } -} diff --git a/src/infra/config/naive/file.rs b/src/infra/config/naive/file.rs deleted file mode 100644 index 4817263..0000000 --- a/src/infra/config/naive/file.rs +++ /dev/null @@ -1,37 +0,0 @@ -use { - crate::{domain::Risk, infra::config::unwrap_or_log}, - serde::Deserialize, - serde_with::serde_as, - std::path::Path, - tokio::fs, -}; - -#[serde_as] -#[derive(Deserialize)] -#[serde(rename_all = "kebab-case", deny_unknown_fields)] -struct Config { - /// Parameters used to calculate the revert risk of a solution. - /// (gas_amount_factor, gas_price_factor, nmb_orders_factor, intercept) - risk_parameters: (f64, f64, f64, f64), -} - -/// Load the driver configuration from a TOML file. -/// -/// # Panics -/// -/// This method panics if the config is invalid or on I/O errors. -pub async fn load(path: &Path) -> super::Config { - let data = fs::read_to_string(path) - .await - .unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}")); - // Not printing detailed error because it could potentially leak secrets. - let config = unwrap_or_log(toml::de::from_str::(&data), &path); - super::Config { - risk: Risk { - gas_amount_factor: config.risk_parameters.0, - gas_price_factor: config.risk_parameters.1, - nmb_orders_factor: config.risk_parameters.2, - intercept: config.risk_parameters.3, - }, - } -} diff --git a/src/infra/config/naive/mod.rs b/src/infra/config/naive/mod.rs deleted file mode 100644 index 8375ef6..0000000 --- a/src/infra/config/naive/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::domain; - -pub mod file; - -pub struct Config { - pub risk: domain::Risk, -} diff --git a/src/tests/baseline/bal_liquidity.rs b/src/tests/baseline/bal_liquidity.rs deleted file mode 100644 index 1180ec5..0000000 --- a/src/tests/baseline/bal_liquidity.rs +++ /dev/null @@ -1,542 +0,0 @@ -//! Test cases to verify baseline computation of Balancer V2 liquidity. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn weighted() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "1" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x6810e776880c02933d47db1b9fc05908e5386b96": { - "decimals": 18, - "symbol": "GNO", - "referencePrice": "59970737022467696", - "availableBalance": "0", - "trusted": true - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "0", - "trusted": true - }, - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "35756662383952", - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x6810e776880c02933d47db1b9fc05908e5386b96", - "buyToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "sellAmount": "1000000000000000000", - "buyAmount": "1", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "weightedproduct", - "tokens": { - "0x6810e776880c02933d47db1b9fc05908e5386b96": { - "balance": "11260752191375725565253", - "scalingFactor": "1", - "weight": "0.5", - }, - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { - "balance": "18764168403990393422000071", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "0", - "address": "0x92762b42a06dcdddc5b7362cfb01e631c4d44b40", - "gasEstimate": "88892", - "version": "v0", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x6810e776880c02933d47db1b9fc05908e5386b96": "1657855325872947866705", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "1000000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "1000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x6810e776880c02933d47db1b9fc05908e5386b96", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "1000000000000000000", - "outputAmount": "1657855325872947866705" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn weighted_v3plus() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "symbol": "xCOW", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "decimals": 18, - "symbol": "xGNO", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "buyToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "sellAmount": "1000000000000000000", - "buyAmount": "1", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "weightedproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "5089632258314443812936111", - "scalingFactor": "1", - "weight": "0.5", - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "3043530764763263654069", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "0", - "address": "0x21d4c792ea7e38e0d0819c2011a2b1cb7252bd99", - "gasEstimate": "88892", - "version": "v3plus", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1000000000000000000", - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": "1663373703594405548696" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "1000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "outputToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "inputAmount": "1000000000000000000", - "outputAmount": "1663373703594405548696" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn stable() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "1" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "decimals": 18, - "symbol": "DAI", - "referencePrice": "597423824203645", - "availableBalance": "0", - "trusted": true - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "decimals": 6, - "symbol": "USDC", - "referencePrice": "597647838715990684620292096", - "availableBalance": "0", - "trusted": true - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "buyToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "sellAmount": "10000000000000000000", - "buyAmount": "9500000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "buyToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "sellAmount": "10500000000000000000", - "buyAmount": "10000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "stable", - "tokens": { - "0x6b175474e89094c44da98b954eedeac495271d0f": { - "balance": "505781036390938593206504", - "scalingFactor": "1", - }, - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "554894862074", - "scalingFactor": "1000000000000", - }, - "0xdac17f958d2ee523a2206206994597c13d831ec7": { - "balance": "1585576741011", - "scalingFactor": "1000000000000", - }, - }, - "fee": "0.0001", - "amplificationParameter": "5000.0", - "id": "0", - "address": "0x06df3b2bbb68adc8b0e302443692037ed9f91b42", - "gasEstimate": "183520", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [ - { - "id": 0, - "prices": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "9999475", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "10000000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "10000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "outputToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "inputAmount": "10000000000000000000", - "outputAmount": "9999475" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }, - { - "id": 1, - "prices": { - "0x6b175474e89094c44da98b954eedeac495271d0f": "10000000", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "10000524328839166557" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "10000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x6b175474e89094c44da98b954eedeac495271d0f", - "outputToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "inputAmount": "10000524328839166557", - "outputAmount": "10000000" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }, - ] - }), - ); -} - -#[tokio::test] -async fn composable_stable_v4() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": { - "decimals": 18, - "symbol": "agEUR", - "referencePrice": "1090118822951692177", - "availableBalance": "0", - "trusted": false - }, - "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { - "decimals": 18, - "symbol": "bb-agEUR-EURe", - "referencePrice": "10915976478387159906", - "availableBalance": "0", - "trusted": false - }, - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { - "decimals": 18, - "symbol": "EURe", - "referencePrice": "10917431192660550458", - "availableBalance": "0", - "trusted": true - }, - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "decimals": 18, - "symbol": "wxDAI", - "referencePrice": "1000000000000000000", - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x4b1e2c2762667331bc91648052f646d1b0d35984", - "buyToken": "0xcb444e90d8198415266c6a2724b7900fb12fc56e", - "sellAmount": "10000000000000000000", - "buyAmount": "9500000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "stable", - "tokens": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": { - "balance": "126041615528606990697699", - "scalingFactor": "1", - }, - "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { - "balance": "2596148429267369423681023550322451", - "scalingFactor": "1", - }, - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { - "balance": "170162457652825667152980", - "scalingFactor": "1", - }, - }, - "fee": "0.0001", - "amplificationParameter": "100.0", - "id": "0", - "address": "0x5c78d05b8ecf97507d1cf70646082c54faa4da95", - "gasEstimate": "183520", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [ - { - "id": 0, - "prices": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": "10029862202766050434", - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": "10000000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "10000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x4b1e2c2762667331bc91648052f646d1b0d35984", - "outputToken": "0xcb444e90d8198415266c6a2724b7900fb12fc56e", - "inputAmount": "10000000000000000000", - "outputAmount": "10029862202766050434" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }, - ] - }), - ); -} diff --git a/src/tests/baseline/buy_order_rounding.rs b/src/tests/baseline/buy_order_rounding.rs deleted file mode 100644 index bc41d57..0000000 --- a/src/tests/baseline/buy_order_rounding.rs +++ /dev/null @@ -1,794 +0,0 @@ -//! Simple test cases that verify that the baseline solver can settle a buy -//! orders, and deal with weird rounding behaviour. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn uniswap() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { - "decimals": 6, - "symbol": "USDC", - "referencePrice": "543222446200924874026413848", - "availableBalance": "556450389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "buyToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "sellAmount": "2000000000", - "buyAmount": "1000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { - "balance": "30493445841295" - }, - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "16551311935742077745684" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - // Note that the interaction executes slightly more than the buy order's - // amount. This is inevitable because of rounding - if we sold 1 less wei - // of the input token, we would not be able to buy enough to cover the buy - // order, the difference stays in the settlement contract. - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "1848013595", - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "1000000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "1000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": true, - "id": "0", - "inputToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "outputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "inputAmount": "1848013595", - "outputAmount": "1000000000428620302" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn balancer_weighted() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = ["0x9c58bacc331c9aa871afd802db6379a98e80cedb"] - max-hops = 1 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "decimals": 18, - "symbol": "wxDAI", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "symbol": "xCOW", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "decimals": 18, - "symbol": "xGNO", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000\ - 00000000", - "sellToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "buyToken": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - "sellAmount": "22300745198530623141535718272648361505980416", - "buyAmount": "1000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - // A xCOW -> xGNO -> wxDAI path with a good price. - { - "kind": "constantproduct", - "tokens": { - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "9661963829146095661" - }, - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "balance": "1070533209145548137343" - } - }, - "fee": "0.0025", - "id": "0", - "address": "0xd7b118271b1b7d26c9e044fc927ca31dccb22a5a", - "gasEstimate": "90171" - }, - { - "kind": "weightedproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "1963528800698237927834721", - "scalingFactor": "1", - "weight": "0.5", - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "1152796145430714835825", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "1", - "address": "0x21d4c792ea7e38e0d0819c2011a2b1cb7252bd99", - "gasEstimate": "88892", - "version": "v0" - }, - // A fake xCOW -> wxDAI path with a BAD price. - { - "kind": "constantproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "1000000000000000000000000000" - }, - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "2", - "address": "0x9090909090909090909090909090909090909090", - "gasEstimate": "90171" - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - // Note that the interaction executes slightly more than the buy order's - // amount. This is inevitable because of rounding. - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "1000000000000000000", - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": "15503270361052085989" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0000000000000000000000000000000000000000000000000000000000000000\ - 0000000000000000000000000000000000000000\ - 00000000", - "executedAmount": "1000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "1", - "inputToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "outputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "inputAmount": "15503270361052085989", - "outputAmount": "9056454904360584" - }, - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "outputToken": "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d", - "inputAmount": "9056454904360584", - "outputAmount": "1000000000000337213" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn balancer_weighted_v3plus() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "symbol": "xCOW", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "decimals": 18, - "symbol": "xGNO", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "buyToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "sellAmount": "100000000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "weightedproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "18764168403990393422000071", - "scalingFactor": "1", - "weight": "0.5", - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "11260752191375725565253", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "0", - "address": "0x21d4c792ea7e38e0d0819c2011a2b1cb7252bd99", - "gasEstimate": "88892", - "version": "v3plus", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "603167793526702182", - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": "1000000000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "1000000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "outputToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "inputAmount": "603167793526702182", - "outputAmount": "1000000000000001964333" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn distant_convergence() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "symbol": "xCOW", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "decimals": 18, - "symbol": "xGNO", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "buyToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "sellAmount": "100000000000000000000000", - "buyAmount": "999999999999999843119", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "weightedproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "5089632258314443812936111", - "scalingFactor": "1", - "weight": "0.5", - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "3043530764763263654069", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "0", - "address": "0x21d4c792ea7e38e0d0819c2011a2b1cb7252bd99", - "gasEstimate": "88892", - "version": "v3plus", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "601109440402472000", - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": "999999999999999843119" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "999999999999999843119" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "outputToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "inputAmount": "601109440402472000", - "outputAmount": "1000000000000015112015" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn same_path() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = ["0x9c58bacc331c9aa871afd802db6379a98e80cedb"] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "decimals": 18, - "symbol": "xCOW", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "decimals": 18, - "symbol": "xGNO", - "referencePrice": null, - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "buyToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "sellAmount": "16000000000000000000", - "buyAmount": "9056454904357528", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "weightedproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "1963528800698237927834721", - "scalingFactor": "1", - "weight": "0.5", - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "1152796145430714835825", - "scalingFactor": "1", - "weight": "0.5", - } - }, - "fee": "0.005", - "id": "0", - "address": "0x21d4c792ea7e38e0d0819c2011a2b1cb7252bd99", - "gasEstimate": "0", - "version": "v0", - }, - { - "kind": "constantproduct", - "tokens": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { - "balance": "1000000000000000000000000000" - }, - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { - "balance": "585921934616708391829053" - } - }, - "fee": "0.003", - "id": "1", - "address": "0x9090909090909090909090909090909090909090", - "gasEstimate": "0" - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - // Lets get down to the math! - // -------------------------- - // - // Balancer V2 weighted pools are unstable - this means that when computing - // `get_amount_out(TOK0, (get_amount_in(TOK1, (a, TOK0)), TOK1)) < a`, - // meaning that we actually need to sell a bit more than computed in order - // to cover the buy order. Specifically, in this example: - // - the computed input amount is `15503270361045187239 xCOW` - // - the corresponding output amount is `9056454904357125 xGNO` (`403` less than - // what is actually needed) - // - the optimal input amount is `15503270361046529181 xCOW`, such that this - // amount -1 would result in an output amount that is not enough to cover the - // order - // - // Interestingly, in the same path, we have an constant product pool (i.e. - // Uniswap-like pool) L1 which if used for a solution would result in - // selling an amount that is higher than the computed input amount for the - // Balancer pool, but lower than its optimal input amount. - // - // This tests asserts that we use L1 in the solution. - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x177127622c4a00f3d409b75571e12cb3c8973d3c": "9056454904357528", - "0x9c58bacc331c9aa871afd802db6379a98e80cedb": "15503270361045187242" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "9056454904357528" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "1", - "inputToken": "0x177127622c4a00f3d409b75571e12cb3c8973d3c", - "outputToken": "0x9c58bacc331c9aa871afd802db6379a98e80cedb", - "inputAmount": "15503270361045187242", - "outputAmount": "9056454904357528" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn balancer_stable() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::String( - r#" - chain-id = "100" - base-tokens = [] - max-hops = 0 - max-partial-attempts = 1 - risk-parameters = [0,0,0,0] - "# - .to_owned(), - ), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": { - "decimals": 18, - "symbol": "agEUR", - "referencePrice": "1090118822951692177", - "availableBalance": "0", - "trusted": false - }, - "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { - "decimals": 18, - "symbol": "bb-agEUR-EURe", - "referencePrice": "10915976478387159906", - "availableBalance": "0", - "trusted": false - }, - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { - "decimals": 18, - "symbol": "EURe", - "referencePrice": "10917431192660550458", - "availableBalance": "0", - "trusted": true - }, - "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d": { - "decimals": 18, - "symbol": "wxDAI", - "referencePrice": "1000000000000000000", - "availableBalance": "0", - "trusted": true - }, - }, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x4b1e2c2762667331bc91648052f646d1b0d35984", - "buyToken": "0xcb444e90d8198415266c6a2724b7900fb12fc56e", - "sellAmount": "10500000000000000000", - "buyAmount": "10000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "stable", - "tokens": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": { - "balance": "126041615528606990697699", - "scalingFactor": "1", - }, - "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { - "balance": "2596148429267369423681023550322451", - "scalingFactor": "1", - }, - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { - "balance": "170162457652825667152980", - "scalingFactor": "1", - }, - }, - "fee": "0.0001", - "amplificationParameter": "100.0", - "id": "0", - "address": "0x5c78d05b8ecf97507d1cf70646082c54faa4da95", - "gasEstimate": "183520", - }, - ], - "effectiveGasPrice": "1000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - // Here: - // - // ``` - // get_amount_in(1.0) = 9.970226684231795303 - // get_amount_out(9.970226684231795303) = 9.999999999999999999 - // get_amount_out(9.970226684231795304) = 1.0 - // ``` - assert_eq!( - solution, - json!({ - "solutions": [ - { - "id": 0, - "prices": { - "0x4b1e2c2762667331bc91648052f646d1b0d35984": "10000000000000000000", - "0xcb444e90d8198415266c6a2724b7900fb12fc56e": "9970226684231795304" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "10000000000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x4b1e2c2762667331bc91648052f646d1b0d35984", - "outputToken": "0xcb444e90d8198415266c6a2724b7900fb12fc56e", - "inputAmount": "9970226684231795304", - "outputAmount": "10000000000000000000" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }, - ] - }), - ); -} diff --git a/src/tests/baseline/direct_swap.rs b/src/tests/baseline/direct_swap.rs deleted file mode 100644 index af58827..0000000 --- a/src/tests/baseline/direct_swap.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! Simple test case that verifies that the baseline solver can settle an order -//! directly with a Uniswap V2 pool. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "740264138483556450389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "133700000000000000", - "outputAmount": "6043910341261930467761" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/baseline/internalization.rs b/src/tests/baseline/internalization.rs deleted file mode 100644 index 7c9d0be..0000000 --- a/src/tests/baseline/internalization.rs +++ /dev/null @@ -1,307 +0,0 @@ -//! Simple test case that verifies that the baseline solver internalizes -//! eligible interactions using Settlement contract buffers. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn trusted_token() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "78402641384835564507389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": true, - "id": "0", - "inputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "133700000000000000", - "outputAmount": "6043910341261930467761" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn untrusted_sell_token() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": false - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "78402641384835564507389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "133700000000000000", - "outputAmount": "6043910341261930467761" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn insufficient_balance() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "0", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "133700000000000000", - "outputAmount": "6043910341261930467761" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/baseline/mod.rs b/src/tests/baseline/mod.rs deleted file mode 100644 index 9ac40d1..0000000 --- a/src/tests/baseline/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! Baseline solver test cases. - -mod bal_liquidity; -mod buy_order_rounding; -mod direct_swap; -mod internalization; -mod partial_fill; diff --git a/src/tests/baseline/partial_fill.rs b/src/tests/baseline/partial_fill.rs deleted file mode 100644 index b4f5273..0000000 --- a/src/tests/baseline/partial_fill.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! Simple test case that verifies that the baseline solver can settle a -//! partially fillable limit order with a Uniswap V2 pool. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "baseline", - tests::Config::File("config/example.baseline.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": 18, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "740264138483556450389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "1000000000000000000", - "buyAmount": "40000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": true, - "class": "limit", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "20694705425542464884657", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "500000000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "500000000000000000", - "fee": "2495865000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "outputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "inputAmount": "500000000000000000", - "outputAmount": "20694705425542464884657" - } - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/legacy/attaching_approvals.rs b/src/tests/legacy/attaching_approvals.rs deleted file mode 100644 index 19f7ef3..0000000 --- a/src/tests/legacy/attaching_approvals.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! Tests that approvals get attached to the first non-internalizable -//! interaction. - -use { - crate::tests::{self, legacy, mock}, - serde_json::json, -}; - -#[tokio::test] -async fn test() { - let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { - path: mock::http::Path::Any, - req: mock::http::RequestBody::Exact(json!({ - "amms": {}, - "metadata": { - "auction_id": 1234, - "environment": "Ethereum / Mainnet", - "gas_price": 15000000000.0, - "native_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "run_id": null, - }, - "orders": {}, - "tokens": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "accepted_for_internalization": true, - "internal_buffer": "0", - "decimals": 18, - "alias": null, - "external_price": null, - "normalize_priority": 1, - }, - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { - "accepted_for_internalization": true, - "internal_buffer": "0", - "decimals": null, - "alias": null, - "external_price": null, - "normalize_priority": 0, - } - } - })), - res: json!({ - "orders": {}, - "prices": {}, - "interaction_data": [ - { - "target": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "value": "0", - "call_data": "0x", - "inputs": [], - "outputs": [], - "exec_plan": { - "sequence": 0, - "position": 2, - "internal": false, - }, - }, - { - "target": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "value": "0", - "call_data": "0x", - "inputs": [], - "outputs": [], - "exec_plan": { - "sequence": 0, - "position": 1, - "internal": true, - }, - }, - ], - "approvals": [ - { - "token": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "spender": "0x1111111111111111111111111111111111111111", - "amount": "1", - }, - { - "token": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "spender": "0x2222222222222222222222222222222222222222", - "amount": "2", - }, - ], - }), - }]) - .await; - - let engine = tests::SolverEngine::new("legacy", legacy::config(&legacy_solver.address)).await; - - let solution = engine - .solve(json!({ - "id": "1234", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "availableBalance": "0", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "availableBalance": "0", - "trusted": true - } - }, - "orders": [], - "liquidity": [], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": {}, - "trades": [], - "interactions": [ - { - "target": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "value": "0", - "callData": "0x", - "inputs": [], - "outputs": [], - "internalize": true, - "allowances": [], - "kind": "custom", - }, - { - "target": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "value": "0", - "callData": "0x", - "inputs": [], - "outputs": [], - "internalize": false, - "allowances": [ - { - "token": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "spender": "0x1111111111111111111111111111111111111111", - "amount": "1", - }, - { - "token": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - "spender": "0x2222222222222222222222222222222222222222", - "amount": "2", - }, - ], - "kind": "custom", - }, - ], - "score": { - "riskadjusted": 1.0 - } - }] - }), - ); -} diff --git a/src/tests/legacy/concentrated_liquidity.rs b/src/tests/legacy/concentrated_liquidity.rs deleted file mode 100644 index b60c613..0000000 --- a/src/tests/legacy/concentrated_liquidity.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! Tests that concentrated liquidity pools (e.g. Uniswap v3) can be -//! (de)serialized. - -use { - crate::tests::{self, legacy, mock}, - serde_json::json, -}; - -#[tokio::test] -async fn test() { - let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { - path: mock::http::Path::Any, - req: mock::http::RequestBody::Exact(json!({ - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", - "cost": { - "amount": "1650000000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "fee": "0.003", - "kind": "Concentrated", - "mandatory": false, - "pool": { - "gas_stats": { - "mean": "110000" - }, - "state": { - "liquidity": "200000000000", - "liquidity_net": { - "-3": "-123", - "3": "432" - }, - "sqrt_price": "1000000000", - "tick": "-1", - }, - "tokens": [ - { - "decimals": "18", - "id": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - }, - { - "decimals": "18", - "id": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - } - ] - }, - }, - }, - "metadata": { - "auction_id": 1, - "environment": "Ethereum / Mainnet", - "gas_price": 15000000000.0, - "native_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "run_id": null - }, - "orders": {}, - "tokens": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "normalize_priority": 1, - "accepted_for_internalization": false, - "internal_buffer": null, - "alias": null, - "external_price": null, - } - } - })), - res: json!({ - "orders": {}, - "prices": {}, - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "execution": [{ - "sell_token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buy_token": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "exec_sell_amount": "133700000000000000", - "exec_buy_amount": "6043910341261930467761", - "exec_plan": { - "sequence": 0, - "position": 1, - "internal": false, - } - }], - } - } - }), - }]) - .await; - - let engine = tests::SolverEngine::new("legacy", legacy::config(&legacy_solver.address)).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [], - "liquidity": [ - { - "kind": "concentratedliquidity", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000", - "tokens": [ - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB" - ], - "liquidity": "200000000000", - "tick": -1, - "sqrtPrice": "1000000000", - "liquidityNet": { - "-3": "-123", - "3": "432" - }, - "fee": "0.003" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": {}, - "trades": [], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "outputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "inputAmount": "6043910341261930467761", - "outputAmount": "133700000000000000", - } - ], - "score": { - "riskadjusted": 1.0 - } - }] - }), - ); -} diff --git a/src/tests/legacy/jit_order.rs b/src/tests/legacy/jit_order.rs deleted file mode 100644 index 700e7c1..0000000 --- a/src/tests/legacy/jit_order.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Tests that solutions that use just-in-time liquidity orders get correctly -//! serialized. - -use { - crate::tests::{self, legacy, mock}, - serde_json::json, -}; - -#[tokio::test] -async fn test() { - let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { - path: mock::http::Path::Any, - req: mock::http::RequestBody::Exact(json!({ - "amms": {}, - "metadata": { - "auction_id": 1, - "environment": "Ethereum / Mainnet", - "gas_price": 15000000000.0, - "native_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "run_id": null - }, - "orders": {}, - "tokens": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "decimals": 18, - "normalize_priority": 1, - "accepted_for_internalization": false, - "internal_buffer": null, - "alias": null, - "external_price": null, - } - } - })), - res: json!({ - "orders": {}, - "prices": {}, - "amms": {}, - "foreign_liquidity_orders": [ - { - "order": { - "from": "0x1111111111111111111111111111111111111111", - "sellToken": "0x2222222222222222222222222222222222222222", - "buyToken": "0x3333333333333333333333333333333333333333", - "receiver": "0x4444444444444444444444444444444444444444", - "sellAmount": "100", - "buyAmount": "200", - "validTo": 1000, - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "feeAmount": "321", - "kind": "sell", - "partiallyFillable": true, - "sellTokenBalance": "erc20", - "buyTokenBalance": "erc20", - "signingScheme": "eip712", - "signature": "0x\ - 0101010101010101010101010101010101010101010101010101010101010101\ - 0202020202020202020202020202020202020202020202020202020202020202\ - 03", - "interactions": { - "pre": [ - { - "target": "0x2222222222222222222222222222222222222222", - "value": "200", - "callData": "0xabcd", - } - ], - "post": [] - } - }, - "exec_sell_amount": "100", - "exec_buy_amount": "200", - } - ], - }), - }]) - .await; - - let engine = tests::SolverEngine::new("legacy", legacy::config(&legacy_solver.address)).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [], - "liquidity": [], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": {}, - "trades": [ - { - "kind": "jit", - "order": { - "sellToken": "0x2222222222222222222222222222222222222222", - "buyToken": "0x3333333333333333333333333333333333333333", - "receiver": "0x4444444444444444444444444444444444444444", - "sellAmount": "100", - "buyAmount": "200", - "validTo": 1000, - "appData": "0x6000000000000000000000000000000000000000000000000000000000000007", - "feeAmount": "321", - "kind": "sell", - "partiallyFillable": true, - "sellTokenBalance": "erc20", - "buyTokenBalance": "erc20", - "signingScheme": "eip712", - "signature": "0x\ - 0101010101010101010101010101010101010101010101010101010101010101\ - 0202020202020202020202020202020202020202020202020202020202020202\ - 03", - "preInteractions": [ - { - "target": "0x2222222222222222222222222222222222222222", - "value": "200", - "callData": "0xabcd", - } - ] - }, - "executedAmount": "100", - } - ], - "interactions": [], - "score": { - "riskadjusted": 1.0 - } - }] - }), - ); -} diff --git a/src/tests/legacy/market_order.rs b/src/tests/legacy/market_order.rs deleted file mode 100644 index 033c3ff..0000000 --- a/src/tests/legacy/market_order.rs +++ /dev/null @@ -1,418 +0,0 @@ -//! Simple test case that verifies the solver can handle market orders. - -use { - crate::tests::{self, legacy, mock}, - serde_json::json, -}; - -/// Tests that orders get marked as "mandatory" in `/quote` requests and that -/// the HTTP query does not contain the `auction_id` parameter. -#[tokio::test] -async fn quote() { - let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { - path: mock::http::Path::glob( - "solve\ - [?]instance_name=*_Mainnet_1_1\ - &time_limit=*\ - &max_nr_exec_orders=100\ - &use_internal_buffers=true\ - &auction_id=1\ - &request_id=0" - ), - req: mock::http::RequestBody::Exact(json!({ - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", - "cost": { - "amount": "1650000000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "fee": "0.003", - "kind": "ConstantProduct", - "mandatory": false, - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3828187314911751990", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "179617892578796375604692" - } - } - }, - "metadata": { - "auction_id": 1, - "environment": "Ethereum / Mainnet", - "gas_price": 15000000000.0, - "native_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "run_id": null - }, - "orders": { - "0": { - "allow_partial_fill": false, - "buy_amount": "6000000000000000000000", - "buy_token": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "cost": { - "amount": "994725000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "fee": { - "amount": "4200000000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "has_atomic_execution": false, - "id": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a", - "is_liquidity_order": false, - "is_mature": true, - "is_sell_order": true, - "mandatory": false, - "reward": 0., - "sell_amount": "133700000000000000", - "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - } - }, - "tokens": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "accepted_for_internalization": true, - "alias": "WETH", - "decimals": 18, - "external_price": 1.0, - "internal_buffer": "1412206645170290748", - "normalize_priority": 1 - }, - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { - "accepted_for_internalization": true, - "alias": "COW", - "decimals": null, - "external_price": 0.000053125132573502, - "internal_buffer": "740264138483556450389", - "normalize_priority": 0 - } - } - })), - res: json!({ - "orders": { - "0": { - "exec_sell_amount": "133700000000000000", - "exec_buy_amount": "6000000000000000000000", - "exec_fee_amount": "6900000000000000" - } - }, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "execution": [{ - "sell_token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buy_token": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "exec_sell_amount": "133700000000000000", - "exec_buy_amount": "6043910341261930467761", - "exec_plan": { - "sequence": 0, - "position": 1, - "internal": false, - } - }], - } - } - }), - }]) - .await; - - let engine = tests::SolverEngine::new("legacy", legacy::config(&legacy_solver.address)).await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": null, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "740264138483556450389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "outputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "inputAmount": "6043910341261930467761", - "outputAmount": "133700000000000000", - } - ], - "score": { - "riskadjusted": 1.0 - } - }] - }), - ); -} - -#[tokio::test] -async fn solve() { - let legacy_solver = mock::http::setup(vec![mock::http::Expectation::Post { - path: mock::http::Path::glob( - "solve\ - [?]instance_name=*_Mainnet_1_1234\ - &time_limit=*\ - &max_nr_exec_orders=100\ - &use_internal_buffers=true\ - &auction_id=1234\ - &request_id=0", - ), - req: mock::http::RequestBody::Exact(json!({ - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "address": "0x97b744df0b59d93a866304f97431d8efad29a08d", - "cost": { - "amount": "1650000000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "fee": "0.003", - "kind": "ConstantProduct", - "mandatory": false, - "reserves": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "3828187314911751990", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "179617892578796375604692" - } - } - }, - "metadata": { - "auction_id": 1234, - "environment": "Ethereum / Mainnet", - "gas_price": 15000000000.0, - "native_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "run_id": null - }, - "orders": { - "0": { - "allow_partial_fill": false, - "buy_amount": "6000000000000000000000", - "buy_token": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "cost": { - "amount": "994725000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "fee": { - "amount": "4200000000000000", - "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "has_atomic_execution": false, - "id": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a", - "is_liquidity_order": false, - "is_mature": true, - "is_sell_order": true, - "mandatory": false, - "reward": 0., - "sell_amount": "133700000000000000", - "sell_token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - } - }, - "tokens": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "accepted_for_internalization": true, - "alias": "WETH", - "decimals": 18, - "external_price": 1.0, - "internal_buffer": "1412206645170290748", - "normalize_priority": 1 - }, - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { - "accepted_for_internalization": true, - "alias": "COW", - "decimals": null, - "external_price": 0.000053125132573502, - "internal_buffer": "740264138483556450389", - "normalize_priority": 0 - } - } - })), - res: json!({ - "orders": { - "0": { - "exec_sell_amount": "133700000000000000", - "exec_buy_amount": "6000000000000000000000", - "exec_fee_amount": "6900000000000000", - } - }, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "amms": { - "0x97b744df0b59d93a866304f97431d8efad29a08d": { - "execution": [{ - "sell_token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buy_token": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "exec_sell_amount": "133700000000000000", - "exec_buy_amount": "6043910341261930467761", - "exec_plan": { - "sequence": 0, - "position": 1, - "internal": false, - } - }], - } - } - }), - }]) - .await; - - let engine = tests::SolverEngine::new("legacy", legacy::config(&legacy_solver.address)).await; - - let solution = engine - .solve(json!({ - "id": "1234", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "decimals": 18, - "symbol": "WETH", - "referencePrice": "1000000000000000000", - "availableBalance": "1412206645170290748", - "trusted": true - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "decimals": null, - "symbol": "COW", - "referencePrice": "53125132573502", - "availableBalance": "740264138483556450389", - "trusted": true - } - }, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", - "buyToken": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB", - "sellAmount": "133700000000000000", - "buyAmount": "6000000000000000000000", - "feeAmount": "4200000000000000", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - } - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { - "balance": "3828187314911751990" - }, - "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB": { - "balance": "179617892578796375604692" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x97b744df0b59d93A866304f97431D8EfAd29a08d", - "gasEstimate": "110000" - } - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z" - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "6043910341261930467761", - "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": "133700000000000000" - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "133700000000000000" - } - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab", - "outputToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "inputAmount": "6043910341261930467761", - "outputAmount": "133700000000000000", - } - ], - "score": { - "riskadjusted": 1.0 - } - }] - }), - ); -} diff --git a/src/tests/legacy/mod.rs b/src/tests/legacy/mod.rs deleted file mode 100644 index ca61a23..0000000 --- a/src/tests/legacy/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use {crate::tests, std::net::SocketAddr}; - -mod attaching_approvals; -mod concentrated_liquidity; -mod jit_order; -mod market_order; - -/// Creates a legacy solver configuration for the specified host. -pub fn config(solver_addr: &SocketAddr) -> tests::Config { - tests::Config::String(format!( - r" -solver-name = 'legacy_solver' -endpoint = 'http://{solver_addr}/solve' -chain-id = '1' -gzip-requests = false - ", - )) -} diff --git a/src/tests/naive/extract_deepest_pool.rs b/src/tests/naive/extract_deepest_pool.rs deleted file mode 100644 index a17aae1..0000000 --- a/src/tests/naive/extract_deepest_pool.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Test that demonstrates that the Naive solver will use the **deepest** pool -//! for solving when multiple different UniswapV2-like pools exist for a -//! specific token pair. -//! -//! The rationale behind this choise is that the deepest pools are typically the -//! most representative of the actual token price, and in general give better -//! prices for larger orders (in theory, for smaller orders it is possible for -//! the shallow pool to offer better prices, but this should be in exceptional -//! cases, and not worth considering in the solver in order to keep things -//! simple). - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::File("config/example.naive.toml".into()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0x0101010101010101010101010101010101010101", - "buyToken": "0x0202020202020202020202020202020202020202", - "sellAmount": "100", - "buyAmount": "1", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x0101010101010101010101010101010101010101": { - "balance": "100" - }, - "0x0202020202020202020202020202020202020202": { - "balance": "100" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x2222222222222222222222222222222222222222", - "gasEstimate": "0" - }, - { - "kind": "constantproduct", - "tokens": { - "0x0101010101010101010101010101010101010101": { - "balance": "10000000" - }, - "0x0202020202020202020202020202020202020202": { - "balance": "10000000" - } - }, - "fee": "0.003", - "id": "1", - "address": "0x1111111111111111111111111111111111111111", - "gasEstimate": "0" - }, - { - "kind": "constantproduct", - "tokens": { - "0x0303030303030303030303030303030303030303": { - "balance": "10000000000000000" - }, - "0x0404040404040404040404040404040404040404": { - "balance": "10000000000000000" - } - }, - "fee": "0.003", - "id": "2", - "address": "0x3333333333333333333333333333333333333333", - "gasEstimate": "0" - }, - ], - "effectiveGasPrice": "0", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x0101010101010101010101010101010101010101": "99", - "0x0202020202020202020202020202020202020202": "100", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "executedAmount": "100", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "1", - "inputToken": "0x0101010101010101010101010101010101010101", - "outputToken": "0x0202020202020202020202020202020202020202", - "inputAmount": "100", - "outputAmount": "99" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/naive/filters_out_of_price_orders.rs b/src/tests/naive/filters_out_of_price_orders.rs deleted file mode 100644 index ea9af51..0000000 --- a/src/tests/naive/filters_out_of_price_orders.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! This test verifies that orders that are out of price get filtered out, but -//! a solution with the "reasonably" priced orders is produced. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn sell_orders_on_both_sides() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - // Unreasonable order a -> b - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - // Reasonable order a -> b - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1000000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - // Reasonable order a -> b - { - "uid": "0x0303030303030303030303030303030303030303030303030303030303030303\ - 0303030303030303030303030303030303030303\ - 03030303", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "1000000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - // Unreasonable order a -> b - { - "uid": "0x0404040404040404040404040404040404040404040404040404040404040404\ - 0404040404040404040404040404040404040404\ - 04040404", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "2000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "1000000000000000000000000", - "0x000000000000000000000000000000000000000b": "1000000000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0303030303030303030303030303030303030303030303030303030303030303\ - 0303030303030303030303030303030303030303\ - 03030303", - "executedAmount": "1000000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "1000000000000000000000", - }, - ], - "interactions": [], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/naive/limit_order_price.rs b/src/tests/naive/limit_order_price.rs deleted file mode 100644 index 1c567e7..0000000 --- a/src/tests/naive/limit_order_price.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! This test verifies that the limit order's limit price is respected after -//! surplus fees are taken from the order. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a\ - 2a2a2a2a", - "sellToken": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - "buyToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "sellAmount": "22397494", - "buyAmount": "18477932550000000", - "feeAmount": "1675785", - "kind": "sell", - "partiallyFillable": false, - "class": "limit", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { - "balance": "36338096110368" - }, - "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": { - "balance": "30072348537379906026018" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x0000000000000000000000000000000000000000", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [] - }), - ); -} diff --git a/src/tests/naive/matches_orders.rs b/src/tests/naive/matches_orders.rs deleted file mode 100644 index 31fa6f7..0000000 --- a/src/tests/naive/matches_orders.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! Tests for various permutiations of matching combinatinos of sell and buy -//! orders. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn sell_orders_on_both_sides() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "57576575881490625723", - "0x000000000000000000000000000000000000000b": "54287532963535509684", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "40000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "57576575881490625723", - "outputAmount": "54287532963535509685" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn sell_orders_on_one_side() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "139560520142598496101", - "0x000000000000000000000000000000000000000b": "140000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "40000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "140000000000000000000", - "outputAmount": "139560520142598496102" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn buy_orders_on_both_sides() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "66231662019024105282", - "0x000000000000000000000000000000000000000b": "61942706346833798925", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "30000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "90000000000000000000", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "66231662019024105282", - "outputAmount": "61942706346833798926" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} - -#[tokio::test] -async fn buy_and_sell_orders() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "40000000000000000000", - "buyAmount": "30000000000000000000", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "100000000000000000000", - "buyAmount": "90000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "70000000000000000000", - "0x000000000000000000000000000000000000000b": "65237102608923246618", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "30000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "100000000000000000000", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000b", - "outputToken": "0x000000000000000000000000000000000000000a", - "inputAmount": "70000000000000000000", - "outputAmount": "65237102608923246619" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/naive/mod.rs b/src/tests/naive/mod.rs deleted file mode 100644 index df3005f..0000000 --- a/src/tests/naive/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod extract_deepest_pool; -mod filters_out_of_price_orders; -mod limit_order_price; -mod matches_orders; -mod reserves_too_small; -mod rounds_prices_in_favour_of_traders; -mod swap_less_than_reserves; -mod without_pool; diff --git a/src/tests/naive/reserves_too_small.rs b/src/tests/naive/reserves_too_small.rs deleted file mode 100644 index 99b2776..0000000 --- a/src/tests/naive/reserves_too_small.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! This test verifies that it filters out large orders when pool reserves are -//! too small to be able to handle them. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "70145218378783248142575", - "buyAmount": "70123226323", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "900000000000000", - "buyAmount": "100", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "25000075" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "2500007500" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "2500007430", - "0x000000000000000000000000000000000000000b": "900000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "900000000000000", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "900000000000000", - "outputAmount": "2500007430" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/naive/rounds_prices_in_favour_of_traders.rs b/src/tests/naive/rounds_prices_in_favour_of_traders.rs deleted file mode 100644 index 0f14208..0000000 --- a/src/tests/naive/rounds_prices_in_favour_of_traders.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! This test verifies that rounding is done in favour of the traders. This is -//! a weird detail that stems from the fact the UniswapV2-like pool swaps are -//! encoded as **swapTokensForExactTokens** (i.e. a "buy" order). The reason for -//! this is to make settlements less likely to revert (because buy swaps have -//! order fees as guaranteed "buffers", while sell swaps only have buffers if -//! they are already in the contract). The rounding is needed because the -//! settlement contract will round executed amounts in favour or the trader, -//! meaning that the clearing prices can cause the total buy amount to be a few -//! wei larger than the exact output that is encoded. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "9000000", - "buyAmount": "8500000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "8500000", - "buyAmount": "8000001", - "feeAmount": "0", - "kind": "buy", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "996999", - "0x000000000000000000000000000000000000000b": "999999", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "9000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "8000001", - }, - ], - "interactions": [ - { - "kind": "liquidity", - "internalize": false, - "id": "0", - "inputToken": "0x000000000000000000000000000000000000000a", - "outputToken": "0x000000000000000000000000000000000000000b", - "inputAmount": "999999", - "outputAmount": "997000" - }, - ], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -} diff --git a/src/tests/naive/swap_less_than_reserves.rs b/src/tests/naive/swap_less_than_reserves.rs deleted file mode 100644 index 45d6250..0000000 --- a/src/tests/naive/swap_less_than_reserves.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! This test verifies that given a swap with multiple orders, including limit -//! orders, the settlement building does not cause the naive solver to generate -//! a solution that swaps more than the pools reserves. -//! -//! This test verifies a regression that was introduced with limit orders, where -//! the incorrect order amounts were used for computing the final pool swap -//! amounts, causing it buy more from the pool than it actual had. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "buyToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "sellAmount": "2161740107040163317224", - "buyAmount": "2146544862", - "feeAmount": "6177386651128093696", - "kind": "sell", - "partiallyFillable": false, - "class": "liquidity", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - "buyToken": "0xD533a949740bb3306d119CC777fa900bA034cd52", - "sellAmount": "495165988", - "buyAmount": "1428571428571428571428", - "feeAmount": "4834012", - "kind": "sell", - "partiallyFillable": false, - "class": "limit", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48": { - "balance": "32275540" - }, - "0xD533a949740bb3306d119CC777fa900bA034cd52": { - "balance": "33308141034569852391" - } - }, - "fee": "0.003", - "id": "0", - "address": "0x210a97ba874a8e279c95b350ae8ba143a143c159", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [] - }), - ); -} diff --git a/src/tests/naive/without_pool.rs b/src/tests/naive/without_pool.rs deleted file mode 100644 index 27681ec..0000000 --- a/src/tests/naive/without_pool.rs +++ /dev/null @@ -1,100 +0,0 @@ -//! This test verifies that the naive solver doesn't use liquidity from the pool -//! when order amounts overlap. - -use {crate::tests, serde_json::json}; - -#[tokio::test] -async fn test() { - let engine = tests::SolverEngine::new( - "naive", - tests::Config::String(r#"risk-parameters = [0,0,0,0]"#.to_owned()), - ) - .await; - - let solution = engine - .solve(json!({ - "id": "1", - "tokens": {}, - "orders": [ - { - "uid": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "sellToken": "0x000000000000000000000000000000000000000a", - "buyToken": "0x000000000000000000000000000000000000000b", - "sellAmount": "1001000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - { - "uid": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "sellToken": "0x000000000000000000000000000000000000000b", - "buyToken": "0x000000000000000000000000000000000000000a", - "sellAmount": "1001000000000000000000", - "buyAmount": "1000000000000000000000", - "feeAmount": "0", - "kind": "sell", - "partiallyFillable": false, - "class": "market", - }, - ], - "liquidity": [ - { - "kind": "constantproduct", - "tokens": { - "0x000000000000000000000000000000000000000a": { - "balance": "1000001000000000000000000" - }, - "0x000000000000000000000000000000000000000b": { - "balance": "1000000000000000000000000" - } - }, - "fee": "0.003", - "id": "0", - "address": "0xffffffffffffffffffffffffffffffffffffffff", - "gasEstimate": "110000" - }, - ], - "effectiveGasPrice": "15000000000", - "deadline": "2106-01-01T00:00:00.000Z", - })) - .await; - - assert_eq!( - solution, - json!({ - "solutions": [{ - "id": 0, - "prices": { - "0x000000000000000000000000000000000000000a": "1000000000000000000000000", - "0x000000000000000000000000000000000000000b": "1000001000000000000000000", - }, - "trades": [ - { - "kind": "fulfillment", - "order": "0x0101010101010101010101010101010101010101010101010101010101010101\ - 0101010101010101010101010101010101010101\ - 01010101", - "executedAmount": "1001000000000000000000", - }, - { - "kind": "fulfillment", - "order": "0x0202020202020202020202020202020202020202020202020202020202020202\ - 0202020202020202020202020202020202020202\ - 02020202", - "executedAmount": "1001000000000000000000", - }, - ], - "interactions": [], - "score": { - "riskadjusted": 0.5 - } - }] - }), - ); -}