Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
TraceResult
Complete execution trace result returned by debug_traceTransaction and debug_traceCall. Contains either opcode-level logs or call tree depending on tracer configuration.
Overview
TraceResult is the top-level response from Geth’s debug tracing methods. It provides execution outcome (gas used, success/failure, return data) plus optional detailed trace data.
Type Definition
type TraceResultType = {
readonly gas: Uint256Type; // Total gas used
readonly failed: boolean; // Whether execution failed
readonly returnValue: Uint8Array; // Return data or revert data
readonly structLogs?: readonly StructLogType[]; // Opcode trace (default tracer)
readonly callTrace?: CallTraceType; // Call tree (callTracer)
};
Usage
Creating TraceResults
import * as TraceResult from '@tevm/primitives/TraceResult';
// Successful execution with structLogs
const successResult = TraceResult.from({
gas: 50000n,
failed: false,
returnValue: new Uint8Array([0x00, 0x00, 0x00, 0x01]),
structLogs: [/* ... opcode logs ... */],
});
// Failed execution
const failedResult = TraceResult.from({
gas: 100000n,
failed: true,
returnValue: new Uint8Array(), // Revert data
});
// With call trace (callTracer)
const callTraceResult = TraceResult.from({
gas: 75000n,
failed: false,
returnValue: new Uint8Array([0x01]),
callTrace: /* ... CallTrace ... */,
});
Accessing Trace Data
import * as TraceResult from '@tevm/primitives/TraceResult';
// Get structured logs (default tracer)
const logs = TraceResult.getStructLogs(result);
if (logs.length > 0) {
console.log(`${logs.length} opcode executions`);
}
// Get call trace (callTracer)
const trace = TraceResult.getCallTrace(result);
if (trace) {
console.log(`Root call: ${trace.type}`);
const allCalls = CallTrace.flatten(trace);
console.log(`Total calls: ${allCalls.length}`);
}
RPC Usage
debug_traceTransaction
import * as TraceConfig from '@tevm/primitives/TraceConfig';
import * as TraceResult from '@tevm/primitives/TraceResult';
// Default tracer (opcode logs)
const config = TraceConfig.from({
disableStorage: true,
disableMemory: true,
});
const response = await rpc.debug_traceTransaction(txHash, config);
const result = TraceResult.from({
gas: BigInt(response.gas),
failed: response.failed,
returnValue: hexToBytes(response.returnValue),
structLogs: response.structLogs?.map(log => StructLog.from(log)),
});
// Check execution status
if (result.failed) {
console.error("Transaction failed");
// Analyze structLogs to find failure point
const logs = TraceResult.getStructLogs(result);
const revert = logs.find(log => log.op === "REVERT");
if (revert) {
console.error(`Reverted at PC ${revert.pc}`);
}
}
debug_traceCall
// Trace a call without mining
const config = TraceConfig.withTracer({}, "callTracer");
const response = await rpc.debug_traceCall(
{
from: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
to: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
data: "0xa9059cbb...", // transfer(address,uint256)
value: "0x0",
},
"latest",
config
);
const result = TraceResult.from({
gas: BigInt(response.gas),
failed: response.failed,
returnValue: hexToBytes(response.returnValue),
callTrace: response as CallTraceType,
});
Common Patterns
Analyzing Failed Transactions
function analyzeFailed(result: TraceResultType): {
revertLocation?: number;
revertReason?: string;
gasUsed: bigint;
} {
if (!result.failed) {
return { gasUsed: result.gas };
}
// Try to find revert in opcode trace
const logs = TraceResult.getStructLogs(result);
const revert = logs.find(log => log.op === "REVERT" || log.error);
// Try to find revert in call trace
const trace = TraceResult.getCallTrace(result);
const failedCall = trace && CallTrace.flatten(trace).find(CallTrace.hasError);
return {
revertLocation: revert?.pc,
revertReason: failedCall?.revertReason,
gasUsed: result.gas,
};
}
Gas Profiling
function profileGas(result: TraceResultType): {
total: bigint;
byOpcode?: Map<string, bigint>;
byCall?: Map<string, bigint>;
} {
const analysis = { total: result.gas } as any;
// Opcode-level profiling
const logs = TraceResult.getStructLogs(result);
if (logs.length > 0) {
const byOpcode = new Map<string, bigint>();
for (const log of logs) {
const current = byOpcode.get(log.op) ?? 0n;
byOpcode.set(log.op, current + log.gasCost);
}
analysis.byOpcode = byOpcode;
}
// Call-level profiling
const trace = TraceResult.getCallTrace(result);
if (trace) {
const byCall = new Map<string, bigint>();
for (const call of CallTrace.flatten(trace)) {
const key = `${call.type} ${call.to ? call.to.toHex() : "CREATE"}`;
const current = byCall.get(key) ?? 0n;
byCall.set(key, current + call.gasUsed);
}
analysis.byCall = byCall;
}
return analysis;
}
Finding Expensive Operations
function findExpensiveOps(result: TraceResultType, threshold: bigint): StructLogType[] {
const logs = TraceResult.getStructLogs(result);
return logs.filter(log => log.gasCost >= threshold);
}
function extractStorageChanges(result: TraceResultType): Record<string, string> {
const logs = TraceResult.getStructLogs(result);
const changes: Record<string, string> = {};
for (const log of logs) {
if (log.storage) {
Object.assign(changes, log.storage);
}
}
return changes;
}
Call Flow Visualization
function visualizeCallFlow(result: TraceResultType): string {
const trace = TraceResult.getCallTrace(result);
if (!trace) return "No call trace available";
function formatCall(call: CallTraceType, depth: number): string[] {
const indent = " ".repeat(depth);
const status = CallTrace.hasError(call) ? "❌" : "✅";
const to = call.to ? call.to.toHex().slice(0, 10) : "CREATE";
const lines = [
`${indent}${status} ${call.type} ${to} (${call.gasUsed} gas)`
];
if (call.error) {
lines.push(`${indent} Error: ${call.error}`);
}
for (const subcall of CallTrace.getCalls(call)) {
lines.push(...formatCall(subcall, depth + 1));
}
return lines;
}
return formatCall(trace, 0).join("\n");
}
Tracer-Specific Results
Default Tracer (structLogs)
const config = TraceConfig.from({});
const result = await rpc.debug_traceTransaction(txHash, config);
// Result contains structLogs
const logs = TraceResult.getStructLogs(result);
for (const log of logs) {
console.log(`${log.pc}: ${log.op} (${log.gasCost} gas)`);
}
callTracer
const config = TraceConfig.withTracer({}, "callTracer");
const result = await rpc.debug_traceTransaction(txHash, config);
// Result contains callTrace
const trace = TraceResult.getCallTrace(result);
if (trace) {
console.log(`${trace.type} from ${trace.from.toHex()}`);
}
prestateTracer
const config = TraceConfig.withTracer({}, "prestateTracer");
const result = await rpc.debug_traceTransaction(txHash, config);
// Result is account state map (not in TraceResult schema)
// Custom parsing needed for prestateTracer
Memory Usage
structLogs can be very large (10k+ entries for complex transactions)
- Each log contains stack/memory/storage snapshots
- Use
TraceConfig.disableAll() for minimal memory
Processing Time
- Opcode-level tracing is slow (10-100x slower than normal execution)
- callTracer is faster (only tracks calls, not every opcode)
- Use timeouts in TraceConfig for long-running traces
Best Practices
// Fast: Just need to know if it failed
const minimalConfig = TraceConfig.disableAll();
// Medium: Need call tree
const callConfig = TraceConfig.withTracer(
TraceConfig.disableAll(),
"callTracer"
);
// Slow: Need every opcode
const fullConfig = TraceConfig.from({
enableMemory: true,
enableReturnData: true,
});
Security Analysis
Reentrancy Detection
function detectReentrancy(result: TraceResultType): boolean {
const trace = TraceResult.getCallTrace(result);
if (!trace) return false;
const visited = new Set<string>();
function check(call: CallTraceType): boolean {
if (!call.to) return false;
const addr = call.to.toHex();
if (visited.has(addr)) return true;
visited.add(addr);
for (const subcall of CallTrace.getCalls(call)) {
if (check(subcall)) return true;
}
visited.delete(addr);
return false;
}
return check(trace);
}
SELFDESTRUCT Detection
function findSelfDestructs(result: TraceResultType): AddressType[] {
const trace = TraceResult.getCallTrace(result);
if (!trace) return [];
return CallTrace.flatten(trace)
.filter(call => call.type === "SELFDESTRUCT")
.map(call => call.from);
}
See Also