From 353805acde158eb5695a6fd9a0c4c6ce2d2a0c0a Mon Sep 17 00:00:00 2001 From: metalurgical <97008724+metalurgical@users.noreply.github.com> Date: Sat, 4 Apr 2026 03:11:13 +0200 Subject: [PATCH] fix: Correct FullMultiply signatures and tracker argument decoding Fix incorrect signatures for FullMultiply. Additionally fixes a bug in tracker, which was surfaced by this fix so that it correctly decodes inputs. Changes: - FullMultiply((A,B),(C,D)) signatures corrected to FullMultiply(A,B,C,D) - Fix argument parsing in tracker by utilizing Unfolder - Removes `collect_product_elements` helper. - Adds regression test covering FullMultiply functions Furthermore, this keeps the semantics unchanged ((A*B) + C + D) while fixing the API surface and ensuring tracker compatibility. --- src/jet.rs | 8 ++-- src/tracker.rs | 124 ++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/jet.rs b/src/jet.rs index cf6a1fcb..209bc577 100644 --- a/src/jet.rs +++ b/src/jet.rs @@ -323,10 +323,10 @@ pub fn source_type(jet: Elements) -> Vec { Elements::FullIncrement16 | Elements::FullDecrement16 => vec![bool(), U16.into()], Elements::FullIncrement32 | Elements::FullDecrement32 => vec![bool(), U32.into()], Elements::FullIncrement64 | Elements::FullDecrement64 => vec![bool(), U64.into()], - Elements::FullMultiply8 => vec![tuple([U8, U8]), tuple([U8, U8])], - Elements::FullMultiply16 => vec![tuple([U16, U16]), tuple([U16, U16])], - Elements::FullMultiply32 => vec![tuple([U32, U32]), tuple([U32, U32])], - Elements::FullMultiply64 => vec![tuple([U64, U64]), tuple([U64, U64])], + Elements::FullMultiply8 => vec![U8.into(), U8.into(), U8.into(), U8.into()], + Elements::FullMultiply16 => vec![U16.into(), U16.into(), U16.into(), U16.into()], + Elements::FullMultiply32 => vec![U32.into(), U32.into(), U32.into(), U32.into()], + Elements::FullMultiply64 => vec![U64.into(), U64.into(), U64.into(), U64.into()], Elements::Median8 => vec![U8.into(), U8.into(), U8.into()], Elements::Median16 => vec![U16.into(), U16.into(), U16.into()], Elements::Median32 => vec![U32.into(), U32.into(), U32.into()], diff --git a/src/tracker.rs b/src/tracker.rs index 4a6f693a..631a4d62 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -1,8 +1,9 @@ use simplicity::bit_machine::{ExecTracker, FrameIter, NodeOutput, PruneTracker, SetTracker}; use simplicity::jet::{Elements, Jet}; use simplicity::node::Inner; -use simplicity::{Ihr, RedeemNode, Value as SimValue, ValueRef}; +use simplicity::{Ihr, RedeemNode, Value as SimValue}; +use crate::array::Unfolder; use crate::debug::{DebugSymbols, TrackedCallName}; use crate::either::Either; use crate::jet::{source_type, target_type}; @@ -338,43 +339,20 @@ fn parse_jet_arguments(jet: Elements, input_frame: &mut FrameIter) -> Result, -) -> Result<(), String> { - match count { - 0 => Ok(()), - 1 => { - elements.push(node.to_value()); - Ok(()) - } - _ => { - let (left, right) = node - .as_product() - .ok_or("expected product type while collecting arguments")?; - elements.push(left.to_value()); - collect_product_elements(&right, count - 1, elements) - } - } -} - /// Resolves an aliased type to its concrete form. fn resolve_jet_type(aliased_type: &AliasedType) -> ResolvedType { aliased_type @@ -572,4 +550,94 @@ mod tests { ); assert_eq!(jets.get("eq_64").unwrap().1, Some("true".to_string())); } + + const TEST_FULL_MULTIPLY_JETS: &str = r#" + fn main() { + let r8: u16 = jet::full_multiply_8(200, 201, 202, 203); + let r16: u32 = jet::full_multiply_16(20000, 20001, 20002, 20003); + let r32: u64 = jet::full_multiply_32(2000000000, 2000000001, 2000000002, 2000000003); + let r64: u128 = jet::full_multiply_64(2000000000, 2000000001, 2000000002, 2000000003); + + assert!(jet::eq_16(r8, 40605)); + assert!(jet::eq_32(r16, 400060005)); + assert!(jet::eq_64(r32, 4000000006000000005)); + + // TODO: Currently no eq_128 jet, this must be revised in future. Placeholder to match on 'unwrap().1`. + let _keep: u128 = r64; + } + "#; + + #[test] + fn test_full_multiply_jet_trace_regression() { + // FullMultiply -> (a * b + c + d) + + let env = create_test_env(); + + let program = TemplateProgram::new(TEST_FULL_MULTIPLY_JETS).unwrap(); + let program = program.instantiate(Arguments::default(), true).unwrap(); + let satisfied = program.satisfy(WitnessValues::default()).unwrap(); + + let (mut tracker, _, jet_store) = create_test_tracker(&satisfied.debug_symbols); + + let _ = satisfied.redeem().prune_with_tracker(&env, &mut tracker); + + let jets = jet_store.borrow(); + + assert_eq!( + jets.get("full_multiply_8").unwrap().0, + Some(vec![ + "200".to_string(), + "201".to_string(), + "202".to_string(), + "203".to_string(), + ]) + ); + assert_eq!( + jets.get("full_multiply_8").unwrap().1, + Some("40605".to_string()) + ); + + assert_eq!( + jets.get("full_multiply_16").unwrap().0, + Some(vec![ + "20000".to_string(), + "20001".to_string(), + "20002".to_string(), + "20003".to_string(), + ]) + ); + assert_eq!( + jets.get("full_multiply_16").unwrap().1, + Some("400060005".to_string()) + ); + + assert_eq!( + jets.get("full_multiply_32").unwrap().0, + Some(vec![ + "2000000000".to_string(), + "2000000001".to_string(), + "2000000002".to_string(), + "2000000003".to_string(), + ]) + ); + assert_eq!( + jets.get("full_multiply_32").unwrap().1, + Some("4000000006000000005".to_string()) + ); + + assert_eq!( + jets.get("full_multiply_64").unwrap().0, + Some(vec![ + "2000000000".to_string(), + "2000000001".to_string(), + "2000000002".to_string(), + "2000000003".to_string(), + ]) + ); + assert_eq!( + jets.get("full_multiply_64").unwrap().1, + // Check: u128 defaults to hex in fmt::Display for UIntValue + Some("0x00000000000000003782dad00330bc05".to_string()) // u128 => 4000000006000000005 + ); + } }