@@ -9,6 +9,7 @@ import React, {
99 useCallback ,
1010 useEffect ,
1111 useMemo ,
12+ useRef ,
1213} from "react" ;
1314import type { Pointer , Program } from "@ethdebug/format" ;
1415import { dereference , Data } from "@ethdebug/pointers" ;
@@ -119,6 +120,24 @@ export interface ResolvedCallInfo {
119120 pointerRefs : ResolvedPointerRef [ ] ;
120121}
121122
123+ /**
124+ * A call frame with resolved argument values.
125+ */
126+ export interface ResolvedCallFrame {
127+ /** Function name */
128+ identifier ?: string ;
129+ /** The step index where this call was invoked */
130+ stepIndex : number ;
131+ /** The call type */
132+ callType ?: "internal" | "external" | "create" ;
133+ /** Argument names paired with resolved values */
134+ resolvedArgs ?: Array < {
135+ name : string ;
136+ value ?: string ;
137+ error ?: string ;
138+ } > ;
139+ }
140+
122141/**
123142 * State provided by the Trace context.
124143 */
@@ -139,6 +158,8 @@ export interface TraceState {
139158 currentVariables : ResolvedVariable [ ] ;
140159 /** Call stack at current step */
141160 callStack : CallFrame [ ] ;
161+ /** Call stack with resolved argument values */
162+ resolvedCallStack : ResolvedCallFrame [ ] ;
142163 /** Call info for current instruction (if any) */
143164 currentCallInfo : ResolvedCallInfo | undefined ;
144165 /** Whether we're at the first step */
@@ -339,6 +360,97 @@ export function TraceProvider({
339360 [ trace , pcToInstruction , currentStepIndex ] ,
340361 ) ;
341362
363+ // Resolve argument values for call stack frames.
364+ // Cache by stepIndex so we don't re-resolve frames that
365+ // haven't changed when the user steps forward.
366+ const argCacheRef = useRef < Map < number , ResolvedCallFrame [ "resolvedArgs" ] > > (
367+ new Map ( ) ,
368+ ) ;
369+
370+ const [ resolvedCallStack , setResolvedCallStack ] = useState <
371+ ResolvedCallFrame [ ]
372+ > ( [ ] ) ;
373+
374+ useEffect ( ( ) => {
375+ if ( callStack . length === 0 ) {
376+ setResolvedCallStack ( [ ] ) ;
377+ return ;
378+ }
379+
380+ // Build initial resolved frames using cached values
381+ const initial : ResolvedCallFrame [ ] = callStack . map ( ( frame ) => ( {
382+ identifier : frame . identifier ,
383+ stepIndex : frame . stepIndex ,
384+ callType : frame . callType ,
385+ resolvedArgs : argCacheRef . current . get ( frame . stepIndex ) ,
386+ } ) ) ;
387+ setResolvedCallStack ( initial ) ;
388+
389+ if ( ! shouldResolve ) {
390+ return ;
391+ }
392+
393+ let cancelled = false ;
394+ const resolved = [ ...initial ] ;
395+
396+ // Resolve frames that aren't cached yet
397+ const promises = callStack . map ( async ( frame , index ) => {
398+ if ( argCacheRef . current . has ( frame . stepIndex ) ) {
399+ return ;
400+ }
401+
402+ const names = frame . argumentNames ;
403+ const pointers = frame . argumentPointers ;
404+ if ( ! pointers || pointers . length === 0 ) {
405+ return ;
406+ }
407+
408+ const step = trace [ frame . stepIndex ] ;
409+ if ( ! step ) {
410+ return ;
411+ }
412+
413+ const args : NonNullable < ResolvedCallFrame [ "resolvedArgs" ] > = pointers . map (
414+ ( _ , i ) => ( {
415+ name : names ?. [ i ] ?? `_${ i } ` ,
416+ } ) ,
417+ ) ;
418+
419+ const resolvePromises = pointers . map ( async ( ptr , i ) => {
420+ try {
421+ const value = await resolveVariableValue (
422+ ptr as Pointer ,
423+ step ,
424+ templates ,
425+ ) ;
426+ args [ i ] = { ...args [ i ] , value } ;
427+ } catch ( err ) {
428+ args [ i ] = {
429+ ...args [ i ] ,
430+ error : err instanceof Error ? err . message : String ( err ) ,
431+ } ;
432+ }
433+ } ) ;
434+
435+ await Promise . all ( resolvePromises ) ;
436+
437+ if ( ! cancelled ) {
438+ argCacheRef . current . set ( frame . stepIndex , args ) ;
439+ resolved [ index ] = {
440+ ...resolved [ index ] ,
441+ resolvedArgs : args ,
442+ } ;
443+ setResolvedCallStack ( [ ...resolved ] ) ;
444+ }
445+ } ) ;
446+
447+ Promise . all ( promises ) . catch ( ( ) => { } ) ;
448+
449+ return ( ) => {
450+ cancelled = true ;
451+ } ;
452+ } , [ callStack , shouldResolve , trace , templates ] ) ;
453+
342454 // Extract call info for current instruction (synchronous)
343455 const extractedCallInfo = useMemo ( ( ) : CallInfo | undefined => {
344456 if ( ! currentInstruction ) {
@@ -488,6 +600,7 @@ export function TraceProvider({
488600 currentInstruction,
489601 currentVariables,
490602 callStack,
603+ resolvedCallStack,
491604 currentCallInfo,
492605 isAtStart : currentStepIndex === 0 ,
493606 isAtEnd : currentStepIndex >= trace . length - 1 ,
0 commit comments