Skip to main content

sentri_core/
fuzzer.rs

1/// Fuzzer utilities for property-based vulnerability detection testing.
2///
3/// Generates code patterns to test detector robustness and minimize false positives.
4/// Deterministic pseudo-random number generator for fuzz testing
5pub struct CodeFuzzer {
6    pub seed: u64,
7    state: u64,
8}
9
10impl CodeFuzzer {
11    /// Create new fuzzer with optional seed
12    pub fn new(seed: Option<u64>) -> Self {
13        let s = seed.unwrap_or_else(|| {
14            std::time::SystemTime::now()
15                .duration_since(std::time::UNIX_EPOCH)
16                .unwrap()
17                .as_secs()
18        });
19        Self { seed: s, state: s }
20    }
21
22    /// Deterministic random number generator using xorshift
23    fn next_u64(&mut self) -> u64 {
24        self.state ^= self.state << 13;
25        self.state ^= self.state >> 7;
26        self.state ^= self.state << 17;
27        self.state
28    }
29
30    /// Generate random number in range [0, max)
31    fn gen_range(&mut self, max: usize) -> usize {
32        (self.next_u64() as usize) % max
33    }
34
35    /// Generate random boolean
36    fn gen_bool(&mut self, numerator: usize, denominator: usize) -> bool {
37        (self.next_u64() as usize) % denominator < numerator
38    }
39
40    /// Generate random function name
41    pub fn random_function_name(&mut self) -> String {
42        const PREFIXES: &[&str] = &["process_", "execute_", "handle_", "check_", "validate_"];
43        const SUFFIXES: &[&str] = &[
44            "transaction",
45            "deposit",
46            "withdraw",
47            "transfer",
48            "swap",
49            "mint",
50            "burn",
51        ];
52        let prefix = PREFIXES[self.gen_range(PREFIXES.len())];
53        let suffix = SUFFIXES[self.gen_range(SUFFIXES.len())];
54        format!("{}{}_{}", prefix, suffix, self.next_u64() % 1000)
55    }
56
57    /// Generate random variable names
58    pub fn random_variable_name(&mut self) -> String {
59        const NAMES: &[&str] = &[
60            "amount",
61            "balance",
62            "reserve",
63            "collateral",
64            "debt",
65            "price",
66            "rate",
67            "value",
68            "token",
69            "user",
70        ];
71        let name = NAMES[self.gen_range(NAMES.len())];
72        format!("{}_{}", name, self.next_u64() % 100)
73    }
74
75    /// Generate random Solidity function with variable control flow
76    pub fn generate_solidity_function(&mut self, include_vulnerable_pattern: bool) -> String {
77        let func_name = self.random_function_name();
78        let var1 = self.random_variable_name();
79        let var2 = self.random_variable_name();
80
81        let health_check = if include_vulnerable_pattern {
82            "// Missing health check".to_string()
83        } else {
84            "require(isHealthy(), \"Not healthy\");".to_string()
85        };
86
87        format!(
88            r#"    function {}(uint256 {} ) public {{
89        {} = {} + 100;
90        {} = {} - 50;
91        {}
92        emit Transfer({}, {});
93    }}"#,
94            func_name, var1, var1, var1, var2, var2, health_check, var1, var2
95        )
96    }
97
98    /// Generate random Solidity with merkle root pattern
99    pub fn generate_merkle_pattern(&mut self, vulnerable: bool) -> String {
100        let init_value = if vulnerable {
101            "bytes32 root = bytes32(0);"
102        } else {
103            "bytes32 root = keccak256(abi.encodePacked(leaves));"
104        };
105
106        format!(
107            r#"    function setupMerkleRoot() public {{
108        {}
109        merkleRoot = root;
110    }}"#,
111            init_value
112        )
113    }
114
115    /// Generate random oracle usage pattern
116    pub fn generate_oracle_pattern(&mut self, vulnerable: bool) -> String {
117        let price_source = if vulnerable {
118            "uint256 price = token.balanceOf(address(this));"
119        } else {
120            "uint256 price = priceOracle.getPrice(token); require(price > 0, \"Invalid price\");"
121        };
122
123        let state_modification = self.random_variable_name();
124
125        format!(
126            r#"    function updatePrice() public {{
127        {}
128        {} = price;
129        emit PriceUpdated(price);
130    }}"#,
131            price_source, state_modification
132        )
133    }
134
135    /// Generate random collateral check pattern
136    pub fn generate_collateral_pattern(&mut self, vulnerable: bool) -> String {
137        let check = if vulnerable {
138            "// No collateral validation"
139        } else {
140            "require(collateral >= amount * RATIO, \"Insufficient collateral\");"
141        };
142
143        format!(
144            r#"    function mintSynthetic(uint256 amount) public {{
145        {}
146        totalMinted += amount;
147    }}"#,
148            check
149        )
150    }
151
152    /// Generate random contract with multiple functions
153    pub fn generate_contract(&mut self, function_count: usize) -> String {
154        let mut contract = "contract TestContract {\n".to_string();
155
156        for _ in 0..function_count {
157            let vulnerable = self.gen_bool(3, 10);
158            let func = self.generate_solidity_function(vulnerable);
159            contract.push_str(&func);
160            contract.push('\n');
161        }
162
163        contract.push('}');
164        contract
165    }
166
167    /// Generate random Rust code pattern
168    pub fn generate_rust_pattern(&mut self, vulnerable: bool) -> String {
169        let check = if vulnerable {
170            "// Missing validation"
171        } else {
172            "if !self.validate_state() { return Err(InvalidState); }"
173        };
174
175        format!(
176            r#"impl Handler {{
177    pub fn process(&mut self) -> Result<()> {{
178        self.update_state();
179        {}
180        Ok(())
181    }}
182}}"#,
183            check
184        )
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn fuzzer_generates_valid_solidity() {
194        let mut fuzzer = CodeFuzzer::new(Some(42));
195        let contract = fuzzer.generate_contract(5);
196        assert!(contract.contains("contract TestContract"));
197        assert!(contract.contains("function"));
198    }
199
200    #[test]
201    fn fuzzer_generates_different_outputs() {
202        let mut fuzzer1 = CodeFuzzer::new(Some(100));
203        let mut fuzzer2 = CodeFuzzer::new(Some(200));
204
205        let func1 = fuzzer1.generate_solidity_function(true);
206        let func2 = fuzzer2.generate_solidity_function(true);
207        assert_ne!(func1, func2);
208    }
209
210    #[test]
211    fn fuzzer_deterministic_with_seed() {
212        let mut f1 = CodeFuzzer::new(Some(999));
213        let mut f2 = CodeFuzzer::new(Some(999));
214
215        assert_eq!(
216            f1.generate_solidity_function(true),
217            f2.generate_solidity_function(true)
218        );
219    }
220
221    #[test]
222    fn fuzzer_generates_merkle_patterns() {
223        let mut fuzzer = CodeFuzzer::new(Some(123));
224        let vuln = fuzzer.generate_merkle_pattern(true);
225        let safe = fuzzer.generate_merkle_pattern(false);
226
227        assert!(vuln.contains("bytes32(0)"));
228        assert!(safe.contains("keccak256"));
229    }
230}