1use core::fmt::Write;
9
10use hopper_schema::{InstructionDescriptor, ProgramManifest};
11
12pub fn program_summary(manifest: &ProgramManifest) -> String {
15 format!("{}", manifest)
16}
17
18pub fn layouts_report(manifest: &ProgramManifest) -> String {
21 let mut out = String::new();
22 let _ = writeln!(out, "=== Layouts ({}) ===", manifest.layouts.len());
23 let _ = writeln!(out);
24 for layout in manifest.layouts.iter() {
25 let _ = writeln!(
26 out,
27 "{} v{} disc={} size={} id={}",
28 layout.name,
29 layout.version,
30 layout.disc,
31 layout.total_size,
32 hex8(&layout.layout_id)
33 );
34 if layout.field_count == 0 {
35 let _ = writeln!(out, " (no fields)");
36 } else {
37 let _ = writeln!(
38 out,
39 " {:>4} {:>20} {:>6} {:>6} {:>12}",
40 "#", "Field", "Off", "Size", "Type"
41 );
42 for (i, f) in layout.fields.iter().take(layout.field_count).enumerate() {
43 let _ = writeln!(
44 out,
45 " {:>4} {:>20} {:>6} {:>6} {:>12}",
46 i, f.name, f.offset, f.size, f.canonical_type
47 );
48 }
49 }
50 let _ = writeln!(out);
51 }
52 out
53}
54
55pub fn fingerprints_report(manifest: &ProgramManifest) -> String {
58 let mut out = String::new();
59 let _ = writeln!(
60 out,
61 "=== Layout Fingerprints ({}) ===",
62 manifest.layouts.len()
63 );
64 let _ = writeln!(out);
65 for layout in manifest.layouts.iter() {
66 let _ = writeln!(out, "{} v{}", layout.name, layout.version);
67 let _ = writeln!(out, " disc : {}", layout.disc);
68 let _ = writeln!(out, " total_size : {}", layout.total_size);
69 let _ = writeln!(out, " layout_id : {}", hex8(&layout.layout_id));
70 let _ = writeln!(out);
71 }
72 out
73}
74
75pub fn policies_report(manifest: &ProgramManifest) -> String {
77 let mut out = String::new();
78 let _ = writeln!(out, "=== Policy Packs ({}) ===", manifest.policies.len());
79 let _ = writeln!(out);
80 if manifest.policies.is_empty() {
81 let _ = writeln!(out, "(no policy packs declared)");
82 return out;
83 }
84 for policy in manifest.policies.iter() {
85 let _ = writeln!(out, "- {}", policy.name);
86 if !policy.capabilities.is_empty() {
87 let _ = writeln!(out, " capabilities : {}", policy.capabilities.join(", "));
88 }
89 if !policy.requirements.is_empty() {
90 let _ = writeln!(out, " requirements : {}", policy.requirements.join(", "));
91 }
92 if !policy.invariants.is_empty() {
93 let _ = writeln!(out, " invariants : {}", policy.invariants.join(", "));
94 }
95 if !policy.receipt_profile.is_empty() {
96 let _ = writeln!(out, " receipt : {}", policy.receipt_profile);
97 }
98 let _ = writeln!(out);
99 }
100 out
101}
102
103pub fn events_report(manifest: &ProgramManifest) -> String {
105 let mut out = String::new();
106 let _ = writeln!(out, "=== Events ({}) ===", manifest.events.len());
107 let _ = writeln!(out);
108 if manifest.events.is_empty() {
109 let _ = writeln!(out, "(no events declared)");
110 return out;
111 }
112 for ev in manifest.events.iter() {
113 let _ = writeln!(out, "{} (tag={})", ev.name, ev.tag);
114 if ev.fields.is_empty() {
115 let _ = writeln!(out, " (no fields)");
116 } else {
117 for f in ev.fields.iter() {
118 let _ = writeln!(
119 out,
120 " {:<20} {:>12} offset={} size={}",
121 f.name, f.canonical_type, f.offset, f.size
122 );
123 }
124 }
125 let _ = writeln!(out);
126 }
127 out
128}
129
130pub fn instruction_report(manifest: &ProgramManifest, selector: &str) -> Result<String, String> {
136 let instr = resolve_instruction(manifest, selector).ok_or_else(|| {
137 format!(
138 "no instruction matches '{}' (known: {})",
139 selector,
140 known_instruction_names(manifest)
141 )
142 })?;
143
144 let mut out = String::new();
145 let _ = writeln!(
146 out,
147 "=== Instruction: {} (tag {}) ===",
148 instr.name, instr.tag
149 );
150 if instr.receipt_expected {
151 let _ = writeln!(out, " receipt: expected");
152 }
153 if !instr.policy_pack.is_empty() {
154 let _ = writeln!(out, " policy : {}", instr.policy_pack);
155 }
156
157 if !instr.args.is_empty() {
158 let _ = writeln!(out);
159 let _ = writeln!(out, " Arguments:");
160 for a in instr.args.iter() {
161 let _ = writeln!(
162 out,
163 " {:<16} {:>12} ({} bytes)",
164 a.name, a.canonical_type, a.size
165 );
166 }
167 }
168
169 if !instr.accounts.is_empty() {
170 let _ = writeln!(out);
171 let _ = writeln!(out, " Accounts:");
172 for (i, acct) in instr.accounts.iter().enumerate() {
173 let mut flags = Vec::with_capacity(3);
174 if acct.signer {
175 flags.push("signer");
176 }
177 if acct.writable {
178 flags.push("mut");
179 } else {
180 flags.push("read");
181 }
182 if !acct.layout_ref.is_empty() {
183 flags.push("typed");
184 }
185 let layout_ref = if acct.layout_ref.is_empty() {
186 String::new()
187 } else {
188 format!("-> {}", acct.layout_ref)
189 };
190 let _ = writeln!(
191 out,
192 " [{}] {:<20} {} {}",
193 i,
194 acct.name,
195 flags.join(","),
196 layout_ref,
197 );
198 }
199 }
200 Ok(out)
201}
202
203fn resolve_instruction<'a>(
204 manifest: &'a ProgramManifest,
205 selector: &str,
206) -> Option<&'a InstructionDescriptor> {
207 if let Ok(tag) = selector.parse::<u8>() {
208 if let Some(ix) = manifest.find_instruction(tag) {
209 return Some(ix);
210 }
211 }
212 manifest.instructions.iter().find(|ix| ix.name == selector)
213}
214
215fn known_instruction_names(manifest: &ProgramManifest) -> String {
216 let names: Vec<&str> = manifest.instructions.iter().map(|i| i.name).collect();
217 if names.is_empty() {
218 String::from("<none>")
219 } else {
220 names.join(", ")
221 }
222}
223
224fn hex8(bytes: &[u8; 8]) -> String {
225 let mut out = String::with_capacity(16);
226 for b in bytes {
227 let _ = write!(out, "{:02x}", b);
228 }
229 out
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 fn empty_manifest() -> ProgramManifest {
238 ProgramManifest {
239 name: "test",
240 version: "0.1.0",
241 description: "",
242 layouts: &[],
243 layout_metadata: &[],
244 instructions: &[],
245 events: &[],
246 policies: &[],
247 compatibility_pairs: &[],
248 tooling_hints: &[],
249 contexts: &[],
250 }
251 }
252
253 #[test]
254 fn empty_policies_reports_zero() {
255 let m = empty_manifest();
256 let s = policies_report(&m);
257 assert!(s.contains("(no policy packs declared)"));
258 }
259
260 #[test]
261 fn empty_events_reports_zero() {
262 let m = empty_manifest();
263 let s = events_report(&m);
264 assert!(s.contains("(no events declared)"));
265 }
266
267 #[test]
268 fn instruction_report_missing_gives_error() {
269 let m = empty_manifest();
270 let err = instruction_report(&m, "deposit").unwrap_err();
271 assert!(err.contains("deposit"));
272 }
273}