From f248ae98b74bb0eb2d0710a9734dbd64d47c3e54 Mon Sep 17 00:00:00 2001 From: Felix Ritzi Date: Sun, 12 Oct 2025 14:39:50 +0200 Subject: [PATCH 01/20] added json logging for auctions and solutions --- .../config/example.baseline.toml | 3 + .../config/example.legacy.toml | 3 + .../src/api/routes/solve/mod.rs | 88 +++++++++++++++++++ crates/balancer-solver/src/domain/solver.rs | 10 +++ crates/balancer-solver/src/infra/config.rs | 4 + 5 files changed, 108 insertions(+) diff --git a/crates/balancer-solver/config/example.baseline.toml b/crates/balancer-solver/config/example.baseline.toml index 28d376008f..a69a55ceb0 100644 --- a/crates/balancer-solver/config/example.baseline.toml +++ b/crates/balancer-solver/config/example.baseline.toml @@ -13,3 +13,6 @@ native-token-price-estimation-amount = "100000000000000000" # driver-url = "http://localhost:8080" # timeout-ms = 5000 # protocols = ["balancer_v2", "uniswap_v2"] + +# Optional: Directory to save auction and solution JSON files for debugging +# auction-save-directory = "/tmp/balancer-auctions" diff --git a/crates/balancer-solver/config/example.legacy.toml b/crates/balancer-solver/config/example.legacy.toml index 5a02a7a047..329a21503e 100644 --- a/crates/balancer-solver/config/example.legacy.toml +++ b/crates/balancer-solver/config/example.legacy.toml @@ -2,3 +2,6 @@ chain-id = "1" solver-name = "CoW Solver" endpoint = "https://solver.cow.fi" gzip-requests = false + +# Optional: Directory to save auction and solution JSON files for debugging +# auction-save-directory = "/tmp/balancer-auctions" diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index d94ba423b5..4a49ac859b 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -79,6 +79,9 @@ pub async fn solve( }; let protocols = state.protocols(); + // Serialize auction DTO for potential saving later (before consuming it) + let auction_json = serde_json::to_value(&auction).ok(); + let auction = match dto::auction::into_domain( auction, liquidity_client, @@ -128,6 +131,18 @@ pub async fn solve( "✅ SENDING RESPONSE TO COW PROTOCOL" ); + // Save auction and solutions to JSON if configured (non-blocking) + if let (Some(save_dir), Some(auction_json)) = (state.auction_save_directory(), auction_json) + { + let solutions_json = serde_json::to_value(&solutions_dto).ok(); + let save_dir = save_dir.to_path_buf(); + tokio::spawn(async move { + if let Some(solutions) = solutions_json { + save_auction_and_solutions(auction_json, solutions, &save_dir).await; + } + }); + } + ( axum::http::StatusCode::OK, axum::response::Json(Response::Ok(solutions_dto)), @@ -138,3 +153,76 @@ pub async fn solve( .instrument(tracing::info_span!("/solve")) .await } + +/// Saves auction and solutions to a JSON file in the configured directory. +/// This function runs in a background task and logs errors without failing the +/// request. +async fn save_auction_and_solutions( + auction: serde_json::Value, + solutions: serde_json::Value, + save_dir: &std::path::Path, +) { + use tokio::fs; + + // Determine filename based on auction ID + let filename = match auction.get("id").and_then(|v| v.as_i64()) { + Some(id) => format!("{}.json", id), + None => { + // Use timestamp for quote auctions + let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f"); + format!("quote_{}.json", timestamp) + } + }; + + let file_path = save_dir.join(&filename); + + // Create directory if it doesn't exist + if let Err(err) = fs::create_dir_all(save_dir).await { + tracing::warn!( + ?err, + directory = ?save_dir, + "Failed to create auction save directory" + ); + return; + } + + // Create combined JSON structure + let combined = serde_json::json!({ + "auction": auction, + "solutions": solutions, + }); + + // Serialize to pretty JSON + let json_content = match serde_json::to_string_pretty(&combined) { + Ok(content) => content, + Err(err) => { + tracing::warn!(?err, "Failed to serialize auction/solutions to JSON"); + return; + } + }; + + // Write to file + let solutions_count = solutions + .get("solutions") + .and_then(|s| s.as_array()) + .map(|a| a.len()) + .unwrap_or(0); + + match fs::write(&file_path, json_content).await { + Ok(_) => { + tracing::info!( + file_path = ?file_path, + auction_id = ?auction.get("id"), + solutions_count, + "💾 Saved auction and solutions to JSON file" + ); + } + Err(err) => { + tracing::warn!( + ?err, + file_path = ?file_path, + "Failed to write auction/solutions JSON file" + ); + } + } +} diff --git a/crates/balancer-solver/src/domain/solver.rs b/crates/balancer-solver/src/domain/solver.rs index 298333ddb4..429a2d107b 100644 --- a/crates/balancer-solver/src/domain/solver.rs +++ b/crates/balancer-solver/src/domain/solver.rs @@ -40,6 +40,7 @@ pub struct Config { pub uni_v3_node_url: Option, pub erc4626_node_url: Option, pub liquidity_client_config: Option, + pub auction_save_directory: Option, } struct Inner { @@ -80,6 +81,9 @@ struct Inner { /// Optional liquidity client for fetching liquidity from external API liquidity_client: Option, + + /// Optional directory to save auction and solution JSON files + auction_save_directory: Option, } impl Solver { @@ -135,6 +139,7 @@ impl Solver { uni_v3_quoter_v2, erc4626_web3, liquidity_client, + auction_save_directory: config.auction_save_directory, })) } @@ -156,6 +161,11 @@ impl Solver { }) } + /// Returns the auction save directory if configured + pub fn auction_save_directory(&self) -> Option<&std::path::Path> { + self.0.auction_save_directory.as_deref() + } + /// Solves the specified auction, returning a vector of all possible /// solutions. pub async fn solve(&self, auction: auction::Auction) -> Vec { diff --git a/crates/balancer-solver/src/infra/config.rs b/crates/balancer-solver/src/infra/config.rs index 638bdeb260..be248d77ad 100644 --- a/crates/balancer-solver/src/infra/config.rs +++ b/crates/balancer-solver/src/infra/config.rs @@ -61,6 +61,9 @@ struct Config { /// Configuration for independent liquidity fetching liquidity: Option, + + /// Optional directory path to save auction and solution JSON files + auction_save_directory: Option, } /// Configuration for the liquidity client @@ -124,6 +127,7 @@ pub async fn load(path: &Path) -> solver::Config { uni_v3_node_url: config.uni_v3_node_url, erc4626_node_url: config.erc4626_node_url, liquidity_client_config: config.liquidity, + auction_save_directory: config.auction_save_directory.map(std::path::PathBuf::from), } } From 9a6d57fe4b51a09a968b72980ad5f240a53b1348 Mon Sep 17 00:00:00 2001 From: Felix Ritzi Date: Mon, 13 Oct 2025 21:17:28 +0200 Subject: [PATCH 02/20] fixed json logging for auctions and solutions --- crates/balancer-solver/src/api/routes/solve/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 4a49ac859b..1cd01ad7cf 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -165,7 +165,7 @@ async fn save_auction_and_solutions( use tokio::fs; // Determine filename based on auction ID - let filename = match auction.get("id").and_then(|v| v.as_i64()) { + let filename = match auction.get("id").and_then(|v| v.as_str()) { Some(id) => format!("{}.json", id), None => { // Use timestamp for quote auctions From 6f22b426cdb4d141ade2760fad653ff823637bc2 Mon Sep 17 00:00:00 2001 From: Felix Ritzi Date: Mon, 13 Oct 2025 21:36:09 +0200 Subject: [PATCH 03/20] split into auctions and solutions file --- .../src/api/routes/solve/mod.rs | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 1cd01ad7cf..840093fe2b 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -154,7 +154,7 @@ pub async fn solve( .await } -/// Saves auction and solutions to a JSON file in the configured directory. +/// Saves auction and solutions to separate JSON files in the configured directory. /// This function runs in a background task and logs errors without failing the /// request. async fn save_auction_and_solutions( @@ -164,17 +164,18 @@ async fn save_auction_and_solutions( ) { use tokio::fs; - // Determine filename based on auction ID - let filename = match auction.get("id").and_then(|v| v.as_str()) { - Some(id) => format!("{}.json", id), + // Determine base filename based on auction ID + let base_filename = match auction.get("id").and_then(|v| v.as_str()) { + Some(id) => id.to_string(), None => { // Use timestamp for quote auctions let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f"); - format!("quote_{}.json", timestamp) + format!("quote_{}", timestamp) } }; - let file_path = save_dir.join(&filename); + let auction_file_path = save_dir.join(format!("{}_auction.json", base_filename)); + let solutions_file_path = save_dir.join(format!("{}_solutions.json", base_filename)); // Create directory if it doesn't exist if let Err(err) = fs::create_dir_all(save_dir).await { @@ -186,42 +187,59 @@ async fn save_auction_and_solutions( return; } - // Create combined JSON structure - let combined = serde_json::json!({ - "auction": auction, - "solutions": solutions, - }); + // Serialize auction to pretty JSON + let auction_json = match serde_json::to_string_pretty(&auction) { + Ok(content) => content, + Err(err) => { + tracing::warn!(?err, "Failed to serialize auction to JSON"); + return; + } + }; - // Serialize to pretty JSON - let json_content = match serde_json::to_string_pretty(&combined) { + // Serialize solutions to pretty JSON + let solutions_json = match serde_json::to_string_pretty(&solutions) { Ok(content) => content, Err(err) => { - tracing::warn!(?err, "Failed to serialize auction/solutions to JSON"); + tracing::warn!(?err, "Failed to serialize solutions to JSON"); return; } }; - // Write to file let solutions_count = solutions .get("solutions") .and_then(|s| s.as_array()) .map(|a| a.len()) .unwrap_or(0); - match fs::write(&file_path, json_content).await { - Ok(_) => { + // Write auction file + let auction_write_result = fs::write(&auction_file_path, auction_json).await; + + // Write solutions file + let solutions_write_result = fs::write(&solutions_file_path, solutions_json).await; + + // Log results + match (auction_write_result, solutions_write_result) { + (Ok(_), Ok(_)) => { tracing::info!( - file_path = ?file_path, + auction_file = ?auction_file_path, + solutions_file = ?solutions_file_path, auction_id = ?auction.get("id"), solutions_count, - "💾 Saved auction and solutions to JSON file" + "💾 Saved auction and solutions to separate JSON files" ); } - Err(err) => { + (Err(err), _) => { + tracing::warn!( + ?err, + file_path = ?auction_file_path, + "Failed to write auction JSON file" + ); + } + (_, Err(err)) => { tracing::warn!( ?err, - file_path = ?file_path, - "Failed to write auction/solutions JSON file" + file_path = ?solutions_file_path, + "Failed to write solutions JSON file" ); } } From 70846f582e24f0803e8b78de19ff8dbd2e5d1ef0 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Tue, 21 Oct 2025 13:45:37 +0200 Subject: [PATCH 04/20] added liquidity json logging --- .../src/api/routes/solve/dto/auction.rs | 79 +++++++++++++++++++ .../src/api/routes/solve/mod.rs | 1 + .../src/infra/liquidity_client.rs | 2 +- 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs index 5bee9a4ee6..ec2cacd50c 100644 --- a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs +++ b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs @@ -84,6 +84,7 @@ pub async fn into_domain( liquidity_client: Option<&LiquidityClient>, base_tokens: Option<&[eth::H160]>, protocols: Option<&[String]>, + save_directory: Option<&std::path::Path>, ) -> Result { Ok(auction::Auction { id: match auction.id { @@ -188,6 +189,18 @@ pub async fn into_domain( "Successfully fetched liquidity from API" ); + // Save liquidity to JSON if save_directory is provided + if let Some(save_dir) = save_directory { + let liquidity_json = serde_json::to_value(&response).ok(); + let save_dir = save_dir.to_path_buf(); + let auction_id = auction.id; + tokio::spawn(async move { + if let Some(liquidity) = liquidity_json { + save_liquidity_json(liquidity, auction_id, &save_dir).await; + } + }); + } + // Process the fetched liquidity response .liquidity @@ -238,6 +251,72 @@ fn convert_dto_liquidity_to_domain(liquidity: &Liquidity) -> Result, + save_dir: &std::path::Path, +) { + use tokio::fs; + + // Determine filename based on auction ID + let base_filename = match auction_id { + Some(id) => id.to_string(), + None => { + // Use timestamp for quote auctions + let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S_%3f"); + format!("quote_{}", timestamp) + } + }; + + let liquidity_file_path = save_dir.join(format!("{}_liquidity.json", base_filename)); + + // Create directory if it doesn't exist + if let Err(err) = fs::create_dir_all(save_dir).await { + tracing::warn!( + ?err, + directory = ?save_dir, + "Failed to create liquidity save directory" + ); + return; + } + + // Serialize liquidity to pretty JSON + let liquidity_json = match serde_json::to_string_pretty(&liquidity) { + Ok(content) => content, + Err(err) => { + tracing::warn!(?err, "Failed to serialize liquidity to JSON"); + return; + } + }; + + let liquidity_count = liquidity + .get("liquidity") + .and_then(|l| l.as_array()) + .map(|a| a.len()) + .unwrap_or(0); + + // Write liquidity file + match fs::write(&liquidity_file_path, liquidity_json).await { + Ok(_) => { + tracing::info!( + liquidity_file = ?liquidity_file_path, + auction_id = ?auction_id, + liquidity_count, + "💾 Saved fetched liquidity to JSON file" + ); + } + Err(err) => { + tracing::warn!( + ?err, + file_path = ?liquidity_file_path, + "Failed to write liquidity JSON file" + ); + } + } +} + mod erc4626 { use super::*; pub fn to_domain(edge: &Erc4626Edge) -> Result { diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 840093fe2b..2c9c924a95 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -87,6 +87,7 @@ pub async fn solve( liquidity_client, base_tokens.as_deref(), protocols.as_deref(), + state.auction_save_directory(), ) .await { diff --git a/crates/balancer-solver/src/infra/liquidity_client.rs b/crates/balancer-solver/src/infra/liquidity_client.rs index 0990eb5d56..43ed685a6d 100644 --- a/crates/balancer-solver/src/infra/liquidity_client.rs +++ b/crates/balancer-solver/src/infra/liquidity_client.rs @@ -71,7 +71,7 @@ pub struct LiquidityRequest { } /// Response from the liquidity-driver API -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct LiquidityResponse { pub auction_id: u64, pub liquidity: Vec, From 4ffcde2a98a07d73b65a06b90382e1d4fe11cb32 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:44:22 +0200 Subject: [PATCH 05/20] added competition info fetching from CoW API --- .../src/api/routes/solve/mod.rs | 147 ++++++++++++++++++ crates/balancer-solver/src/domain/solver.rs | 24 +++ crates/balancer-solver/src/infra/config.rs | 1 + 3 files changed, 172 insertions(+) diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 2c9c924a95..36a571c99a 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -137,11 +137,19 @@ pub async fn solve( { let solutions_json = serde_json::to_value(&solutions_dto).ok(); let save_dir = save_dir.to_path_buf(); + let save_dir_for_competition = save_dir.clone(); + tokio::spawn(async move { if let Some(solutions) = solutions_json { save_auction_and_solutions(auction_json, solutions, &save_dir).await; } }); + + // Spawn background task to fetch competition data + let cow_api_url = state.cow_api_base_url(); + tokio::spawn(async move { + fetch_and_save_competition_data(auction_id, cow_api_url, &save_dir_for_competition).await; + }); } ( @@ -245,3 +253,142 @@ async fn save_auction_and_solutions( } } } + +/// Fetches competition data from the CoW API and saves it to a JSON file. +/// This function waits 60 seconds before attempting to fetch, then retries up to 10 times. +async fn fetch_and_save_competition_data( + auction_id: crate::domain::auction::Id, + cow_api_base_url: &str, + save_dir: &std::path::Path, +) { + use tokio::fs; + use tokio::time::{sleep, Duration}; + + // Extract the numeric auction ID + let auction_id_num = match auction_id { + crate::domain::auction::Id::Solve(id) => id, + crate::domain::auction::Id::Quote => { + tracing::debug!("Skipping competition data fetch for quote auction"); + return; + } + }; + + // Wait 60 seconds for the competition to settle + tracing::info!( + auction_id = auction_id_num, + "Waiting 60 seconds before fetching competition data" + ); + sleep(Duration::from_secs(60)).await; + + let url = format!( + "{}/api/v2/solver_competition/{}", + cow_api_base_url, auction_id_num + ); + let client = reqwest::Client::new(); + + // Retry up to 10 times with 10 second delays between attempts + for attempt in 1..=10 { + tracing::debug!( + auction_id = auction_id_num, + attempt, + "Fetching competition data" + ); + + match client.get(&url).send().await { + Ok(response) => { + if response.status().is_success() { + match response.json::().await { + Ok(competition_data) => { + // Save to file + let filename = format!("{}_competition.json", auction_id_num); + let file_path = save_dir.join(filename); + + // Create directory if needed + if let Err(err) = fs::create_dir_all(save_dir).await { + tracing::warn!( + ?err, + directory = ?save_dir, + "Failed to create competition data directory" + ); + return; + } + + // Serialize to pretty JSON + let json_string = match serde_json::to_string_pretty(&competition_data) + { + Ok(s) => s, + Err(err) => { + tracing::warn!( + ?err, + "Failed to serialize competition data" + ); + return; + } + }; + + // Write to file + match fs::write(&file_path, json_string).await { + Ok(_) => { + tracing::info!( + auction_id = auction_id_num, + file_path = ?file_path, + attempt, + "💾 Successfully saved competition data" + ); + return; // Success! + } + Err(err) => { + tracing::warn!( + ?err, + file_path = ?file_path, + "Failed to write competition data file" + ); + return; + } + } + } + Err(err) => { + tracing::warn!( + ?err, + auction_id = auction_id_num, + attempt, + "Failed to parse competition data JSON" + ); + } + } + } else if response.status().as_u16() == 404 { + tracing::debug!( + auction_id = auction_id_num, + attempt, + "Competition data not yet available (404), will retry" + ); + } else { + tracing::warn!( + auction_id = auction_id_num, + status = response.status().as_u16(), + attempt, + "Unexpected HTTP status when fetching competition data" + ); + } + } + Err(err) => { + tracing::warn!( + ?err, + auction_id = auction_id_num, + attempt, + "HTTP request failed when fetching competition data" + ); + } + } + + // Wait 10 seconds before next retry (unless this was the last attempt) + if attempt < 10 { + sleep(Duration::from_secs(10)).await; + } + } + + tracing::warn!( + auction_id = auction_id_num, + "Failed to fetch competition data after 10 attempts" + ); +} diff --git a/crates/balancer-solver/src/domain/solver.rs b/crates/balancer-solver/src/domain/solver.rs index 429a2d107b..5c8bfcdedd 100644 --- a/crates/balancer-solver/src/domain/solver.rs +++ b/crates/balancer-solver/src/domain/solver.rs @@ -31,6 +31,7 @@ pub struct Solver(Arc); const DEADLINE_SLACK: chrono::Duration = chrono::Duration::milliseconds(500); pub struct Config { + pub chain_id: u64, pub weth: eth::WethAddress, pub base_tokens: Vec, pub max_hops: usize, @@ -44,6 +45,7 @@ pub struct Config { } struct Inner { + chain_id: u64, weth: eth::WethAddress, /// Set of tokens to additionally consider as intermediary hops when @@ -130,6 +132,7 @@ impl Solver { }); Self(Arc::new(Inner { + chain_id: config.chain_id, weth: config.weth, base_tokens: config.base_tokens.into_iter().collect(), max_hops: config.max_hops, @@ -166,6 +169,27 @@ impl Solver { self.0.auction_save_directory.as_deref() } + /// Returns the chain ID for this solver + pub fn chain_id(&self) -> u64 { + self.0.chain_id + } + + /// Returns the CoW API base URL for this chain + pub fn cow_api_base_url(&self) -> &'static str { + match self.0.chain_id { + 1 => "https://api.cow.fi/mainnet", + 100 => "https://api.cow.fi/xdai", + 42161 => "https://api.cow.fi/arbitrum_one", + 8453 => "https://api.cow.fi/base", + 43114 => "https://api.cow.fi/avalanche", + 137 => "https://api.cow.fi/polygon", + _ => { + tracing::warn!(chain_id = self.0.chain_id, "Unknown chain ID for CoW API"); + "https://api.cow.fi/mainnet" // Fallback + } + } + } + /// Solves the specified auction, returning a vector of all possible /// solutions. pub async fn solve(&self, auction: auction::Auction) -> Vec { diff --git a/crates/balancer-solver/src/infra/config.rs b/crates/balancer-solver/src/infra/config.rs index be248d77ad..a614c00078 100644 --- a/crates/balancer-solver/src/infra/config.rs +++ b/crates/balancer-solver/src/infra/config.rs @@ -114,6 +114,7 @@ pub async fn load(path: &Path) -> solver::Config { }; solver::Config { + chain_id: config.chain_id.map(|c| c as u64).unwrap_or(1), weth, base_tokens: config .base_tokens From 719fce66a6ea07383b34701173f7d7cee7ea8b56 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Wed, 22 Oct 2025 17:52:55 +0200 Subject: [PATCH 06/20] added liquidity-enhanced solutions logging --- .../src/api/routes/solve/dto/auction.rs | 78 +++++++++++++++++-- .../src/api/routes/solve/mod.rs | 75 +++++++++++++++++- 2 files changed, 147 insertions(+), 6 deletions(-) diff --git a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs index ec2cacd50c..3a3b87c856 100644 --- a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs +++ b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs @@ -79,14 +79,17 @@ fn extract_token_pairs_from_auction( /// Converts a data transfer object into its domain object representation. /// If liquidity_client is provided and auction has empty liquidity, fetches /// independently. +/// Returns the auction and optionally the fetched liquidity response. pub async fn into_domain( auction: Auction, liquidity_client: Option<&LiquidityClient>, base_tokens: Option<&[eth::H160]>, protocols: Option<&[String]>, save_directory: Option<&std::path::Path>, -) -> Result { - Ok(auction::Auction { +) -> Result<(auction::Auction, Option), Error> { + let mut fetched_liquidity_response = None; + + let auction_domain = auction::Auction { id: match auction.id { Some(id) => auction::Id::Solve(id), None => auction::Id::Quote, @@ -147,6 +150,7 @@ pub async fn into_domain( }) .collect(), liquidity: { + if auction.liquidity.is_empty() && liquidity_client.is_some() { // Fetch liquidity independently from the liquidity-driver API let client = liquidity_client.unwrap(); @@ -202,11 +206,16 @@ pub async fn into_domain( } // Process the fetched liquidity - response + let domain_liquidity = response .liquidity .iter() .map(|liquidity| convert_dto_liquidity_to_domain(liquidity)) - .try_collect()? + .try_collect()?; + + // Store the response for enhanced solutions + fetched_liquidity_response = Some(response); + + domain_liquidity } Err(e) => { tracing::warn!( @@ -228,7 +237,9 @@ pub async fn into_domain( }, gas_price: auction::GasPrice(eth::Ether(auction.effective_gas_price)), deadline: auction::Deadline(auction.deadline), - }) + }; + + Ok((auction_domain, fetched_liquidity_response)) } /// Helper function to convert DTO liquidity to domain liquidity @@ -804,3 +815,60 @@ mod quant_amm_pool { }) } } + +/// Creates an enhanced solutions JSON with full liquidity details embedded +pub fn create_enhanced_solutions( + solutions: &solvers_dto::solution::Solutions, + liquidity_response: &crate::infra::liquidity_client::LiquidityResponse, +) -> serde_json::Value { + // Convert to JSON value + let mut solutions_json = serde_json::to_value(solutions).unwrap(); + + // Build a map of liquidity ID -> full liquidity details + let mut liquidity_map: std::collections::HashMap = + std::collections::HashMap::new(); + + for liq in &liquidity_response.liquidity { + let id = extract_liquidity_id(liq); + liquidity_map.insert(id, liq); + } + + // Enhance each solution's interactions + if let Some(solutions_array) = solutions_json["solutions"].as_array_mut() { + for solution in solutions_array { + if let Some(interactions) = solution["interactions"].as_array_mut() { + for interaction in interactions { + if interaction["kind"] == "liquidity" { + if let Some(id) = interaction["id"].as_str() { + if let Some(liquidity_details) = liquidity_map.get(id) { + // Embed full liquidity details + interaction["liquidityDetails"] = + serde_json::to_value(liquidity_details).unwrap(); + } + } + } + } + } + } + } + + solutions_json +} + +fn extract_liquidity_id(liq: &solvers_dto::auction::Liquidity) -> String { + // Extract ID from each liquidity variant + match liq { + solvers_dto::auction::Liquidity::ConstantProduct(p) => p.id.clone(), + solvers_dto::auction::Liquidity::WeightedProduct(p) => p.id.clone(), + solvers_dto::auction::Liquidity::Stable(p) => p.id.clone(), + solvers_dto::auction::Liquidity::ConcentratedLiquidity(p) => p.id.clone(), + solvers_dto::auction::Liquidity::GyroE(p) => p.id.clone(), + solvers_dto::auction::Liquidity::Gyro2CLP(p) => p.id.clone(), + solvers_dto::auction::Liquidity::Gyro3CLP(p) => p.id.clone(), + solvers_dto::auction::Liquidity::LimitOrder(p) => p.id.clone(), + solvers_dto::auction::Liquidity::Erc4626(p) => p.id.clone(), + solvers_dto::auction::Liquidity::ReClamm(p) => p.id.clone(), + solvers_dto::auction::Liquidity::QuantAmm(p) => p.id.clone(), + solvers_dto::auction::Liquidity::StableSurge(p) => p.id.clone(), + } +} diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 36a571c99a..396e65268e 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -82,7 +82,7 @@ pub async fn solve( // Serialize auction DTO for potential saving later (before consuming it) let auction_json = serde_json::to_value(&auction).ok(); - let auction = match dto::auction::into_domain( + let (auction, fetched_liquidity) = match dto::auction::into_domain( auction, liquidity_client, base_tokens.as_deref(), @@ -138,6 +138,7 @@ pub async fn solve( let solutions_json = serde_json::to_value(&solutions_dto).ok(); let save_dir = save_dir.to_path_buf(); let save_dir_for_competition = save_dir.clone(); + let save_dir_for_enhanced = save_dir.clone(); tokio::spawn(async move { if let Some(solutions) = solutions_json { @@ -150,6 +151,25 @@ pub async fn solve( tokio::spawn(async move { fetch_and_save_competition_data(auction_id, cow_api_url, &save_dir_for_competition).await; }); + + // Spawn background task to create enhanced solutions if liquidity was fetched + if let Some(liq_response) = fetched_liquidity { + // Serialize solutions_dto for the enhanced file before returning + let solutions_json_for_enhanced = serde_json::to_value(&solutions_dto).ok(); + tokio::spawn(async move { + if let Some(solutions_json) = solutions_json_for_enhanced { + // Deserialize back to Solutions for the function + if let Ok(solutions_for_enhance) = serde_json::from_value(solutions_json) { + save_enhanced_solutions( + solutions_for_enhance, + liq_response, + auction_id, + &save_dir_for_enhanced, + ).await; + } + } + }); + } } ( @@ -392,3 +412,56 @@ async fn fetch_and_save_competition_data( "Failed to fetch competition data after 10 attempts" ); } + +/// Saves enhanced solutions with embedded liquidity details +async fn save_enhanced_solutions( + solutions: dto::Solutions, + liquidity_response: crate::infra::liquidity_client::LiquidityResponse, + auction_id: crate::domain::auction::Id, + save_dir: &std::path::Path, +) { + use tokio::fs; + + let auction_id_num = match auction_id { + crate::domain::auction::Id::Solve(id) => id, + crate::domain::auction::Id::Quote => { + tracing::debug!("Skipping enhanced solutions for quote auction"); + return; + } + }; + + // Create enhanced solutions + let enhanced = dto::auction::create_enhanced_solutions(&solutions, &liquidity_response); + + let filename = format!("{}_enhanced_solutions.json", auction_id_num); + let file_path = save_dir.join(filename); + + // Create directory if needed + if let Err(err) = fs::create_dir_all(save_dir).await { + tracing::warn!(?err, directory = ?save_dir, "Failed to create directory"); + return; + } + + // Serialize to pretty JSON + let json_string = match serde_json::to_string_pretty(&enhanced) { + Ok(s) => s, + Err(err) => { + tracing::warn!(?err, "Failed to serialize enhanced solutions"); + return; + } + }; + + // Write to file + match fs::write(&file_path, json_string).await { + Ok(_) => { + tracing::info!( + auction_id = auction_id_num, + file_path = ?file_path, + "💾 Saved enhanced solutions with liquidity details" + ); + } + Err(err) => { + tracing::warn!(?err, file_path = ?file_path, "Failed to write enhanced solutions"); + } + } +} From 364693fd781d02cce9c1ead4008f19abe4dc801f Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:26:04 +0200 Subject: [PATCH 07/20] added solution verification through quoting Balancer V2 Vault and Balancer V3 Batch Router --- .../src/api/routes/solve/mod.rs | 98 +++++++ crates/balancer-solver/src/domain/solver.rs | 26 ++ crates/balancer-solver/src/infra/config.rs | 12 + crates/balancer-solver/src/infra/mod.rs | 1 + .../src/infra/solution_verifier.rs | 248 ++++++++++++++++++ 5 files changed, 385 insertions(+) create mode 100644 crates/balancer-solver/src/infra/solution_verifier.rs diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index 396e65268e..a54bcee756 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -139,6 +139,7 @@ pub async fn solve( let save_dir = save_dir.to_path_buf(); let save_dir_for_competition = save_dir.clone(); let save_dir_for_enhanced = save_dir.clone(); + let save_dir_for_verify = save_dir.clone(); tokio::spawn(async move { if let Some(solutions) = solutions_json { @@ -170,6 +171,23 @@ pub async fn solve( } }); } + + // Spawn background task to verify solutions if verifier is configured + if let Some(verifier) = state.verifier() { + let solutions_json_for_verify = serde_json::to_value(&solutions_dto).ok(); + let verifier = verifier.clone(); + + tokio::spawn(async move { + if let Some(solutions_json) = solutions_json_for_verify { + verify_and_save_solutions( + solutions_json, + verifier, + auction_id, + &save_dir_for_verify, + ).await; + } + }); + } } ( @@ -413,6 +431,86 @@ async fn fetch_and_save_competition_data( ); } +/// Verifies solutions against on-chain Balancer contracts and saves results +async fn verify_and_save_solutions( + solutions_json: serde_json::Value, + verifier: crate::infra::solution_verifier::SolutionVerifier, + auction_id: crate::domain::auction::Id, + save_dir: &std::path::Path, +) { + use tokio::fs; + + let auction_id_num = match auction_id { + crate::domain::auction::Id::Solve(id) => id, + crate::domain::auction::Id::Quote => { + tracing::debug!("Skipping verification for quote auction"); + return; + } + }; + + // Deserialize solutions + let solutions: solvers_dto::solution::Solutions = match serde_json::from_value(solutions_json) { + Ok(s) => s, + Err(e) => { + tracing::warn!(?e, "Failed to deserialize solutions for verification"); + return; + } + }; + + tracing::info!( + auction_id = auction_id_num, + solutions_count = solutions.solutions.len(), + "Starting solution verification" + ); + + // Verify each solution in parallel + let mut verification_futures = Vec::new(); + for (idx, solution) in solutions.solutions.iter().enumerate() { + let verifier_clone = verifier.clone(); + let solution = solution.clone(); + verification_futures.push(tokio::spawn(async move { + verifier_clone.verify_solution(&solution, idx).await + })); + } + + let results: Vec<_> = futures::future::join_all(verification_futures) + .await + .into_iter() + .filter_map(|r| r.ok()) + .collect(); + + // Save results + let filename = format!("{}_solution_verification.json", auction_id_num); + let file_path = save_dir.join(filename); + + if let Err(err) = fs::create_dir_all(save_dir).await { + tracing::warn!(?err, "Failed to create verification directory"); + return; + } + + let json_string = match serde_json::to_string_pretty(&results) { + Ok(s) => s, + Err(err) => { + tracing::warn!(?err, "Failed to serialize verification results"); + return; + } + }; + + match fs::write(&file_path, json_string).await { + Ok(_) => { + tracing::info!( + auction_id = auction_id_num, + file_path = ?file_path, + solutions_verified = results.len(), + "💾 Saved solution verification results" + ); + } + Err(err) => { + tracing::warn!(?err, "Failed to write verification file"); + } + } +} + /// Saves enhanced solutions with embedded liquidity details async fn save_enhanced_solutions( solutions: dto::Solutions, diff --git a/crates/balancer-solver/src/domain/solver.rs b/crates/balancer-solver/src/domain/solver.rs index 5c8bfcdedd..55adbedd76 100644 --- a/crates/balancer-solver/src/domain/solver.rs +++ b/crates/balancer-solver/src/domain/solver.rs @@ -42,6 +42,9 @@ pub struct Config { pub erc4626_node_url: Option, pub liquidity_client_config: Option, pub auction_save_directory: Option, + pub vault_address: Option, + pub batch_router_address: Option, + pub node_url: Option, } struct Inner { @@ -86,6 +89,9 @@ struct Inner { /// Optional directory to save auction and solution JSON files auction_save_directory: Option, + + /// Optional solution verifier for on-chain quote verification + verifier: Option, } impl Solver { @@ -131,6 +137,20 @@ impl Solver { ) }); + // Create solution verifier if vault and batch router addresses are provided + let verifier = match (config.vault_address, config.batch_router_address, config.node_url) { + (Some(vault_addr), Some(batch_router_addr), Some(ref node_url)) => { + let web3 = ethrpc::web3(Default::default(), Default::default(), node_url, "verifier"); + let vault = contracts::BalancerV2Vault::at(&web3, vault_addr.0); + let batch_router = contracts::BalancerV3BatchRouter::at(&web3, batch_router_addr.0); + Some(crate::infra::solution_verifier::SolutionVerifier::new( + vault, + batch_router, + )) + } + _ => None, + }; + Self(Arc::new(Inner { chain_id: config.chain_id, weth: config.weth, @@ -143,6 +163,7 @@ impl Solver { erc4626_web3, liquidity_client, auction_save_directory: config.auction_save_directory, + verifier, })) } @@ -190,6 +211,11 @@ impl Solver { } } + /// Returns a reference to the solution verifier if configured + pub fn verifier(&self) -> Option<&crate::infra::solution_verifier::SolutionVerifier> { + self.0.verifier.as_ref() + } + /// Solves the specified auction, returning a vector of all possible /// solutions. pub async fn solve(&self, auction: auction::Auction) -> Vec { diff --git a/crates/balancer-solver/src/infra/config.rs b/crates/balancer-solver/src/infra/config.rs index a614c00078..8eef01e655 100644 --- a/crates/balancer-solver/src/infra/config.rs +++ b/crates/balancer-solver/src/infra/config.rs @@ -64,6 +64,15 @@ struct Config { /// Optional directory path to save auction and solution JSON files auction_save_directory: Option, + + /// Balancer V2 Vault address for solution verification + vault_address: Option, + + /// Balancer V3 Batch Router address for solution verification + batch_router_address: Option, + + /// Node URL for solution verification + node_url: Option, } /// Configuration for the liquidity client @@ -129,6 +138,9 @@ pub async fn load(path: &Path) -> solver::Config { erc4626_node_url: config.erc4626_node_url, liquidity_client_config: config.liquidity, auction_save_directory: config.auction_save_directory.map(std::path::PathBuf::from), + vault_address: config.vault_address.map(eth::Address), + batch_router_address: config.batch_router_address.map(eth::Address), + node_url: config.node_url, } } diff --git a/crates/balancer-solver/src/infra/mod.rs b/crates/balancer-solver/src/infra/mod.rs index 8612ed4b6d..0e97262500 100644 --- a/crates/balancer-solver/src/infra/mod.rs +++ b/crates/balancer-solver/src/infra/mod.rs @@ -3,3 +3,4 @@ pub mod config; pub mod contracts; pub mod liquidity_client; pub mod metrics; +pub mod solution_verifier; diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs new file mode 100644 index 0000000000..93d8ec3598 --- /dev/null +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -0,0 +1,248 @@ +use { + contracts::{BalancerV2Vault, BalancerV3BatchRouter}, + ethcontract::{Address, Bytes, H160, U256}, + serde::{Deserialize, Serialize}, +}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct VerificationResult { + pub solution_index: usize, + pub swaps: Vec, + pub total_gas_estimate: Option, + pub verification_timestamp: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SwapVerification { + pub interaction_index: usize, + pub pool_id: String, + pub pool_version: PoolVersion, + pub token_in: Address, + pub token_out: Address, + pub amount_in: String, + pub expected_amount_out: String, + pub quoted_amount_out: Option, + pub difference_bps: Option, + pub quote_error: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum PoolVersion { + V2, + V3, +} + +#[derive(Clone)] +pub struct SolutionVerifier { + vault: BalancerV2Vault, + batch_router: BalancerV3BatchRouter, +} + +impl SolutionVerifier { + pub fn new( + vault: BalancerV2Vault, + batch_router: BalancerV3BatchRouter, + ) -> Self { + Self { + vault, + batch_router, + } + } + + /// Detect pool version by ID length + fn detect_pool_version(pool_id: &str) -> PoolVersion { + // V2 pool IDs are 66 chars (0x + 64 hex chars) + // V3 pool IDs are 42 chars (same as address: 0x + 40 hex chars) + if pool_id.len() > 42 { + PoolVersion::V2 + } else { + PoolVersion::V3 + } + } + + /// Verify a single solution + pub async fn verify_solution( + &self, + solution: &solvers_dto::solution::Solution, + solution_index: usize, + ) -> VerificationResult { + let mut swaps = Vec::new(); + + for (idx, interaction) in solution.interactions.iter().enumerate() { + if let solvers_dto::solution::Interaction::Liquidity(liq) = interaction { + let verification = self.verify_swap(liq, idx).await; + swaps.push(verification); + } + } + + VerificationResult { + solution_index, + swaps, + total_gas_estimate: None, + verification_timestamp: chrono::Utc::now().timestamp() as u64, + } + } + + /// Verify a single swap interaction + async fn verify_swap( + &self, + interaction: &solvers_dto::solution::LiquidityInteraction, + interaction_index: usize, + ) -> SwapVerification { + let pool_version = Self::detect_pool_version(&interaction.id); + + let quoted_amount = match pool_version { + PoolVersion::V2 => self.quote_v2_swap(interaction).await, + PoolVersion::V3 => self.quote_v3_swap(interaction).await, + }; + + let (quoted_amount_out, difference_bps, quote_error) = match quoted_amount { + Ok(quote) => { + let diff = calculate_difference_bps( + &interaction.output_amount, + "e, + ); + (Some(quote), diff, None) + } + Err(e) => (None, None, Some(e.to_string())), + }; + + SwapVerification { + interaction_index, + pool_id: interaction.id.clone(), + pool_version, + token_in: interaction.input_token, + token_out: interaction.output_token, + amount_in: interaction.input_amount.to_string(), + expected_amount_out: interaction.output_amount.to_string(), + quoted_amount_out, + difference_bps, + quote_error, + } + } + + /// Quote V2 swap via Vault.queryBatchSwap + async fn quote_v2_swap( + &self, + interaction: &solvers_dto::solution::LiquidityInteraction, + ) -> Result> { + // Parse pool ID (it's a hex string starting with 0x) + let pool_id_bytes = if interaction.id.starts_with("0x") { + hex::decode(&interaction.id[2..])? + } else { + hex::decode(&interaction.id)? + }; + + if pool_id_bytes.len() != 32 { + return Err(format!("Invalid V2 pool ID length: {}", pool_id_bytes.len()).into()); + } + + let mut pool_id = [0u8; 32]; + pool_id.copy_from_slice(&pool_id_bytes); + + // Build assets array: [token_in, token_out] + let assets = vec![ + H160::from(interaction.input_token.0), + H160::from(interaction.output_token.0), + ]; + + // Create BatchSwapStep + let swap = self.vault.methods().query_batch_swap( + 0u8.into(), // SwapKind.GIVEN_IN + vec![( + Bytes(pool_id), + 0u64.into(), // assetInIndex + 1u64.into(), // assetOutIndex + interaction.input_amount, + Bytes(vec![]), // empty userData + )], + assets, + ( + H160::zero(), // sender (not needed for query) + false, // fromInternalBalance + H160::zero(), // recipient (not needed for query) + false, // toInternalBalance + ), + ); + + // Call the query (static call) + let deltas = swap.call().await?; + + // Parse output: assetDeltas[1] should be positive (amount out) + if deltas.len() < 2 { + return Err("Invalid deltas returned from queryBatchSwap".into()); + } + + // Convert I256 to U256 (take absolute value since output is positive) + let amount_out = if deltas[1].is_negative() { + return Err("Expected positive output delta".into()); + } else { + deltas[1].into_raw() + }; + + Ok(amount_out.to_string()) + } + + /// Quote V3 swap via Batch Router.querySwapExactIn + async fn quote_v3_swap( + &self, + interaction: &solvers_dto::solution::LiquidityInteraction, + ) -> Result> { + // Parse pool address from ID + let pool_address: H160 = interaction.id.parse()?; + + // Build SwapPathExactAmountIn + let path = ( + H160::from(interaction.input_token.0), // tokenIn + vec![ + ( + pool_address, // pool + H160::from(interaction.output_token.0), // tokenOut + false, // isBuffer + ) + ], + interaction.input_amount, // exactAmountIn + U256::zero(), // minAmountOut (no minimum for query) + ); + + // Call querySwapExactIn + let query = self.batch_router.methods().query_swap_exact_in( + vec![path], + H160::zero(), // sender (not needed for query) + Bytes(vec![]), // empty userData + ); + + let (path_amounts_out, _tokens_out, _amounts_out) = query.call().await?; + + // Get the first path's output amount + if path_amounts_out.is_empty() { + return Err("No output amounts returned from querySwapExactIn".into()); + } + + Ok(path_amounts_out[0].to_string()) + } +} + +fn calculate_difference_bps(expected: &U256, actual: &str) -> Option { + // Parse actual amount + let actual_u256 = U256::from_dec_str(actual).ok()?; + + // Calculate difference in basis points + // diff_bps = ((actual - expected) / expected) * 10000 + if *expected == U256::zero() { + return None; + } + + let diff = if actual_u256 > *expected { + let delta = actual_u256 - *expected; + let bps = (delta * 10000u64) / *expected; + bps.as_u64() as i64 + } else { + let delta = *expected - actual_u256; + let bps = (delta * 10000u64) / *expected; + -(bps.as_u64() as i64) + }; + + Some(diff) +} + From 653fab37f52157fa8826d26cb282d36f4574e2ef Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 12:56:45 +0200 Subject: [PATCH 08/20] solution verification now based on enhanced solutions --- .../src/api/routes/solve/dto/auction.rs | 32 +-- .../src/api/routes/solve/mod.rs | 133 +++++++----- crates/balancer-solver/src/domain/solver.rs | 9 +- .../src/infra/solution_verifier.rs | 197 ++++++++++++------ 4 files changed, 233 insertions(+), 138 deletions(-) diff --git a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs index 3a3b87c856..0e06a9a668 100644 --- a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs +++ b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs @@ -86,9 +86,15 @@ pub async fn into_domain( base_tokens: Option<&[eth::H160]>, protocols: Option<&[String]>, save_directory: Option<&std::path::Path>, -) -> Result<(auction::Auction, Option), Error> { +) -> Result< + ( + auction::Auction, + Option, + ), + Error, +> { let mut fetched_liquidity_response = None; - + let auction_domain = auction::Auction { id: match auction.id { Some(id) => auction::Id::Solve(id), @@ -150,7 +156,6 @@ pub async fn into_domain( }) .collect(), liquidity: { - if auction.liquidity.is_empty() && liquidity_client.is_some() { // Fetch liquidity independently from the liquidity-driver API let client = liquidity_client.unwrap(); @@ -211,10 +216,10 @@ pub async fn into_domain( .iter() .map(|liquidity| convert_dto_liquidity_to_domain(liquidity)) .try_collect()?; - + // Store the response for enhanced solutions fetched_liquidity_response = Some(response); - + domain_liquidity } Err(e) => { @@ -238,7 +243,7 @@ pub async fn into_domain( gas_price: auction::GasPrice(eth::Ether(auction.effective_gas_price)), deadline: auction::Deadline(auction.deadline), }; - + Ok((auction_domain, fetched_liquidity_response)) } @@ -263,7 +268,8 @@ fn convert_dto_liquidity_to_domain(liquidity: &Liquidity) -> Result, @@ -823,16 +829,16 @@ pub fn create_enhanced_solutions( ) -> serde_json::Value { // Convert to JSON value let mut solutions_json = serde_json::to_value(solutions).unwrap(); - + // Build a map of liquidity ID -> full liquidity details - let mut liquidity_map: std::collections::HashMap = + let mut liquidity_map: std::collections::HashMap = std::collections::HashMap::new(); - + for liq in &liquidity_response.liquidity { let id = extract_liquidity_id(liq); liquidity_map.insert(id, liq); } - + // Enhance each solution's interactions if let Some(solutions_array) = solutions_json["solutions"].as_array_mut() { for solution in solutions_array { @@ -842,7 +848,7 @@ pub fn create_enhanced_solutions( if let Some(id) = interaction["id"].as_str() { if let Some(liquidity_details) = liquidity_map.get(id) { // Embed full liquidity details - interaction["liquidityDetails"] = + interaction["liquidityDetails"] = serde_json::to_value(liquidity_details).unwrap(); } } @@ -851,7 +857,7 @@ pub fn create_enhanced_solutions( } } } - + solutions_json } diff --git a/crates/balancer-solver/src/api/routes/solve/mod.rs b/crates/balancer-solver/src/api/routes/solve/mod.rs index a54bcee756..a2c5a39ba7 100644 --- a/crates/balancer-solver/src/api/routes/solve/mod.rs +++ b/crates/balancer-solver/src/api/routes/solve/mod.rs @@ -140,7 +140,7 @@ pub async fn solve( let save_dir_for_competition = save_dir.clone(); let save_dir_for_enhanced = save_dir.clone(); let save_dir_for_verify = save_dir.clone(); - + tokio::spawn(async move { if let Some(solutions) = solutions_json { save_auction_and_solutions(auction_json, solutions, &save_dir).await; @@ -150,33 +150,54 @@ pub async fn solve( // Spawn background task to fetch competition data let cow_api_url = state.cow_api_base_url(); tokio::spawn(async move { - fetch_and_save_competition_data(auction_id, cow_api_url, &save_dir_for_competition).await; + fetch_and_save_competition_data(auction_id, cow_api_url, &save_dir_for_competition) + .await; }); // Spawn background task to create enhanced solutions if liquidity was fetched + // If verifier is also configured, verify using the enhanced solutions if let Some(liq_response) = fetched_liquidity { - // Serialize solutions_dto for the enhanced file before returning + let verifier_opt = state.verifier().cloned(); let solutions_json_for_enhanced = serde_json::to_value(&solutions_dto).ok(); + tokio::spawn(async move { if let Some(solutions_json) = solutions_json_for_enhanced { // Deserialize back to Solutions for the function - if let Ok(solutions_for_enhance) = serde_json::from_value(solutions_json) { - save_enhanced_solutions( - solutions_for_enhance, - liq_response, + if let Ok(solutions_for_enhance) = + serde_json::from_value::(solutions_json) + { + // Create enhanced solutions with liquidityDetails + let enhanced = dto::auction::create_enhanced_solutions( + &solutions_for_enhance, + &liq_response, + ); + + // Save enhanced solutions file + save_enhanced_solutions_json( + enhanced.clone(), auction_id, &save_dir_for_enhanced, - ).await; + ) + .await; + + // Verify using enhanced solutions if verifier is configured + if let Some(verifier) = verifier_opt { + verify_and_save_solutions( + enhanced, + verifier, + auction_id, + &save_dir_for_verify, + ) + .await; + } } } }); - } - - // Spawn background task to verify solutions if verifier is configured - if let Some(verifier) = state.verifier() { + } else if let Some(verifier) = state.verifier() { + // No liquidity fetched, but verifier configured - use basic solutions let solutions_json_for_verify = serde_json::to_value(&solutions_dto).ok(); let verifier = verifier.clone(); - + tokio::spawn(async move { if let Some(solutions_json) = solutions_json_for_verify { verify_and_save_solutions( @@ -184,7 +205,8 @@ pub async fn solve( verifier, auction_id, &save_dir_for_verify, - ).await; + ) + .await; } }); } @@ -201,9 +223,9 @@ pub async fn solve( .await } -/// Saves auction and solutions to separate JSON files in the configured directory. -/// This function runs in a background task and logs errors without failing the -/// request. +/// Saves auction and solutions to separate JSON files in the configured +/// directory. This function runs in a background task and logs errors without +/// failing the request. async fn save_auction_and_solutions( auction: serde_json::Value, solutions: serde_json::Value, @@ -260,7 +282,7 @@ async fn save_auction_and_solutions( // Write auction file let auction_write_result = fs::write(&auction_file_path, auction_json).await; - + // Write solutions file let solutions_write_result = fs::write(&solutions_file_path, solutions_json).await; @@ -293,14 +315,17 @@ async fn save_auction_and_solutions( } /// Fetches competition data from the CoW API and saves it to a JSON file. -/// This function waits 60 seconds before attempting to fetch, then retries up to 10 times. +/// This function waits 60 seconds before attempting to fetch, then retries up +/// to 10 times. async fn fetch_and_save_competition_data( auction_id: crate::domain::auction::Id, cow_api_base_url: &str, save_dir: &std::path::Path, ) { - use tokio::fs; - use tokio::time::{sleep, Duration}; + use tokio::{ + fs, + time::{Duration, sleep}, + }; // Extract the numeric auction ID let auction_id_num = match auction_id { @@ -356,10 +381,7 @@ async fn fetch_and_save_competition_data( { Ok(s) => s, Err(err) => { - tracing::warn!( - ?err, - "Failed to serialize competition data" - ); + tracing::warn!(?err, "Failed to serialize competition data"); return; } }; @@ -432,6 +454,7 @@ async fn fetch_and_save_competition_data( } /// Verifies solutions against on-chain Balancer contracts and saves results +/// Accepts JSON solutions (possibly enhanced with liquidityDetails) async fn verify_and_save_solutions( solutions_json: serde_json::Value, verifier: crate::infra::solution_verifier::SolutionVerifier, @@ -439,7 +462,7 @@ async fn verify_and_save_solutions( save_dir: &std::path::Path, ) { use tokio::fs; - + let auction_id_num = match auction_id { crate::domain::auction::Id::Solve(id) => id, crate::domain::auction::Id::Quote => { @@ -447,47 +470,53 @@ async fn verify_and_save_solutions( return; } }; - - // Deserialize solutions - let solutions: solvers_dto::solution::Solutions = match serde_json::from_value(solutions_json) { - Ok(s) => s, - Err(e) => { - tracing::warn!(?e, "Failed to deserialize solutions for verification"); + + // Extract solutions array from JSON + let solutions_array = match solutions_json["solutions"].as_array() { + Some(arr) => arr, + None => { + tracing::warn!("Solutions JSON missing 'solutions' array"); return; } }; - + tracing::info!( auction_id = auction_id_num, - solutions_count = solutions.solutions.len(), - "Starting solution verification" + solutions_count = solutions_array.len(), + has_liquidity_details = solutions_array + .get(0) + .and_then(|s| s["interactions"].as_array()) + .and_then(|i| i.get(0)) + .and_then(|i| i.get("liquidityDetails")) + .is_some(), + "Starting solution verification with enhanced liquidity data" ); - + // Verify each solution in parallel let mut verification_futures = Vec::new(); - for (idx, solution) in solutions.solutions.iter().enumerate() { + for (idx, solution) in solutions_array.iter().enumerate() { let verifier_clone = verifier.clone(); let solution = solution.clone(); verification_futures.push(tokio::spawn(async move { verifier_clone.verify_solution(&solution, idx).await })); } - + let results: Vec<_> = futures::future::join_all(verification_futures) .await .into_iter() .filter_map(|r| r.ok()) .collect(); - + // Save results let filename = format!("{}_solution_verification.json", auction_id_num); let file_path = save_dir.join(filename); - + if let Err(err) = fs::create_dir_all(save_dir).await { tracing::warn!(?err, "Failed to create verification directory"); return; } - + let json_string = match serde_json::to_string_pretty(&results) { Ok(s) => s, Err(err) => { @@ -495,7 +524,7 @@ async fn verify_and_save_solutions( return; } }; - + match fs::write(&file_path, json_string).await { Ok(_) => { tracing::info!( @@ -511,15 +540,14 @@ async fn verify_and_save_solutions( } } -/// Saves enhanced solutions with embedded liquidity details -async fn save_enhanced_solutions( - solutions: dto::Solutions, - liquidity_response: crate::infra::liquidity_client::LiquidityResponse, +/// Saves enhanced solutions (already created) to a JSON file +async fn save_enhanced_solutions_json( + enhanced: serde_json::Value, auction_id: crate::domain::auction::Id, save_dir: &std::path::Path, ) { use tokio::fs; - + let auction_id_num = match auction_id { crate::domain::auction::Id::Solve(id) => id, crate::domain::auction::Id::Quote => { @@ -527,19 +555,16 @@ async fn save_enhanced_solutions( return; } }; - - // Create enhanced solutions - let enhanced = dto::auction::create_enhanced_solutions(&solutions, &liquidity_response); - + let filename = format!("{}_enhanced_solutions.json", auction_id_num); let file_path = save_dir.join(filename); - + // Create directory if needed if let Err(err) = fs::create_dir_all(save_dir).await { tracing::warn!(?err, directory = ?save_dir, "Failed to create directory"); return; } - + // Serialize to pretty JSON let json_string = match serde_json::to_string_pretty(&enhanced) { Ok(s) => s, @@ -548,7 +573,7 @@ async fn save_enhanced_solutions( return; } }; - + // Write to file match fs::write(&file_path, json_string).await { Ok(_) => { diff --git a/crates/balancer-solver/src/domain/solver.rs b/crates/balancer-solver/src/domain/solver.rs index 55adbedd76..b8954ba495 100644 --- a/crates/balancer-solver/src/domain/solver.rs +++ b/crates/balancer-solver/src/domain/solver.rs @@ -138,9 +138,14 @@ impl Solver { }); // Create solution verifier if vault and batch router addresses are provided - let verifier = match (config.vault_address, config.batch_router_address, config.node_url) { + let verifier = match ( + config.vault_address, + config.batch_router_address, + config.node_url, + ) { (Some(vault_addr), Some(batch_router_addr), Some(ref node_url)) => { - let web3 = ethrpc::web3(Default::default(), Default::default(), node_url, "verifier"); + let web3 = + ethrpc::web3(Default::default(), Default::default(), node_url, "verifier"); let vault = contracts::BalancerV2Vault::at(&web3, vault_addr.0); let batch_router = contracts::BalancerV3BatchRouter::at(&web3, batch_router_addr.0); Some(crate::infra::solution_verifier::SolutionVerifier::new( diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 93d8ec3598..b831253c20 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -39,10 +39,7 @@ pub struct SolutionVerifier { } impl SolutionVerifier { - pub fn new( - vault: BalancerV2Vault, - batch_router: BalancerV3BatchRouter, - ) -> Self { + pub fn new(vault: BalancerV2Vault, batch_router: BalancerV3BatchRouter) -> Self { Self { vault, batch_router, @@ -60,21 +57,23 @@ impl SolutionVerifier { } } - /// Verify a single solution + /// Verify a single solution (accepts JSON to support enhanced solutions) pub async fn verify_solution( &self, - solution: &solvers_dto::solution::Solution, + solution: &serde_json::Value, solution_index: usize, ) -> VerificationResult { let mut swaps = Vec::new(); - - for (idx, interaction) in solution.interactions.iter().enumerate() { - if let solvers_dto::solution::Interaction::Liquidity(liq) = interaction { - let verification = self.verify_swap(liq, idx).await; - swaps.push(verification); + + if let Some(interactions) = solution["interactions"].as_array() { + for (idx, interaction) in interactions.iter().enumerate() { + if interaction["kind"] == "liquidity" { + let verification = self.verify_swap(interaction, idx).await; + swaps.push(verification); + } } } - + VerificationResult { solution_index, swaps, @@ -83,38 +82,98 @@ impl SolutionVerifier { } } - /// Verify a single swap interaction + /// Verify a single swap interaction (accepts JSON to support enhanced + /// solutions) async fn verify_swap( &self, - interaction: &solvers_dto::solution::LiquidityInteraction, + interaction: &serde_json::Value, interaction_index: usize, ) -> SwapVerification { - let pool_version = Self::detect_pool_version(&interaction.id); - + // Extract basic fields + let pool_id = interaction["id"].as_str().unwrap_or("unknown"); + let input_token_str = interaction["inputToken"].as_str().unwrap_or(""); + let output_token_str = interaction["outputToken"].as_str().unwrap_or(""); + let input_amount_str = interaction["inputAmount"].as_str().unwrap_or("0"); + let output_amount_str = interaction["outputAmount"].as_str().unwrap_or("0"); + + // Parse token addresses + let input_token: Address = input_token_str.parse().unwrap_or_default(); + let output_token: Address = output_token_str.parse().unwrap_or_default(); + let input_amount = U256::from_dec_str(input_amount_str).unwrap_or_default(); + let output_amount = U256::from_dec_str(output_amount_str).unwrap_or_default(); + + // Try to extract liquidityDetails (enhanced solutions) + let pool_details = interaction.get("liquidityDetails"); + + // Extract pool address and Balancer pool ID from liquidityDetails if available + let (pool_address_opt, balancer_pool_id_opt, pool_kind_opt) = + if let Some(details) = pool_details { + ( + details["address"].as_str(), + details["balancerPoolId"].as_str(), + details["kind"].as_str(), + ) + } else { + (None, None, None) + }; + + // Determine pool version using liquidityDetails if available + let pool_version = if let Some(kind) = pool_kind_opt { + // Use pool kind to determine version + match kind { + "constantProduct" | "weightedProduct" | "stable" => PoolVersion::V2, + _ => PoolVersion::V3, // gyroE, gyro2CLP, gyro3CLP, etc are V3 + } + } else { + Self::detect_pool_version(pool_id) + }; + + // Quote using appropriate method with enhanced data let quoted_amount = match pool_version { - PoolVersion::V2 => self.quote_v2_swap(interaction).await, - PoolVersion::V3 => self.quote_v3_swap(interaction).await, + PoolVersion::V2 => { + if let Some(pool_id_hex) = balancer_pool_id_opt { + self.quote_v2_swap( + pool_id_hex, + H160::from(input_token.0), + H160::from(output_token.0), + input_amount, + ) + .await + } else { + Err("Missing balancerPoolId for V2 pool in liquidityDetails".into()) + } + } + PoolVersion::V3 => { + if let Some(address) = pool_address_opt { + self.quote_v3_swap( + address, + H160::from(input_token.0), + H160::from(output_token.0), + input_amount, + ) + .await + } else { + Err("Missing pool address for V3 pool in liquidityDetails".into()) + } + } }; - + let (quoted_amount_out, difference_bps, quote_error) = match quoted_amount { Ok(quote) => { - let diff = calculate_difference_bps( - &interaction.output_amount, - "e, - ); + let diff = calculate_difference_bps(&output_amount, "e); (Some(quote), diff, None) } Err(e) => (None, None, Some(e.to_string())), }; - + SwapVerification { interaction_index, - pool_id: interaction.id.clone(), + pool_id: pool_id.to_string(), pool_version, - token_in: interaction.input_token, - token_out: interaction.output_token, - amount_in: interaction.input_amount.to_string(), - expected_amount_out: interaction.output_amount.to_string(), + token_in: input_token, + token_out: output_token, + amount_in: input_amount.to_string(), + expected_amount_out: output_amount.to_string(), quoted_amount_out, difference_bps, quote_error, @@ -124,36 +183,36 @@ impl SolutionVerifier { /// Quote V2 swap via Vault.queryBatchSwap async fn quote_v2_swap( &self, - interaction: &solvers_dto::solution::LiquidityInteraction, + balancer_pool_id: &str, + input_token: H160, + output_token: H160, + input_amount: U256, ) -> Result> { // Parse pool ID (it's a hex string starting with 0x) - let pool_id_bytes = if interaction.id.starts_with("0x") { - hex::decode(&interaction.id[2..])? + let pool_id_bytes = if balancer_pool_id.starts_with("0x") { + hex::decode(&balancer_pool_id[2..])? } else { - hex::decode(&interaction.id)? + hex::decode(balancer_pool_id)? }; - + if pool_id_bytes.len() != 32 { return Err(format!("Invalid V2 pool ID length: {}", pool_id_bytes.len()).into()); } - + let mut pool_id = [0u8; 32]; pool_id.copy_from_slice(&pool_id_bytes); // Build assets array: [token_in, token_out] - let assets = vec![ - H160::from(interaction.input_token.0), - H160::from(interaction.output_token.0), - ]; + let assets = vec![input_token, output_token]; // Create BatchSwapStep let swap = self.vault.methods().query_batch_swap( 0u8.into(), // SwapKind.GIVEN_IN vec![( Bytes(pool_id), - 0u64.into(), // assetInIndex - 1u64.into(), // assetOutIndex - interaction.input_amount, + 0u64.into(), // assetInIndex + 1u64.into(), // assetOutIndex + input_amount, Bytes(vec![]), // empty userData )], assets, @@ -167,58 +226,59 @@ impl SolutionVerifier { // Call the query (static call) let deltas = swap.call().await?; - + // Parse output: assetDeltas[1] should be positive (amount out) if deltas.len() < 2 { return Err("Invalid deltas returned from queryBatchSwap".into()); } - + // Convert I256 to U256 (take absolute value since output is positive) let amount_out = if deltas[1].is_negative() { return Err("Expected positive output delta".into()); } else { deltas[1].into_raw() }; - + Ok(amount_out.to_string()) } /// Quote V3 swap via Batch Router.querySwapExactIn async fn quote_v3_swap( &self, - interaction: &solvers_dto::solution::LiquidityInteraction, + pool_address_str: &str, + input_token: H160, + output_token: H160, + input_amount: U256, ) -> Result> { - // Parse pool address from ID - let pool_address: H160 = interaction.id.parse()?; - + // Parse pool address from string + let pool_address: H160 = pool_address_str.parse()?; + // Build SwapPathExactAmountIn let path = ( - H160::from(interaction.input_token.0), // tokenIn - vec![ - ( - pool_address, // pool - H160::from(interaction.output_token.0), // tokenOut - false, // isBuffer - ) - ], - interaction.input_amount, // exactAmountIn - U256::zero(), // minAmountOut (no minimum for query) + input_token, // tokenIn + vec![( + pool_address, // pool + output_token, // tokenOut + false, // isBuffer + )], + input_amount, // exactAmountIn + U256::zero(), // minAmountOut (no minimum for query) ); - + // Call querySwapExactIn let query = self.batch_router.methods().query_swap_exact_in( vec![path], - H160::zero(), // sender (not needed for query) - Bytes(vec![]), // empty userData + H160::zero(), // sender (not needed for query) + Bytes(vec![]), // empty userData ); - + let (path_amounts_out, _tokens_out, _amounts_out) = query.call().await?; - + // Get the first path's output amount if path_amounts_out.is_empty() { return Err("No output amounts returned from querySwapExactIn".into()); } - + Ok(path_amounts_out[0].to_string()) } } @@ -226,13 +286,13 @@ impl SolutionVerifier { fn calculate_difference_bps(expected: &U256, actual: &str) -> Option { // Parse actual amount let actual_u256 = U256::from_dec_str(actual).ok()?; - + // Calculate difference in basis points // diff_bps = ((actual - expected) / expected) * 10000 if *expected == U256::zero() { return None; } - + let diff = if actual_u256 > *expected { let delta = actual_u256 - *expected; let bps = (delta * 10000u64) / *expected; @@ -242,7 +302,6 @@ fn calculate_difference_bps(expected: &U256, actual: &str) -> Option { let bps = (delta * 10000u64) / *expected; -(bps.as_u64() as i64) }; - + Some(diff) } - From 67d59c6f96a762c8c0c85887a17004e114512f9f Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:48:13 +0200 Subject: [PATCH 09/20] fixed version detection --- .../src/infra/solution_verifier.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index b831253c20..942072e648 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -117,14 +117,18 @@ impl SolutionVerifier { (None, None, None) }; - // Determine pool version using liquidityDetails if available - let pool_version = if let Some(kind) = pool_kind_opt { - // Use pool kind to determine version - match kind { - "constantProduct" | "weightedProduct" | "stable" => PoolVersion::V2, - _ => PoolVersion::V3, // gyroE, gyro2CLP, gyro3CLP, etc are V3 + // Determine pool version ONLY by ID length (not by pool kind) + let pool_version = if let Some(balancer_pool_id) = balancer_pool_id_opt { + // Use balancerPoolId from liquidityDetails if available + // V2 pool IDs are 66 chars (0x + 64 hex chars) + // V3 pool IDs are 42 chars (0x + 40 hex chars - same as address) + if balancer_pool_id.len() > 42 { + PoolVersion::V2 + } else { + PoolVersion::V3 } } else { + // Fall back to detecting from the pool_id string Self::detect_pool_version(pool_id) }; From 4b4f9ef6a16841856e121fec1860912d076ea5f3 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 13:50:40 +0200 Subject: [PATCH 10/20] removed duplicate code --- .../src/infra/solution_verifier.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 942072e648..42389aa7e4 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -118,19 +118,8 @@ impl SolutionVerifier { }; // Determine pool version ONLY by ID length (not by pool kind) - let pool_version = if let Some(balancer_pool_id) = balancer_pool_id_opt { - // Use balancerPoolId from liquidityDetails if available - // V2 pool IDs are 66 chars (0x + 64 hex chars) - // V3 pool IDs are 42 chars (0x + 40 hex chars - same as address) - if balancer_pool_id.len() > 42 { - PoolVersion::V2 - } else { - PoolVersion::V3 - } - } else { - // Fall back to detecting from the pool_id string - Self::detect_pool_version(pool_id) - }; + // Prefer balancerPoolId from liquidityDetails, fall back to pool_id + let pool_version = Self::detect_pool_version(balancer_pool_id_opt.unwrap_or(pool_id)); // Quote using appropriate method with enhanced data let quoted_amount = match pool_version { From ab5a2a8fa89baec4b9865f9cd088fc98e4dcc5b3 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:54:47 +0200 Subject: [PATCH 11/20] contract calldata logging & issues remediation --- .../src/infra/solution_verifier.rs | 104 +++++++++++++++--- 1 file changed, 90 insertions(+), 14 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 42389aa7e4..953e704f4c 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -24,6 +24,16 @@ pub struct SwapVerification { pub quoted_amount_out: Option, pub difference_bps: Option, pub quote_error: Option, + pub contract_call: Option, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ContractCallDetails { + pub contract_address: String, + pub contract_name: String, + pub function_name: String, + pub calldata: String, + pub decoded_params: serde_json::Value, } #[derive(Debug, Serialize, Deserialize)] @@ -151,12 +161,12 @@ impl SolutionVerifier { } }; - let (quoted_amount_out, difference_bps, quote_error) = match quoted_amount { - Ok(quote) => { + let (quoted_amount_out, difference_bps, quote_error, contract_call) = match quoted_amount { + Ok((quote, call_details)) => { let diff = calculate_difference_bps(&output_amount, "e); - (Some(quote), diff, None) + (Some(quote), diff, None, Some(call_details)) } - Err(e) => (None, None, Some(e.to_string())), + Err(e) => (None, None, Some(e.to_string()), None), }; SwapVerification { @@ -170,6 +180,7 @@ impl SolutionVerifier { quoted_amount_out, difference_bps, quote_error, + contract_call, } } @@ -180,7 +191,7 @@ impl SolutionVerifier { input_token: H160, output_token: H160, input_amount: U256, - ) -> Result> { + ) -> Result<(String, ContractCallDetails), Box> { // Parse pool ID (it's a hex string starting with 0x) let pool_id_bytes = if balancer_pool_id.starts_with("0x") { hex::decode(&balancer_pool_id[2..])? @@ -208,7 +219,7 @@ impl SolutionVerifier { input_amount, Bytes(vec![]), // empty userData )], - assets, + assets.clone(), ( H160::zero(), // sender (not needed for query) false, // fromInternalBalance @@ -217,22 +228,60 @@ impl SolutionVerifier { ), ); + // Capture contract call details for debugging + let calldata = swap.tx.data.clone().map(|d| format!("0x{}", hex::encode(d.0))) + .unwrap_or_else(|| "0x".to_string()); + + let decoded_params = serde_json::json!({ + "kind": "GIVEN_IN (0)", + "swaps": [{ + "poolId": balancer_pool_id, + "assetInIndex": 0, + "assetOutIndex": 1, + "amount": input_amount.to_string(), + "userData": "0x" + }], + "assets": vec![ + format!("{:?}", assets[0]), + format!("{:?}", assets[1]) + ], + "funds": { + "sender": "0x0000000000000000000000000000000000000000", + "fromInternalBalance": false, + "recipient": "0x0000000000000000000000000000000000000000", + "toInternalBalance": false + } + }); + + let call_details = ContractCallDetails { + contract_address: format!("{:?}", self.vault.address()), + contract_name: "BalancerV2Vault".to_string(), + function_name: "queryBatchSwap".to_string(), + calldata, + decoded_params, + }; + // Call the query (static call) let deltas = swap.call().await?; - // Parse output: assetDeltas[1] should be positive (amount out) + // Parse output: assetDeltas[1] represents net token flow for output token + // In Balancer V2: + // - Positive delta = tokens going INTO vault (user sends) + // - Negative delta = tokens coming OUT of vault (user receives) + // For the output token in a swap, we expect a NEGATIVE delta if deltas.len() < 2 { return Err("Invalid deltas returned from queryBatchSwap".into()); } - // Convert I256 to U256 (take absolute value since output is positive) let amount_out = if deltas[1].is_negative() { - return Err("Expected positive output delta".into()); + // Negative means tokens out - negate to get positive amount + (-deltas[1]).into_raw() } else { - deltas[1].into_raw() + // Positive means tokens in, which is wrong for output token + return Err("Expected negative output delta (tokens out of vault)".into()); }; - Ok(amount_out.to_string()) + Ok((amount_out.to_string(), call_details)) } /// Quote V3 swap via Batch Router.querySwapExactIn @@ -242,7 +291,7 @@ impl SolutionVerifier { input_token: H160, output_token: H160, input_amount: U256, - ) -> Result> { + ) -> Result<(String, ContractCallDetails), Box> { // Parse pool address from string let pool_address: H160 = pool_address_str.parse()?; @@ -260,11 +309,38 @@ impl SolutionVerifier { // Call querySwapExactIn let query = self.batch_router.methods().query_swap_exact_in( - vec![path], + vec![path.clone()], H160::zero(), // sender (not needed for query) Bytes(vec![]), // empty userData ); + // Capture contract call details for debugging + let calldata = query.tx.data.clone().map(|d| format!("0x{}", hex::encode(d.0))) + .unwrap_or_else(|| "0x".to_string()); + + let decoded_params = serde_json::json!({ + "paths": [{ + "tokenIn": format!("{:?}", input_token), + "steps": [{ + "pool": pool_address_str, + "tokenOut": format!("{:?}", output_token), + "isBuffer": false + }], + "exactAmountIn": input_amount.to_string(), + "minAmountOut": "0" + }], + "sender": "0x0000000000000000000000000000000000000000", + "userData": "0x" + }); + + let call_details = ContractCallDetails { + contract_address: format!("{:?}", self.batch_router.address()), + contract_name: "BalancerV3BatchRouter".to_string(), + function_name: "querySwapExactIn".to_string(), + calldata, + decoded_params, + }; + let (path_amounts_out, _tokens_out, _amounts_out) = query.call().await?; // Get the first path's output amount @@ -272,7 +348,7 @@ impl SolutionVerifier { return Err("No output amounts returned from querySwapExactIn".into()); } - Ok(path_amounts_out[0].to_string()) + Ok((path_amounts_out[0].to_string(), call_details)) } } From 9ce5867d4b69b72e309277dda8ea54a13f8b6a56 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 19:53:08 +0200 Subject: [PATCH 12/20] fixed rate provider propagation --- .../src/infra/solution_verifier.rs | 42 ++++++++++++------- .../src/tests/cases/bal_liquidity.rs | 10 +++++ .../src/tests/cases/buy_order_rounding.rs | 19 +++++++++ .../src/tests/cases/limit_order_quoting.rs | 8 ++++ .../liquidity/balancer/v2/gyro_2clp.rs | 1 + .../liquidity/balancer/v2/gyro_3clp.rs | 1 + .../boundary/liquidity/balancer/v2/gyro_e.rs | 1 + .../boundary/liquidity/balancer/v2/stable.rs | 1 + .../liquidity/balancer/v2/weighted.rs | 1 + .../liquidity/balancer/v3/gyro_2clp.rs | 1 + .../boundary/liquidity/balancer/v3/gyro_e.rs | 1 + .../liquidity/balancer/v3/quantamm.rs | 1 + .../boundary/liquidity/balancer/v3/reclamm.rs | 1 + .../boundary/liquidity/balancer/v3/stable.rs | 1 + .../liquidity/balancer/v3/stable_surge.rs | 1 + .../liquidity/balancer/v3/weighted.rs | 1 + .../domain/liquidity/balancer/v2/gyro_2clp.rs | 1 + .../domain/liquidity/balancer/v2/gyro_3clp.rs | 1 + .../domain/liquidity/balancer/v2/gyro_e.rs | 1 + .../domain/liquidity/balancer/v2/stable.rs | 1 + .../domain/liquidity/balancer/v2/weighted.rs | 1 + .../domain/liquidity/balancer/v3/gyro_2clp.rs | 1 + .../domain/liquidity/balancer/v3/gyro_e.rs | 1 + .../domain/liquidity/balancer/v3/quantamm.rs | 1 + .../domain/liquidity/balancer/v3/reclamm.rs | 1 + .../domain/liquidity/balancer/v3/stable.rs | 1 + .../domain/liquidity/balancer/v3/weighted.rs | 1 + crates/driver/src/infra/solver/dto/auction.rs | 16 +++++++ .../liquidity/balancer/v2/gyro_2clp.rs | 1 + .../liquidity/balancer/v2/gyro_3clp.rs | 1 + .../boundary/liquidity/balancer/v2/gyro_e.rs | 1 + .../boundary/liquidity/balancer/v2/stable.rs | 1 + .../liquidity/balancer/v2/weighted.rs | 1 + .../liquidity/balancer/v3/gyro_2clp.rs | 1 + .../boundary/liquidity/balancer/v3/gyro_e.rs | 1 + .../liquidity/balancer/v3/quantamm.rs | 1 + .../boundary/liquidity/balancer/v3/reclamm.rs | 1 + .../boundary/liquidity/balancer/v3/stable.rs | 1 + .../liquidity/balancer/v3/stable_surge.rs | 1 + .../liquidity/balancer/v3/weighted.rs | 1 + .../domain/liquidity/balancer/v2/gyro_2clp.rs | 1 + .../domain/liquidity/balancer/v2/gyro_3clp.rs | 1 + .../domain/liquidity/balancer/v2/gyro_e.rs | 1 + .../domain/liquidity/balancer/v2/stable.rs | 1 + .../domain/liquidity/balancer/v2/weighted.rs | 1 + .../domain/liquidity/balancer/v3/gyro_2clp.rs | 1 + .../domain/liquidity/balancer/v3/gyro_e.rs | 1 + .../domain/liquidity/balancer/v3/quantamm.rs | 1 + .../domain/liquidity/balancer/v3/reclamm.rs | 1 + .../domain/liquidity/balancer/v3/stable.rs | 1 + .../domain/liquidity/balancer/v3/weighted.rs | 1 + .../src/infra/api/routes/liquidity/mod.rs | 16 +++++++ .../src/infra/solver/dto/auction.rs | 16 +++++++ crates/solvers-dto/src/auction.rs | 7 ++++ .../solvers/src/tests/cases/bal_liquidity.rs | 14 +++++++ .../src/tests/cases/buy_order_rounding.rs | 19 +++++++++ .../src/tests/cases/limit_order_quoting.rs | 8 ++++ 57 files changed, 206 insertions(+), 15 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 953e704f4c..b1c20ba055 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -116,16 +116,14 @@ impl SolutionVerifier { let pool_details = interaction.get("liquidityDetails"); // Extract pool address and Balancer pool ID from liquidityDetails if available - let (pool_address_opt, balancer_pool_id_opt, pool_kind_opt) = - if let Some(details) = pool_details { - ( - details["address"].as_str(), - details["balancerPoolId"].as_str(), - details["kind"].as_str(), - ) - } else { - (None, None, None) - }; + let (pool_address_opt, balancer_pool_id_opt) = if let Some(details) = pool_details { + ( + details["address"].as_str(), + details["balancerPoolId"].as_str(), + ) + } else { + (None, None) + }; // Determine pool version ONLY by ID length (not by pool kind) // Prefer balancerPoolId from liquidityDetails, fall back to pool_id @@ -166,7 +164,13 @@ impl SolutionVerifier { let diff = calculate_difference_bps(&output_amount, "e); (Some(quote), diff, None, Some(call_details)) } - Err(e) => (None, None, Some(e.to_string()), None), + Err(e) => { + // Try to extract contract call details from error context if available + // For now, we don't have call details on error (they're created inside the + // functions) This is acceptable as errors typically happen + // during call execution + (None, None, Some(e.to_string()), None) + } }; SwapVerification { @@ -229,9 +233,13 @@ impl SolutionVerifier { ); // Capture contract call details for debugging - let calldata = swap.tx.data.clone().map(|d| format!("0x{}", hex::encode(d.0))) + let calldata = swap + .tx + .data + .clone() + .map(|d| format!("0x{}", hex::encode(d.0))) .unwrap_or_else(|| "0x".to_string()); - + let decoded_params = serde_json::json!({ "kind": "GIVEN_IN (0)", "swaps": [{ @@ -315,9 +323,13 @@ impl SolutionVerifier { ); // Capture contract call details for debugging - let calldata = query.tx.data.clone().map(|d| format!("0x{}", hex::encode(d.0))) + let calldata = query + .tx + .data + .clone() + .map(|d| format!("0x{}", hex::encode(d.0))) .unwrap_or_else(|| "0x".to_string()); - + let decoded_params = serde_json::json!({ "paths": [{ "tokenIn": format!("{:?}", input_token), diff --git a/crates/balancer-solver/src/tests/cases/bal_liquidity.rs b/crates/balancer-solver/src/tests/cases/bal_liquidity.rs index 6e74eb830c..c3caa41de4 100644 --- a/crates/balancer-solver/src/tests/cases/bal_liquidity.rs +++ b/crates/balancer-solver/src/tests/cases/bal_liquidity.rs @@ -79,11 +79,13 @@ async fn weighted() { "balance": "11260752191375725565253", "scalingFactor": "1", "weight": "0.5", + "rate": "1000000000000000000", }, "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { "balance": "18764168403990393422000071", "scalingFactor": "1", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -207,11 +209,13 @@ async fn weighted_v3plus() { "balance": "5089632258314443812936111", "scalingFactor": "1", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -381,14 +385,17 @@ async fn stable() { "0x6b175474e89094c44da98b954eedeac495271d0f": { "balance": "505781036390938593206504", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { "balance": "554894862074", "scalingFactor": "1000000000000", + "rate": "1000000000000000000", }, "0xdac17f958d2ee523a2206206994597c13d831ec7": { "balance": "1585576741011", "scalingFactor": "1000000000000", + "rate": "1000000000000000000", }, }, "fee": "0.0001", @@ -557,14 +564,17 @@ async fn composable_stable_v4() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", + "rate": "1000000000000000000", }, }, "fee": "0.0001", diff --git a/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs b/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs index b0d96c60a1..0c7332dbf5 100644 --- a/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs +++ b/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs @@ -214,12 +214,16 @@ async fn balancer_weighted() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "1963528800698237927834721", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "1152796145430714835825", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -370,12 +374,16 @@ async fn balancer_weighted_v3plus() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "18764168403990393422000071", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "11260752191375725565253", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -514,12 +522,16 @@ async fn distant_convergence() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -658,12 +670,16 @@ async fn same_path() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "1963528800698237927834721", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "1152796145430714835825", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -852,14 +868,17 @@ async fn balancer_stable() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", + "rate": "1000000000000000000", }, }, "fee": "0.0001", diff --git a/crates/balancer-solver/src/tests/cases/limit_order_quoting.rs b/crates/balancer-solver/src/tests/cases/limit_order_quoting.rs index fec623e088..057bf36a98 100644 --- a/crates/balancer-solver/src/tests/cases/limit_order_quoting.rs +++ b/crates/balancer-solver/src/tests/cases/limit_order_quoting.rs @@ -71,12 +71,16 @@ async fn sell_order() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -216,12 +220,16 @@ async fn buy_order() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs index 09220f0a14..b99a91a39c 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs @@ -34,6 +34,7 @@ pub fn to_domain(id: liquidity::Id, pool: Gyro2CLPPoolOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs index 4f269a967c..141b40bae2 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: Gyro3CLPPoolOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_e.rs b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_e.rs index 4b1b68ad71..f35c52cade 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/gyro_e.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/gyro_e.rs @@ -34,6 +34,7 @@ pub fn to_domain(id: liquidity::Id, pool: GyroEPoolOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs b/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs index 1ce175a390..6290245f39 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/stable.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: StablePoolOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs b/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs index 5d86fe77db..729ab4e69e 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v2/weighted.rs @@ -37,6 +37,7 @@ pub fn to_domain(id: liquidity::Id, pool: WeightedProductOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs b/crates/driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs index 4dbc165ba4..b7028f9456 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs @@ -32,6 +32,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3Gyro2CLPOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/gyro_e.rs b/crates/driver/src/boundary/liquidity/balancer/v3/gyro_e.rs index 1df51e170b..423c5a9839 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/gyro_e.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/gyro_e.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3GyroEOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/quantamm.rs b/crates/driver/src/boundary/liquidity/balancer/v3/quantamm.rs index bc09c9e9e8..9ce4b735be 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/quantamm.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/quantamm.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3QuantAmmOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/reclamm.rs b/crates/driver/src/boundary/liquidity/balancer/v3/reclamm.rs index 98877833eb..343be5d182 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/reclamm.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/reclamm.rs @@ -32,6 +32,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3ReClammOrder) -> Result>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/stable.rs b/crates/driver/src/boundary/liquidity/balancer/v3/stable.rs index d99c2a092d..3ce15cb2af 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/stable.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/stable.rs @@ -38,6 +38,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.scaling_factor.as_uint256(), )?, + rate: reserve.rate.into(), }) }) .collect::>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/stable_surge.rs b/crates/driver/src/boundary/liquidity/balancer/v3/stable_surge.rs index ce4cdcf120..79697e054e 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/stable_surge.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/stable_surge.rs @@ -40,6 +40,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.scaling_factor.as_uint256(), )?, + rate: reserve.rate.into(), }) }) .collect::>()?, diff --git a/crates/driver/src/boundary/liquidity/balancer/v3/weighted.rs b/crates/driver/src/boundary/liquidity/balancer/v3/weighted.rs index 64574f0aad..9365689a8d 100644 --- a/crates/driver/src/boundary/liquidity/balancer/v3/weighted.rs +++ b/crates/driver/src/boundary/liquidity/balancer/v3/weighted.rs @@ -39,6 +39,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.common.scaling_factor.as_uint256(), )?, + rate: reserve.common.rate.into(), }) }) .collect::>()?, diff --git a/crates/driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs b/crates/driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs index 2766cbb157..5cd80ad197 100644 --- a/crates/driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs +++ b/crates/driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs @@ -103,6 +103,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope 2-CLP parameters. diff --git a/crates/driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs b/crates/driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs index 5f076922f1..6eab560102 100644 --- a/crates/driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs +++ b/crates/driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs @@ -108,6 +108,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Fixed point number used for Gyroscope 3-CLP parameters. diff --git a/crates/driver/src/domain/liquidity/balancer/v2/gyro_e.rs b/crates/driver/src/domain/liquidity/balancer/v2/gyro_e.rs index b9a1584e24..06f27910d6 100644 --- a/crates/driver/src/domain/liquidity/balancer/v2/gyro_e.rs +++ b/crates/driver/src/domain/liquidity/balancer/v2/gyro_e.rs @@ -113,6 +113,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope E-CLP parameters. diff --git a/crates/driver/src/domain/liquidity/balancer/v2/stable.rs b/crates/driver/src/domain/liquidity/balancer/v2/stable.rs index fbcdbbdb6d..e065fdbee7 100644 --- a/crates/driver/src/domain/liquidity/balancer/v2/stable.rs +++ b/crates/driver/src/domain/liquidity/balancer/v2/stable.rs @@ -95,6 +95,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Balancer V2 stable pool amplification parameter. diff --git a/crates/driver/src/domain/liquidity/balancer/v2/weighted.rs b/crates/driver/src/domain/liquidity/balancer/v2/weighted.rs index 53da2a6ac8..96c06d4e1c 100644 --- a/crates/driver/src/domain/liquidity/balancer/v2/weighted.rs +++ b/crates/driver/src/domain/liquidity/balancer/v2/weighted.rs @@ -102,6 +102,7 @@ pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, pub weight: Weight, + pub rate: eth::U256, } /// A Balancer token weight. diff --git a/crates/driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs b/crates/driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs index 0a8873c98c..4abfc5ea33 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs @@ -118,6 +118,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope 2-CLP parameters. diff --git a/crates/driver/src/domain/liquidity/balancer/v3/gyro_e.rs b/crates/driver/src/domain/liquidity/balancer/v3/gyro_e.rs index 02da3a4469..aeca35af43 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/gyro_e.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/gyro_e.rs @@ -113,6 +113,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope E-CLP parameters. diff --git a/crates/driver/src/domain/liquidity/balancer/v3/quantamm.rs b/crates/driver/src/domain/liquidity/balancer/v3/quantamm.rs index d654f12cb6..dbf6975a09 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/quantamm.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/quantamm.rs @@ -95,6 +95,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } #[derive(Clone, Copy, Debug)] diff --git a/crates/driver/src/domain/liquidity/balancer/v3/reclamm.rs b/crates/driver/src/domain/liquidity/balancer/v3/reclamm.rs index 6e5c56d421..1ab2be1051 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/reclamm.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/reclamm.rs @@ -89,6 +89,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } #[derive(Clone, Copy, Debug)] diff --git a/crates/driver/src/domain/liquidity/balancer/v3/stable.rs b/crates/driver/src/domain/liquidity/balancer/v3/stable.rs index 1b49805419..e39fec18d2 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/stable.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/stable.rs @@ -99,6 +99,7 @@ pub enum InvalidReserves { pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Balancer V3 stable pool amplification parameter. diff --git a/crates/driver/src/domain/liquidity/balancer/v3/weighted.rs b/crates/driver/src/domain/liquidity/balancer/v3/weighted.rs index fde83e2b71..769b093fda 100644 --- a/crates/driver/src/domain/liquidity/balancer/v3/weighted.rs +++ b/crates/driver/src/domain/liquidity/balancer/v3/weighted.rs @@ -97,6 +97,7 @@ pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, pub weight: Weight, + pub rate: eth::U256, } /// A Balancer V3 token weight. diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index 276b83af70..df0420b0b5 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -241,6 +241,7 @@ pub fn new( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -272,6 +273,7 @@ pub fn new( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -302,6 +304,7 @@ pub fn new( balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), weight: weight_to_decimal(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -340,6 +343,7 @@ pub fn new( r.scale, ), weight: weight_to_decimal_v3(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -373,6 +377,7 @@ pub fn new( solvers_dto::auction::GyroEReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -417,6 +422,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -451,6 +457,7 @@ pub fn new( solvers_dto::auction::Gyro3CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -487,6 +494,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -536,6 +544,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -573,6 +582,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -622,6 +632,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -729,6 +740,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -864,6 +876,10 @@ fn scaling_factor_to_decimal_v3( bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) } +fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { + bigdecimal::BigDecimal::new(rate.to_big_int(), 18) +} + fn signed_fixed_point_to_decimal( sfp: liquidity::balancer::v2::gyro_e::SignedFixedPoint, ) -> bigdecimal::BigDecimal { diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs index 09220f0a14..b99a91a39c 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_2clp.rs @@ -34,6 +34,7 @@ pub fn to_domain(id: liquidity::Id, pool: Gyro2CLPPoolOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs index 4f269a967c..141b40bae2 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_3clp.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: Gyro3CLPPoolOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_e.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_e.rs index 4b1b68ad71..f35c52cade 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_e.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/gyro_e.rs @@ -34,6 +34,7 @@ pub fn to_domain(id: liquidity::Id, pool: GyroEPoolOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/stable.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/stable.rs index 1ce175a390..6290245f39 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/stable.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/stable.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: StablePoolOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/weighted.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/weighted.rs index 5d86fe77db..729ab4e69e 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/weighted.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v2/weighted.rs @@ -37,6 +37,7 @@ pub fn to_domain(id: liquidity::Id, pool: WeightedProductOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs index 4dbc165ba4..b7028f9456 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_2clp.rs @@ -32,6 +32,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3Gyro2CLPOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_e.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_e.rs index 1df51e170b..423c5a9839 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_e.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/gyro_e.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3GyroEOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/quantamm.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/quantamm.rs index bc09c9e9e8..9ce4b735be 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/quantamm.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/quantamm.rs @@ -33,6 +33,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3QuantAmmOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/reclamm.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/reclamm.rs index 98877833eb..343be5d182 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/reclamm.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/reclamm.rs @@ -32,6 +32,7 @@ pub fn to_domain(id: liquidity::Id, pool: BalancerV3ReClammOrder) -> Result>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable.rs index d99c2a092d..3ce15cb2af 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable.rs @@ -38,6 +38,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.scaling_factor.as_uint256(), )?, + rate: reserve.rate.into(), }) }) .collect::>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable_surge.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable_surge.rs index ce4cdcf120..79697e054e 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable_surge.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/stable_surge.rs @@ -40,6 +40,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.scaling_factor.as_uint256(), )?, + rate: reserve.rate.into(), }) }) .collect::>()?, diff --git a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/weighted.rs b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/weighted.rs index 64574f0aad..9365689a8d 100644 --- a/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/weighted.rs +++ b/crates/liquidity-driver/src/boundary/liquidity/balancer/v3/weighted.rs @@ -39,6 +39,7 @@ pub fn to_domain( scale: balancer::v3::ScalingFactor::from_raw( reserve.common.scaling_factor.as_uint256(), )?, + rate: reserve.common.rate.into(), }) }) .collect::>()?, diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs index 2766cbb157..5cd80ad197 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_2clp.rs @@ -103,6 +103,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope 2-CLP parameters. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs index 5f076922f1..6eab560102 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_3clp.rs @@ -108,6 +108,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Fixed point number used for Gyroscope 3-CLP parameters. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_e.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_e.rs index b9a1584e24..06f27910d6 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_e.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/gyro_e.rs @@ -113,6 +113,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope E-CLP parameters. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/stable.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/stable.rs index fbcdbbdb6d..e065fdbee7 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/stable.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/stable.rs @@ -95,6 +95,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Balancer V2 stable pool amplification parameter. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/weighted.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/weighted.rs index 53da2a6ac8..96c06d4e1c 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v2/weighted.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v2/weighted.rs @@ -102,6 +102,7 @@ pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, pub weight: Weight, + pub rate: eth::U256, } /// A Balancer token weight. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs index 0a8873c98c..4abfc5ea33 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_2clp.rs @@ -118,6 +118,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope 2-CLP parameters. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_e.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_e.rs index 02da3a4469..aeca35af43 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_e.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/gyro_e.rs @@ -113,6 +113,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Signed fixed point number used for Gyroscope E-CLP parameters. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/quantamm.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/quantamm.rs index d654f12cb6..dbf6975a09 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/quantamm.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/quantamm.rs @@ -95,6 +95,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } #[derive(Clone, Copy, Debug)] diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/reclamm.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/reclamm.rs index 6e5c56d421..1ab2be1051 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/reclamm.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/reclamm.rs @@ -89,6 +89,7 @@ pub struct InvalidReserves; pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } #[derive(Clone, Copy, Debug)] diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/stable.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/stable.rs index 1b49805419..e39fec18d2 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/stable.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/stable.rs @@ -99,6 +99,7 @@ pub enum InvalidReserves { pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, + pub rate: eth::U256, } /// Balancer V3 stable pool amplification parameter. diff --git a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/weighted.rs b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/weighted.rs index fde83e2b71..769b093fda 100644 --- a/crates/liquidity-driver/src/domain/liquidity/balancer/v3/weighted.rs +++ b/crates/liquidity-driver/src/domain/liquidity/balancer/v3/weighted.rs @@ -97,6 +97,7 @@ pub struct Reserve { pub asset: eth::Asset, pub scale: ScalingFactor, pub weight: Weight, + pub rate: eth::U256, } /// A Balancer V3 token weight. diff --git a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs index 197c9375ec..6067834c16 100644 --- a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs +++ b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs @@ -144,6 +144,7 @@ fn convert_domain_to_dto( balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), weight: weight_to_decimal(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -181,6 +182,7 @@ fn convert_domain_to_dto( balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), weight: weight_to_decimal_v3(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -211,6 +213,7 @@ fn convert_domain_to_dto( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -241,6 +244,7 @@ fn convert_domain_to_dto( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -271,6 +275,7 @@ fn convert_domain_to_dto( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -304,6 +309,7 @@ fn convert_domain_to_dto( solvers_dto::auction::GyroEReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -346,6 +352,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro2CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -376,6 +383,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro3CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -408,6 +416,7 @@ fn convert_domain_to_dto( solvers_dto::auction::GyroEReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -453,6 +462,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro2CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -482,6 +492,7 @@ fn convert_domain_to_dto( solvers_dto::auction::ReClammReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -524,6 +535,7 @@ fn convert_domain_to_dto( solvers_dto::auction::QuantAmmReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -646,6 +658,10 @@ fn scaling_factor_to_decimal_v3( bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) } +fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { + bigdecimal::BigDecimal::new(rate.to_big_int(), 18) +} + fn surge_threshold_to_decimal_v3( surge_threshold: liquidity::balancer::v3::stable_surge::SurgeThresholdPercentage, ) -> bigdecimal::BigDecimal { diff --git a/crates/liquidity-driver/src/infra/solver/dto/auction.rs b/crates/liquidity-driver/src/infra/solver/dto/auction.rs index 276b83af70..df0420b0b5 100644 --- a/crates/liquidity-driver/src/infra/solver/dto/auction.rs +++ b/crates/liquidity-driver/src/infra/solver/dto/auction.rs @@ -241,6 +241,7 @@ pub fn new( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -272,6 +273,7 @@ pub fn new( solvers_dto::auction::StableReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal_v3(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -302,6 +304,7 @@ pub fn new( balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), weight: weight_to_decimal(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -340,6 +343,7 @@ pub fn new( r.scale, ), weight: weight_to_decimal_v3(r.weight), + rate: rate_to_decimal(r.rate), }, ) }) @@ -373,6 +377,7 @@ pub fn new( solvers_dto::auction::GyroEReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -417,6 +422,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -451,6 +457,7 @@ pub fn new( solvers_dto::auction::Gyro3CLPReserve { balance: r.asset.amount.into(), scaling_factor: scaling_factor_to_decimal(r.scale), + rate: rate_to_decimal(r.rate), }, ) }) @@ -487,6 +494,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -536,6 +544,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -573,6 +582,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -622,6 +632,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -729,6 +740,7 @@ pub fn new( scaling_factor: scaling_factor_to_decimal_v3( r.scale, ), + rate: rate_to_decimal(r.rate), }, ) }) @@ -864,6 +876,10 @@ fn scaling_factor_to_decimal_v3( bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) } +fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { + bigdecimal::BigDecimal::new(rate.to_big_int(), 18) +} + fn signed_fixed_point_to_decimal( sfp: liquidity::balancer::v2::gyro_e::SignedFixedPoint, ) -> bigdecimal::BigDecimal { diff --git a/crates/solvers-dto/src/auction.rs b/crates/solvers-dto/src/auction.rs index a6fd8b2a6b..fc3150d258 100644 --- a/crates/solvers-dto/src/auction.rs +++ b/crates/solvers-dto/src/auction.rs @@ -219,6 +219,7 @@ pub struct WeightedProductReserve { pub balance: U256, pub scaling_factor: BigDecimal, pub weight: BigDecimal, + pub rate: BigDecimal, } #[derive(Debug, Serialize, Deserialize)] @@ -249,6 +250,7 @@ pub struct StableReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[serde_as] @@ -356,6 +358,7 @@ pub struct GyroEReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[derive(Debug, Serialize, Deserialize)] @@ -404,6 +407,7 @@ pub struct Gyro2CLPReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[derive(Debug, Serialize, Deserialize)] @@ -419,6 +423,7 @@ pub struct Gyro3CLPReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[derive(Debug, Serialize, Deserialize)] @@ -455,6 +460,7 @@ pub struct ReClammReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[serde_as] @@ -485,6 +491,7 @@ pub struct QuantAmmReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, pub scaling_factor: BigDecimal, + pub rate: BigDecimal, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/solvers/src/tests/cases/bal_liquidity.rs b/crates/solvers/src/tests/cases/bal_liquidity.rs index 6e74eb830c..499781da39 100644 --- a/crates/solvers/src/tests/cases/bal_liquidity.rs +++ b/crates/solvers/src/tests/cases/bal_liquidity.rs @@ -78,12 +78,16 @@ async fn weighted() { "0x6810e776880c02933d47db1b9fc05908e5386b96": { "balance": "11260752191375725565253", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab": { "balance": "18764168403990393422000071", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -206,12 +210,16 @@ async fn weighted_v3plus() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -381,14 +389,17 @@ async fn stable() { "0x6b175474e89094c44da98b954eedeac495271d0f": { "balance": "505781036390938593206504", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { "balance": "554894862074", "scalingFactor": "1000000000000", + "rate": "1000000000000000000", }, "0xdac17f958d2ee523a2206206994597c13d831ec7": { "balance": "1585576741011", "scalingFactor": "1000000000000", + "rate": "1000000000000000000", }, }, "fee": "0.0001", @@ -557,14 +568,17 @@ async fn composable_stable_v4() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", + "rate": "1000000000000000000", }, }, "fee": "0.0001", diff --git a/crates/solvers/src/tests/cases/buy_order_rounding.rs b/crates/solvers/src/tests/cases/buy_order_rounding.rs index b0d96c60a1..0c7332dbf5 100644 --- a/crates/solvers/src/tests/cases/buy_order_rounding.rs +++ b/crates/solvers/src/tests/cases/buy_order_rounding.rs @@ -214,12 +214,16 @@ async fn balancer_weighted() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "1963528800698237927834721", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "1152796145430714835825", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -370,12 +374,16 @@ async fn balancer_weighted_v3plus() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "18764168403990393422000071", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "11260752191375725565253", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -514,12 +522,16 @@ async fn distant_convergence() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -658,12 +670,16 @@ async fn same_path() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "1963528800698237927834721", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "1152796145430714835825", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -852,14 +868,17 @@ async fn balancer_stable() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", + "rate": "1000000000000000000", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", + "rate": "1000000000000000000", }, }, "fee": "0.0001", diff --git a/crates/solvers/src/tests/cases/limit_order_quoting.rs b/crates/solvers/src/tests/cases/limit_order_quoting.rs index fec623e088..057bf36a98 100644 --- a/crates/solvers/src/tests/cases/limit_order_quoting.rs +++ b/crates/solvers/src/tests/cases/limit_order_quoting.rs @@ -71,12 +71,16 @@ async fn sell_order() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", @@ -216,12 +220,16 @@ async fn buy_order() { "0x177127622c4a00f3d409b75571e12cb3c8973d3c": { "balance": "5089632258314443812936111", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", }, "0x9c58bacc331c9aa871afd802db6379a98e80cedb": { "balance": "3043530764763263654069", "scalingFactor": "1", + "rate": "1000000000000000000", "weight": "0.5", + "rate": "1000000000000000000", } }, "fee": "0.005", From 4cb3d7a9b5bab1c1ad426dd6931ce299484957e5 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:34:19 +0200 Subject: [PATCH 13/20] fixed rate propagation --- .../solvers/src/api/routes/solve/dto/auction.rs | 16 ++++++++++++++++ .../solvers/src/boundary/liquidity/gyro_2clp.rs | 8 +++++++- .../solvers/src/boundary/liquidity/gyro_3clp.rs | 8 +++++++- crates/solvers/src/boundary/liquidity/gyro_e.rs | 8 +++++++- .../solvers/src/boundary/liquidity/quantamm.rs | 7 ++++++- crates/solvers/src/boundary/liquidity/reclamm.rs | 7 ++++++- crates/solvers/src/boundary/liquidity/stable.rs | 8 +++++++- .../src/boundary/liquidity/weighted_product.rs | 8 +++++++- crates/solvers/src/domain/liquidity/gyro_2clp.rs | 1 + crates/solvers/src/domain/liquidity/gyro_3clp.rs | 1 + crates/solvers/src/domain/liquidity/gyro_e.rs | 1 + crates/solvers/src/domain/liquidity/quantamm.rs | 1 + crates/solvers/src/domain/liquidity/reclamm.rs | 1 + crates/solvers/src/domain/liquidity/stable.rs | 1 + .../src/domain/liquidity/weighted_product.rs | 1 + 15 files changed, 70 insertions(+), 7 deletions(-) diff --git a/crates/solvers/src/api/routes/solve/dto/auction.rs b/crates/solvers/src/api/routes/solve/dto/auction.rs index ceab817135..29765089c9 100644 --- a/crates/solvers/src/api/routes/solve/dto/auction.rs +++ b/crates/solvers/src/api/routes/solve/dto/auction.rs @@ -163,6 +163,8 @@ mod weighted_product_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -202,6 +204,8 @@ mod stable_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -301,6 +305,8 @@ mod gyro_e_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -364,6 +370,8 @@ mod gyro_2clp_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -409,6 +417,8 @@ mod gyro_3clp_pool { .ok_or("invalid scaling factor")?, ) .ok_or("invalid scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -447,6 +457,8 @@ mod reclamm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -506,6 +518,8 @@ mod stable_surge_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -547,6 +561,8 @@ mod quant_amm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; diff --git a/crates/solvers/src/boundary/liquidity/gyro_2clp.rs b/crates/solvers/src/boundary/liquidity/gyro_2clp.rs index 81c9c99f6a..0ba98313d8 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_2clp.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_2clp.rs @@ -26,7 +26,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_2clp::Pool) -> Opt TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -66,3 +66,9 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { let scaled = ratio.numer().checked_mul(base)? / *ratio.denom(); Some(SBfp::from_wei(scaled)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/boundary/liquidity/gyro_3clp.rs b/crates/solvers/src/boundary/liquidity/gyro_3clp.rs index 8a306c6c68..b3129efd88 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_3clp.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_3clp.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_3clp::Pool) -> Opt TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -62,3 +62,9 @@ fn to_fixed_point(ratio: &crate::domain::eth::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: &crate::domain::eth::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/boundary/liquidity/gyro_e.rs b/crates/solvers/src/boundary/liquidity/gyro_e.rs index dd6537a2ca..3e8ef354a4 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_e.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_e.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_e::Pool) -> Option TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -93,3 +93,9 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { Some(SBfp::from_wei(wei_i256)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/boundary/liquidity/quantamm.rs b/crates/solvers/src/boundary/liquidity/quantamm.rs index b2d4ccabe4..0c20a380cf 100644 --- a/crates/solvers/src/boundary/liquidity/quantamm.rs +++ b/crates/solvers/src/boundary/liquidity/quantamm.rs @@ -28,7 +28,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::quantamm::Pool) -> Opti TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -61,6 +61,11 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } +fn to_u256(ratio: ð::Rational) -> Option { + // Rate is already in wei (18 decimals), so we just convert the rational directly. + ratio.numer().checked_div(*ratio.denom()) +} + fn to_signed_i256(ratio: ð::SignedRational) -> Option { // Follow the same pattern as GyroE's to_signed_fixed_point function let base = I256::from(10u64.pow(18)); diff --git a/crates/solvers/src/boundary/liquidity/reclamm.rs b/crates/solvers/src/boundary/liquidity/reclamm.rs index 245682181b..636a3566e2 100644 --- a/crates/solvers/src/boundary/liquidity/reclamm.rs +++ b/crates/solvers/src/boundary/liquidity/reclamm.rs @@ -27,7 +27,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::reclamm::Pool) -> Optio TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -57,3 +57,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +fn to_u256(ratio: ð::Rational) -> Option { + // Rate is already in wei (18 decimals), so we just convert the rational directly. + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/boundary/liquidity/stable.rs b/crates/solvers/src/boundary/liquidity/stable.rs index f7f85954ac..0258b4c57b 100644 --- a/crates/solvers/src/boundary/liquidity/stable.rs +++ b/crates/solvers/src/boundary/liquidity/stable.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::stable::Pool) -> Option TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -63,3 +63,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/boundary/liquidity/weighted_product.rs b/crates/solvers/src/boundary/liquidity/weighted_product.rs index a4552c9c53..2bb06cb95d 100644 --- a/crates/solvers/src/boundary/liquidity/weighted_product.rs +++ b/crates/solvers/src/boundary/liquidity/weighted_product.rs @@ -32,7 +32,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::weighted_product::Pool) common: TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, weight: to_fixed_point(&reserve.weight)?, }, @@ -64,3 +64,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/solvers/src/domain/liquidity/gyro_2clp.rs b/crates/solvers/src/domain/liquidity/gyro_2clp.rs index 9244afd9c7..ee5590bb80 100644 --- a/crates/solvers/src/domain/liquidity/gyro_2clp.rs +++ b/crates/solvers/src/domain/liquidity/gyro_2clp.rs @@ -60,6 +60,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope 2-CLP pool version. diff --git a/crates/solvers/src/domain/liquidity/gyro_3clp.rs b/crates/solvers/src/domain/liquidity/gyro_3clp.rs index e20f728433..de73083f17 100644 --- a/crates/solvers/src/domain/liquidity/gyro_3clp.rs +++ b/crates/solvers/src/domain/liquidity/gyro_3clp.rs @@ -66,6 +66,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope 3-CLP pool version. diff --git a/crates/solvers/src/domain/liquidity/gyro_e.rs b/crates/solvers/src/domain/liquidity/gyro_e.rs index 0cf93772b2..35ef58d935 100644 --- a/crates/solvers/src/domain/liquidity/gyro_e.rs +++ b/crates/solvers/src/domain/liquidity/gyro_e.rs @@ -72,6 +72,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope E-CLP pool version. diff --git a/crates/solvers/src/domain/liquidity/quantamm.rs b/crates/solvers/src/domain/liquidity/quantamm.rs index 1e7033edf1..e0526f53bd 100644 --- a/crates/solvers/src/domain/liquidity/quantamm.rs +++ b/crates/solvers/src/domain/liquidity/quantamm.rs @@ -59,6 +59,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The QuantAMM pool version. diff --git a/crates/solvers/src/domain/liquidity/reclamm.rs b/crates/solvers/src/domain/liquidity/reclamm.rs index 5b024846bc..660e62bbd5 100644 --- a/crates/solvers/src/domain/liquidity/reclamm.rs +++ b/crates/solvers/src/domain/liquidity/reclamm.rs @@ -48,4 +48,5 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: super::ScalingFactor, + pub rate: eth::Rational, } diff --git a/crates/solvers/src/domain/liquidity/stable.rs b/crates/solvers/src/domain/liquidity/stable.rs index 81eeb6bd3d..3848034f9d 100644 --- a/crates/solvers/src/domain/liquidity/stable.rs +++ b/crates/solvers/src/domain/liquidity/stable.rs @@ -56,4 +56,5 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } diff --git a/crates/solvers/src/domain/liquidity/weighted_product.rs b/crates/solvers/src/domain/liquidity/weighted_product.rs index 730369b050..513869fcf1 100644 --- a/crates/solvers/src/domain/liquidity/weighted_product.rs +++ b/crates/solvers/src/domain/liquidity/weighted_product.rs @@ -57,6 +57,7 @@ pub struct Reserve { pub asset: eth::Asset, pub weight: eth::Rational, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The version of the weighted product math to use. Different versions have From 231e42b4a80560410193861d6996064dfafdd960 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 24 Oct 2025 21:50:10 +0200 Subject: [PATCH 14/20] added fixes to balancer solver --- .../src/api/routes/solve/dto/auction.rs | 16 ++++++++++++++++ .../src/boundary/liquidity/gyro_2clp.rs | 8 +++++++- .../src/boundary/liquidity/gyro_3clp.rs | 8 +++++++- .../src/boundary/liquidity/gyro_e.rs | 8 +++++++- .../src/boundary/liquidity/quantamm.rs | 8 +++++++- .../src/boundary/liquidity/reclamm.rs | 8 +++++++- .../src/boundary/liquidity/stable.rs | 8 +++++++- .../src/boundary/liquidity/weighted_product.rs | 8 +++++++- .../src/domain/liquidity/gyro_2clp.rs | 1 + .../src/domain/liquidity/gyro_3clp.rs | 1 + .../src/domain/liquidity/gyro_e.rs | 1 + .../src/domain/liquidity/quantamm.rs | 1 + .../src/domain/liquidity/reclamm.rs | 1 + .../src/domain/liquidity/stable.rs | 1 + .../src/domain/liquidity/weighted_product.rs | 1 + 15 files changed, 72 insertions(+), 7 deletions(-) diff --git a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs index 0e06a9a668..3c8b5b7cc3 100644 --- a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs +++ b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs @@ -397,6 +397,8 @@ mod weighted_product_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -436,6 +438,8 @@ mod stable_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -535,6 +539,8 @@ mod gyro_e_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -598,6 +604,8 @@ mod gyro_2clp_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -643,6 +651,8 @@ mod gyro_3clp_pool { .ok_or("invalid scaling factor")?, ) .ok_or("invalid scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -681,6 +691,8 @@ mod reclamm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -740,6 +752,8 @@ mod stable_surge_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -781,6 +795,8 @@ mod quant_amm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, + rate: conv::decimal_to_rational(&token.rate) + .ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs index 81c9c99f6a..0ba98313d8 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs @@ -26,7 +26,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_2clp::Pool) -> Opt TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -66,3 +66,9 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { let scaled = ratio.numer().checked_mul(base)? / *ratio.denom(); Some(SBfp::from_wei(scaled)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs index 8a306c6c68..b3129efd88 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_3clp::Pool) -> Opt TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -62,3 +62,9 @@ fn to_fixed_point(ratio: &crate::domain::eth::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: &crate::domain::eth::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs index dd6537a2ca..5e43fc2dea 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::gyro_e::Pool) -> Option TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -76,6 +76,12 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} + /// Converts a signed rational to a Balancer signed fixed point number. fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { // For SignedRational (based on I256), we can work directly with signed values diff --git a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs index b2d4ccabe4..4e35fbf7ff 100644 --- a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs @@ -28,7 +28,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::quantamm::Pool) -> Opti TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -77,3 +77,9 @@ fn to_signed_i256(ratio: ð::SignedRational) -> Option { Some(wei_i256) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs index 245682181b..7ee0d90974 100644 --- a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs @@ -27,7 +27,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::reclamm::Pool) -> Optio TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -57,3 +57,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/boundary/liquidity/stable.rs b/crates/balancer-solver/src/boundary/liquidity/stable.rs index f7f85954ac..0258b4c57b 100644 --- a/crates/balancer-solver/src/boundary/liquidity/stable.rs +++ b/crates/balancer-solver/src/boundary/liquidity/stable.rs @@ -31,7 +31,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::stable::Pool) -> Option TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, )) }) @@ -63,3 +63,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs index a4552c9c53..2bb06cb95d 100644 --- a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs +++ b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs @@ -32,7 +32,7 @@ pub fn to_boundary_pool(address: H160, pool: &liquidity::weighted_product::Pool) common: TokenState { balance: reserve.asset.amount, scaling_factor: to_fixed_point(&reserve.scale.get())?, - rate: U256::exp10(18), + rate: to_u256(&reserve.rate)?, }, weight: to_fixed_point(&reserve.weight)?, }, @@ -64,3 +64,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { let wei = ratio.numer().checked_mul(base)? / ratio.denom(); Some(Bfp::from_wei(wei)) } + +/// Converts a rational to a U256. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +fn to_u256(ratio: ð::Rational) -> Option { + ratio.numer().checked_div(*ratio.denom()) +} diff --git a/crates/balancer-solver/src/domain/liquidity/gyro_2clp.rs b/crates/balancer-solver/src/domain/liquidity/gyro_2clp.rs index 9244afd9c7..ee5590bb80 100644 --- a/crates/balancer-solver/src/domain/liquidity/gyro_2clp.rs +++ b/crates/balancer-solver/src/domain/liquidity/gyro_2clp.rs @@ -60,6 +60,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope 2-CLP pool version. diff --git a/crates/balancer-solver/src/domain/liquidity/gyro_3clp.rs b/crates/balancer-solver/src/domain/liquidity/gyro_3clp.rs index e20f728433..de73083f17 100644 --- a/crates/balancer-solver/src/domain/liquidity/gyro_3clp.rs +++ b/crates/balancer-solver/src/domain/liquidity/gyro_3clp.rs @@ -66,6 +66,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope 3-CLP pool version. diff --git a/crates/balancer-solver/src/domain/liquidity/gyro_e.rs b/crates/balancer-solver/src/domain/liquidity/gyro_e.rs index 0cf93772b2..35ef58d935 100644 --- a/crates/balancer-solver/src/domain/liquidity/gyro_e.rs +++ b/crates/balancer-solver/src/domain/liquidity/gyro_e.rs @@ -72,6 +72,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The Gyroscope E-CLP pool version. diff --git a/crates/balancer-solver/src/domain/liquidity/quantamm.rs b/crates/balancer-solver/src/domain/liquidity/quantamm.rs index 1e7033edf1..e0526f53bd 100644 --- a/crates/balancer-solver/src/domain/liquidity/quantamm.rs +++ b/crates/balancer-solver/src/domain/liquidity/quantamm.rs @@ -59,6 +59,7 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The QuantAMM pool version. diff --git a/crates/balancer-solver/src/domain/liquidity/reclamm.rs b/crates/balancer-solver/src/domain/liquidity/reclamm.rs index 5b024846bc..660e62bbd5 100644 --- a/crates/balancer-solver/src/domain/liquidity/reclamm.rs +++ b/crates/balancer-solver/src/domain/liquidity/reclamm.rs @@ -48,4 +48,5 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: super::ScalingFactor, + pub rate: eth::Rational, } diff --git a/crates/balancer-solver/src/domain/liquidity/stable.rs b/crates/balancer-solver/src/domain/liquidity/stable.rs index 81eeb6bd3d..3848034f9d 100644 --- a/crates/balancer-solver/src/domain/liquidity/stable.rs +++ b/crates/balancer-solver/src/domain/liquidity/stable.rs @@ -56,4 +56,5 @@ impl Reserves { pub struct Reserve { pub asset: eth::Asset, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } diff --git a/crates/balancer-solver/src/domain/liquidity/weighted_product.rs b/crates/balancer-solver/src/domain/liquidity/weighted_product.rs index 730369b050..513869fcf1 100644 --- a/crates/balancer-solver/src/domain/liquidity/weighted_product.rs +++ b/crates/balancer-solver/src/domain/liquidity/weighted_product.rs @@ -57,6 +57,7 @@ pub struct Reserve { pub asset: eth::Asset, pub weight: eth::Rational, pub scale: liquidity::ScalingFactor, + pub rate: eth::Rational, } /// The version of the weighted product math to use. Different versions have From 3f25d2942f7f499697617ddad9bc2c096b8a509a Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:55:31 +0100 Subject: [PATCH 15/20] adjusted rate handling --- .../src/api/routes/solve/dto/auction.rs | 24 +++++++------------ .../src/boundary/liquidity/gyro_2clp.rs | 3 ++- .../src/boundary/liquidity/gyro_3clp.rs | 3 ++- .../src/boundary/liquidity/gyro_e.rs | 3 ++- .../src/boundary/liquidity/quantamm.rs | 3 ++- .../src/boundary/liquidity/reclamm.rs | 3 ++- .../src/boundary/liquidity/stable.rs | 3 ++- .../boundary/liquidity/weighted_product.rs | 3 ++- crates/driver/src/infra/solver/dto/auction.rs | 8 +++++-- .../src/infra/api/routes/liquidity/mod.rs | 8 +++++-- .../src/infra/solver/dto/auction.rs | 8 +++++-- .../balancer_v2/pools/composable_stable.rs | 6 ++++- .../src/api/routes/solve/dto/auction.rs | 24 +++++++------------ .../src/boundary/liquidity/gyro_2clp.rs | 3 ++- .../src/boundary/liquidity/gyro_3clp.rs | 3 ++- .../solvers/src/boundary/liquidity/gyro_e.rs | 3 ++- .../src/boundary/liquidity/quantamm.rs | 3 ++- .../solvers/src/boundary/liquidity/reclamm.rs | 3 ++- .../solvers/src/boundary/liquidity/stable.rs | 3 ++- .../boundary/liquidity/weighted_product.rs | 3 ++- 20 files changed, 67 insertions(+), 53 deletions(-) diff --git a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs index 3c8b5b7cc3..0feec5e5bf 100644 --- a/crates/balancer-solver/src/api/routes/solve/dto/auction.rs +++ b/crates/balancer-solver/src/api/routes/solve/dto/auction.rs @@ -397,8 +397,7 @@ mod weighted_product_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -438,8 +437,7 @@ mod stable_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -539,8 +537,7 @@ mod gyro_e_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -604,8 +601,7 @@ mod gyro_2clp_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -651,8 +647,7 @@ mod gyro_3clp_pool { .ok_or("invalid scaling factor")?, ) .ok_or("invalid scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -691,8 +686,7 @@ mod reclamm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -752,8 +746,7 @@ mod stable_surge_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -795,8 +788,7 @@ mod quant_amm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs index 0ba98313d8..b3c455b5c4 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs @@ -68,7 +68,8 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs index b3129efd88..7864e2ad7a 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs @@ -64,7 +64,8 @@ fn to_fixed_point(ratio: &crate::domain::eth::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: &crate::domain::eth::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs index 5e43fc2dea..4634cab8fa 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs @@ -77,7 +77,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs index 4e35fbf7ff..9470b2800c 100644 --- a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs @@ -79,7 +79,8 @@ fn to_signed_i256(ratio: ð::SignedRational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs index 7ee0d90974..2f8071cd01 100644 --- a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs @@ -59,7 +59,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/stable.rs b/crates/balancer-solver/src/boundary/liquidity/stable.rs index 0258b4c57b..eeff6e776e 100644 --- a/crates/balancer-solver/src/boundary/liquidity/stable.rs +++ b/crates/balancer-solver/src/boundary/liquidity/stable.rs @@ -65,7 +65,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs index 2bb06cb95d..0205db1874 100644 --- a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs +++ b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs @@ -66,7 +66,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index df0420b0b5..600fde9352 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -867,13 +867,17 @@ fn weight_to_decimal_v3( fn scaling_factor_to_decimal( scale: liquidity::balancer::v2::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn scaling_factor_to_decimal_v3( scale: liquidity::balancer::v3::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { diff --git a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs index 6067834c16..a204a06d35 100644 --- a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs +++ b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs @@ -649,13 +649,17 @@ fn weight_to_decimal_v3( fn scaling_factor_to_decimal( scale: liquidity::balancer::v2::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn scaling_factor_to_decimal_v3( scale: liquidity::balancer::v3::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { diff --git a/crates/liquidity-driver/src/infra/solver/dto/auction.rs b/crates/liquidity-driver/src/infra/solver/dto/auction.rs index df0420b0b5..600fde9352 100644 --- a/crates/liquidity-driver/src/infra/solver/dto/auction.rs +++ b/crates/liquidity-driver/src/infra/solver/dto/auction.rs @@ -867,13 +867,17 @@ fn weight_to_decimal_v3( fn scaling_factor_to_decimal( scale: liquidity::balancer::v2::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn scaling_factor_to_decimal_v3( scale: liquidity::balancer::v3::ScalingFactor, ) -> bigdecimal::BigDecimal { - bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 18) + // Scaling factors are raw values (e.g., 10^12 for 6-decimal tokens), + // NOT in 18-decimal format like rates. Use exponent 0. + bigdecimal::BigDecimal::new(scale.as_raw().to_big_int(), 0) } fn rate_to_decimal(rate: eth::U256) -> bigdecimal::BigDecimal { diff --git a/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs b/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs index b8fc359018..4d1a1790ec 100644 --- a/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs +++ b/crates/shared/src/sources/balancer_v2/pools/composable_stable.rs @@ -95,8 +95,12 @@ impl FactoryIndexing for BalancerV2ComposableStablePoolFactory::Instance { ( address, common::TokenState { + balance: token.balance, scaling_factor: Bfp::from_wei(scaling_factor.into_legacy()), - ..token + // For composable stable pools, getScalingFactors() returns + // scaling factors that already include the rate provider rate. + // We set rate to 1.0 to avoid double-applying the rate. + rate: ethcontract::U256::exp10(18), }, ) }) diff --git a/crates/solvers/src/api/routes/solve/dto/auction.rs b/crates/solvers/src/api/routes/solve/dto/auction.rs index 29765089c9..00320505d8 100644 --- a/crates/solvers/src/api/routes/solve/dto/auction.rs +++ b/crates/solvers/src/api/routes/solve/dto/auction.rs @@ -163,8 +163,7 @@ mod weighted_product_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -204,8 +203,7 @@ mod stable_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -305,8 +303,7 @@ mod gyro_e_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -370,8 +367,7 @@ mod gyro_2clp_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -417,8 +413,7 @@ mod gyro_3clp_pool { .ok_or("invalid scaling factor")?, ) .ok_or("invalid scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -457,8 +452,7 @@ mod reclamm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -518,8 +512,7 @@ mod stable_surge_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; @@ -561,8 +554,7 @@ mod quant_amm_pool { scale: conv::decimal_to_rational(&token.scaling_factor) .and_then(liquidity::ScalingFactor::new) .ok_or("invalid token scaling factor")?, - rate: conv::decimal_to_rational(&token.rate) - .ok_or("invalid token rate")?, + rate: conv::decimal_to_rational(&token.rate).ok_or("invalid token rate")?, }) }) .collect::, Error>>()?; diff --git a/crates/solvers/src/boundary/liquidity/gyro_2clp.rs b/crates/solvers/src/boundary/liquidity/gyro_2clp.rs index 0ba98313d8..b3c455b5c4 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_2clp.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_2clp.rs @@ -68,7 +68,8 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/gyro_3clp.rs b/crates/solvers/src/boundary/liquidity/gyro_3clp.rs index b3129efd88..7864e2ad7a 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_3clp.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_3clp.rs @@ -64,7 +64,8 @@ fn to_fixed_point(ratio: &crate::domain::eth::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: &crate::domain::eth::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/gyro_e.rs b/crates/solvers/src/boundary/liquidity/gyro_e.rs index 3e8ef354a4..bbe457678e 100644 --- a/crates/solvers/src/boundary/liquidity/gyro_e.rs +++ b/crates/solvers/src/boundary/liquidity/gyro_e.rs @@ -95,7 +95,8 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/quantamm.rs b/crates/solvers/src/boundary/liquidity/quantamm.rs index 0c20a380cf..f3655b3c2b 100644 --- a/crates/solvers/src/boundary/liquidity/quantamm.rs +++ b/crates/solvers/src/boundary/liquidity/quantamm.rs @@ -62,7 +62,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } fn to_u256(ratio: ð::Rational) -> Option { - // Rate is already in wei (18 decimals), so we just convert the rational directly. + // Rate is already in wei (18 decimals), so we just convert the rational + // directly. ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/reclamm.rs b/crates/solvers/src/boundary/liquidity/reclamm.rs index 636a3566e2..38afce8b75 100644 --- a/crates/solvers/src/boundary/liquidity/reclamm.rs +++ b/crates/solvers/src/boundary/liquidity/reclamm.rs @@ -59,6 +59,7 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } fn to_u256(ratio: ð::Rational) -> Option { - // Rate is already in wei (18 decimals), so we just convert the rational directly. + // Rate is already in wei (18 decimals), so we just convert the rational + // directly. ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/stable.rs b/crates/solvers/src/boundary/liquidity/stable.rs index 0258b4c57b..eeff6e776e 100644 --- a/crates/solvers/src/boundary/liquidity/stable.rs +++ b/crates/solvers/src/boundary/liquidity/stable.rs @@ -65,7 +65,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } diff --git a/crates/solvers/src/boundary/liquidity/weighted_product.rs b/crates/solvers/src/boundary/liquidity/weighted_product.rs index 2bb06cb95d..0205db1874 100644 --- a/crates/solvers/src/boundary/liquidity/weighted_product.rs +++ b/crates/solvers/src/boundary/liquidity/weighted_product.rs @@ -66,7 +66,8 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { } /// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational directly. +/// Note: Rate is already in wei (18 decimals), so we just convert the rational +/// directly. fn to_u256(ratio: ð::Rational) -> Option { ratio.numer().checked_div(*ratio.denom()) } From b8284c1564d31651198e0f64220322f54126ae48 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Thu, 13 Nov 2025 23:27:13 +0100 Subject: [PATCH 16/20] attempt to fix rate provider handling --- .../src/boundary/liquidity/gyro_2clp.rs | 8 ++++---- .../src/boundary/liquidity/gyro_3clp.rs | 8 ++++---- .../balancer-solver/src/boundary/liquidity/gyro_e.rs | 8 ++++---- .../src/boundary/liquidity/quantamm.rs | 8 ++++---- .../src/boundary/liquidity/reclamm.rs | 8 ++++---- .../balancer-solver/src/boundary/liquidity/stable.rs | 8 ++++---- .../src/boundary/liquidity/weighted_product.rs | 8 ++++---- .../balancer-solver/src/tests/cases/bal_liquidity.rs | 12 ++++++------ .../src/tests/cases/buy_order_rounding.rs | 6 +++--- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs index b3c455b5c4..15934893c5 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_2clp.rs @@ -67,9 +67,9 @@ fn to_signed_fixed_point(ratio: ð::SignedRational) -> Option { Some(SBfp::from_wei(scaled)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs index 7864e2ad7a..4a7877a2d7 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_3clp.rs @@ -63,9 +63,9 @@ fn to_fixed_point(ratio: &crate::domain::eth::Rational) -> Option { Some(Bfp::from_wei(wei)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: &crate::domain::eth::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = ethereum_types::U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs index 4634cab8fa..f2562d21dc 100644 --- a/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs +++ b/crates/balancer-solver/src/boundary/liquidity/gyro_e.rs @@ -76,11 +76,11 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } /// Converts a signed rational to a Balancer signed fixed point number. diff --git a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs index 9470b2800c..ca87b424cb 100644 --- a/crates/balancer-solver/src/boundary/liquidity/quantamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/quantamm.rs @@ -78,9 +78,9 @@ fn to_signed_i256(ratio: ð::SignedRational) -> Option { Some(wei_i256) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs index 2f8071cd01..d3992cdee4 100644 --- a/crates/balancer-solver/src/boundary/liquidity/reclamm.rs +++ b/crates/balancer-solver/src/boundary/liquidity/reclamm.rs @@ -58,9 +58,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/stable.rs b/crates/balancer-solver/src/boundary/liquidity/stable.rs index eeff6e776e..cbb808e310 100644 --- a/crates/balancer-solver/src/boundary/liquidity/stable.rs +++ b/crates/balancer-solver/src/boundary/liquidity/stable.rs @@ -64,9 +64,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs index 0205db1874..86bf43b0cf 100644 --- a/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs +++ b/crates/balancer-solver/src/boundary/liquidity/weighted_product.rs @@ -65,9 +65,9 @@ fn to_fixed_point(ratio: ð::Rational) -> Option { Some(Bfp::from_wei(wei)) } -/// Converts a rational to a U256. -/// Note: Rate is already in wei (18 decimals), so we just convert the rational -/// directly. +/// Converts a rational to a U256 rate in wei (18 decimals). +/// Rates are stored as Rationals and need to be scaled to 18 decimals. fn to_u256(ratio: ð::Rational) -> Option { - ratio.numer().checked_div(*ratio.denom()) + let base = U256::exp10(18); + ratio.numer().checked_mul(base)?.checked_div(*ratio.denom()) } diff --git a/crates/balancer-solver/src/tests/cases/bal_liquidity.rs b/crates/balancer-solver/src/tests/cases/bal_liquidity.rs index c3caa41de4..31db6fd511 100644 --- a/crates/balancer-solver/src/tests/cases/bal_liquidity.rs +++ b/crates/balancer-solver/src/tests/cases/bal_liquidity.rs @@ -385,17 +385,17 @@ async fn stable() { "0x6b175474e89094c44da98b954eedeac495271d0f": { "balance": "505781036390938593206504", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": { "balance": "554894862074", "scalingFactor": "1000000000000", - "rate": "1000000000000000000", + "rate": "1.0", }, "0xdac17f958d2ee523a2206206994597c13d831ec7": { "balance": "1585576741011", "scalingFactor": "1000000000000", - "rate": "1000000000000000000", + "rate": "1.0", }, }, "fee": "0.0001", @@ -564,17 +564,17 @@ async fn composable_stable_v4() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, }, "fee": "0.0001", diff --git a/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs b/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs index 0c7332dbf5..16c0a62048 100644 --- a/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs +++ b/crates/balancer-solver/src/tests/cases/buy_order_rounding.rs @@ -868,17 +868,17 @@ async fn balancer_stable() { "0x4b1e2c2762667331bc91648052f646d1b0d35984": { "balance": "126041615528606990697699", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, "0x5c78d05b8ecf97507d1cf70646082c54faa4da95": { "balance": "2596148429267369423681023550322451", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, "0xcb444e90d8198415266c6a2724b7900fb12fc56e": { "balance": "170162457652825667152980", "scalingFactor": "1", - "rate": "1000000000000000000", + "rate": "1.0", }, }, "fee": "0.0001", From ba0f4fdfb9cb2fcea2f511002186648dac4856ed Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 14 Nov 2025 10:04:17 +0100 Subject: [PATCH 17/20] adjusted v3 poolId usage --- crates/balancer-solver/openapi.yml | 2 - .../src/infra/solution_verifier.rs | 12 ++++-- crates/driver/src/infra/solver/dto/auction.rs | 40 +++++-------------- .../src/infra/api/routes/liquidity/mod.rs | 40 +++++-------------- .../src/infra/solver/dto/auction.rs | 40 +++++-------------- crates/solvers-dto/src/auction.rs | 21 ++++++---- crates/solvers/openapi.yml | 2 - 7 files changed, 56 insertions(+), 101 deletions(-) diff --git a/crates/balancer-solver/openapi.yml b/crates/balancer-solver/openapi.yml index 24025a7123..045e3226e8 100644 --- a/crates/balancer-solver/openapi.yml +++ b/crates/balancer-solver/openapi.yml @@ -518,7 +518,6 @@ components: - kind - tokens - fee - - balancer_pool_id properties: kind: type: string @@ -557,7 +556,6 @@ components: - tokens - amplificationParameter - fee - - balancer_pool_id properties: kind: type: string diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index b1c20ba055..f07c4b167f 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -125,9 +125,15 @@ impl SolutionVerifier { (None, None) }; - // Determine pool version ONLY by ID length (not by pool kind) - // Prefer balancerPoolId from liquidityDetails, fall back to pool_id - let pool_version = Self::detect_pool_version(balancer_pool_id_opt.unwrap_or(pool_id)); + // Determine pool version: + // - If balancerPoolId is None/null, it's a V3 pool (V3 pools don't have + // V2-style pool IDs) + // - If balancerPoolId exists, detect version by ID length + let pool_version = if balancer_pool_id_opt.is_none() { + PoolVersion::V3 + } else { + Self::detect_pool_version(balancer_pool_id_opt.unwrap_or(pool_id)) + }; // Quote using appropriate method with enhanced data let quoted_amount = match pool_version { diff --git a/crates/driver/src/infra/solver/dto/auction.rs b/crates/driver/src/infra/solver/dto/auction.rs index 600fde9352..1d2fe5a295 100644 --- a/crates/driver/src/infra/solver/dto/auction.rs +++ b/crates/driver/src/infra/solver/dto/auction.rs @@ -230,7 +230,7 @@ pub fn new( solvers_dto::auction::Liquidity::Stable(solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -259,10 +259,7 @@ pub fn new( solvers_dto::auction::Liquidity::Stable(solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -292,7 +289,7 @@ pub fn new( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -326,10 +323,7 @@ pub fn new( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -366,7 +360,7 @@ pub fn new( solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -411,7 +405,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -446,7 +440,7 @@ pub fn new( solvers_dto::auction::Gyro3CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -478,10 +472,7 @@ pub fn new( solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -528,10 +519,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -616,10 +604,7 @@ pub fn new( solvers_dto::auction::QuantAmmPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -724,10 +709,7 @@ pub fn new( solvers_dto::auction::StableSurgePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves diff --git a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs index a204a06d35..c3d2a07108 100644 --- a/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs +++ b/crates/liquidity-driver/src/infra/api/routes/liquidity/mod.rs @@ -132,7 +132,7 @@ fn convert_domain_to_dto( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -167,10 +167,7 @@ fn convert_domain_to_dto( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -202,7 +199,7 @@ fn convert_domain_to_dto( solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -230,10 +227,7 @@ fn convert_domain_to_dto( solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -261,10 +255,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Liquidity::StableSurge(solvers_dto::auction::StableSurgePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -298,7 +289,7 @@ fn convert_domain_to_dto( Box::new(solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -341,7 +332,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -372,7 +363,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro3CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -402,10 +393,7 @@ fn convert_domain_to_dto( Box::new(solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -448,10 +436,7 @@ fn convert_domain_to_dto( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves @@ -521,10 +506,7 @@ fn convert_domain_to_dto( solvers_dto::auction::QuantAmmPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.0.into(), tokens: pool .reserves diff --git a/crates/liquidity-driver/src/infra/solver/dto/auction.rs b/crates/liquidity-driver/src/infra/solver/dto/auction.rs index 600fde9352..1d2fe5a295 100644 --- a/crates/liquidity-driver/src/infra/solver/dto/auction.rs +++ b/crates/liquidity-driver/src/infra/solver/dto/auction.rs @@ -230,7 +230,7 @@ pub fn new( solvers_dto::auction::Liquidity::Stable(solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -259,10 +259,7 @@ pub fn new( solvers_dto::auction::Liquidity::Stable(solvers_dto::auction::StablePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -292,7 +289,7 @@ pub fn new( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -326,10 +323,7 @@ pub fn new( solvers_dto::auction::WeightedProductPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -366,7 +360,7 @@ pub fn new( solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -411,7 +405,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -446,7 +440,7 @@ pub fn new( solvers_dto::auction::Gyro3CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: pool.id.into(), + balancer_pool_id: Some(pool.id.into()), gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -478,10 +472,7 @@ pub fn new( solvers_dto::auction::GyroEPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -528,10 +519,7 @@ pub fn new( solvers_dto::auction::Gyro2CLPPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -616,10 +604,7 @@ pub fn new( solvers_dto::auction::QuantAmmPool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves @@ -724,10 +709,7 @@ pub fn new( solvers_dto::auction::StableSurgePool { id: liquidity.id.0.to_string(), address: pool.id.address().into(), - balancer_pool_id: { - let pool_id_h160: eth::H160 = pool.id.into(); - pool_id_h160.into() - }, + balancer_pool_id: None, // V3 pools don't have V2-style pool IDs gas_estimate: liquidity.gas.into(), tokens: pool .reserves diff --git a/crates/solvers-dto/src/auction.rs b/crates/solvers-dto/src/auction.rs index fc3150d258..054c702173 100644 --- a/crates/solvers-dto/src/auction.rs +++ b/crates/solvers-dto/src/auction.rs @@ -203,7 +203,8 @@ pub struct ConstantProductReserve { pub struct WeightedProductPool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -235,7 +236,8 @@ pub enum WeightedProductVersion { pub struct StablePool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -259,7 +261,8 @@ pub struct StableReserve { pub struct StableSurgePool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -328,7 +331,8 @@ pub struct Erc4626Edge { pub struct GyroEPool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -373,7 +377,8 @@ pub enum GyroEVersion { pub struct Gyro2CLPPool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -390,7 +395,8 @@ pub struct Gyro2CLPPool { pub struct Gyro3CLPPool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, @@ -469,7 +475,8 @@ pub struct ReClammReserve { pub struct QuantAmmPool { pub id: String, pub address: H160, - pub balancer_pool_id: H256, + #[serde(skip_serializing_if = "Option::is_none")] + pub balancer_pool_id: Option, #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, pub tokens: HashMap, diff --git a/crates/solvers/openapi.yml b/crates/solvers/openapi.yml index 24025a7123..045e3226e8 100644 --- a/crates/solvers/openapi.yml +++ b/crates/solvers/openapi.yml @@ -518,7 +518,6 @@ components: - kind - tokens - fee - - balancer_pool_id properties: kind: type: string @@ -557,7 +556,6 @@ components: - tokens - amplificationParameter - fee - - balancer_pool_id properties: kind: type: string From c67c677bd67e5b698363e1973fec5720422d3d81 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:43:57 +0100 Subject: [PATCH 18/20] attempt to fix v3 quoting --- crates/balancer-solver/src/infra/solution_verifier.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index f07c4b167f..12c751c32d 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -324,7 +324,7 @@ impl SolutionVerifier { // Call querySwapExactIn let query = self.batch_router.methods().query_swap_exact_in( vec![path.clone()], - H160::zero(), // sender (not needed for query) + self.batch_router.address(), // sender (required for pools with hooks) Bytes(vec![]), // empty userData ); @@ -347,7 +347,7 @@ impl SolutionVerifier { "exactAmountIn": input_amount.to_string(), "minAmountOut": "0" }], - "sender": "0x0000000000000000000000000000000000000000", + "sender": format!("{:?}", self.batch_router.address()), "userData": "0x" }); From 37bbfc99971de16b862e4da1f62e92ae3c7bb62b Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:15:55 +0100 Subject: [PATCH 19/20] added debugging for v3 batch router call --- .../src/infra/solution_verifier.rs | 73 ++++++++++++++++--- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 12c751c32d..74f9ff2113 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -36,7 +36,7 @@ pub struct ContractCallDetails { pub decoded_params: serde_json::Value, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq)] pub enum PoolVersion { V2, V3, @@ -171,11 +171,24 @@ impl SolutionVerifier { (Some(quote), diff, None, Some(call_details)) } Err(e) => { - // Try to extract contract call details from error context if available - // For now, we don't have call details on error (they're created inside the - // functions) This is acceptable as errors typically happen - // during call execution - (None, None, Some(e.to_string()), None) + // For V3 calls, we still want to save the call details even on error + // so the user can see what was actually attempted + let error_call_details = if pool_version == PoolVersion::V3 { + if let Some(address) = pool_address_opt { + Some(create_v3_call_details( + &self.batch_router, + address, + &input_token, + &output_token, + input_amount, + )) + } else { + None + } + } else { + None + }; + (None, None, Some(e.to_string()), error_call_details) } }; @@ -359,14 +372,52 @@ impl SolutionVerifier { decoded_params, }; - let (path_amounts_out, _tokens_out, _amounts_out) = query.call().await?; + let result = query.call().await; - // Get the first path's output amount - if path_amounts_out.is_empty() { - return Err("No output amounts returned from querySwapExactIn".into()); + match result { + Ok((path_amounts_out, _tokens_out, _amounts_out)) => { + // Get the first path's output amount + if path_amounts_out.is_empty() { + return Err("No output amounts returned from querySwapExactIn".into()); + } + Ok((path_amounts_out[0].to_string(), call_details)) + } + Err(e) => { + // Return the error - call details will be saved separately in the JSON + Err(Box::new(e)) + } } + } +} - Ok((path_amounts_out[0].to_string(), call_details)) +fn create_v3_call_details( + batch_router: &BalancerV3BatchRouter, + pool_address: &str, + input_token: &Address, + output_token: &Address, + input_amount: U256, +) -> ContractCallDetails { + let decoded_params = serde_json::json!({ + "paths": [{ + "tokenIn": format!("{:?}", H160::from(input_token.0)), + "steps": [{ + "pool": pool_address, + "tokenOut": format!("{:?}", H160::from(output_token.0)), + "isBuffer": false + }], + "exactAmountIn": input_amount.to_string(), + "minAmountOut": "0" + }], + "sender": format!("{:?}", batch_router.address()), + "userData": "0x" + }); + + ContractCallDetails { + contract_address: format!("{:?}", batch_router.address()), + contract_name: "BalancerV3BatchRouter".to_string(), + function_name: "querySwapExactIn".to_string(), + calldata: "(error - call details captured without execution)".to_string(), + decoded_params, } } From cbf165eab2c8600bb7e5f6c38a8662af1e6de567 Mon Sep 17 00:00:00 2001 From: Felix <95569029+xdecentralix@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:54:20 +0100 Subject: [PATCH 20/20] attempt to fix static call --- crates/balancer-solver/src/infra/solution_verifier.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/balancer-solver/src/infra/solution_verifier.rs b/crates/balancer-solver/src/infra/solution_verifier.rs index 74f9ff2113..50efca2dd5 100644 --- a/crates/balancer-solver/src/infra/solution_verifier.rs +++ b/crates/balancer-solver/src/infra/solution_verifier.rs @@ -1,6 +1,6 @@ use { contracts::{BalancerV2Vault, BalancerV3BatchRouter}, - ethcontract::{Address, Bytes, H160, U256}, + ethcontract::{Account, Address, Bytes, H160, U256}, serde::{Deserialize, Serialize}, }; @@ -335,11 +335,13 @@ impl SolutionVerifier { ); // Call querySwapExactIn + // IMPORTANT: Must set .from() to make this a proper staticcall let query = self.batch_router.methods().query_swap_exact_in( vec![path.clone()], self.batch_router.address(), // sender (required for pools with hooks) Bytes(vec![]), // empty userData - ); + ) + .from(Account::Local(H160::zero(), None)); // Set from address for the eth_call // Capture contract call details for debugging let calldata = query @@ -361,7 +363,8 @@ impl SolutionVerifier { "minAmountOut": "0" }], "sender": format!("{:?}", self.batch_router.address()), - "userData": "0x" + "userData": "0x", + "from": "0x0000000000000000000000000000000000000000" }); let call_details = ContractCallDetails {