1use sentri_core::model::Invariant;
4use std::collections::BTreeMap;
5
6pub struct InvariantLibrary {
8 pub categories: BTreeMap<String, Vec<Invariant>>,
10}
11
12impl InvariantLibrary {
13 pub fn new() -> Self {
15 Self {
16 categories: BTreeMap::new(),
17 }
18 }
19
20 pub fn with_defaults(chain: &str) -> Self {
22 let mut lib = Self::new();
23 lib.add_defaults(chain);
24 lib
25 }
26
27 fn add_defaults(&mut self, chain: &str) {
29 match chain.to_lowercase().as_str() {
30 "evm" => self.add_evm_defaults(),
31 "solana" => self.add_solana_defaults(),
32 "move" => self.add_move_defaults(),
33 _ => {}
34 }
35 }
36
37 fn add_evm_defaults(&mut self) {
39 let evm_invariants = vec![
40 (
41 "evm_reentrancy_protection",
42 "Reentrancy Protection",
43 "call_order_respected AND no_recursive_calls",
44 ),
45 (
46 "evm_integer_overflow",
47 "Integer Overflow",
48 "arithmetic_values_bounded AND checked_arithmetic",
49 ),
50 (
51 "evm_integer_underflow",
52 "Integer Underflow",
53 "subtraction_checked AND minimum_value_respected",
54 ),
55 (
56 "evm_unchecked_returns",
57 "Unchecked Return Values",
58 "all_external_calls_checked AND return_validation",
59 ),
60 (
61 "evm_delegatecall_injection",
62 "Delegatecall Injection",
63 "delegatecall_target_whitelist AND no_user_delegatecall",
64 ),
65 (
66 "evm_access_control",
67 "Access Control",
68 "caller_authentication AND permission_respected",
69 ),
70 (
71 "evm_timestamp_dependence",
72 "Timestamp Dependence",
73 "no_timestamp_for_security AND block_properties_consistent",
74 ),
75 (
76 "evm_frontrunning",
77 "Front-running",
78 "state_order_independent AND tx_ordering_irrelevant",
79 ),
80 (
81 "evm_uninitialized_pointers",
82 "Uninitialized Pointers",
83 "memory_initialized_before_use AND storage_initialized",
84 ),
85 (
86 "evm_division_by_zero",
87 "Division by Zero",
88 "divisor_nonzero AND modulo_nonzero",
89 ),
90 ];
91
92 for (id, name, _constraint) in evm_invariants {
93 self.add(
94 "EVM".to_string(),
95 Invariant {
96 name: id.to_string(),
97 description: Some(format!("EVM invariant: {}", name)),
98 expression: sentri_core::model::Expression::Var(id.to_string()),
99 severity: "high".to_string(),
100 category: "evm".to_string(),
101 is_always_true: true,
102 layers: vec!["control_flow".to_string(), "data_flow".to_string()],
103 phases: vec!["execution".to_string(), "finalization".to_string()],
104 },
105 );
106 }
107 }
108
109 fn add_solana_defaults(&mut self) {
111 let solana_invariants = vec![
112 (
113 "sol_signer_checks",
114 "Signer Checks",
115 "all_required_signers_present AND signatures_valid",
116 ),
117 (
118 "sol_account_validation",
119 "Account Validation",
120 "expected_accounts_provided AND account_state_valid",
121 ),
122 (
123 "sol_integer_overflow",
124 "Integer Overflow",
125 "arithmetic_checked AND values_within_bounds",
126 ),
127 (
128 "sol_rent_exemption",
129 "Rent Exemption",
130 "rent_paid_or_exempt AND account_cleanup_proper",
131 ),
132 (
133 "sol_pda_derivation",
134 "PDA Derivation",
135 "pda_seeds_deterministic AND derivation_consistent",
136 ),
137 (
138 "sol_lamport_balance",
139 "Lamport Balance",
140 "lamports_conserved AND no_lamport_leaks",
141 ),
142 (
143 "sol_instruction_parsing",
144 "Instruction Parsing",
145 "instruction_data_valid AND account_order_correct",
146 ),
147 ];
148
149 for (id, name, _constraint) in solana_invariants {
150 self.add(
151 "Solana".to_string(),
152 Invariant {
153 name: id.to_string(),
154 description: Some(format!("Solana invariant: {}", name)),
155 expression: sentri_core::model::Expression::Var(id.to_string()),
156 severity: "high".to_string(),
157 category: "solana".to_string(),
158 is_always_true: true,
159 layers: vec!["account_layer".to_string(), "instruction_layer".to_string()],
160 phases: vec!["parsing".to_string(), "execution".to_string()],
161 },
162 );
163 }
164 }
165
166 fn add_move_defaults(&mut self) {
168 let move_invariants = vec![
169 (
170 "move_access_control",
171 "Access Control",
172 "caller_has_required_capability AND resource_protected",
173 ),
174 (
175 "move_integer_overflow",
176 "Integer Overflow",
177 "addition_checked AND values_bounded",
178 ),
179 (
180 "move_resource_leaks",
181 "Resource Leaks",
182 "all_resources_returned AND no_abort_without_cleanup",
183 ),
184 (
185 "move_type_safety",
186 "Type Safety",
187 "types_match_at_boundaries AND resources_typed",
188 ),
189 (
190 "move_signer_requirement",
191 "Signer Requirement",
192 "signer_passed_and_verified AND signer_not_optional",
193 ),
194 ];
195
196 for (id, name, _constraint) in move_invariants {
197 self.add(
198 "Move".to_string(),
199 Invariant {
200 name: id.to_string(),
201 description: Some(format!("Move invariant: {}", name)),
202 expression: sentri_core::model::Expression::Var(id.to_string()),
203 severity: "high".to_string(),
204 category: "move".to_string(),
205 is_always_true: true,
206 layers: vec!["module_layer".to_string(), "transaction_layer".to_string()],
207 phases: vec!["execution".to_string()],
208 },
209 );
210 }
211 }
212
213 pub fn add(&mut self, category: String, invariant: Invariant) {
215 self.categories.entry(category).or_default().push(invariant);
216 }
217
218 pub fn get_category(&self, category: &str) -> Option<&[Invariant]> {
220 self.categories.get(category).map(|v| v.as_slice())
221 }
222
223 pub fn all(&self) -> Vec<&Invariant> {
225 self.categories.values().flat_map(|v| v.iter()).collect()
226 }
227
228 pub fn count(&self) -> usize {
230 self.categories.values().map(|v| v.len()).sum()
231 }
232}
233
234impl Default for InvariantLibrary {
235 fn default() -> Self {
236 Self::new()
237 }
238}