Skip to main content

simulator_api/reroute_report/
record.rs

1//! The source-agnostic input both reports consume — one row of a reroute population,
2//! whether it arrived as a websocket notification or a session-DB row. Adapters own the
3//! mapping; nothing here does I/O.
4
5use serde::{Deserialize, Serialize};
6use solana_address::Address;
7
8use crate::route_plan::RoutePlan;
9
10/// Whether the swap fixes the input amount (`ExactIn`) or the output amount (`ExactOut`).
11///
12/// Spelled `ExactIn`/`ExactOut` on the wire — the same rendering as [`Self::as_str`], the
13/// sqlite column and the stream's `swapMode`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum SwapMode {
16    ExactIn,
17    ExactOut,
18}
19
20impl SwapMode {
21    pub const fn as_str(self) -> &'static str {
22        match self {
23            Self::ExactIn => "ExactIn",
24            Self::ExactOut => "ExactOut",
25        }
26    }
27
28    /// Anything unrecognized reads as `ExactIn`, the overwhelming default in every source.
29    pub fn parse(raw: &str) -> Self {
30        match raw == Self::ExactOut.as_str() {
31            true => Self::ExactOut,
32            false => Self::ExactIn,
33        }
34    }
35}
36
37/// One re-quoted swap. The three plans share one shape ([`RoutePlan`]); a side that was not
38/// recorded is `None`, never an empty plan.
39#[derive(Debug, Clone)]
40pub struct LegRecord {
41    pub original_signature: String,
42    pub input_mint: Address,
43    pub output_mint: Address,
44    /// In the input token for ExactIn, the output token for ExactOut.
45    pub amount: u64,
46    pub swap_mode: SwapMode,
47    /// The replacement never landed — a reverted simulation or a not-ok execution. Neither
48    /// report column counts it as flow, but pricing still reads the original's side.
49    pub failed: bool,
50    /// The original failed on L1, so its route events are not flow any venue served.
51    pub original_failed: bool,
52    /// What the replacement actually produced; `None` when unattributable.
53    pub realized_output_amount: Option<u64>,
54    /// What the historical original produced. `None` when it cannot be attributed to this
55    /// record alone — a multi-swap transaction's total is nobody's single leg.
56    pub original_realized_output_amount: Option<u64>,
57    /// The route the replacement was quoted over.
58    pub route_plan: Option<RoutePlan>,
59    /// The route the original actually took on L1.
60    pub original_route_plan: Option<RoutePlan>,
61    /// What the replacement's own simulation moved, hop by hop — the fill to compare
62    /// `original_route_plan` against.
63    pub realized_route_plan: Option<RoutePlan>,
64    /// The replacement's identity: `new_route` for a metis row, the template label for a probe.
65    pub template: Option<String>,
66    /// The original side, in the measured mint (output for ExactIn, input charged for ExactOut):
67    /// as quoted on metis and stream rows, or what the original filled against the same state as
68    /// the probe on probe rows.
69    pub quoted_output_amount: Option<u64>,
70    /// What the replacement itself produced at its own simulation, in the measured mint.
71    pub new_output_amount: Option<u64>,
72    /// Why an unfilled probe did not fill — `reverted_*`, `unattributed` (ran clean, but the
73    /// seeded account was gone or unreadable), `unsimulatable` (could not be compiled or run) —
74    /// or the requote failure bucket on metis rows.
75    pub error_category: Option<String>,
76    /// The recorded `output_bps` column, clamped by the writer. The historical fill itself is
77    /// not persisted, so this cannot be recomputed downstream.
78    pub output_bps: Option<i64>,
79}
80
81#[cfg(test)]
82mod tests {
83    use rstest::rstest;
84
85    use crate::SwapMode;
86
87    #[rstest]
88    #[case::exact_in(SwapMode::ExactIn)]
89    #[case::exact_out(SwapMode::ExactOut)]
90    fn swap_mode_serializes_as_its_as_str(#[case] mode: SwapMode) {
91        assert_eq!(serde_json::to_value(mode).unwrap(), mode.as_str());
92    }
93}