Solidity REPL
GUIDE9 min readMaintained by @Bitmay

Diagnose a Solidity execution from result to opcode

Inspection is useful when it answers a specific “why.” This guide starts with the cheapest evidence, follows the message and state changes that matter, and uses opcode capture only when higher-level results leave the cause unresolved.

OUTCOMEAn evidence-backed explanation of what an execution returned, where it failed, and which effects were committed or reverted.
  • Choose the minimum inspection depth for the question
  • Reconstruct nested calls and decoded event evidence
  • Separate attempted writes from committed state
  • Use source-mapped opcode data to resolve the remaining cause
01

Write the diagnosis question first

Decide whether you need to explain a wrong value, revert, missing event, unexpected external call, storage change, or gas difference. The question determines which evidence matters and prevents a large trace from becoming the investigation itself.

Reproduce the behavior with the smallest input and fixed context. Record compiler, caller, value, and fork block before comparing runs. A changed input or snapshot can produce a different path even when the Solidity source is identical.

02

Triage the result before opening a trace

Read the inline Solidity type and value or decoded error first. Change the display format when decimal, hexadecimal, an ABI word, bytes, or JSON makes the discrepancy clearer. The replay summary then shows whether calls, logs, or storage writes are worth opening.

For a revert, distinguish Error(string), Panic(uint256), and custom error data. A panic points toward a language-level check, a reason string toward an explicit condition, and custom data toward a declared protocol rule.

03

Reconstruct messages and emitted evidence

Open Calls when control crosses a contract boundary. Follow depth, destination, call kind, value, input selector, return data, and gas to find the first frame that differs from the expected path. A top-level success can contain an inner failure that the caller handled.

Use Logs to verify event address, topics, and decoded arguments. Confirm that the emitting address is expected, especially with proxies and delegatecall. An event proves that a path reached an emission point; it does not prove the final storage state.

contract Counter {
  event Changed(uint256 value);
  uint256 public value;
  function set(uint256 next) external {
    value = next;
    emit Changed(next);
  }
}

Counter counter = new Counter()
counter.set(42)
04

Prove which state effects committed

Open State to connect an SSTORE effect to its account, slot, previous word, and next word. Use the contract storage layout or compute mapping and array positions when a raw slot needs interpretation. Read a public getter afterward when a typed final value provides a useful cross-check.

A write attempted inside a reverted frame is not a committed effect. Compare call status with the final State view before concluding that a setter changed storage. This matters for caught reverts and low-level calls that return false instead of reverting the outer frame.

05

Descend to opcodes for the unresolved cause

Enable debug capture before rerunning when values, errors, calls, logs, and state do not explain the path. Use the source map to move from the relevant Solidity line to its program counter, then inspect nearby opcodes, stack items, and memory instead of reading from step zero.

Opcode data is most useful for inline assembly, ABI memory layout, compiler-generated checks, unexpected revert data, and gas differences within one path. Compare runs under identical compiler and optimizer settings because generated opcodes and source mappings can change.

06

Write the conclusion as an evidence chain

State the observed result, the first execution point that explains it, and the final committed effects. Link each part to the matching value, error, call frame, log, state write, or opcode step. Include context and compiler settings that affect the path.

Export the session with the conclusion so another person can challenge the interpretation without rebuilding the setup. A concise evidence chain is more useful than an unfiltered trace because it shows which observations support each claim.

FAQ

Common questions

What should I inspect first?

Start with the typed value or decoded error and the replay summary. Open calls, logs, and state only when the question involves those effects. Capture opcode debug data when higher-level evidence leaves the cause unresolved.

Are reverted storage writes shown as committed state?

No. Storage changes from reverted frames are excluded from committed State results, matching the final execution outcome.

Can a successful call contain an inner revert?

Yes. A contract can catch a revert or handle a failed low-level call. The Calls view helps identify the inner failure even when the outer execution returns successfully.