1use simplicity::bit_machine::{ExecTracker, FrameIter, NodeOutput, PruneTracker, SetTracker};
2use simplicity::node::Inner;
3use simplicity::{Ihr, RedeemNode, Value as SimValue};
4
5use crate::array::Unfolder;
6use crate::ast::{ElementsJetHinter, JetHinter};
7use crate::debug::{DebugSymbols, TrackedCallName};
8use crate::either::Either;
9use crate::jet::{source_type, target_type, JetHL};
10use crate::str::AliasName;
11use crate::types::AliasedType;
12use crate::value::StructuralValue;
13use crate::{ResolvedType, Value};
14
15type DebugSink<'a> = Box<dyn FnMut(&str, &Value) + 'a>;
20
21type JetTraceSink<'a> = Box<dyn FnMut(&dyn JetHL, Option<&[Value]>, Option<Value>) + 'a>;
26
27type WarningSink<'a> = Box<dyn Fn(&str) + 'a>;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
32pub enum TrackerLogLevel {
33 #[default]
34 None,
35 Debug,
36 Warning,
37 Trace,
38}
39
40fn default_debug_sink(label: &str, value: &Value) {
42 println!("DBG: {label} = {value}");
43}
44
45fn default_jet_trace_sink(jet: &dyn JetHL, args: Option<&[Value]>, result: Option<Value>) {
47 print!("{jet:?}(");
48 if let Some(args) = args {
49 for (i, arg) in args.iter().enumerate() {
50 if i > 0 {
51 print!(", ");
52 }
53 print!("{arg}");
54 }
55 } else {
56 print!("...");
57 }
58
59 match result {
60 Some(value) => println!(") = {value}"),
61 None => println!(") -> [failed]"),
62 }
63}
64
65fn default_warning_sink(message: &str) {
67 println!("WARN: {message}");
68}
69
70pub struct DefaultTracker<'a> {
87 debug_symbols: &'a DebugSymbols,
88 jet_hinter: Box<dyn JetHinter>,
89 debug_sink: Option<DebugSink<'a>>,
90 jet_trace_sink: Option<JetTraceSink<'a>>,
91 warning_sink: Option<WarningSink<'a>>,
92 inner: SetTracker,
93}
94
95impl<'a> DefaultTracker<'a> {
96 #[deprecated(since = "0.6.0", note = "Please use `build` instead")]
102 pub fn new(debug_symbols: &'a DebugSymbols) -> Self {
103 Self::build(debug_symbols, Box::new(ElementsJetHinter::new()))
104 }
105
106 pub fn build(debug_symbols: &'a DebugSymbols, jet_hinter: Box<dyn JetHinter>) -> Self {
108 Self {
109 debug_symbols,
110 jet_hinter,
111 debug_sink: None,
112 jet_trace_sink: None,
113 warning_sink: None,
114 inner: SetTracker::default(),
115 }
116 }
117
118 pub fn with_debug_sink<F>(mut self, sink: F) -> Self
120 where
121 F: FnMut(&str, &Value) + 'a,
122 {
123 self.debug_sink = Some(Box::new(sink));
124 self
125 }
126
127 pub fn with_default_debug_sink(self) -> Self {
129 self.with_debug_sink(default_debug_sink)
130 }
131
132 pub fn with_jet_trace_sink<F>(mut self, sink: F) -> Self
134 where
135 F: FnMut(&dyn JetHL, Option<&[Value]>, Option<Value>) + 'a,
136 {
137 self.jet_trace_sink = Some(Box::new(sink));
138 self
139 }
140
141 pub fn with_default_jet_trace_sink(self) -> Self {
143 self.with_jet_trace_sink(default_jet_trace_sink)
144 }
145
146 pub fn with_warning_sink<F>(mut self, sink: F) -> Self
148 where
149 F: Fn(&str) + 'a,
150 {
151 self.warning_sink = Some(Box::new(sink));
152 self
153 }
154
155 pub fn with_default_warning_sink(self) -> Self {
157 self.with_warning_sink(default_warning_sink)
158 }
159
160 pub fn with_log_level(self, log_level: TrackerLogLevel) -> Self {
167 let tracker = if log_level >= TrackerLogLevel::Debug {
168 self.with_default_debug_sink()
169 } else {
170 self
171 };
172
173 let tracker = if log_level >= TrackerLogLevel::Warning {
174 tracker.with_default_warning_sink()
175 } else {
176 tracker
177 };
178
179 if log_level >= TrackerLogLevel::Trace {
180 tracker.with_default_jet_trace_sink()
181 } else {
182 tracker
183 }
184 }
185
186 fn handle_jet(
188 &mut self,
189 node: &RedeemNode,
190 jet: &dyn JetHL,
191 input: &FrameIter,
192 output: &NodeOutput,
193 ) {
194 if self.jet_trace_sink.is_none() {
195 return;
196 }
197
198 let mut input_frame = input.clone();
199
200 let args = match parse_jet_arguments(jet, &mut input_frame) {
201 Ok(args) => args,
202 Err(e) => {
203 self.warn(&format!("Failed to parse arguments for jet {jet:?}: {e}"));
204
205 let result = Self::parse_jet_result(node, jet, output);
207 if let Some(sink) = self.jet_trace_sink.as_mut() {
208 sink(jet, None, result);
209 }
210
211 return;
212 }
213 };
214
215 let result = Self::parse_jet_result(node, jet, output);
216
217 if let Some(sink) = self.jet_trace_sink.as_mut() {
218 sink(jet, Some(&args), result);
219 }
220 }
221
222 fn parse_jet_result(node: &RedeemNode, jet: &dyn JetHL, output: &NodeOutput) -> Option<Value> {
224 match output.clone() {
225 NodeOutput::Success(mut output_frame) => {
226 let target_ty = &node.arrow().target;
227 let jet_target_ty = resolve_jet_type(&target_type(jet));
228
229 let output_value = SimValue::from_padded_bits(&mut output_frame, target_ty)
230 .expect("output from bit machine is always well-formed");
231
232 Value::reconstruct(&StructuralValue::from(output_value), &jet_target_ty)
233 }
234 _ => None,
235 }
236 }
237
238 fn warn(&self, message: &str) {
240 if let Some(sink) = self.warning_sink.as_ref() {
241 sink(message);
242 }
243 }
244
245 fn handle_debug(&mut self, node: &RedeemNode, input: &FrameIter, cmr: &simplicity::Cmr) {
247 if self.debug_sink.is_none() {
248 return;
249 }
250
251 let Some(tracked_call) = self.debug_symbols.get(cmr) else {
252 self.warn(&format!("Unknown debug symbol: CMR {cmr}"));
253 return;
254 };
255
256 let TrackedCallName::Debug(_) = tracked_call.name() else {
257 return;
258 };
259
260 let mut input_frame = input.clone();
261
262 let _ = input_frame.next();
267
268 let Ok(input_val) = SimValue::from_padded_bits(&mut input_frame, &node.arrow().target)
271 else {
272 self.warn(&format!("Failed to decode debug value for CMR {cmr}"));
273 return;
274 };
275
276 let Some(Either::Right(debug_value)) =
277 tracked_call.map_value(&StructuralValue::from(input_val))
278 else {
279 return;
280 };
281
282 if let Some(sink) = self.debug_sink.as_mut() {
283 sink(debug_value.text(), debug_value.value());
284 }
285 }
286}
287
288impl PruneTracker for DefaultTracker<'_> {
289 fn contains_left(&self, ihr: Ihr) -> bool {
290 if PruneTracker::contains_left(&self.inner, ihr) {
291 return true;
292 }
293
294 if let Some(sink) = self.warning_sink.as_ref() {
295 sink(&format!("Pruning unexecuted left child of IHR {ihr}"));
296 }
297
298 false
299 }
300
301 fn contains_right(&self, ihr: Ihr) -> bool {
302 if PruneTracker::contains_right(&self.inner, ihr) {
303 return true;
304 }
305
306 if let Some(sink) = self.warning_sink.as_ref() {
307 sink(&format!("Pruning unexecuted right child of IHR {ihr}"));
308 }
309
310 false
311 }
312}
313
314impl ExecTracker for DefaultTracker<'_> {
315 fn visit_node(&mut self, node: &RedeemNode, input: FrameIter, output: NodeOutput) {
316 match node.inner() {
317 Inner::Jet(jet) => {
318 if let Some(jet) = self.jet_hinter.conjure(jet.as_ref()) {
319 self.handle_jet(node, jet.as_ref(), &input, &output);
320 } else {
321 self.warn(&format!("Unexpected jet type for tracker: {jet:?}"));
322 }
323 }
324 Inner::AssertL(_, cmr) => self.handle_debug(node, &input, cmr),
325 _ => {}
326 }
327
328 self.inner.visit_node(node, input, output);
329 }
330}
331
332fn parse_jet_arguments(jet: &dyn JetHL, input_frame: &mut FrameIter) -> Result<Vec<Value>, String> {
334 let source_types = source_type(jet);
335 if source_types.is_empty() {
336 return Ok(vec![]);
337 }
338
339 let arguments_blob = SimValue::from_padded_bits(input_frame, &jet.source_ty().to_final())
340 .expect("input from bit machine is always well-formed");
341
342 let args = Unfolder::new(arguments_blob.as_ref(), source_types.len())
343 .unfold(|v| v.as_product())
344 .ok_or("expected product type while collecting arguments")?;
345
346 Ok(args
347 .into_iter()
348 .zip(source_types.iter())
349 .map(|(arg, aliased_type)| {
350 Value::reconstruct(&arg.to_value().into(), &resolve_jet_type(aliased_type))
351 .expect("compiled program produces correctly structured values")
352 })
353 .collect())
354}
355
356fn resolve_jet_type(aliased_type: &AliasedType) -> ResolvedType {
358 aliased_type
359 .resolve(|name: &AliasName| Err(name.clone()))
360 .expect("jet types always resolve without aliases")
361}
362
363#[cfg(test)]
364mod tests {
365 use std::cell::RefCell;
366 use std::collections::HashMap;
367 use std::rc::Rc;
368 use std::sync::Arc;
369
370 use simplicity::elements::taproot::ControlBlock;
371 use simplicity::elements::BlockHash;
372 use simplicity::elements::{self, pset::PartiallySignedTransaction};
373 use simplicity::jet::elements::{ElementsEnv, ElementsUtxo};
374 use simplicity::Cmr;
375
376 use crate::ast::ElementsJetHinter;
377 use crate::elements::confidential::Asset;
378 use crate::elements::hashes::Hash;
379 use crate::elements::pset::Input;
380 use crate::elements::{AssetId, OutPoint, Script, Txid};
381 use crate::{Arguments, TemplateProgram, WitnessValues};
382
383 use super::*;
384
385 const TEST_PROGRAM: &str = r#"
386 fn get_input_explicit_asset_amount(index: u32) -> (u256, u64) {
387 let pair: (Asset1, Amount1) = unwrap(jet::input_amount(index));
388 let (asset, amount): (Asset1, Amount1) = dbg!(pair);
389 let asset_bits: u256 = unwrap_right::<(u1, u256)>(asset);
390 let amount: u64 = unwrap_right::<(u1, u256)>(amount);
391 (asset_bits, amount)
392 }
393
394 fn main() {
395 let a: u32 = jet::num_inputs();
396 let b: bool = dbg!(jet::eq_32(20, 21));
397 let c: (u256, u64) = dbg!(get_input_explicit_asset_amount(0));
398 }
399 "#;
400
401 type DebugStore = Rc<RefCell<HashMap<String, String>>>;
402 type JetStore = Rc<RefCell<HashMap<String, (Option<Vec<String>>, Option<String>)>>>;
403
404 fn create_test_elements_tracker(
405 debug_symbols: &DebugSymbols,
406 ) -> (DefaultTracker<'_>, DebugStore, JetStore) {
407 let debug_store: DebugStore = Rc::default();
408 let jet_store: JetStore = Rc::default();
409
410 let debug_clone = debug_store.clone();
411 let jet_clone = jet_store.clone();
412
413 let tracker = DefaultTracker::build(debug_symbols, Box::new(ElementsJetHinter::new()))
414 .with_debug_sink(move |label, value| {
415 debug_clone
416 .borrow_mut()
417 .insert(label.to_string(), value.to_string());
418 })
419 .with_jet_trace_sink(move |jet, args, result| {
420 jet_clone.borrow_mut().insert(
421 jet.to_string(),
422 (
423 args.map(|a| a.iter().map(|v| v.to_string()).collect()),
424 result.map(|r| r.to_string()),
425 ),
426 );
427 });
428
429 (tracker, debug_store, jet_store)
430 }
431
432 fn create_test_env() -> ElementsEnv<Arc<elements::Transaction>> {
433 let mut tx = PartiallySignedTransaction::new_v2();
434 let outpoint = OutPoint::new(Txid::from_slice(&[2; 32]).unwrap(), 33);
435 tx.add_input(Input::from_prevout(outpoint));
436
437 ElementsEnv::new(
438 Arc::new(tx.extract_tx().unwrap()),
439 vec![ElementsUtxo {
440 script_pubkey: Script::new(),
441 asset: Asset::Explicit(AssetId::LIQUID_BTC),
442 value: elements::confidential::Value::Explicit(1000),
443 }],
444 0,
445 Cmr::from_byte_array([0; 32]),
446 ControlBlock::from_slice(&[0xc0; 33]).unwrap(),
447 None,
448 BlockHash::all_zeros(),
449 )
450 }
451
452 #[test]
453 fn test_debug_and_jet_tracing() {
454 let program =
455 TemplateProgram::new(TEST_PROGRAM, Box::new(ElementsJetHinter::new())).unwrap();
456 let program = program.instantiate(Arguments::default(), true).unwrap();
457 let satisfied = program.satisfy(WitnessValues::default()).unwrap();
458
459 let (mut tracker, debug_store, jet_store) =
460 create_test_elements_tracker(&satisfied.debug_symbols);
461 let env = create_test_env();
462
463 let _ = satisfied
464 .redeem()
465 .prune_with_tracker(&env, &mut tracker)
466 .unwrap();
467
468 let debug = debug_store.borrow();
469 assert_eq!(
470 debug.get("get_input_explicit_asset_amount(0)"),
471 Some(
472 &"(0x6d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f, 1000)"
473 .to_string()
474 ),
475 );
476 assert_eq!(
477 debug.get("pair"),
478 Some(
479 &"(Right(0x6d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f), Right(1000))"
480 .to_string()
481 ),
482 );
483 assert_eq!(debug.get("jet::eq_32(20, 21)"), Some(&"false".to_string()));
484
485 let jets = jet_store.borrow();
486
487 assert_eq!(
488 jets.get("num_inputs").unwrap().0.as_deref(),
489 Some([].as_slice())
490 );
491 assert_eq!(jets.get("num_inputs").unwrap().1.as_deref(), Some("1"));
492
493 assert_eq!(
494 jets.get("eq_32").unwrap().0,
495 Some(vec!["20".to_string(), "21".to_string()])
496 );
497 assert_eq!(jets.get("eq_32").unwrap().1.as_deref(), Some("false"));
498
499 assert_eq!(
500 jets.get("input_amount").unwrap().0,
501 Some(vec!["0".to_string()])
502 );
503 assert_eq!(
504 jets.get("input_amount").unwrap().1.as_deref(),
505 Some("Some((Right(0x6d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f), Right(1000)))")
506 );
507 }
508 const TEST_ARITHMETIC_JETS: &str = r#"
509 fn main() {
510
511 let x: u32 = 5;
512 let y: u32 = 4;
513
514 let sum: (bool, u32) = jet::add_32(x, y);
515 let prod: u64 = jet::multiply_32(x, y);
516
517 assert!(jet::eq_64(prod, 20));
518 }
519 "#;
520
521 #[test]
522 fn test_arith_jet_trace_regression() {
523 let env = create_test_env();
524
525 let program =
526 TemplateProgram::new(TEST_ARITHMETIC_JETS, Box::new(ElementsJetHinter::new())).unwrap();
527 let program = program.instantiate(Arguments::default(), true).unwrap();
528 let satisfied = program.satisfy(WitnessValues::default()).unwrap();
529
530 let (mut tracker, _, jet_store) = create_test_elements_tracker(&satisfied.debug_symbols);
531
532 let _ = satisfied.redeem().prune_with_tracker(&env, &mut tracker);
533
534 let jets = jet_store.borrow();
535
536 assert_eq!(
537 jets.get("add_32").unwrap().0,
538 Some(vec!["5".to_string(), "4".to_string()])
539 );
540 assert_eq!(
541 jets.get("add_32").unwrap().1,
542 Some("(false, 9)".to_string())
543 );
544
545 assert_eq!(
546 jets.get("multiply_32").unwrap().0,
547 Some(vec!["5".to_string(), "4".to_string()])
548 );
549 assert_eq!(jets.get("multiply_32").unwrap().1, Some("20".to_string()));
550
551 assert_eq!(
552 jets.get("eq_64").unwrap().0,
553 Some(vec!["20".to_string(), "20".to_string()])
554 );
555 assert_eq!(jets.get("eq_64").unwrap().1, Some("true".to_string()));
556 }
557
558 const TEST_FULL_MULTIPLY_JETS: &str = r#"
559 fn main() {
560 let r8: u16 = jet::full_multiply_8(200, 201, 202, 203);
561 let r16: u32 = jet::full_multiply_16(20000, 20001, 20002, 20003);
562 let r32: u64 = jet::full_multiply_32(2000000000, 2000000001, 2000000002, 2000000003);
563 let r64: u128 = jet::full_multiply_64(2000000000, 2000000001, 2000000002, 2000000003);
564
565 assert!(jet::eq_16(r8, 40605));
566 assert!(jet::eq_32(r16, 400060005));
567 assert!(jet::eq_64(r32, 4000000006000000005));
568
569 // TODO: Currently no eq_128 jet, this must be revised in future. Placeholder to match on 'unwrap().1`.
570 let _keep: u128 = r64;
571 }
572 "#;
573
574 #[test]
575 fn test_full_multiply_jet_trace_regression() {
576 let env = create_test_env();
579
580 let program =
581 TemplateProgram::new(TEST_FULL_MULTIPLY_JETS, Box::new(ElementsJetHinter::new()))
582 .unwrap();
583 let program = program.instantiate(Arguments::default(), true).unwrap();
584 let satisfied = program.satisfy(WitnessValues::default()).unwrap();
585
586 let (mut tracker, _, jet_store) = create_test_elements_tracker(&satisfied.debug_symbols);
587
588 let _ = satisfied.redeem().prune_with_tracker(&env, &mut tracker);
589
590 let jets = jet_store.borrow();
591
592 assert_eq!(
593 jets.get("full_multiply_8").unwrap().0,
594 Some(vec![
595 "200".to_string(),
596 "201".to_string(),
597 "202".to_string(),
598 "203".to_string(),
599 ])
600 );
601 assert_eq!(
602 jets.get("full_multiply_8").unwrap().1,
603 Some("40605".to_string())
604 );
605
606 assert_eq!(
607 jets.get("full_multiply_16").unwrap().0,
608 Some(vec![
609 "20000".to_string(),
610 "20001".to_string(),
611 "20002".to_string(),
612 "20003".to_string(),
613 ])
614 );
615 assert_eq!(
616 jets.get("full_multiply_16").unwrap().1,
617 Some("400060005".to_string())
618 );
619
620 assert_eq!(
621 jets.get("full_multiply_32").unwrap().0,
622 Some(vec![
623 "2000000000".to_string(),
624 "2000000001".to_string(),
625 "2000000002".to_string(),
626 "2000000003".to_string(),
627 ])
628 );
629 assert_eq!(
630 jets.get("full_multiply_32").unwrap().1,
631 Some("4000000006000000005".to_string())
632 );
633
634 assert_eq!(
635 jets.get("full_multiply_64").unwrap().0,
636 Some(vec![
637 "2000000000".to_string(),
638 "2000000001".to_string(),
639 "2000000002".to_string(),
640 "2000000003".to_string(),
641 ])
642 );
643 assert_eq!(
644 jets.get("full_multiply_64").unwrap().1,
645 Some("0x00000000000000003782dad00330bc05".to_string()) );
648 }
649}