Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 47 additions & 9 deletions fynd-core/src/algorithm/sim_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use tycho_simulation::tycho_common::{
errors::SimulationError,
protocol_sim::{GetAmountOutResult, ProtocolSim},
},
Bytes,
};

/// Extension trait adding panic-guarded simulation calls to every [`ProtocolSim`].
Expand All @@ -33,6 +34,29 @@ pub(crate) trait GuardedProtocolSim {
token_in: &Token,
token_out: &Token,
) -> Result<GetAmountOutResult, SimulationError>;

/// Calls `get_limits`, converting a panic into a `SimulationError::FatalError`.
///
/// On a contained panic, logs the token pair so the offending component/quote can be
/// tracked down from the logs.
fn get_limits_guarded(
&self,
sell_token: Bytes,
buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError>;
}

/// Best-effort extraction of a human-readable message from a contained panic payload.
fn panic_message(panic_payload: &(dyn std::any::Any + Send)) -> &str {
panic_payload
.downcast_ref::<&str>()
.copied()
.or_else(|| {
panic_payload
.downcast_ref::<String>()
.map(String::as_str)
})
.unwrap_or("<non-string panic payload>")
}

impl<T: ProtocolSim + ?Sized> GuardedProtocolSim for T {
Expand All @@ -48,15 +72,7 @@ impl<T: ProtocolSim + ?Sized> GuardedProtocolSim for T {
}));

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>");
let message = panic_message(panic_payload.as_ref());
warn!(
%amount_in,
token_in = %token_in.address,
Expand All @@ -69,6 +85,28 @@ impl<T: ProtocolSim + ?Sized> GuardedProtocolSim for T {
Err(SimulationError::FatalError(format!("get_amount_out panicked: {message}")))
})
}

fn get_limits_guarded(
&self,
sell_token: Bytes,
buy_token: Bytes,
) -> Result<(BigUint, BigUint), SimulationError> {
// Tokens are cloned into the call so the originals stay available for the panic log.
let outcome = catch_unwind(AssertUnwindSafe(|| {
self.get_limits(sell_token.clone(), buy_token.clone())
}));

outcome.unwrap_or_else(|panic_payload| {
let message = panic_message(panic_payload.as_ref());
warn!(
%sell_token,
%buy_token,
panic = message,
"component get_limits panicked; skipping component"
);
Err(SimulationError::FatalError(format!("get_limits panicked: {message}")))
})
}
}

#[cfg(test)]
Expand Down
Loading
Loading