Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Guides

How to Interpret EVM Execution Traces

A developer guide to reading, interpreting, and debugging EVM execution traces using tools like debug_traceTransaction and foundry. Learn to analyze gas, opcodes, and state changes.
Chainscore © 2026
introduction
DEBUGGING AND ANALYSIS

What Are EVM Execution Traces?

EVM execution traces provide a step-by-step record of a smart contract's internal state changes and logic flow, essential for debugging, security auditing, and gas optimization.

An EVM execution trace is a detailed, low-level log of every operation performed during a transaction. When a transaction is executed, the Ethereum Virtual Machine (EVM) processes a series of opcodes. A trace records each opcode's execution, including the program counter, remaining gas, stack state, memory state, and any storage changes. This granular visibility is crucial for developers to understand exactly how and why a transaction succeeded, failed, or consumed a specific amount of gas. Tools like the debug_traceTransaction RPC method in clients like Geth or Erigon generate these traces.

Interpreting a trace requires understanding its core components. Key elements include the opcode (e.g., PUSH1, SSTORE, CALL), the stack (a LIFO data structure holding 256-bit words), memory (a volatile byte array), and storage (persistent key-value storage for the contract). A failed transaction trace will show the exact opcode where execution reverted. By stepping through these components, you can pinpoint logic errors, unexpected state changes, or inefficient code paths that lead to high gas costs.

For security researchers and auditors, execution traces are indispensable for manual review and automated analysis. They allow you to follow the flow of value and data through a series of external calls, checking for reentrancy vulnerabilities or unsafe delegate calls. By examining the CALL and STATICCALL opcodes and their parameters, you can reconstruct the call graph of a transaction, identifying which contracts were invoked and with what calldata. This is how tools like Tenderly and OpenZeppelin Defender visualize transaction failure points.

Here's a simplified example of what a trace step might look in JSON, focusing on an SSTORE operation that writes to contract storage:

json
{
  "pc": 142,
  "op": "SSTORE",
  "gas": 24123,
  "gasCost": 20000,
  "depth": 1,
  "stack": ["0x0", "0x..."],
  "memory": ["0x..."],
  "storage": {
    "0x0...": "0xNewValue"
  }
}

This step shows the program counter (pc), the opcode, remaining gas before and after the operation (gas and gasCost), and the resulting change in the storage object.

To generate and analyze traces, you can use several methods. For local development, Hardhat and Foundry have built-in trace capabilities (console.log-style debugging in Hardhat, forge test --debug in Foundry). For mainnet or testnet transactions, you can use the debug_traceTransaction RPC call on an archive node or a service like Alchemy's Composer or BlockPi. Analyzing these traces helps optimize gas usage by identifying expensive operations like repeated SLOAD calls or unnecessary memory expansions, directly leading to more efficient and cost-effective smart contracts.

prerequisites
PREREQUISITES

How to Interpret EVM Execution Traces

Understanding the low-level mechanics of transaction execution is essential for debugging, gas optimization, and security auditing. This guide covers the fundamentals of EVM execution traces.

An EVM execution trace is a step-by-step log of every operation the Ethereum Virtual Machine performs while processing a transaction. It details the sequence of opcodes, the state of the stack, memory, and storage at each step, and the gas consumed. Tools like the debug_traceTransaction RPC method or frameworks like Hardhat and Foundry generate these traces, which are indispensable for developers needing to understand why a transaction succeeded, failed, or consumed a specific amount of gas.

To effectively read a trace, you must be familiar with core EVM concepts. The stack is a last-in-first-out (LIFO) data structure holding 256-bit words for calculations. The memory is a volatile, expandable byte array used for temporary data during a call. Storage is the persistent key-value store tied to a contract address. Each opcode in the trace, like PUSH1, SSTORE, or CALL, manipulates these components. Recognizing common opcode patterns is key to tracing logic flow.

A typical trace entry from debug_traceTransaction includes fields like pc (program counter), op (opcode name), gas (remaining gas), stack (current stack items), and memory (memory contents). For example, a failed transaction will show a REVERT opcode; the stack entries before it often contain the error pointer and size, which point to an error message in memory. Analyzing the steps leading to the revert reveals the exact condition that triggered it.

Execution traces become particularly critical when analyzing complex interactions, such as smart contract exploits or gas-griefing attacks. By tracing a malicious transaction, you can observe how an attacker manipulates storage layouts, exploits reentrancy, or forces excessive gas consumption in a victim contract. Security auditors rely on this level of granularity to construct and validate attack vectors that are not apparent from transaction receipts or event logs alone.

For practical analysis, use the Foundry cast command: cast run <tx_hash> --debug. This outputs a colored, annotated trace to your terminal. Alternatively, Hardhat Network's console.log-style tracing or specialized tools like Tenderly's debugger provide more visual interfaces. Start by tracing simple transactions you understand to learn the structure before moving to complex DeFi interactions where multiple contracts and delegate calls are involved.

key-concepts-text
KEY CONCEPTS IN A TRACE

How to Interpret EVM Execution Traces

An EVM execution trace is a step-by-step log of a transaction's internal operations. Interpreting it is essential for debugging smart contracts, analyzing gas costs, and understanding complex state changes.

An EVM execution trace records every low-level operation (opcode) executed during a transaction, along with the resulting changes to the stack, memory, and storage. Unlike a simple transaction receipt, a trace reveals the internal logic flow, showing conditional branches, function calls, and state modifications. Tools like the debug_traceTransaction RPC method in Geth or Erigon generate this data, which is fundamental for developers auditing contracts or investigating failed transactions.

The core components of a trace are its opcodes and the associated gas consumption. Each opcode, like PUSH1, SSTORE, or CALL, performs a specific action and consumes a predefined amount of gas. By examining the sequence, you can reconstruct the contract's execution path. For example, a series of SLOAD and SSTORE opcodes indicates storage reads and writes, which are expensive and a primary target for gas optimization.

Traces also detail internal calls between contracts. A CALL or DELEGATECALL opcode in the trace signifies an inter-contract interaction, creating a nested "call trace." This is crucial for understanding composable DeFi transactions where a single user action may trigger dozens of calls across multiple protocols, helping to map out the complete flow of funds and logic.

To analyze a trace, focus on key sections: the program counter (pc) shows execution progress, the stack holds temporary values for calculations, and the memory is a volatile byte array. A sudden REVERT opcode will show the point of failure. By correlating opcodes with Solidity source code using a debugger, you can pinpoint the exact line that caused an error or unexpected behavior.

Practical use cases include gas profiling to identify opcodes consuming the most gas (like repeated EXP operations), security analysis to detect unexpected DELEGATECALL patterns, and state reconciliation to verify all storage changes. Forks like Hardhat and Anvil provide local tracing, while block explorers like Tenderly offer visualized traces for mainnet transactions.

tools
DEVELOPER TOOLKIT

Tools for Generating Traces

Execution traces are a detailed log of a transaction's step-by-step operations. These tools generate and present them for debugging, security analysis, and gas optimization.

generating-traces
GUIDE

How to Interpret EVM Execution Traces

Learn to read and analyze EVM execution traces to debug smart contracts, optimize gas, and understand on-chain behavior.

An EVM execution trace is a detailed, step-by-step log of a transaction's execution, showing the state changes and operations performed by the Ethereum Virtual Machine. Unlike a simple transaction receipt, a trace reveals the internal calls, memory writes, and storage modifications that occur during contract execution. This granular view is essential for developers needing to debug complex contract interactions, security researchers auditing for vulnerabilities, and analysts seeking to understand the precise on-chain impact of a transaction. Tools like debug_traceTransaction in Geth or trace_transaction in Erigon provide this data.

A standard trace is composed of a series of opcodes and their associated context. Each step in the trace includes key fields: the program counter (pc), the executed opcode (op), the remaining gas (gas), the gas cost of the operation (gasCost), the depth of the call stack (depth), and the state of the stack (stack) and memory (memory). For calls (like CALL, STATICCALL, DELEGATECALL), the trace shows the target address, value sent, and input data. By following these steps, you can reconstruct the exact logic flow of a smart contract, identifying where gas is consumed or where a transaction may have reverted.

Interpreting traces is critical for gas optimization. You can identify expensive operations like high SSTORE costs for writing to storage, repeated SLOAD operations, or inefficient loops. For example, a trace showing multiple SLOAD opcodes reading the same storage slot within a single transaction indicates a chance to cache that value in memory, significantly reducing gas costs. Similarly, tracing a failed transaction will show the exact opcode where an error like an assertion failure or an out-of-gas exception occurred, pinpointing the root cause of the revert.

Beyond debugging, traces are vital for security analysis. They allow you to verify the behavior of a protocol during a specific interaction. For instance, when a user interacts with a DeFi protocol, a trace can confirm that token approvals were only used for the intended swap and no unexpected transfers occurred. You can also detect malicious patterns, such as a contract making an unexpected DELEGATECALL to an unknown address, which is a common red flag for exploit attempts. Analyzing the call depth and jump destinations helps map out potential re-entrancy attack paths.

To generate and work with traces, you can use RPC methods directly or leverage developer tools. The primary JSON-RPC method is debug_traceTransaction, which takes a transaction hash and returns a structured trace. For more advanced analysis, libraries like ethers.js and frameworks like Foundry (via cast run) or Hardhat (via hardhat-tracer) provide higher-level abstractions. These tools can format the raw trace data, filter for specific events, and even generate visual call graphs, making the complex opcode-level data more accessible for practical development and investigation workflows.

COMPARISON

Trace Output Formats and Methods

A comparison of common EVM trace output formats and the RPC methods that produce them, detailing their structure, verbosity, and primary use cases.

Feature / Metricdebug_traceTransactiondebug_traceCalltrace_replayTransactiontrace_filter

Output Format

Structured step-by-step opcodes

Structured step-by-step opcodes

Nested call traces with state diffs

Flat list of call/action traces

Includes Opcode-Level Execution

Includes State Diffs

Includes Gas Costs per Step

Memory/Storage Changes Shown

Trace Depth Limit

Unlimited

Unlimited

Unlimited

10,000 traces max

Primary Use Case

Deep transaction debugging

Simulating call execution

Accounting & state analysis

Indexing & event sourcing

Supported Clients

Geth, Erigon, Nethermind

Geth, Erigon, Nethermind

Erigon, OpenEthereum

Erigon, OpenEthereum

step-by-step-analysis
GUIDE

How to Interpret EVM Execution Traces

Execution traces are a detailed log of every operation performed during a transaction. This guide explains how to read them to debug smart contracts and understand gas costs.

An EVM execution trace is a step-by-step record of the computational state changes that occur when a transaction is processed. Each line in a trace represents a single opcode execution, showing the program counter, opcode mnemonic (like PUSH1, SSTORE), remaining gas, and the stack/memory state. Tools like the debug_traceTransaction RPC method, found in clients like Geth and Erigon, generate these traces. They are essential for developers to verify contract logic, identify inefficiencies, and audit for security vulnerabilities by observing the exact flow of execution.

To interpret a trace, you must understand its core components. The stack is a LIFO (Last-In, First-Out) data structure holding 256-bit words for calculations. The memory is a volatile, expandable byte array for temporary data. Storage is the persistent key-value store on the blockchain. A trace entry will show the stack before and after the opcode runs. For example, an SSTORE opcode will pop two items from the stack: the storage key and the value to store. Watching these values change helps you confirm if a contract is writing data correctly.

A practical analysis often starts with a failed transaction. By examining the trace, you can pinpoint the exact opcode where execution reverted. Look for the REVERT or INVALID opcode. The stack and memory state just before this point reveal the error conditions. For instance, a failed require() statement will typically lead to a REVERT with a specific error message in memory. Using a structured trace format, like the call-trace which summarizes internal calls, can quickly show you which contract call failed in a nested transaction, saving you from parsing thousands of low-level opcodes.

Gas analysis is another critical use case. Each opcode has a fixed gas cost (defined in the Ethereum Yellow Paper). By summing the gas of each opcode in a trace, you can verify the total gas used. Expensive operations like SSTORE (writing to storage) or CALL (making an external call) will be highlighted. This allows you to optimize contracts by identifying gas-guzzling patterns. For example, a loop that performs an SLOAD (storage read) on each iteration is inefficient; the trace makes this pattern visually obvious, guiding you to cache the value in memory instead.

Advanced tracing options provide deeper insights. Using tracer: "callTracer" with debug_traceTransaction returns a nested JSON of all CALL, STATICCALL, DELEGATECALL, and CREATE operations, showing value transfers and execution flow. The prestateTracer outputs the state (storage, balance, nonce) of all touched accounts before and after the transaction, crucial for understanding complex DeFi interactions. For the most granular detail, a custom JavaScript tracer can be written to output specific data, as supported by Geth's tracing API.

common-patterns
COMMON DEBUGGING PATTERNS

How to Interpret EVM Execution Traces

Execution traces provide a step-by-step log of a smart contract's internal state changes and logic flow, essential for debugging complex transactions.

An EVM execution trace is a detailed, low-level record of every operation performed during a transaction. It logs each OPCODE, the resulting changes to the stack, memory, and storage, and the remaining gas after each step. Tools like the Hardhat Network, Foundry's forge, and Etherscan's transaction decoder generate these traces. They are indispensable for diagnosing failed transactions, understanding gas consumption, and verifying that contract logic executes as intended, especially when dealing with reentrancy, delegate calls, or complex state transitions.

To read a trace, start by identifying the program counter (PC) and the executed OPCODE. For example, PUSH1 0x80 places 0x80 on the stack, while SSTORE saves a value to contract storage. The key is to follow the data flow: watch how values move between the stack and memory, and note when storage is read (SLOAD) or written. Pay close attention to revert opcodes (REVERT, INVALID) and jumps (JUMP, JUMPI), as they indicate control flow changes or errors. A common pattern is a JUMPI leading to a REVERT, pinpointing the exact condition that caused a transaction to fail.

Effective debugging involves isolating the problematic segment. If a transaction reverts, find the last successful SSTORE or external call before the revert to understand the failing state. For high gas usage, look for loops indicated by repeated JUMP instructions or expensive operations like EXP or repeated SLOAD/SSTORE. When using Foundry, the command forge test --debug <test_function_name> launches an interactive debugger that steps through the trace. For Hardhat, console.log statements compiled into the bytecode can annotate the trace, making it easier to correlate opcodes with your Solidity source code.

Advanced patterns involve interpreting traces for delegatecall and internal transactions. A DELEGATECALL opcode executes code from another contract but preserves the original contract's storage context. In the trace, you'll see execution jump to a new address; you must mentally map this to the logic of the called contract. For token transfers or multi-contract interactions, the trace may show multiple internal CALL opcodes. Each creates a sub-context with its own gas and memory scope. Analyzing these nested calls is crucial for debugging cross-contract composability issues or unexpected side-effects.

EVM EXECUTION TRACES

Frequently Asked Questions

Common questions and troubleshooting steps for developers working with Ethereum Virtual Machine execution traces.

An EVM execution trace is a detailed, step-by-step log of every operation the Ethereum Virtual Machine performs during a transaction. It shows the sequence of opcodes, changes to storage and memory, and the flow of gas consumption.

This is essential for:

  • Debugging: Identifying why a transaction reverted or a smart contract failed.
  • Security Auditing: Analyzing contract logic for vulnerabilities by inspecting low-level state changes.
  • Gas Optimization: Pinpointing expensive operations to reduce transaction costs.
  • Understanding Protocols: Reverse-engineering complex interactions in protocols like Uniswap V3 or Aave.

Unlike a transaction receipt, which only shows the final outcome, a trace reveals the entire execution path.

conclusion
PUTTING IT ALL TOGETHER

Conclusion and Next Steps

You now have the foundational knowledge to interpret EVM execution traces. This guide has covered the core components—opcodes, gas, stack, memory, and storage—and how they interact during a transaction.

Interpreting traces is a skill that improves with practice. Start by analyzing simple token transfers or approvals on a block explorer like Etherscan, then move to more complex interactions like a Uniswap swap or an Aave deposit. Focus on identifying the key phases of a transaction: the initial call, any internal calls to other contracts, state changes, and the final outcome. Pay close attention to gas consumption spikes and revert opcodes (REVERT, INVALID) to pinpoint where transactions fail.

To deepen your expertise, integrate trace analysis into your development workflow. Use tools like Hardhat's console.log in your Solidity contracts or the Tenderly debugger to step through transactions in real-time. For advanced security auditing, learn to recognize common vulnerability patterns in traces, such as reentrancy (look for recursive CALL opcodes), integer overflows/underflows, and improper access control checks. The Ethereum Yellow Paper remains the definitive technical reference for opcode behavior.

Your next steps should involve exploring specialized tools. For batch analysis or building monitoring systems, use the debug_traceTransaction JSON-RPC method directly via providers like Alchemy or Infura. Frameworks like Foundry's cast command offer powerful trace capabilities from the CLI. To stay current, follow EIPs related to the EVM, such as those introducing new opcodes or modifying gas costs, as these changes directly impact trace interpretation and contract optimization strategies.

How to Interpret EVM Execution Traces | ChainScore Guides