simulator-api 0.20.0

Wire-protocol types for the Solana simulator backtest WebSocket API
Documentation
use crate::CreateSessionParams;

/// How a session is being used, derived from its creation params.
///
/// The underlying flags are not mutually exclusive in a request, so variants
/// are ordered most-restrictive first and matched in that order.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum SessionType {
    /// Nothing executes; state is reconstructed from a delta bundle.
    AccountDeltaReplay,
    Counterfactual,
    ScheduledActions,
    /// Asked to be told when to pause, so the client can drive it by hand.
    InteractiveReplay,
    PureReplay,
    /// A route row written before the classification was persisted.
    #[default]
    Unknown,
}

impl SessionType {
    pub fn from_params(params: &CreateSessionParams) -> Self {
        if params.replay_account_state {
            Self::AccountDeltaReplay
        } else if params.reroute_order_flow() {
            Self::Counterfactual
        } else if !params.actions.is_empty() {
            Self::ScheduledActions
        } else if !params.discoveries.is_empty() {
            Self::InteractiveReplay
        } else {
            Self::PureReplay
        }
    }

    /// Dashboard-visible: these values MUST stay byte-identical once shipped.
    pub const fn as_metric_value(self) -> &'static str {
        match self {
            Self::AccountDeltaReplay => "account_delta_replay",
            Self::Counterfactual => "counterfactual",
            Self::ScheduledActions => "scheduled_actions",
            Self::InteractiveReplay => "interactive_replay",
            Self::PureReplay => "pure_replay",
            Self::Unknown => "unknown",
        }
    }

    pub fn from_metric_value(value: &str) -> Option<Self> {
        [
            Self::AccountDeltaReplay,
            Self::Counterfactual,
            Self::ScheduledActions,
            Self::InteractiveReplay,
            Self::PureReplay,
            Self::Unknown,
        ]
        .into_iter()
        .find(|kind| kind.as_metric_value() == value)
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;
    use solana_address::Address;

    use super::*;
    use crate::{
        AccountModifications, ActionAnchor, ActionKind, DiscoveryFilter, RerouteParams,
        ScheduledAction,
    };

    fn params() -> CreateSessionParams {
        CreateSessionParams::builder()
            .start_slot(1)
            .end_slot(10)
            .build()
    }

    fn action() -> ScheduledAction {
        ScheduledAction {
            anchor: ActionAnchor::BeforeSlot { slots: vec![1] },
            kind: ActionKind::Simulate,
            transactions: vec!["dGVzdA==".to_string()],
            account_overrides: AccountModifications::default(),
            feeds_reroute: false,
            return_accounts: Vec::new(),
            label: None,
        }
    }

    fn discovery() -> DiscoveryFilter {
        DiscoveryFilter::ProgramExecuted(Address::default())
    }

    #[rstest]
    #[case::replay_beats_reroute(
        CreateSessionParams { replay_account_state: true, reroute: Some(RerouteParams::default()), ..params() },
        SessionType::AccountDeltaReplay,
    )]
    #[case::reroute_beats_actions(
        CreateSessionParams { reroute: Some(RerouteParams::default()), actions: vec![action()], ..params() },
        SessionType::Counterfactual,
    )]
    #[case::actions_beat_discoveries(
        CreateSessionParams { actions: vec![action()], discoveries: vec![discovery()], ..params() },
        SessionType::ScheduledActions,
    )]
    #[case::discoveries_alone_are_interactive(
        CreateSessionParams { discoveries: vec![discovery()], ..params() },
        SessionType::InteractiveReplay,
    )]
    #[case::nothing_set_is_pure_replay(params(), SessionType::PureReplay)]
    fn classification_precedence(
        #[case] params: CreateSessionParams,
        #[case] expected: SessionType,
    ) {
        assert_eq!(SessionType::from_params(&params), expected);
    }
}