@@ -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