sentri-core 0.3.0

Sentri: Core types and utilities for the Sentri multi-chain smart contract security analyzer.
Documentation
/// Synthetic Mint Detector Fuzzer
/// Property-based testing for unbacked synthetic token minting
///
/// Validates detection of collateral insufficiency in synthetic token minting.
use crate::CodeFuzzer;

/// Synthetic mint fuzzer
pub struct SyntheticMintFuzzer {
    _fuzzer: CodeFuzzer,
}

impl SyntheticMintFuzzer {
    /// Create new fuzzer
    pub fn new(seed: Option<u64>) -> Self {
        Self {
            _fuzzer: CodeFuzzer::new(seed),
        }
    }

    /// Generate vulnerable pattern
    fn gen_vulnerable_pattern(&self) -> String {
        r#"contract SyntheticToken {
    mapping(address => uint256) collateral;
    uint256 public totalMinted;
    uint256 constant MINT_RATIO = 2; // 2:1 ratio required
    
    function mint(address user, uint256 amount) public {
        // Vulnerable: No collateral verification
        totalMinted += amount;
        balances[user] += amount;
    }
    
    function addCollateral(address user, uint256 amount) public {
        collateral[user] += amount;
    }
}"#
        .to_string()
    }

    /// Generate safe pattern
    fn gen_safe_pattern(&self) -> String {
        r#"contract SyntheticToken {
    mapping(address => uint256) collateral;
    uint256 public totalMinted;
    uint256 constant MINT_RATIO = 2;
    
    function mint(address user, uint256 amount) public {
        // Safe: Validates collateral backing
        uint256 requiredCollateral = amount / MINT_RATIO;
        require(
            collateral[user] >= requiredCollateral,
            "Insufficient collateral"
        );
        
        totalMinted += amount;
        balances[user] += amount;
        
        // Verify conservation invariant
        assert(totalMinted <= getTotalCollateral() / MINT_RATIO);
    }
    
    function addCollateral(address user, uint256 amount) public {
        require(amount > 0, "Invalid amount");
        collateral[user] += amount;
    }
}"#
        .to_string()
    }

    /// Run fuzz tests
    pub fn fuzz(&mut self, iterations: usize) -> FuzzResult {
        let mut detections = 0;
        let mut false_positives = 0;
        let mut false_negatives = 0;

        for i in 0..iterations {
            let vulnerable = i % 2 == 0;
            let pattern = if vulnerable {
                self.gen_vulnerable_pattern()
            } else {
                self.gen_safe_pattern()
            };

            let has_mint = pattern.contains("function mint");
            let has_collateral_check =
                pattern.contains("require(") && pattern.contains("collateral");
            let detected = has_mint && !has_collateral_check;

            if vulnerable && detected {
                detections += 1;
            } else if vulnerable && !detected {
                false_negatives += 1;
            } else if !vulnerable && detected {
                false_positives += 1;
            }
        }

        FuzzResult {
            true_positives: detections,
            false_positives,
            false_negatives,
            total: iterations,
        }
    }
}

/// Fuzz result
#[derive(Debug, Clone)]
pub struct FuzzResult {
    /// True positive detections
    pub true_positives: usize,
    /// False positive detections
    pub false_positives: usize,
    /// False negative detections (missed vulnerabilities)
    pub false_negatives: usize,
    /// Total test iterations
    pub total: usize,
}

impl FuzzResult {
    /// Calculate precision metric
    pub fn precision(&self) -> f64 {
        let total = self.true_positives + self.false_positives;
        if total == 0 {
            0.0
        } else {
            self.true_positives as f64 / total as f64
        }
    }

    /// Calculate recall metric
    pub fn recall(&self) -> f64 {
        let total = self.true_positives + self.false_negatives;
        if total == 0 {
            0.0
        } else {
            self.true_positives as f64 / total as f64
        }
    }

    /// Calculate F1 score
    pub fn f1_score(&self) -> f64 {
        let p = self.precision();
        let r = self.recall();
        if p + r == 0.0 {
            0.0
        } else {
            2.0 * p * r / (p + r)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fuzzer_detects_unbacked_minting() {
        let mut fuzzer = SyntheticMintFuzzer::new(Some(42));
        let result = fuzzer.fuzz(100);
        assert!(result.true_positives > 0);
    }

    #[test]
    fn fuzz_prevents_false_positives() {
        let mut fuzzer = SyntheticMintFuzzer::new(Some(99));
        let result = fuzzer.fuzz(100);
        assert!(result.false_positives < 10);
    }
}