Skip to content

Cross-Contract Call Tracing

Source: docs/design/cross-contract-call-tracing.md

Status: Draft Issue: chain-3gy7a Date: 2026-02-03

Cross-contract call tracing captures the full call tree for a single VM execution. Each call frame records gas usage, inputs/outputs, storage operations, events, and nested calls so debugging and profiling can happen with full context.

Tracing is represented by a TraceFrame tree in crates/vm-runtime and mirrored into src/core/execution/types.rs for node-level use. Each frame includes:

  • Call metadata: call type, caller, callee, value, gas limit, gas used.
  • Payloads: input calldata and output bytes.
  • Side effects: storage ops (read/write/delete) with before/after values and gas.
  • Events: emitted logs with contract + topics + data.
  • Children: nested call frames.
  • Error metadata and a wall-clock duration in nanoseconds.

Related types:

  • TraceStorageOp and TraceStorageOpType
  • TraceLogEntry
  • TraceError
  • TracingConfig (max depth)
  1. TxContext::enable_tracing(TracingConfig { max_depth }) turns tracing on.
  2. Syscall handlers call enter_call_frame on call entry and exit_call_frame on return.
  3. Storage reads/writes and log emissions are pushed into the current frame via trace_storage_op and trace_event.
  4. take_root_trace() returns the root TraceFrame tree for formatting or post-processing.

Depth is capped by TracingConfig.max_depth (default: 64). Frames beyond the limit are suppressed rather than partially recorded.

crates/vm-runtime/src/trace_format.rs renders the trace tree into:

  • JSON (to_json, to_json_compact, to_json_with_metadata)
  • Chrome trace format (to_chrome_trace / to_chrome_trace_pretty)
  • Human-readable tree output (to_tree / to_tree_with_config)

src/core/execution/trace_format.rs mirrors similar formatters for core TraceFrame types at the node layer.

These helpers live in crates/vm-runtime and can be wired into higher-level interfaces as needed:

  • trace_compress: deduplicates repeated subtrees to keep large traces small.
  • trace_filter: selective tracing by contract, depth range, gas threshold, value transfer, selector, or failure-only.
  • trace_diff: structured comparison between two traces for regressions.
  • trace_summary: aggregates net storage deltas, events, touched contracts, and gas/time stats.

The RPC IDL defines hierarchical trace endpoints:

  • chain_traceTransaction: returns a detailed frame tree with storage ops and events (TraceFrameDetailed). Accepts max_depth.
  • chain_traceCall: returns a simpler frame tree (TraceFrame) for view calls.

See src/rpc/node_rpc_v1.idl for the request/response fields.

src/debug/trace.rs stores flat execution traces (ExecutionTrace) built from VmTxTrace (storage writes, logs, transfers, return data). Storage is enabled with environment variables:

  • ASHEN_DEBUG_TRACE_DIR
  • ASHEN_DEBUG_TRACE_MAX_BLOCKS
  • ASHEN_DEBUG_TRACE_MAX_BYTES

This path is for deterministic replay and audit trails, not the hierarchical call tree.

  • Runtime tracing core: crates/vm-runtime/src/context.rs
  • Formatters: crates/vm-runtime/src/trace_format.rs
  • Compression/diff/filter/summary: crates/vm-runtime/src/trace_*.rs
  • Core type bridge: src/core/execution/types.rs
  • RPC definitions: src/rpc/node_rpc_v1.idl
  • Trace storage: src/debug/trace.rs