@@ -614,30 +614,52 @@ def test_trace_transaction_missing_hash_returns_invalid_params(server: StellarRp
614614
615615
616616def test_trace_transaction_returns_full_instruction_trace_for_foo (server : StellarRpcServer ) -> None :
617- """traceTransaction returns the complete, ordered instruction trace of an invocation.
617+ """traceTransaction returns the complete, ordered trace of an invocation: a ``callContract``
618+ entry frame, the executed WebAssembly instructions, and an ``endWasm`` exit frame.
618619
619620 empty.wat's ``foo()`` body is a single ``i64.const 2`` (the Void return); the three leading
620- records are the contract's global initialisation and the ``block`` is the function frame.
621- This is the exact trace shown in the README, asserted record-for-record so any drift in the
622- format, ordering, or the array-vs-string shape of the result is caught.
621+ instruction records are the contract's global initialisation and the ``block`` is the
622+ function frame. The instruction records are asserted record-for-record (the exact trace
623+ shown in the README) so any drift in format, ordering, or the array-vs-string shape of the
624+ result is caught. The entry/exit frames carry per-run contract and account ids, so they are
625+ checked structurally rather than by value.
623626 """
624627 invoke = _deploy_and_get_invoker (server , EMPTY_CONTRACT_WAT )
625628 tx_hash = invoke ('foo' )
626629
627630 trace = _rpc (server .port (), 'traceTransaction' , {'hash' : tx_hash })['result' ]
628631
629- assert trace == [
632+ # A callContract entry frame opens the trace: the account calls foo() on the contract with
633+ # no arguments at call depth 1.
634+ entry = trace [0 ]
635+ assert entry ['instr' ] == ['callContract' ]
636+ assert entry ['function' ] == 'foo'
637+ assert entry ['args' ] == []
638+ assert entry ['depth' ] == 1
639+ assert entry ['from' ]['addrType' ] == 'account'
640+ assert entry ['to' ]['addrType' ] == 'contract'
641+
642+ # The executed WebAssembly instructions, exactly as shown in the README.
643+ assert trace [1 :- 1 ] == [
630644 {'pos' : 3 , 'instr' : ['const' , 'i32' , 1048576 ], 'stack' : [], 'locals' : {}},
631645 {'pos' : 11 , 'instr' : ['const' , 'i32' , 1048576 ], 'stack' : [], 'locals' : {}},
632646 {'pos' : 19 , 'instr' : ['const' , 'i32' , 1048576 ], 'stack' : [], 'locals' : {}},
633647 {'pos' : None , 'instr' : ['block' ], 'stack' : [], 'locals' : {}},
634648 {'pos' : 3 , 'instr' : ['const' , 'i64' , 2 ], 'stack' : [], 'locals' : {}},
635649 ]
636650
651+ # An endWasm exit frame closes the trace: the call succeeded and returned Void.
652+ exit_frame = trace [- 1 ]
653+ assert exit_frame ['instr' ] == ['endWasm' ]
654+ assert exit_frame ['success' ] is True
655+ assert exit_frame ['result' ] == {'type' : 'void' }
656+ assert exit_frame ['depth' ] == 1
657+
637658
638659def test_trace_records_have_expected_structure_and_reflect_arguments (server : StellarRpcServer ) -> None :
639- """Each trace record is a ``{pos, instr, stack, locals}`` object, and for a call that takes
640- arguments the decoded arguments are bound as locals while intermediate values build up on the
660+ """The trace opens with a ``callContract`` frame that echoes the decoded arguments, and each
661+ WebAssembly instruction record is a ``{pos, instr, stack, locals}`` object. For a call that
662+ takes arguments the arguments are bound as locals while intermediate values build up on the
641663 stack — exercising a richer trace than the argument-less ``foo()`` case.
642664 """
643665 invoke = _deploy_and_get_invoker (server , ARGS_CONTRACT_WAT )
@@ -655,7 +677,22 @@ def test_trace_records_have_expected_structure_and_reflect_arguments(server: Ste
655677
656678 assert isinstance (trace , list )
657679 assert len (trace ) > 0
658- for record in trace :
680+
681+ # The callContract entry frame echoes the call target and its decoded arguments.
682+ entry = trace [0 ]
683+ assert entry ['instr' ] == ['callContract' ]
684+ assert entry ['function' ] == 'test_integers'
685+ assert entry ['args' ] == [
686+ {'type' : 'u32' , 'value' : 42 },
687+ {'type' : 'i32' , 'value' : - 7 },
688+ {'type' : 'u64' , 'value' : 100 },
689+ {'type' : 'i64' , 'value' : - 200 },
690+ ]
691+
692+ # The instruction records (everything between the call-boundary frames) share one shape.
693+ instr_records = [record for record in trace if 'locals' in record ]
694+ assert instr_records
695+ for record in instr_records :
659696 assert set (record ) == {'pos' , 'instr' , 'stack' , 'locals' }
660697 assert record ['pos' ] is None or isinstance (record ['pos' ], int )
661698 assert isinstance (record ['instr' ], list ) and record ['instr' ]
@@ -667,12 +704,12 @@ def test_trace_records_have_expected_structure_and_reflect_arguments(server: Ste
667704 assert all (isinstance (e , list ) and len (e ) == 2 and isinstance (e [0 ], str ) for e in record ['locals' ].values ())
668705
669706 # The four call arguments are bound as locals 0..3 by the time the body runs.
670- locals_seen = {key for record in trace for key in record ['locals' ]}
707+ locals_seen = {key for record in instr_records for key in record ['locals' ]}
671708 assert {'0' , '1' , '2' , '3' } <= locals_seen
672709 # Intermediate computation puts values on the stack at some point.
673- assert any (record ['stack' ] for record in trace )
710+ assert any (record ['stack' ] for record in instr_records )
674711 # The function body returns Void: the final instruction pushes the i64 constant 2.
675- assert trace [- 1 ]['instr' ] == ['const' , 'i64' , 2 ]
712+ assert instr_records [- 1 ]['instr' ] == ['const' , 'i64' , 2 ]
676713
677714
678715def test_call_tx_with_args (server : StellarRpcServer ) -> None :
0 commit comments