use std::time::{Duration, SystemTime, UNIX_EPOCH};
use num_bigint::BigUint;
use tycho_simulation::tycho_common::models::Address;
use super::provider::PriceProviderError;
use crate::feed::market_data::SharedMarketDataRef;
pub const STALENESS_THRESHOLD: Duration = Duration::from_secs(30);
pub async fn resolve_token(
market_data: &SharedMarketDataRef,
address: &Address,
) -> Result<(String, u32), PriceProviderError> {
let data = market_data.read().await;
let token = data
.get_token(address)
.ok_or_else(|| PriceProviderError::TokenNotFound { address: address.to_string() })?;
Ok((token.symbol.clone(), token.decimals))
}
pub fn check_staleness(ticker_ts: u64) -> Result<(), PriceProviderError> {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let age_ms = now_ms.saturating_sub(ticker_ts);
if age_ms > STALENESS_THRESHOLD.as_millis() as u64 {
return Err(PriceProviderError::StaleData { age_ms });
}
Ok(())
}
pub fn expected_out_from_price(
amount_in: &BigUint,
price: f64,
decimals_in: u32,
decimals_out: u32,
) -> BigUint {
const PRECISION: f64 = 1_000_000_000_000_000_000.0;
let price_scaled = (price * PRECISION) as u128;
if price_scaled == 0 {
return BigUint::ZERO;
}
let numerator =
amount_in * BigUint::from(price_scaled) * BigUint::from(10u64).pow(decimals_out);
let denominator = BigUint::from(10u64).pow(decimals_in) * BigUint::from(PRECISION as u128);
numerator / denominator
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expected_out_eth_to_usdc() {
let amount_in = BigUint::from(10u64).pow(18);
let result = expected_out_from_price(&amount_in, 2000.0, 18, 6);
assert_eq!(result, BigUint::from(2_000_000_000u64));
}
#[test]
fn expected_out_usdc_to_eth() {
let amount_in = BigUint::from(2_000_000_000u64);
let result = expected_out_from_price(&amount_in, 0.0005, 6, 18);
let one_eth = BigUint::from(10u64).pow(18);
let diff = if result > one_eth { &result - &one_eth } else { &one_eth - &result };
let tolerance = &one_eth / BigUint::from(1000u64); assert!(diff < tolerance, "result={result}, expected ~{one_eth}");
}
#[test]
fn expected_out_zero_price() {
let amount_in = BigUint::from(10u64).pow(18);
let result = expected_out_from_price(&amount_in, 0.0, 18, 6);
assert_eq!(result, BigUint::ZERO);
}
#[test]
fn expected_out_micro_price() {
let amount_in = BigUint::from(10u64).pow(27); let result = expected_out_from_price(&amount_in, 8e-11, 18, 8);
assert_eq!(result, BigUint::from(8_000_000u64));
}
#[test]
fn staleness_detection() {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
assert!(check_staleness(now_ms - 60_000).is_err());
assert!(check_staleness(now_ms - 1_000).is_ok());
}
}