Skip to main content

sentri_simulator/
engine.rs

1//! Simulation engine for analyzing program invariants.
2
3use sentri_core::model::{Invariant, ProgramModel, SimulationReport};
4use sentri_core::traits::Simulator;
5use sentri_core::Result;
6use tracing::info;
7
8/// Deterministic simulation engine for invariant testing.
9///
10/// This engine performs static analysis of program models against invariants.
11/// It analyzes function control flow, state access patterns, and structural properties
12/// to detect potential violations.
13pub struct SimulationEngine;
14
15impl SimulationEngine {
16    /// Create a new simulation engine.
17    pub fn new(_seed: u64) -> Self {
18        Self
19    }
20}
21
22impl Default for SimulationEngine {
23    fn default() -> Self {
24        Self
25    }
26}
27
28impl Simulator for SimulationEngine {
29    fn simulate(
30        &self,
31        program: &ProgramModel,
32        invariants: &[Invariant],
33    ) -> Result<SimulationReport> {
34        info!(
35            "Starting analysis of {} with {} invariants",
36            program.name,
37            invariants.len()
38        );
39
40        let mut traces = Vec::new();
41        let mut detected_violations = 0;
42
43        // Analyze each invariant against the program
44        for invariant in invariants {
45            // Check if invariant is applicable to this program type
46            if !is_invariant_applicable(invariant, program) {
47                continue;
48            }
49
50            // Analyze program-level patterns
51            if let Some(violation) = analyze_program_invariant(invariant, program) {
52                detected_violations += 1;
53                traces.push(violation);
54            }
55
56            // Analyze each function against the invariant
57            for (func_name, function) in &program.functions {
58                if let Some(violation) =
59                    analyze_function_invariant(invariant, program, function, func_name)
60                {
61                    detected_violations += 1;
62                    traces.push(violation);
63                }
64            }
65        }
66
67        // Calculate coverage based on violations found
68        let total_checks = invariants.len();
69        let coverage = if total_checks > 0 {
70            ((total_checks - detected_violations.min(total_checks)) as f64 / total_checks as f64)
71                * 100.0
72        } else {
73            100.0
74        };
75
76        info!(
77            "Analysis complete: {} violations detected in {} invariants, {:.1}% coverage",
78            detected_violations, total_checks, coverage
79        );
80
81        Ok(SimulationReport {
82            violations: detected_violations,
83            traces,
84            coverage: coverage.max(0.0),
85            seed: 0,
86        })
87    }
88
89    fn chain(&self) -> &str {
90        "analysis"
91    }
92}
93
94/// Check if an invariant is applicable to this program type.
95///
96/// An invariant applies if:
97/// - It's a cross-platform invariant (access control, overflow), OR
98/// - Its category matches the program's blockchain platform
99fn is_invariant_applicable(invariant: &Invariant, program: &ProgramModel) -> bool {
100    let program_chain = program.chain.to_lowercase();
101    let invariant_category = invariant.category.to_lowercase();
102
103    // Cross-platform invariants apply to all programs
104    if invariant_category.contains("access")
105        || invariant_category.contains("overflow")
106        || invariant_category.contains("general")
107    {
108        return true;
109    }
110
111    // Platform-specific invariants must match the program's chain
112    program_chain.contains(&invariant_category)
113}
114
115/// Analyze program-level invariants to detect violations.
116///
117/// Performs static analysis of:
118/// - Reentrancy risks (multiple entry points with state mutations)
119/// - Access control patterns (entry point functions without state checks)
120/// - Arithmetic overflow risks (functions with numeric parameters)
121fn analyze_program_invariant(invariant: &Invariant, program: &ProgramModel) -> Option<String> {
122    let invariant_name_lower = invariant.name.to_lowercase();
123
124    // Reentrancy risk: multiple entry points with state mutations
125    if invariant_name_lower.contains("reentrancy") {
126        let entry_points: Vec<_> = program
127            .functions
128            .iter()
129            .filter(|(_, f)| f.is_entry_point)
130            .collect();
131
132        let mutating_functions: Vec<_> = program
133            .functions
134            .iter()
135            .filter(|(_, f)| !f.mutates.is_empty())
136            .collect();
137
138        if entry_points.len() > 1 && !mutating_functions.is_empty() {
139            return Some(format!(
140                "Invariant '{}' violated in {}: Reentrancy risk detected - {} entry points with state mutations across {} functions",
141                invariant.name,
142                program.name,
143                entry_points.len(),
144                mutating_functions.len()
145            ));
146        }
147    }
148
149    // Access control risk: entry points with public state access
150    if invariant_name_lower.contains("access") {
151        let public_entry_points: Vec<_> = program
152            .functions
153            .iter()
154            .filter(|(_, f)| f.is_entry_point && !f.reads.is_empty())
155            .collect();
156
157        if !public_entry_points.is_empty() {
158            return Some(format!(
159                "Invariant '{}' violated in {}: Access control risk - {} entry points access state without guards",
160                invariant.name,
161                program.name,
162                public_entry_points.len()
163            ));
164        }
165    }
166
167    // Overflow/underflow risk: functions with numeric operations
168    if invariant_name_lower.contains("overflow") || invariant_name_lower.contains("underflow") {
169        let numeric_functions: Vec<_> = program
170            .functions
171            .iter()
172            .filter(|(_, f)| {
173                f.parameters.iter().any(|p| {
174                    let p_lower = p.to_lowercase();
175                    p_lower.contains("u") || p_lower.contains("i") || p_lower.contains("int")
176                })
177            })
178            .collect();
179
180        if numeric_functions.len() > 2 {
181            return Some(format!(
182                "Invariant '{}' violated in {}: Arithmetic risk - {} functions with numeric parameters may have unchecked operations",
183                invariant.name,
184                program.name,
185                numeric_functions.len()
186            ));
187        }
188    }
189
190    None
191}
192
193/// Analyze function-level invariants to detect violations.
194///
195/// Performs static analysis of:
196/// - Complex state interactions (reads + mutates)
197/// - Entry point authorization patterns
198/// - Pure function expectations
199fn analyze_function_invariant(
200    invariant: &Invariant,
201    program: &ProgramModel,
202    function: &sentri_core::model::FunctionModel,
203    func_name: &str,
204) -> Option<String> {
205    let invariant_name_lower = invariant.name.to_lowercase();
206    let state_interaction_count = function.reads.len() + function.mutates.len();
207
208    // Reentrancy: entry points with complex state interactions
209    if invariant_name_lower.contains("reentrancy")
210        && function.is_entry_point
211        && state_interaction_count > 2
212    {
213        return Some(format!(
214            "Function '{}' in {} violates '{}': Complex state interactions (reads: {}, writes: {}) without reentrancy guards",
215            func_name,
216            program.name,
217            invariant.name,
218            function.reads.len(),
219            function.mutates.len()
220        ));
221    }
222
223    // Access control: public entry points without authorization
224    if invariant_name_lower.contains("access")
225        && function.is_entry_point
226        && !function.reads.is_empty()
227        && !function.is_pure
228    {
229        return Some(format!(
230            "Function '{}' in {} violates '{}': Entry point accesses {} state variables without authorization checks",
231            func_name,
232            program.name,
233            invariant.name,
234            function.reads.len()
235        ));
236    }
237
238    // Arithmetic: functions with multiple numeric parameters
239    if (invariant_name_lower.contains("overflow") || invariant_name_lower.contains("underflow"))
240        && function.parameters.len() > 1
241    {
242        let numeric_params = function
243            .parameters
244            .iter()
245            .filter(|p| {
246                let p_lower = p.to_lowercase();
247                p_lower.contains("u") || p_lower.contains("i") || p_lower.contains("int")
248            })
249            .count();
250
251        if numeric_params > 1 {
252            return Some(format!(
253                "Function '{}' in {} violates '{}': {} numeric parameters without overflow checks",
254                func_name, program.name, invariant.name, numeric_params
255            ));
256        }
257    }
258
259    None
260}