use std::panic::{catch_unwind, AssertUnwindSafe};
use num_bigint::BigUint;
use tracing::warn;
use tycho_simulation::tycho_common::{
models::token::Token,
simulation::{
errors::SimulationError,
protocol_sim::{GetAmountOutResult, ProtocolSim},
},
};
pub(crate) trait GuardedProtocolSim {
fn get_amount_out_guarded(
&self,
amount_in: BigUint,
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError>;
}
impl<T: ProtocolSim + ?Sized> GuardedProtocolSim for T {
fn get_amount_out_guarded(
&self,
amount_in: BigUint,
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError> {
let outcome = catch_unwind(AssertUnwindSafe(|| {
self.get_amount_out(amount_in.clone(), token_in, token_out)
}));
outcome.unwrap_or_else(|panic_payload| {
let message = panic_payload
.downcast_ref::<&str>()
.copied()
.or_else(|| {
panic_payload
.downcast_ref::<String>()
.map(String::as_str)
})
.unwrap_or("<non-string panic payload>");
warn!(
%amount_in,
token_in = %token_in.address,
token_out = %token_out.address,
token_in_symbol = %token_in.symbol,
token_out_symbol = %token_out.symbol,
panic = message,
"pool simulation panicked; skipping pool"
);
Err(SimulationError::FatalError(format!("get_amount_out panicked: {message}")))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithm::test_utils::{token, DivByZeroSim, MockProtocolSim};
#[test]
fn test_get_amount_out_guarded_converts_panic_to_error() {
let sim = DivByZeroSim::default();
let token_a = token(0x0A, "A");
let token_b = token(0x0B, "B");
let result = sim.get_amount_out_guarded(BigUint::from(1000u64), &token_a, &token_b);
match result {
Err(SimulationError::FatalError(message)) => {
assert!(
message.contains("panicked") && message.contains("divide by zero"),
"unexpected message: {message}"
);
}
Err(other) => panic!("expected FatalError, got {other:?}"),
Ok(_) => panic!("expected FatalError, got Ok"),
}
}
#[test]
fn test_get_amount_out_guarded_passes_through_success() {
let sim = MockProtocolSim::new(2.0);
let token_a = token(0x0A, "A");
let token_b = token(0x0B, "B");
let result = sim
.get_amount_out_guarded(BigUint::from(1000u64), &token_a, &token_b)
.expect("mock simulation should succeed");
assert_eq!(result.amount, BigUint::from(2000u64));
}
}