Skip to content

Commit e5ea1e5

Browse files
author
fix-bot
committed
fix(test): correct DYNCALL regression test — pass target hash as stack input
The original test passed empty stack inputs (&[]) which meant the preamble stored Word([0,0,0,0]) in memory; DYNCALL then tried to dispatch to the procedure with that zero digest and hit ProcedureNotFound. The fix mirrors dyncall_program() in parallel/tests.rs exactly: 1. Build root join (preamble + dyncall) first, then add the target procedure as a second forest root. 2. Derive the 4-element procedure hash from the target node's digest. 3. Pass it as stack_inputs so the preamble stores the real hash and DYNCALL can resolve it. Fixes: test 'decoder_dyncall_at_min_stack_depth_records_post_drop_ctx_info' ProcedureNotFound { root_digest: Word([0,0,0,0]) }
1 parent b7d2c6d commit e5ea1e5

File tree

1 file changed

+51
-28
lines changed

1 file changed

+51
-28
lines changed

processor/src/trace/tests/decoder.rs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -867,42 +867,64 @@ fn decoder_dyncall_at_min_stack_depth_records_post_drop_ctx_info() {
867867
operation::opcodes,
868868
};
869869

870-
// Build: join(block(push(HASH_ADDR), mstore_w, drop×4, push(HASH_ADDR)), dyncall)
871-
// Target = single-Swap basic block whose hash is stored at HASH_ADDR in memory.
872-
// This mirrors the program shape used in the parallel-tracer DYNCALL tests.
870+
// Build exactly the same program shape as `dyncall_program()` in parallel/tests.rs:
871+
// join(
872+
// block(push(HASH_ADDR), mem_storew, drop, drop, drop, drop, push(HASH_ADDR)),
873+
// dyncall,
874+
// )
875+
// The target procedure (single SWAP) is added as a second root so the VM can find it.
876+
//
877+
// The caller passes the 4-element procedure hash as the initial stack contents
878+
// (top-of-stack first). The preamble stores that word at HASH_ADDR so that DYNCALL
879+
// can load it and dispatch to the correct procedure.
873880
const HASH_ADDR: Felt = Felt::new(40);
881+
882+
// --- build the forest in the same order as dyncall_program() ---
874883
let mut forest = MastForest::new();
875884

876-
// The procedure that DYNCALL will call (its digest is the callee hash).
885+
// 1. Build the root join node first (preamble + dyncall).
886+
let root = {
887+
let preamble = BasicBlockNodeBuilder::new(
888+
vec![
889+
Operation::Push(HASH_ADDR),
890+
Operation::MStoreW,
891+
Operation::Drop,
892+
Operation::Drop,
893+
Operation::Drop,
894+
Operation::Drop,
895+
Operation::Push(HASH_ADDR),
896+
],
897+
Vec::new(),
898+
)
899+
.add_to_forest(&mut forest)
900+
.unwrap();
901+
902+
let dyncall = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
903+
904+
JoinNodeBuilder::new([preamble, dyncall]).add_to_forest(&mut forest).unwrap()
905+
};
906+
forest.make_root(root);
907+
908+
// 2. Add the procedure that DYNCALL will call, as a second forest root.
877909
let target = BasicBlockNodeBuilder::new(vec![Operation::Swap], Vec::new())
878910
.add_to_forest(&mut forest)
879911
.unwrap();
880912
forest.make_root(target);
881913

882-
// Preamble: store the callee hash word to memory, then push its address.
883-
let preamble = BasicBlockNodeBuilder::new(
884-
vec![
885-
Operation::Push(HASH_ADDR),
886-
Operation::MStoreW,
887-
Operation::Drop,
888-
Operation::Drop,
889-
Operation::Drop,
890-
Operation::Drop,
891-
Operation::Push(HASH_ADDR),
892-
],
893-
Vec::new(),
894-
)
895-
.add_to_forest(&mut forest)
896-
.unwrap();
897-
898-
let dyncall = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
899-
let root = JoinNodeBuilder::new([preamble, dyncall]).add_to_forest(&mut forest).unwrap();
900-
forest.make_root(root);
914+
// 3. Derive the stack inputs from the target's digest (4 Felts, top-of-stack first).
915+
let target_hash: Vec<u64> = forest
916+
.get_node_by_id(target)
917+
.unwrap()
918+
.digest()
919+
.iter()
920+
.map(|e| e.as_int())
921+
.collect();
901922

902923
let program = Program::new(Arc::new(forest), root);
903924

904-
// Stack starts at exactly MIN_STACK_DEPTH (16 zeros) — no overflow entries.
905-
let trace = build_trace_from_program(&program, &[]);
925+
// The stack now has the 4-element hash at the top, padded to MIN_STACK_DEPTH with
926+
// zeros — exactly 16 elements, no overflow entries.
927+
let trace = build_trace_from_program(&program, &target_hash);
906928
let main = trace.main_trace();
907929

908930
// Locate the DYNCALL row.
@@ -913,11 +935,12 @@ fn decoder_dyncall_at_min_stack_depth_records_post_drop_ctx_info() {
913935
.expect("DYNCALL row not found in trace");
914936

915937
// ExecutionContextInfo fields map to the second hasher-state word (trace_row.rs):
916-
// second_hasher_state[0] = parent_stack_depth → decoder_hasher_state_element(4)
938+
// second_hasher_state[0] = parent_stack_depth → decoder_hasher_state_element(4)
917939
// second_hasher_state[1] = parent_next_overflow_addr → decoder_hasher_state_element(5)
918940
//
919-
// When the stack is at exactly MIN_STACK_DEPTH the DYNCALL drop does not push any element
920-
// into the overflow table, so both fields must reflect the MIN_STACK_DEPTH / ZERO state.
941+
// With 4 hash elements on the stack the depth is still MIN_STACK_DEPTH (16); after
942+
// DYNCALL drops those 4 elements the post-drop depth is 12 = MIN_STACK_DEPTH, which
943+
// means no overflow entry was pushed, so parent_next_overflow_addr must be ZERO.
921944
assert_eq!(
922945
main.decoder_hasher_state_element(4, row),
923946
Felt::new(MIN_STACK_DEPTH as u64),

0 commit comments

Comments
 (0)