diff --git a/packages/programs-react/src/components/CallStackDisplay.tsx b/packages/programs-react/src/components/CallStackDisplay.tsx
index 7f9b087c6..09e2bf7aa 100644
--- a/packages/programs-react/src/components/CallStackDisplay.tsx
+++ b/packages/programs-react/src/components/CallStackDisplay.tsx
@@ -19,10 +19,49 @@ export interface CallStackDisplayProps {
* Shows function names separated by arrows, e.g.:
* main() -> transfer() -> _update()
*/
+function formatArgs(
+ frame: { identifier?: string; stepIndex: number },
+ resolvedCallStack: Array<{
+ stepIndex: number;
+ resolvedArgs?: Array<{
+ name: string;
+ value?: string;
+ }>;
+ }>,
+): string {
+ const resolved = resolvedCallStack.find(
+ (r) => r.stepIndex === frame.stepIndex,
+ );
+ if (!resolved?.resolvedArgs) {
+ return "";
+ }
+ return resolved.resolvedArgs
+ .map((arg) => {
+ if (arg.value === undefined) {
+ return arg.name;
+ }
+ const decimal = formatAsDecimal(arg.value);
+ return `${arg.name}: ${decimal}`;
+ })
+ .join(", ");
+}
+
+function formatAsDecimal(hex: string): string {
+ try {
+ const n = BigInt(hex);
+ if (n <= 9999n) {
+ return n.toString();
+ }
+ return hex;
+ } catch {
+ return hex;
+ }
+}
+
export function CallStackDisplay({
className = "",
}: CallStackDisplayProps): JSX.Element {
- const { callStack, jumpToStep } = useTraceContext();
+ const { callStack, resolvedCallStack, jumpToStep } = useTraceContext();
if (callStack.length === 0) {
return (
@@ -53,7 +92,7 @@ export function CallStackDisplay({
{frame.identifier || "(anonymous)"}
- ({frame.argumentNames ? frame.argumentNames.join(", ") : ""})
+ ({formatArgs(frame, resolvedCallStack)})
diff --git a/packages/programs-react/src/components/TraceContext.tsx b/packages/programs-react/src/components/TraceContext.tsx
index c086daf26..581b72143 100644
--- a/packages/programs-react/src/components/TraceContext.tsx
+++ b/packages/programs-react/src/components/TraceContext.tsx
@@ -9,6 +9,7 @@ import React, {
useCallback,
useEffect,
useMemo,
+ useRef,
} from "react";
import type { Pointer, Program } from "@ethdebug/format";
import { dereference, Data } from "@ethdebug/pointers";
@@ -119,6 +120,24 @@ export interface ResolvedCallInfo {
pointerRefs: ResolvedPointerRef[];
}
+/**
+ * A call frame with resolved argument values.
+ */
+export interface ResolvedCallFrame {
+ /** Function name */
+ identifier?: string;
+ /** The step index where this call was invoked */
+ stepIndex: number;
+ /** The call type */
+ callType?: "internal" | "external" | "create";
+ /** Argument names paired with resolved values */
+ resolvedArgs?: Array<{
+ name: string;
+ value?: string;
+ error?: string;
+ }>;
+}
+
/**
* State provided by the Trace context.
*/
@@ -139,6 +158,8 @@ export interface TraceState {
currentVariables: ResolvedVariable[];
/** Call stack at current step */
callStack: CallFrame[];
+ /** Call stack with resolved argument values */
+ resolvedCallStack: ResolvedCallFrame[];
/** Call info for current instruction (if any) */
currentCallInfo: ResolvedCallInfo | undefined;
/** Whether we're at the first step */
@@ -339,6 +360,97 @@ export function TraceProvider({
[trace, pcToInstruction, currentStepIndex],
);
+ // Resolve argument values for call stack frames.
+ // Cache by stepIndex so we don't re-resolve frames that
+ // haven't changed when the user steps forward.
+ const argCacheRef = useRef