Skip to main content
All examples from this article are available on Github.
Errors in smart contracts can produce an exit code, often indicating a bug in the contract. Use debugging methods to locate and fix the issue.

Log to the console

Most commonly used to print common values: transactions and get-method results.
  • Use findTransaction() to find a transaction by its properties.
  • Use flattenTransaction() to inspect transactions in a more human-readable format.
TypeScript
import '@ton/test-utils';
import { toNano } from '@ton/core';
import { Blockchain } from '@ton/sandbox';
import { Test } from './output/sample_Test';
import { findTransaction } from '@ton/test-utils';
import { flattenTransaction } from '@ton/test-utils';

const setup = async () => {
    const blockchain = await Blockchain.create();
    const owner = await blockchain.treasury('deployer');
    const contract = blockchain.openContract(
        await Test.fromInit(),
    );
    const deployResult = await contract.send(
        owner.getSender(),
        { value: toNano(0.5), bounce: true },
        null,
    );
    return { blockchain, owner, contract, deployResult };
};

it('should deploy correctly', async () => {
    const { contract, deployResult } = await setup();

    const txToInspect = findTransaction(
        deployResult.transactions,
        {
            to: contract.address,
            deploy: true,
        },
    );
    if (txToInspect === undefined) {
        throw new Error('Requested tx was not found.');
    }
    // User-friendly output
    console.log(flattenTransaction(txToInspect));
    // Verbose output
    console.log(txToInspect);
});

Dump values from a contract

There are three TVM debug instructions: DUMPSTK, STRDUMP, and DUMP. These instructions are wrapped in functions with different names in each language:
  • Tolk: Functions on a global debug object.
  • FunC: Global functions from stdlib.fc.
  • Tact: dumpStack for DUMPSTK and the dump function for the other two. Tact also prints the exact line where dump is called, so it can quickly be found in the code.
Debug instructions consume gas and affect gas measurement. Remove them before measuring gas or deploying to production.

Explore TVM logs

TypeScript
const blockchain = await Blockchain.create();
blockchain.verbosity.vmLogs = "vm_logs";
Of all verbosity levels, the two are the most useful:
  • vm_logs — outputs VM logs for each transaction; includes executed instructions and occurred exceptions.
  • vm_logs_full — outputs full VM logs for each transaction; includes executed instructions with binary offsets, the current stack for each instruction, and gas used by each instruction.
Typical output for vm_logs looks like this:
...
execute SWAP
execute PUSHCONT x30
execute IFJMP
execute LDU 64
handling exception code 9: cell underflow
default exception handler, terminating vm with exit code 9
The contract attempts to load a 64-bit integer from the slice using LDU 64. Since there is not enough data, execution stops with exit code 9. Inspect the same code with the vm_logs_full verbosity level. The output is heavily truncated at the top.
...
execute PUSHCONT x30
gas remaining: 999018
stack: [ 500000000 CS{Cell{02b168008d0d4580cd8f09522be7c0390a7a632bda4a99291c435b767c95367ebe78e9af0023d36bc5f97853f4c898f868f95b035ae8f555a321d0ffce8d9f6165e2252d7a9077359400060e9fc800000000003d0902d1b85b3919} bits: 711..711; refs: 2..2} 0 Cont{vmc_std} ]
code cell hash: F9EAC82B7999AEEF696D592FE2469B9069FB05ED35C92213D7EE516F45AB97CA offset: 344
execute IFJMP
gas remaining: 999000
stack: [ 500000000 CS{Cell{02b168008d0d4580cd8f09522be7c0390a7a632bda4a99291c435b767c95367ebe78e9af0023d36bc5f97853f4c898f868f95b035ae8f555a321d0ffce8d9f6165e2252d7a9077359400060e9fc800000000003d0902d1b85b3919} bits: 711..725; refs: 2..2} ]
code cell hash: F9EAC82B7999AEEF696D592FE2469B9069FB05ED35C92213D7EE516F45AB97CA offset: 352
execute LDU 64
handling exception code 9: cell underflow
default exception handler, terminating vm with exit code 9
To investigate the error in more detail, examine the TVM source code for the LDU instruction. Sometimes several instructions are implemented within a single exec_* method. For example, LDU (load_uint), LDI (load_int) and it’s preload versions (preload_uintandpreload_int).Check how LDU is implemented.
Stack is printed as [bottom, ..., top], where top is the top of the stack. Here, the stack contains two values:
  • Top: the slice from which data is being read — CS{Cell{...} bits: 711..725; refs: 2..2}
  • Bottom: an integer value — 500000000
However, the slice contains only 725 bits, of which 711 bits and both references have already been read. The contract attempted to read 64 more bits, but the slice did not contain enough remaining data. In FunC, locate the load_uint(64) call causing the issue and ensure enough bits are available or adjust the read width.

TVM log limits

The size of TVM debug output depends on the verbosity level:
LevelSettingMax size
0none256 bytes (default)
1–4vm_logs
vm_logs_location
vm_logs_gas
vm_logs_full
1 MB
5vm_logs_verbose32 MB
When the output exceeds its limit, it is truncated from the bottom — older entries are discarded, and only the most recent lines are kept. Logs are not rotated.

Explore the trace

For traces that are not too large, print all transactions and inspect them.
TypeScript
const result = await contract.send(
    owner.getSender(),
    { value: toNano(0.5), bounce: true },
    null,
);
for (const tx of result.transactions) {
    console.log(flattenTransaction(tx));
}
For large traces, use a GUI tool. Two tools are commonly used:
  • TonDevWallet trace view — requires the TonDevWallet application; does not require a custom @ton/sandbox; requires the @tondevwallet/traces package.
  • TxTracer Sandbox — requires a custom @ton/sandbox package; runs in the browser.
Also, these tools allow to explore logs of each transaction.

Debugging with TVM Retracer

Even when a contract executes successfully (exit code = 0) with no errors, the actions may not produce the expected on-chain result. TVM Retracer replays the transaction and displays VM-level execution in detail.

Scenarios for retracing

  • All execution phases complete without errors, yet the expected outcome is missing.
  • An action is skipped, or a transfer does not reach its destination.
  • A step-by-step view of how the TVM executes contract logic is required, i.e. to trace a bug in a high-level smart-contract language compiler.

How to analyze a transaction

  1. Obtain the transaction hash from a blockchain explorer.
  2. Open TVM Retracer and enter the transaction hash.
  3. Review the execution:
    • Inspect Logs section for executed instructions and exceptions.
    • Examine Actions cell (C5) to review data passed between contracts.
    • Check message modes — some modes can suppress errors, causing actions to be skipped.