Debugging Tact Contracts
Tact has first-class support for Jest and @tact-lang/emulator
for contract tests and debugging. The preferred type of test is the one with inline snapshots. They are easy to write and easy to debug.
For a quick start, it is better to use tact-template
that has everything built in.
Printing out debug data
Tact has a built-in dump
function that is similar to the one in FunC that allows you to print out data in your contract. It is very useful for debugging.
To make dump
work you need to enable the feature debug
in tact.conf.json
.
Example:
dump("Hello World");
dump(123 + 444);
dump(a != null);
Using @tact-lang/emulator
Ton Emulator allows you to have a small virtual blockchain in your Node.js code. This library is built specifically for testing smart contracts in unit tests.
import { ContractSystem } from '@tact-lang/emulator';
import { sample_Contract } from './output/sample_Contract';
//
// Init System
//
// Contract System is a virtual environment that emulates the TON blockchain
const system = await ContractSystem.create();
// Treasure is a contract that has 1m of TONs and is a handy entry point for your smart contracts
let treasure = await system.treasure('name of treasure');
//
// Open contract
//
// Contract itself
let contract = system.open(sample_Contract.fromInit(treasure.address));
// This object would track all transactions in this contract
let tracker = system.track(contract.address);
// This object would track all logs
let logger = system.log(contract.address);
//
// Sending a message
//
// First we enqueue messages. NOTE: You can enqueue multiple messages in a row
await contract.send(treasure, { value: toNano(1) }, { $$type: "Deploy", queryId: 0n });
await contract.send(treasure, { value: toNano(1) }, { $$type: "Increment" });
// Run the system until there are no more messages
await system.run();
//
// Collecting results
//
console.log(track.collect()); // Prints out all transactions in contract
console.log(logger.collect()); // Prints out all logs for each transaction
//
// Invoking get methods
//
let counter = await contract.getCounter();
console.log(counter); // Prints out counter value
Snapshot testing with jest
One of the most powerful features of Jest is the ability to write snapshot tests. Snapshot tests are a great way to test your contracts.
Initial setup
Example of a minimal test file:
import { ContractSystem } from '@tact-lang/emulator';
import { sample_Contract } from './output/sample_Contract';
describe("contract", () => {
it("should deploy correctly", async () => {
// Init test
const system = await ContractSystem.create();
const treasure = await system.treasure('my treasure');
const contract = system.open(sample_Contract.fromInit(treasure.address));
const tracker = system.track(contract.address);
// Send a message
await contract.send(treasure, { value: toNano(1) }, { $$type: "Deploy", queryId: 0n });
await system.run();
// Testing output
expect(tracker.collect()).toMatchInlineSnapshot();
});
});
Generating snapshots
After running the yarn jest
command, the line with toMatchInlineSnapshot
of the test will be automatically updated with a snapshot of the output.
// ...
expect(tracker.collect()).toMatchInlineSnapshot(`
[
{
"$seq": 0,
"events": [
{
"$type": "deploy",
},
{
"$type": "received",
"message": {
"body": {
"cell": "x{946A98B60000000000000000}",
"type": "cell",
},
"bounce": true,
"from": "kQAI-3FJVc_ywSuY4vq0bYrzR7S4Och4y7bTU_i5yLOB3A6P",
"to": "kQBrSAP2y7QIUw4_1q0qciHfqdFmOYR9CC1oinn7kyWWRuoV",
"type": "internal",
"value": 1000000000n,
},
},
{
"$type": "processed",
"gasUsed": 6077n,
},
{
"$type": "sent",
"messages": [
{
"body": {
"cell": "x{AFF90F570000000000000000}",
"type": "cell",
},
"bounce": true,
"from": "kQBrSAP2y7QIUw4_1q0qciHfqdFmOYR9CC1oinn7kyWWRuoV",
"to": "kQAI-3FJVc_ywSuY4vq0bYrzR7S4Och4y7bTU_i5yLOB3A6P",
"type": "internal",
"value": 992727000n,
},
],
},
],
},
]
`);
// ...
Updating snapshots
When you change your contract, your snapshots will be outdated. For example, gas usage or addresses were changed. To update them, you need to run the yarn jest -u
command.