use anyhow::Result;
use swap_kit_types::{SimulateRequest, SimulateResponse};
pub async fn simulate(req: &SimulateRequest) -> Result<SimulateResponse> {
let from_amount: u128 = req
.from_amount
.parse()
.map_err(|_| anyhow::anyhow!("Invalid from_amount: must be a positive integer within u128 bounds"))?;
let amount_out: u128 = req
.amount_out
.parse()
.map_err(|_| anyhow::anyhow!("Invalid amount_out: must be a positive integer within u128 bounds"))?;
let slippage_bps: u64 = req.slippage_bps as u64;
let trade_size_eth = from_amount as f64 / 1e18;
let is_mainnet = req.chain_id == 1;
let sandwich_risk = classify_risk(trade_size_eth, slippage_bps, is_mainnet);
let mev_fraction = (slippage_bps * 70) / 10000; let estimated_mev = (amount_out as u128)
.checked_mul(mev_fraction as u128)
.unwrap_or(u128::MAX) / 10000;
let recommended_slippage = if sandwich_risk == "high" {
std::cmp::min(slippage_bps as u32, 30)
} else if sandwich_risk == "medium" {
std::cmp::min(slippage_bps as u32, 50)
} else {
slippage_bps as u32
};
Ok(SimulateResponse {
sandwich_risk: sandwich_risk.to_string(),
estimated_mev_wei: estimated_mev.to_string(),
recommended_slippage_bps: recommended_slippage,
detected_bots: vec![], })
}
pub fn safe_default() -> SimulateResponse {
SimulateResponse {
sandwich_risk: "low".to_string(),
estimated_mev_wei: "0".to_string(),
recommended_slippage_bps: 50,
detected_bots: vec![],
}
}
fn classify_risk(trade_size_eth: f64, slippage_bps: u64, is_mainnet: bool) -> &'static str {
let risk_multiplier = if is_mainnet { 1.0 } else { 0.5 };
let risk_score = trade_size_eth * (slippage_bps as f64) * risk_multiplier;
if risk_score > 5000.0 {
"high"
} else if risk_score > 500.0 {
"medium"
} else if risk_score > 50.0 {
"low"
} else {
"none"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_simulate_low_risk() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "100000000000000000".to_string(), chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "200000000".to_string(), slippage_bps: 50,
};
let result = simulate(&req).await.unwrap();
assert!(result.sandwich_risk == "none" || result.sandwich_risk == "low");
}
#[tokio::test]
async fn test_simulate_high_risk() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "100000000000000000000".to_string(), chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "200000000000".to_string(), slippage_bps: 200,
};
let result = simulate(&req).await.unwrap();
assert_eq!(result.sandwich_risk, "high");
assert!(result.recommended_slippage_bps <= 30);
}
#[test]
fn test_safe_default() {
let result = safe_default();
assert_eq!(result.sandwich_risk, "low");
assert_eq!(result.estimated_mev_wei, "0");
}
#[tokio::test]
async fn test_simulate_negative_amount() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "-100000000000000000".to_string(), chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "200000000".to_string(),
slippage_bps: 50,
};
let result = simulate(&req).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_simulate_massive_amount() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "99999999999999999999999999999999999999999999999999999999999".to_string(), chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "200000000".to_string(),
slippage_bps: 50,
};
let result = simulate(&req).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_simulate_invalid_type_amount() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "invalid_amount".to_string(),
chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "invalid_out".to_string(),
slippage_bps: 50,
};
let result = simulate(&req).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_simulate_overflow_mev() {
let req = SimulateRequest {
from_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".to_string(),
to_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48".to_string(),
from_amount: "100000000000000000000".to_string(),
chain_id: 1,
protocol: "uniswap-v4".to_string(),
amount_out: "340282366920938463463374607431768211455".to_string(), slippage_bps: 2000,
};
let result = simulate(&req).await.unwrap();
assert_eq!(result.estimated_mev_wei, (u128::MAX / 10000).to_string());
}
}