sentri_simulator/
engine.rs1use sentri_core::model::{Invariant, ProgramModel, SimulationReport};
4use sentri_core::traits::Simulator;
5use sentri_core::Result;
6use tracing::info;
7
8pub struct SimulationEngine;
14
15impl SimulationEngine {
16 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 for invariant in invariants {
45 if !is_invariant_applicable(invariant, program) {
47 continue;
48 }
49
50 if let Some(violation) = analyze_program_invariant(invariant, program) {
52 detected_violations += 1;
53 traces.push(violation);
54 }
55
56 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 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
94fn 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 if invariant_category.contains("access")
105 || invariant_category.contains("overflow")
106 || invariant_category.contains("general")
107 {
108 return true;
109 }
110
111 program_chain.contains(&invariant_category)
113}
114
115fn analyze_program_invariant(invariant: &Invariant, program: &ProgramModel) -> Option<String> {
122 let invariant_name_lower = invariant.name.to_lowercase();
123
124 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 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 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
193fn 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 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 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 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}