provable_contracts/
obligation_matrix.rs1use serde::{Deserialize, Serialize};
6
7use crate::proof_status::ProofLevel;
8use crate::schema::Contract;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ObligationStatus {
13 pub property: String,
15 pub obligation_type: String,
17 pub l2_tested: bool,
19 pub l3_kani: bool,
21 pub l4_lean: bool,
23 pub max_level: ProofLevel,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ContractObligationMatrix {
30 pub stem: String,
32 pub obligations: Vec<ObligationStatus>,
34}
35
36pub fn obligation_matrix(contracts: &[(String, &Contract)]) -> Vec<ContractObligationMatrix> {
43 contracts
44 .iter()
45 .map(|(stem, contract)| {
46 let obligations = contract
47 .proof_obligations
48 .iter()
49 .enumerate()
50 .map(|(idx, ob)| {
51 let prop_lower = ob.property.to_lowercase();
52
53 let l2_tested = idx < contract.falsification_tests.len()
57 || contract.falsification_tests.iter().any(|ft| {
58 let rule_lower = ft.rule.to_lowercase();
59 prop_lower.contains(&rule_lower) || rule_lower.contains(&prop_lower)
60 });
61
62 let l3_kani = contract.kani_harnesses.iter().any(|kh| {
66 if let Some(ref kh_prop) = kh.property {
67 let kh_lower = kh_prop.to_lowercase();
68 property_words_match(&prop_lower, &kh_lower)
69 } else {
70 false
71 }
72 });
73
74 let l4_lean = ob
76 .lean
77 .as_ref()
78 .is_some_and(|lp| lp.status == crate::schema::LeanStatus::Proved);
79
80 let max_level = if l4_lean {
81 ProofLevel::L4
82 } else if l3_kani {
83 ProofLevel::L3
84 } else if l2_tested {
85 ProofLevel::L2
86 } else {
87 ProofLevel::L1
88 };
89
90 ObligationStatus {
91 property: ob.property.clone(),
92 obligation_type: ob.obligation_type.to_string(),
93 l2_tested,
94 l3_kani,
95 l4_lean,
96 max_level,
97 }
98 })
99 .collect();
100
101 ContractObligationMatrix {
102 stem: stem.clone(),
103 obligations,
104 }
105 })
106 .collect()
107}
108
109pub fn format_obligation_table(matrices: &[ContractObligationMatrix]) -> String {
111 let mut out = String::new();
112
113 out.push_str("\nObligation Status Matrix\n");
114 out.push_str("========================\n");
115
116 for matrix in matrices {
117 if matrix.obligations.is_empty() {
118 continue;
119 }
120
121 let max_prop_width = matrix
123 .obligations
124 .iter()
125 .map(|o| o.property.len())
126 .max()
127 .unwrap_or(20)
128 .min(40);
129
130 out.push_str(&format!("Contract: {}\n", matrix.stem));
131 out.push_str(&format!(
132 " {:<width$} | L2 Test | L3 Kani | L4 Lean | Status\n",
133 "Obligation",
134 width = max_prop_width
135 ));
136 out.push_str(&format!(" {}\n", "-".repeat(max_prop_width + 39)));
137
138 for ob in &matrix.obligations {
139 let check = "\u{2713}";
140 let cross = "\u{2717}";
141 let l2 = if ob.l2_tested { check } else { cross };
142 let l3 = if ob.l3_kani { check } else { cross };
143 let l4 = if ob.l4_lean { check } else { cross };
144 let prop_display = truncate(&ob.property, max_prop_width);
145 out.push_str(&format!(
146 " {:<width$} | {} | {} | {} | {}\n",
147 prop_display,
148 l2,
149 l3,
150 l4,
151 ob.max_level,
152 width = max_prop_width
153 ));
154 }
155
156 out.push('\n');
157 }
158
159 out
160}
161
162pub fn truncate(s: &str, max: usize) -> &str {
166 if s.len() <= max {
167 s
168 } else {
169 &s[..max]
170 }
171}
172
173pub fn property_words_match(a: &str, b: &str) -> bool {
175 let stop_words: &[&str] = &[
176 "the", "for", "and", "all", "are", "with", "from", "that", "this", "has", "not", "any",
177 "each", "into",
178 ];
179
180 let words_a: Vec<&str> = a
181 .split(|c: char| !c.is_alphanumeric())
182 .filter(|w| w.len() >= 3 && !stop_words.contains(w))
183 .collect();
184
185 words_a.iter().any(|wa| {
186 b.split(|c: char| !c.is_alphanumeric())
187 .filter(|w| w.len() >= 3 && !stop_words.contains(w))
188 .any(|wb| wa == &wb)
189 })
190}
191
192