use std::{
cmp::Ordering,
collections::{BTreeMap, HashMap, HashSet, hash_map::Entry},
};
use serde::Serialize;
use solana_address::Address;
use super::{
prices::Prices,
record::{LegRecord, SwapMode},
};
use crate::{mints, route_plan::RoutePlan};
pub const VENUE_DUST_USD: f64 = 1e4;
pub const OUTLIER_BPS: f64 = 2000.0;
#[derive(Default, Debug)]
pub struct LoadStats {
pub dropped_multi_row: usize,
}
pub fn collect(rows: Vec<LegRecord>) -> (Vec<LegRecord>, LoadStats) {
let mut kept: Vec<LegRecord> = Vec::new();
let mut first_row: HashMap<String, usize> = HashMap::new();
let mut dropped: HashSet<usize> = HashSet::new();
for row in rows {
if row.swap_mode == SwapMode::ExactOut && !mints::is_major(&row.input_mint) {
continue;
}
match first_row.entry(row.original_signature.clone()) {
Entry::Vacant(slot) => {
slot.insert(kept.len());
kept.push(row);
}
Entry::Occupied(slot) => {
dropped.insert(*slot.get());
}
}
}
let stats = LoadStats {
dropped_multi_row: dropped.len(),
};
(
kept.into_iter()
.enumerate()
.filter(|(index, _)| !dropped.contains(index))
.map(|(_, row)| row)
.collect(),
stats,
)
}
fn plan_input(plan: &RoutePlan, mint: &Address) -> Option<i64> {
let mint = mint.to_string();
let mut total = 0i64;
for hop in plan.hops() {
if hop.input_mint() != Some(mint.as_str()) {
continue;
}
total += match hop.swap_info.in_amount.as_deref() {
None => 0,
Some(amount) => amount
.parse::<i64>()
.ok()
.or_else(|| amount.parse::<f64>().ok().map(|float| float as i64))?,
};
}
(total > 0).then_some(total)
}
fn venue_weights(plan: &RoutePlan) -> Vec<(Option<&str>, f64)> {
plan.hops()
.iter()
.map(|hop| (hop.label(), hop.percent as f64 / 100.0))
.collect()
}
pub struct Measurement {
pub outcome: Ordering,
pub l1_usd: f64,
pub our_usd: f64,
}
pub fn measure(record: &LegRecord, prices: &Prices) -> Option<Measurement> {
match record.swap_mode {
SwapMode::ExactOut => measure_exact_out(record, prices),
SwapMode::ExactIn => measure_exact_in(record, prices),
}
}
fn measure_exact_out(record: &LegRecord, prices: &Prices) -> Option<Measurement> {
let mint = &record.input_mint;
let ours = plan_input(record.route_plan.as_ref()?, mint)?;
let l1 = plan_input(record.original_route_plan.as_ref()?, mint)?;
let l1_usd = prices.usd(mint, l1 as f64)?;
let our_usd = prices.usd(mint, (2 * l1 - ours) as f64)?;
if l1_usd <= 0.0 || (our_usd / l1_usd - 1.0).abs() * 1e4 > OUTLIER_BPS {
return None;
}
Some(Measurement {
outcome: l1.cmp(&ours),
l1_usd,
our_usd,
})
}
fn measure_exact_in(record: &LegRecord, prices: &Prices) -> Option<Measurement> {
let ours = record.realized_output_amount.unwrap_or(0) as i64;
let l1 = record.original_realized_output_amount.unwrap_or(0) as i64;
let bps = (ours as f64 / l1 as f64 - 1.0) * 1e4;
let scaled = |usd: f64| usd * (1.0 + bps / 1e4);
let (l1_usd, our_usd) = if mints::is_major(&record.output_mint) {
(
prices.usd(&record.output_mint, l1 as f64),
prices.usd(&record.output_mint, ours as f64),
)
} else if mints::is_major(&record.input_mint) {
let spend = prices.usd(&record.input_mint, record.amount as f64);
(spend, spend.map(scaled))
} else if let Some(l1_usd) = prices.usd(&record.output_mint, l1 as f64) {
(Some(l1_usd), prices.usd(&record.output_mint, ours as f64))
} else {
let spend = prices.usd(&record.input_mint, record.amount as f64)?;
(Some(spend), Some(scaled(spend)))
};
let (l1_usd, our_usd) = (l1_usd?, our_usd?);
if l1_usd <= 0.0 || bps.abs() > OUTLIER_BPS {
return None;
}
Some(Measurement {
outcome: ours.cmp(&l1),
l1_usd,
our_usd,
})
}
#[derive(Default, Clone, Serialize)]
pub struct SideTotals {
pub vol: f64,
pub net: f64,
}
#[derive(Default, Clone, Serialize)]
pub struct VenueTotals {
pub us: f64,
pub l1: f64,
pub net: f64,
}
#[derive(Default, Clone, Serialize)]
pub struct RangeReport {
pub n: u64,
pub better: u64,
pub same: u64,
pub worse: u64,
pub vol: f64,
pub net: f64,
pub vol_better: f64,
pub vol_same: f64,
pub vol_worse: f64,
pub bps: Vec<f64>,
pub majors: SideTotals,
pub exotic: SideTotals,
pub venues: BTreeMap<String, VenueTotals>,
}
impl RangeReport {
pub fn from_records(records: &[LegRecord], prices: &Prices) -> Self {
let mut report = Self::default();
for record in records {
if let Some(measurement) = measure(record, prices) {
report.record(record, &measurement);
}
}
report
}
fn record(&mut self, leg: &LegRecord, measurement: &Measurement) {
let delta = measurement.our_usd - measurement.l1_usd;
self.n += 1;
let (count, vol) = match measurement.outcome {
Ordering::Greater => (&mut self.better, &mut self.vol_better),
Ordering::Equal => (&mut self.same, &mut self.vol_same),
Ordering::Less => (&mut self.worse, &mut self.vol_worse),
};
*count += 1;
*vol += measurement.l1_usd;
self.vol += measurement.l1_usd;
self.net += delta;
if measurement.l1_usd > 0.0 {
self.bps.push(delta / measurement.l1_usd * 1e4);
}
let side = if mints::trades_only_majors(&leg.input_mint, &leg.output_mint) {
&mut self.majors
} else {
&mut self.exotic
};
side.vol += measurement.l1_usd;
side.net += delta;
self.attribute_venues(leg, measurement.l1_usd, delta);
}
fn attribute_venues(&mut self, leg: &LegRecord, l1_usd: f64, delta: f64) {
for (ours, plan) in [(true, &leg.route_plan), (false, &leg.original_route_plan)] {
let Some(plan) = plan else {
continue;
};
let weights = venue_weights(plan);
let total: f64 = weights.iter().map(|(_, weight)| weight).sum();
let total = if total == 0.0 { 1.0 } else { total };
for (label, weight) in weights {
let label = label.unwrap_or("(unlabeled)");
let entry = self.venues.entry(label.to_string()).or_default();
let share = weight / total;
if ours {
entry.us += l1_usd * share;
entry.net += delta * share;
} else {
entry.l1 += l1_usd * share;
}
}
}
}
pub fn merge(&mut self, other: &Self) {
self.n += other.n;
self.better += other.better;
self.same += other.same;
self.worse += other.worse;
self.vol += other.vol;
self.net += other.net;
self.vol_better += other.vol_better;
self.vol_same += other.vol_same;
self.vol_worse += other.vol_worse;
self.bps.extend_from_slice(&other.bps);
self.majors.vol += other.majors.vol;
self.majors.net += other.majors.net;
self.exotic.vol += other.exotic.vol;
self.exotic.net += other.exotic.net;
for (venue, totals) in &other.venues {
let entry = self.venues.entry(venue.clone()).or_default();
entry.us += totals.us;
entry.l1 += totals.l1;
entry.net += totals.net;
}
}
pub fn vw_bps(&self) -> f64 {
if self.vol == 0.0 {
0.0
} else {
self.net / self.vol * 1e4
}
}
pub fn render(&self, name: &str) -> String {
if self.vol == 0.0 {
return format!("### {name}: no measurable legs\n");
}
let mut lines = self.summary_lines(name);
lines.push(String::new());
lines.extend(self.venue_table());
lines.push(String::new());
lines.join("\n")
}
fn summary_lines(&self, name: &str) -> Vec<String> {
let mut bps = self.bps.clone();
let (q1_at, q3_at) = (bps.len() / 4, 3 * bps.len() / 4);
let med = median(&mut bps);
let mut lines = vec![
format!("### {name}"),
format!(
"vw vs L1: **{:+.2} bps** (net {}$ on ${})",
self.vw_bps(),
commas_signed(self.net),
commas(self.vol)
),
format!(
"b/s/w: {} / {} / {} (by volume {:.1} / {:.1} / {:.1}%)",
commas_u(self.better),
commas_u(self.same),
commas_u(self.worse),
self.vol_better / self.vol * 100.0,
self.vol_same / self.vol * 100.0,
self.vol_worse / self.vol * 100.0
),
format!(
"per-leg bps: median {med:+.2}, IQR [{:+.2}, {:+.2}]",
bps[q1_at], bps[q3_at]
),
];
for (label, side) in [
("majors<->majors", &self.majors),
("exotic-involved", &self.exotic),
] {
if side.vol > 0.0 {
lines.push(format!(
"{label}: {:+.2} bps (net {}$ on ${})",
side.net / side.vol * 1e4,
commas_signed(side.net),
commas(side.vol)
));
}
}
lines
}
fn venue_table(&self) -> Vec<String> {
let mut lines = vec![
"| Venue | our share | L1 share | vw bps (our legs) |".to_string(),
"|---|---|---|---|".to_string(),
];
let total_us: f64 = self.venues.values().map(|e| e.us).sum::<f64>().max(1.0);
let total_l1: f64 = self.venues.values().map(|e| e.l1).sum::<f64>().max(1.0);
let mut ranked: Vec<_> = self.venues.iter().collect();
ranked.sort_by(|a, b| b.1.us.total_cmp(&a.1.us));
for (venue, e) in ranked {
if e.us < VENUE_DUST_USD && e.l1 < VENUE_DUST_USD {
continue;
}
let vw = if e.us == 0.0 { 0.0 } else { e.net / e.us * 1e4 };
lines.push(format!(
"| {venue} | {:.1}% | {:.1}% | {vw:+.2} |",
e.us / total_us * 100.0,
e.l1 / total_l1 * 100.0
));
}
lines
}
}
fn median(values: &mut [f64]) -> f64 {
values.sort_by(|a, b| a.total_cmp(b));
let n = values.len();
match n % 2 {
1 => values[n / 2],
_ => (values[n / 2 - 1] + values[n / 2]) / 2.0,
}
}
fn commas_u(n: u64) -> String {
let s = n.to_string();
let mut out = String::new();
for (i, c) in s.chars().enumerate() {
if i > 0 && (s.len() - i).is_multiple_of(3) {
out.push(',');
}
out.push(c);
}
out
}
fn commas(x: f64) -> String {
commas_u(x.round().abs() as u64)
}
fn commas_signed(x: f64) -> String {
format!("{}{}", if x < 0.0 { "-" } else { "+" }, commas(x))
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::{
reroute_report::{
Samples,
testing::{hop, plan, record_of},
},
route_plan::RouteHop,
};
#[rstest]
#[case::odd(&[3.0, 1.0, 2.0], 2.0)]
#[case::even(&[4.0, 1.0, 2.0, 3.0], 2.5)]
fn median_sorts_then_splits(#[case] values: &[f64], #[case] expected: f64) {
assert_eq!(median(&mut values.to_vec()), expected);
}
#[rstest]
#[case::zero(0, "0")]
#[case::under_a_group(999, "999")]
#[case::first_group(1_000, "1,000")]
#[case::many_groups(1_234_567, "1,234,567")]
fn commas_group_by_thousands(#[case] n: u64, #[case] expected: &str) {
assert_eq!(commas_u(n), expected);
}
#[rstest]
#[case::rounds_up("+1,235", 1_234.6)]
#[case::negative("-1,235", -1_234.6)]
fn signed_commas_carry_the_sign(#[case] expected: &str, #[case] x: f64) {
assert_eq!(commas_signed(x), expected);
}
fn hop_of(input_mint: &Address, in_amount: u64) -> RouteHop {
hop(
None,
"Venue",
&input_mint.to_string(),
&mints::USDT.to_string(),
in_amount,
)
}
fn amount_hop(in_amount: Option<&str>) -> RouteHop {
let mut hop = hop_of(&mints::USDC, 0);
hop.swap_info.in_amount = in_amount.map(str::to_string);
hop
}
#[rstest]
#[case::integer_amount(Some("1000"), Some(1_000))]
#[case::float_amount_truncates(Some("1000.9"), Some(1_000))]
#[case::float_string_truncates(Some("1.5e3"), Some(1_500))]
#[case::unparseable_voids_the_plan(Some("abc"), None)]
#[case::an_absent_amount_alone_sums_to_nothing(None, None)]
fn plan_input_reads_integer_and_float_amounts(
#[case] amount: Option<&str>,
#[case] expected: Option<i64>,
) {
let plan = plan(vec![amount_hop(amount)]).expect("a plan");
assert_eq!(plan_input(&plan, &mints::USDC), expected);
}
#[test]
fn plan_input_sums_only_the_asked_mints_hops() {
let plan = plan(vec![
hop_of(&mints::USDC, 300),
hop_of(&Address::default(), 999),
hop_of(&mints::USDC, 700),
])
.expect("a plan");
assert_eq!(plan_input(&plan, &mints::USDC), Some(1_000));
let empty: RoutePlan = serde_json::from_str("[]").expect("an empty plan");
assert_eq!(plan_input(&empty, &mints::USDC), None);
}
#[test]
fn an_absent_amount_contributes_zero_without_voiding() {
let plan = plan(vec![amount_hop(None), hop_of(&mints::USDC, 700)]).expect("a plan");
assert_eq!(plan_input(&plan, &mints::USDC), Some(700));
}
fn prices() -> Prices {
let mut samples = Samples::default();
for n in 1..=3u64 {
samples.push(&LegRecord {
original_realized_output_amount: Some(n * 100_000_000),
..record_of(mints::SOL, mints::USDC, n * 1_000_000_000, None, None, None)
});
}
samples.prices().expect("anchored")
}
fn stable_record(mode: SwapMode, ours: u64, l1: u64) -> LegRecord {
LegRecord {
swap_mode: mode,
realized_output_amount: Some(ours),
original_realized_output_amount: Some(l1),
route_plan: plan(vec![hop_of(&mints::USDC, ours)]),
original_route_plan: plan(vec![hop_of(&mints::USDC, l1)]),
..record_of(mints::USDC, mints::USDT, 1_000_000, None, None, None)
}
}
#[test]
fn exact_out_credits_the_input_saved() {
let m = measure(
&stable_record(SwapMode::ExactOut, 999_000, 1_000_000),
&prices(),
)
.expect("measurable");
assert_eq!(m.outcome, Ordering::Greater);
assert!((m.l1_usd - 1.0).abs() < 1e-9);
assert!((m.our_usd - 1.001).abs() < 1e-9);
}
#[rstest]
#[case::win(1_010_000, 1_000_000, Some(Ordering::Greater))]
#[case::tie(1_000_000, 1_000_000, Some(Ordering::Equal))]
#[case::exactly_at_the_outlier_bound_stays(1_200_000, 1_000_000, Some(Ordering::Greater))]
#[case::past_the_outlier_bound_drops(1_210_000, 1_000_000, None)]
fn exact_in_measures_major_output_with_the_outlier_gate(
#[case] ours: u64,
#[case] l1: u64,
#[case] expected: Option<Ordering>,
) {
let m = measure(&stable_record(SwapMode::ExactIn, ours, l1), &prices());
assert_eq!(m.map(|m| m.outcome), expected);
}
#[test]
fn collect_drops_multi_row_signatures_wholesale() {
let mut a = stable_record(SwapMode::ExactIn, 1_000_000, 999_000);
a.original_signature = "sigA".to_string();
let mut a2 = a.clone();
a2.amount = 2_000_000;
let mut b = stable_record(SwapMode::ExactIn, 1_000_000, 999_000);
b.original_signature = "sigB".to_string();
let (kept, stats) = collect(vec![a, a2, b]);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].original_signature, "sigB");
assert_eq!(stats.dropped_multi_row, 1);
}
#[test]
fn collect_skips_exact_out_legs_it_cannot_price() {
let exotic = LegRecord {
input_mint: Address::default(),
..stable_record(SwapMode::ExactOut, 1_000_000, 999_000)
};
let major = stable_record(SwapMode::ExactOut, 1_000_000, 999_000);
let (kept, stats) = collect(vec![exotic, major]);
assert_eq!(kept.len(), 1);
assert_eq!(kept[0].input_mint, mints::USDC);
assert_eq!(stats.dropped_multi_row, 0, "a skipped row is not a pairing");
}
}