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:
| Question | Best tool | Why |
|---|---|---|
| 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 output | Fastest transaction-level feedback |
| How to obtain source locations without a live debugger? | --backtrace full | Midweight source attribution |
| How to make breakpoints and step through them? | acton test --debug or acton script --debug | Live 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 TxCursor | Transaction-chain control, not source stepping |
| How to obtain real deployed state fast and locally? | --fork-net | Fetch remote accounts, execute locally |
| How to freeze an expensive local setup? | world-state snapshots | Reuse prepared emulator state |
| How to obtain the richest offline test artifact? | acton test --save-test-trace | Reusable 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 --debugacton script path/to/script.tolk --debugacton retrace <HASH> --contract <NAME> --debug
These are not identical modes:
acton test --debugandacton script --debugdebug a live local session.acton retrace --debugdebugs 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 4711Acton 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 4711Capabilities
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 ExceptionsandAll 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.tolkis 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
nullliterals !and unary-&&and||==,!=,<,<=,>,>=- debugger-only typed cell casts of the form
someCell as Cell<Foo>orsomeSlice as Cell<Foo>, decoded from the current frame'sSourceMap
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/slicetoCell<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 --debugis a poor fit for broad suite runs. In practice, narrow to one test with--filter.acton script --debugdebugs 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 --debugis stricter than live debugging: it requires--contract, a local.tolkcontract, 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
Registersscope 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 fullThis 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)orprintln(result)in a test- failure output from transaction matchers
- script output with
--show-bodieswhen decoded message bodies matter
Examples:
acton test --show-bodies
acton script scripts/demo.tolk --show-bodiesExecutor 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:
--verboseenables only level-1 executor logs.--coverageraises 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 fullraises verbosity further to collect full source locations and call stacks for failures.--debuguses 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 --uiTest 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 --uiCombining --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/tracesThis 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 --debugRetracing 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:
--debugrequires--contract- source-level retrace only works for a matching local
.tolkcontract - 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 testnetStrengths:
- 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-numberpersist fetched accounts underbuild/cache/<network>/<seqno>/, so repeated CLI runs can skip remote account fetches for the same network, block, and address - nested debug across
net.send*andnet.runGetMethod()still works for the local parts of execution
Important limits:
- fork mode is not historical replay
--fork-block-numberpins remote account reads, not the full chain environment--no-fork-cachedisables 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