Skip to main content

sentri_library/
library.rs

1//! Invariant library management.
2
3use sentri_core::model::Invariant;
4use std::collections::BTreeMap;
5
6/// A collection of invariants organized by category.
7pub struct InvariantLibrary {
8    /// Invariants by category.
9    pub categories: BTreeMap<String, Vec<Invariant>>,
10}
11
12impl InvariantLibrary {
13    /// Create a new empty library.
14    pub fn new() -> Self {
15        Self {
16            categories: BTreeMap::new(),
17        }
18    }
19
20    /// Create a library with default built-in invariants for a chain.
21    pub fn with_defaults(chain: &str) -> Self {
22        let mut lib = Self::new();
23        lib.add_defaults(chain);
24        lib
25    }
26
27    /// Add default invariants for a specific blockchain.
28    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    /// Add EVM-specific invariants.
38    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    /// Add Solana-specific invariants.
110    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    /// Add Move-specific invariants.
167    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    /// Add an invariant to the library.
214    pub fn add(&mut self, category: String, invariant: Invariant) {
215        self.categories.entry(category).or_default().push(invariant);
216    }
217
218    /// Get all invariants in a category.
219    pub fn get_category(&self, category: &str) -> Option<&[Invariant]> {
220        self.categories.get(category).map(|v| v.as_slice())
221    }
222
223    /// Get all invariants.
224    pub fn all(&self) -> Vec<&Invariant> {
225        self.categories.values().flat_map(|v| v.iter()).collect()
226    }
227
228    /// Count total invariants.
229    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}