Docs

Debugging

Deep guide to source-level debugging, backtraces, traces, retrace, fork testing, snapshots, and debug output in Acton

Acton has several debugging layers: some are cheap and fast; some are reliant on source maps and debug metadata; some are for local execution only; some are designed for replaying real on-chain history.

Pick the best tool that answers the target debugging question:

QuestionBest toolWhy
How to print a value from a test or a script?println(...)Cheap host-side output
How to print a value from contract code running in the VM?debug.print*Raw VM-side debug logging
How to inspect a transaction tree quickly?println(txs) / matcher outputFastest transaction-level feedback
How to obtain source locations without a live debugger?--backtrace fullMidweight source attribution
How to make breakpoints and step through them?acton test --debug or acton script --debugLive source-level debugging
How to debug a real historical transaction?acton retrace <HASH> --contract <NAME> [--debug]On-chain replay
How to stop a transaction chain between hops?testing.createTraceIterationCursor() and TxCursorTransaction-chain control, not source stepping
How to obtain real deployed state fast and locally?--fork-netFetch remote accounts, execute locally
How to freeze an expensive local setup?world-state snapshotsReuse prepared emulator state
How to obtain the richest offline test artifact?acton test --save-test-traceReusable trace bundle for later inspection

Treat Acton's debugging options as a ladder, not a single tool. Start with terminal traces or Test UI, add --backtrace full when source locations are needed, switch to --debug for stepping, and move to retrace or fork workflows only when the bug depends on real chain state.

Source-level debugger

Acton's source-level debugger speaks the Debug Adapter Protocol (DAP). Run an Acton command with --debug to open a local debug server on 127.0.0.1:<port>, and attach the editor.

Where it is available

  • acton test --debug
  • acton script path/to/script.tolk --debug
  • acton retrace <HASH> --contract <NAME> --debug

These are not identical modes:

  • acton test --debug and acton script --debug debug a live local session.
  • acton retrace --debug debugs a replay reconstructed from VM logs.

That distinction matters because live sessions can expose runtime registers and live nested calls, while retrace sessions are stricter and replay-only.

For live contract debugging, Acton runs the same contract code path as in production. No separate debug-only contract variant, disabled optimizations, or special debug-build workflow is required to attach and step through execution.

Starting a session

# Debug one test or a small filtered subset
acton test tests/counter.test.tolk --filter "deploy" --debug

# Debug a script
acton script scripts/deploy.tolk --debug

# Debug a historical on-chain transaction
acton retrace <HASH> --contract Counter --debug --debug-port 4711

Acton prints when the server is listening. Attach from the editor next.

Connecting from an editor

Acton works best with official IDE integrations such as TON VS Code extension and TON JetBrains plugin. Other DAP clients can attach too, but they should expect only the subset of requests and scopes that Acton implements today.

Minimal VS Code-style setup:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "tolk",
      "request": "launch",
      "name": "Attach to Acton",
      "debugServer": 4711
    }
  ]
}

Start the matching Acton command first:

acton test tests/counter.test.tolk --debug --debug-port 4711

Capabilities

The current debugger supports:

  • source breakpoints
  • conditional breakpoints
  • continue, step over, step into, and step out
  • instruction-level stepping when the client requests instruction granularity
  • exception break filters for Uncaught Exceptions and All Exceptions
  • stack traces
  • locals inspection with current values
  • structured expansion for simple and complex values, including tuples, maps, arrays, structs, addresses, cells, and unions
  • hover, watch, and debug-console evaluation for a safe expression subset

Live debug sessions can cross common runtime boundaries. When stepping into helpers such as net.send* or net.runGetMethod(), Acton can splice a child execution into the same logical debug session instead of starting a separate debugger manually.

Acton lists variables visible in the current context with their current values, rendered in structured form rather than opaque low-level blobs. That applies to simple values and complex nested types, so the Variables view is often enough to inspect contract state at the current stop point.

Live sessions also expose a Registers scope:

  • c4 (storage)
  • c5 (output actions)
  • c7 (temporary data)

This is not only a raw register dump. Acton renders storage and output actions in a readable contract-aware form, which makes the register view practical for understanding runtime state without decoding low-level values manually.

In practice, the Registers scope is often the fastest way to answer questions such as:

  • what storage shape the contract has at the stop point
  • which output actions are already prepared
  • what temporary runtime data is visible

This scope is available only for live execution. Replay sessions such as acton retrace --debug do not have the same live register view.

Important debugger semantics

  • Conditional breakpoints are fail-loud. If the condition cannot be evaluated, Acton stops and reports the evaluation failure instead of silently ignoring it.
  • Nested send and get executions stay inside one logical debug session. The visible stack can jump across child contexts even though the adapter still behaves like a single thread to the client.
  • Some runtime glue in emulation/network.tolk is treated as transparent when stepping into calls, so Acton may skip over it to land on a more useful stop.

Watches, hovers, and conditions

Expression evaluation is intentionally limited to a side-effect-free subset. Supported shapes are roughly:

  • local variables, including shadowed locals from the selected frame
  • backticked identifiers
  • field access such as foo.bar
  • dot-based numeric indexing such as foo.items.0
  • parentheses
  • boolean, string, integer, and null literals
  • ! and unary -
  • && and ||
  • ==, !=, <, <=, >, >=
  • debugger-only typed cell casts of the form someCell as Cell<Foo> or someSlice as Cell<Foo>, decoded from the current frame's SourceMap

What is not supported:

  • function calls
  • arbitrary statement execution
  • mutating expressions
  • general arithmetic beyond the supported unary and comparison cases
  • general type casts; only cell/slice to Cell<T> is supported

evaluate is an inspection feature, not a full Tolk REPL. It operates on debug-rendered values, and some comparison behavior is renderer-driven rather than language-accurate execution semantics. Use it for watches, hovers, and conditional breakpoints, not to validate complex logic end to end.

For typed cell casts, the debugger uses type declarations from the active frame's SourceMap. The cast succeeds only when the source expression resolves to a cell or slice, the target is Cell<T>, and the payload decodes fully as T without leftover bits or refs.

Practical limits

  • Live stepping into remote or forked contracts may still run, but source-level fidelity depends on having matching local debug info for that code.
  • acton test --debug is a poor fit for broad suite runs. In practice, narrow to one test with --filter.
  • acton script --debug debugs the local script execution path. It does not provide source-level stepping inside a real on-chain wallet broadcast after the script hands control to the network.
  • acton retrace --debug is stricter than live debugging: it requires --contract, a local .tolk contract, compiler-produced source maps and debug marks, and matching local/on-chain code hash.
  • Replay debug sessions are single-threaded and do not expose the live Registers scope that local sessions can show.
  • Acton currently supports source breakpoints, stepping, stacks, variables, exception info, and evaluate, but not step-back, data breakpoints, function breakpoints, or restart-style flows.
  • Breakpoint locations can slide to the next stoppable line while still being reported as verified.

Backtraces without the debugger

When a live debugger is unnecessary but precise source locations and the call path that led there are needed, use full backtraces.

acton test --backtrace full
acton script scripts/deploy.tolk --backtrace full

This is the middle layer between plain console output and a debugger. It provides source attribution and a source-level backtrace. Use it when:

  • the compute phase failed and a source location is needed
  • the action phase failed and Acton instructs to rerun with a backtrace
  • a matcher failure already produced the right transaction tree but not enough source context

In practice this often means more than a single source location. The backtrace can show the chain of nested contract-side calls that led to the failure, and for some nested message-driven failures Acton can also surface the caller path that led into the failing execution.

Acton automatically prepares the required debug metadata when using --backtrace full.

This goes beyond --verbose: full backtraces require location and stack data, not only low-level executor logs. Coverage also raises internal verbosity for source mapping, but it does not print a backtrace unless one is requested.

For test suites, do not treat --backtrace full as the default way to run everything. It is noticeably slower and more memory-hungry because Acton must compile with richer debug info and keep more verbose execution data.

Prefer rerunning the relevant test file(s) or narrowing the run with --filter.

Test UI, terminal traces, and saved traces

For many bugs, these are the fastest tools in Acton to squash them.

Terminal-native debugging

Acton can render transaction trees directly in the terminal:

  • println(txs) or println(result) in a test
  • failure output from transaction matchers
  • script output with --show-bodies when decoded message bodies matter

Examples:

acton test --show-bodies
acton script scripts/demo.tolk --show-bodies

Executor debug logs are hidden by default. Re-run with acton test --verbose or acton script <path> --verbose for level-1 executor output such as debug.dumpStack(). For richer debug output, use --backtrace full or --debug.

These flags also change how much execution metadata Acton collects:

  • --verbose enables only level-1 executor logs.
  • --coverage raises internal verbosity to source-location mode so coverage can map VM execution back to files and lines, but it does not print backtraces or enable stepping by itself.
  • --backtrace full raises verbosity further to collect full source locations and call stacks for failures.
  • --debug uses the same rich location and stack data, then opens the live debug adapter on top.

Strengths:

  • cheapest feedback loop
  • keeps transaction chains, actions, and debug logs close to the failing code
  • works well with matcher failures and quick println(...) probes

For the exact output shape, see the reading transaction chains page.

Test UI

Run:

acton test --ui

Test UI is a post-run inspector, not a live debugger. It is good for:

  • failed test summaries
  • per-test stdout and stderr
  • top-level VM log output
  • coverage views when combined with --coverage
  • browsing saved trace artifacts after the run

Useful variants:

acton test --ui --ui-port 23456
acton test tests/counter.test.tolk --ui --filter "deploy"
acton test --coverage --ui

Combining --coverage with --ui adds a Coverage tab in Test UI.

See:

Saved traces

Export reusable artifacts:

acton test --save-test-trace
acton test --save-test-trace build/traces

This is the richest offline inspection path. Use it when:

  • deterministic artifacts for later review are needed
  • sharing a failure with teammates
  • inspecting the same trace bundle shape that Test UI reads

When multiple traces are produced in one test, name them with txs.giveName(...) so exported artifacts stay readable.

Saved trace bundles use one <test-name>_trace.json file per executed test and a sibling contracts/ directory with shared contract metadata. Relative paths are resolved from the project root.

See the saved trace bundles page for the file layout and JSON schema overview.

Step-by-step transaction control with TxCursor

Acton also supports step-by-step transaction-chain control through testing.createTraceIterationCursor() and TxCursor. This is separate from the DAP debugger: it controls when later hops in a transaction chain execute.

It is useful when:

  • inspecting intermediate state after the first hop
  • stopping when a specific downstream message appears
  • interleaving two competing chains
  • stopping before the remaining tail executes

This mechanism is available in local emulation workflows and is useful in tests or locally emulated scripts.

testing.createTraceIterationCursor() is transaction-chain execution in local emulation. It is not available in broadcast mode, but it can be used under --debug and the executed transactions can be stepped through with the debugger's step controls.

See the step-by-step execution page for more.

Retracing, fork testing, and snapshots

These workflows matter when the bug depends on real or previously prepared state, not only on a fresh local test.

Retrace: closest to a real historical transaction

Use acton retrace when the bug already happened on-chain and the closest Acton reproduction of that exact transaction is needed.

acton retrace <HASH>
acton retrace <HASH> --contract JettonMinter
acton retrace <HASH> --contract JettonMinter --debug

Retracing goes beyond fetching state and running locally. It replays real transaction context, including block configuration, seed, same-block prehistory, and library resolution, then can layer source-level replay on top when a matching local contract is provided.

That makes retrace the closest match to on-chain execution among Acton debugging paths.

Key tradeoffs:

  • --debug requires --contract
  • source-level retrace only works for a matching local .tolk contract
  • local code hash must match the retraced account code
  • retrace supports mainnet and testnet, not localnet/custom backends
  • the replay summary shows state-hash check status, but it is surfaced as output rather than a hard failure

See acton retrace.

Fork testing and script --fork-net

Fork mode is much lighter than retrace. Tests and scripts still execute locally, but missing accounts are resolved remotely from testnet or mainnet.

Examples:

acton test --fork-net testnet
TONCENTER_MAINNET_API_KEY=YOUR_API_KEY acton test --fork-net mainnet
acton script scripts/query.tolk --fork-net testnet

Strengths:

  • fast local iteration against real deployed account state
  • suited to integration debugging
  • test runs share a suite-level remote cache, which helps repeated accesses
  • pinned fork runs with --fork-block-number persist fetched accounts under build/cache/<network>/<seqno>/, so repeated CLI runs can skip remote account fetches for the same network, block, and address
  • nested debug across net.send* and net.runGetMethod() still works for the local parts of execution

Important limits:

  • fork mode is not historical replay
  • --fork-block-number pins remote account reads, not the full chain environment
  • --no-fork-cache disables the persistent cache when you need fresh remote reads
  • time, randomness, and configuration still come from the local run model
  • remote lookup failures can degrade into what looks like a non-existing account
  • remote contracts may execute under debug but still lack useful local source-level information

See the fork testing page for more.

Contract debug output vs test output

Acton exposes two distinct output channels; confusing them causes misleading diagnostics.

Use println(...) in tests and scripts

println(...) and eprintln(...) are host-side output. In tests they are captured and shown through console reporters and Test UI. In scripts they print directly as the script runs.

Use them for:

  • test diagnostics
  • quick script progress output
  • printing transaction trees or matcher results

Use debug.print* inside contract code

For contract code or other VM-executed Tolk code, use the built-in debug helpers:

These are raw VM-side debug helpers. They write #DEBUG#: lines into VM logs.

Use them for:

  • quick value probes inside VM-executed code
  • branch confirmation in contracts
  • raw TVM-level inspection when higher-level formatting is not enough

Where debug.print* output appears

Behavior differs by context:

  • Top-level test VM debug lines are appended into captured test output.
  • Nested contract transaction debug lines are best seen in transaction trees, matcher diagnostics, saved traces, or Test UI log views.
  • Test UI may show top-level VM debug output separately from per-transaction log views.
  • Ordering relative to test-side println(...) is not guaranteed to look purely chronological, because host output and VM debug output are surfaced through different paths.

Use println(...) for tests and scripts; use debug.print* only inside contracts or other code that runs inside the VM.

Last updated on

On this page