slotstrike 1.0.0

Low-latency Solana slotstrike runtime for event-driven token execution
Documentation
use std::sync::Arc;

use crate::domain::{
    aggregates::RuleBook,
    entities::{SnipeRuleCold, SnipeRuleHot},
    specifications::{DeployerAddressMatchSpecification, MintAddressMatchSpecification},
};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RuleSource {
    Mint,
    Deployer,
}

#[derive(Clone, Debug)]
pub struct MatchedRule {
    pub source: RuleSource,
    pub hot: SnipeRuleHot,
    pub cold: Arc<SnipeRuleCold>,
}

pub struct RuleMatcher;

impl RuleMatcher {
    #[inline(always)]
    pub fn match_rule(
        rule_book: &RuleBook,
        token_address: &str,
        deployer_address: &str,
    ) -> Option<MatchedRule> {
        let mint_specification = MintAddressMatchSpecification::new(token_address);
        if let Some(rule) = mint_specification.select(rule_book) {
            return Some(MatchedRule {
                source: RuleSource::Mint,
                hot: rule.hot(),
                cold: rule.cold_arc(),
            });
        }

        let deployer_specification = DeployerAddressMatchSpecification::new(deployer_address);
        deployer_specification
            .select(rule_book)
            .map(|rule| MatchedRule {
                source: RuleSource::Deployer,
                hot: rule.hot(),
                cold: rule.cold_arc(),
            })
    }
}

#[cfg(test)]
mod tests {
    use super::{RuleMatcher, RuleSource};
    use crate::domain::{
        aggregates::RuleBook,
        entities::SnipeRule,
        value_objects::{RuleAddress, RuleSlippageBps, RuleSolAmount, sol_amount::Lamports},
    };

    fn build_rule(address: &str) -> Option<SnipeRule> {
        let address = RuleAddress::try_from(address).ok()?;
        let slippage = RuleSlippageBps::from_pct_str("1").ok()?;
        Some(SnipeRule::new(
            address,
            RuleSolAmount::new(Lamports::new(1_000_000_000)),
            RuleSolAmount::new(Lamports::new(100_000_000)),
            slippage,
        ))
    }

    #[test]
    fn prioritizes_mint_match_over_deployer_match() {
        let mint = build_rule("So11111111111111111111111111111111111111112");
        let deployer = build_rule("11111111111111111111111111111111");
        assert!(mint.is_some());
        assert!(deployer.is_some());

        if let (Some(mint), Some(deployer)) = (mint, deployer) {
            let book = RuleBook::new(vec![mint], vec![deployer]);
            let matched = RuleMatcher::match_rule(
                &book,
                "So11111111111111111111111111111111111111112",
                "11111111111111111111111111111111",
            );
            assert!(matched.is_some());
            if let Some(matched) = matched {
                assert_eq!(matched.source, RuleSource::Mint);
            }
        }
    }

    #[test]
    fn falls_back_to_deployer_match() {
        let deployer = build_rule("11111111111111111111111111111111");
        assert!(deployer.is_some());

        if let Some(deployer) = deployer {
            let book = RuleBook::new(Vec::new(), vec![deployer]);
            let matched = RuleMatcher::match_rule(
                &book,
                "So11111111111111111111111111111111111111112",
                "11111111111111111111111111111111",
            );
            assert!(matched.is_some());
            if let Some(matched) = matched {
                assert_eq!(matched.source, RuleSource::Deployer);
            }
        }
    }
}