use num_bigint::BigUint;
use num_traits::ToPrimitive;
use crate::OrderQuote;
pub(crate) fn deviation_bps(quote: &OrderQuote, simulated_amount_out: &BigUint) -> Option<f64> {
let fees = quote.fee_breakdown()?;
let quoted = fees.min_amount_received() + fees.max_slippage();
if quoted == BigUint::ZERO {
return None;
}
let quoted = quoted.to_f64()?;
let simulated = simulated_amount_out.to_f64()?;
let deviation = (simulated - quoted) / quoted * 10_000.0;
deviation
.is_finite()
.then_some(deviation)
}
#[cfg(test)]
pub(crate) mod fixtures {
use num_bigint::BigUint;
use crate::OrderQuote;
pub(super) const FIXTURE_ROUTER_FEE: u64 = 7_000;
pub(super) const FIXTURE_CLIENT_FEE: u64 = 3_000;
pub(crate) fn quote_with_fees(after_fees: u64) -> OrderQuote {
let slippage = after_fees / 100;
let mut quote = quote_without_fees();
quote.set_amount_out(BigUint::from(after_fees + FIXTURE_ROUTER_FEE + FIXTURE_CLIENT_FEE));
quote.set_fee_breakdown(crate::FeeBreakdown::new(
BigUint::from(FIXTURE_ROUTER_FEE),
BigUint::from(FIXTURE_CLIENT_FEE),
BigUint::from(slippage),
BigUint::from(after_fees - slippage),
));
quote
}
pub(super) fn quote_without_fees() -> OrderQuote {
OrderQuote::new(
"test-order".to_string(),
crate::QuoteStatus::Success,
BigUint::from(1_000u64),
BigUint::from(1_000_000u64),
BigUint::from(50_000u64),
BigUint::from(1_000_000u64),
crate::BlockInfo::new(1, "0x1".to_string(), 1),
"test_algorithm".to_string(),
tycho_simulation::tycho_common::Bytes::from(vec![0xAA; 20]),
tycho_simulation::tycho_common::Bytes::from(vec![0xAA; 20]),
"1".to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::{fixtures::*, *};
#[test]
fn test_deviation_bps_simulated_below_quote() {
let deviation = deviation_bps("e_with_fees(1_000_000), &BigUint::from(999_000u64))
.expect("a quote carrying fees has a baseline");
assert!((deviation - -10.0).abs() < 1e-9, "got {deviation}");
}
#[test]
fn test_deviation_bps_simulated_above_quote() {
let deviation = deviation_bps("e_with_fees(1_000_000), &BigUint::from(1_001_000u64))
.expect("a quote carrying fees has a baseline");
assert!((deviation - 10.0).abs() < 1e-9, "got {deviation}");
}
#[test]
fn test_deviation_bps_excludes_the_router_fee_from_the_baseline() {
let quote = quote_with_fees(1_000_000);
let after_fees = quote
.fee_breakdown()
.expect("the quote carries fees")
.min_amount_received() +
quote
.fee_breakdown()
.expect("the quote carries fees")
.max_slippage();
let deviation =
deviation_bps("e, &after_fees).expect("a quote carrying fees has a baseline");
assert!(
deviation.abs() < 1e-9,
"a simulation matching the post-fee quote is not a deviation"
);
assert!(after_fees < *quote.amount_out(), "the baseline sits below the raw swap output");
}
#[test]
fn test_deviation_bps_without_a_fee_breakdown() {
assert_eq!(deviation_bps("e_without_fees(), &BigUint::from(1_000u64)), None);
}
#[test]
fn test_deviation_bps_zero_quoted_amount() {
let mut quote = quote_with_fees(1_000_000);
quote.set_fee_breakdown(crate::FeeBreakdown::new(
BigUint::ZERO,
BigUint::ZERO,
BigUint::ZERO,
BigUint::ZERO,
));
assert_eq!(deviation_bps("e, &BigUint::from(1_000u64)), None);
}
#[test]
fn test_deviation_bps_amount_beyond_f64() {
let huge = BigUint::from(1u8) << 2_000;
assert_eq!(deviation_bps("e_with_fees(1_000_000), &huge), None);
}
}