diff --git a/cspell.json b/cspell.json index e1823628..9e06221d 100644 --- a/cspell.json +++ b/cspell.json @@ -59,9 +59,10 @@ ], "flagWords": [], "ignorePaths": [ + ".github/temp-archive", "components/icons", - "grammars/grammar-ohm.json", "dist", + "grammars/grammar-ohm.json", "grammars/grammar-tact.json", "next.config.js", "node_modules", diff --git a/pages/book/debug.mdx b/pages/book/debug.mdx index 0a4f1ff7..0cf44102 100644 --- a/pages/book/debug.mdx +++ b/pages/book/debug.mdx @@ -1,166 +1,623 @@ -# Debugging Tact Contracts +# 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. +import { Callout, Steps, Tabs } from 'nextra/components' -For a quick start, it is better to use `tact-template` that has everything built in. +Without fail, the code we write as smart contract developers doesn’t always do what we expected it to do. Sometimes it does something completely different! When the unexpected happens, the next task is to figure out why. To do so, there are various ways to reveal problems or "bugs" in the code. Let's get to *debugging*! -## Printing out debug data +## General approach [#approach] -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`. +### Clarify the problem by asking yourself the right questions [#approach-1] -Example: -```tact -dump("Hello World"); -dump(123 + 444); -dump(a != null); +It helps to clarify the problem that you ran into before you try to fix it. Furthermore, clearly stated problems are much more easier to understand for someone else — this is very handy if you want to get meaningful help when asking in Tact's [Telegram chat][tg] or filling an issue on Tact's [GitHub](https://github.com/tact-lang). + +So, before you start debugging, make sure you've identified the problem you're trying to solve: + +1. What did you expect your code to do? + +2. What happened instead? + + If you've run into a syntax error (breaking the rules of the language), reference error (using the wrong names), type error (confusing one type for another) or some other exception during compilation, that's great! This means that compiler has already found the issue for you, and now all that's left is to fix it. + + If something else happened, it's most likely a logic error in your code, when your expectations didn't match the actual state the contract got in. To resolve that, try stepping through your code and checking state of variables in it via [`dump(){:tact}`][dump] function or alike. + +### Examine your assumptions [#approach-2] + +Before you investigate a bug or an error, think of the assumptions that made you expect a certain result. Unknown or unclear expectations can get in the way of identifying a problem, even when you're looking right at the cause of the problem. + +Here are a few questions to ask yourself to challenge your assumptions: + +* Are you using the right API (that is, the right [global static function](/book/functions#global-static-functions) or [extension function](/book/functions#extension-function))? An API that you're using might not do what you think it does, so make sure to consult with the [Reference](/ref) section or ask in the Tact [Telegram chat][tg]. + +* Are you using an API correctly? Maybe, you used the right API but didn't use it in the right way. + +* Did you make a change to your code and assume it's unrelated to the issue you're facing? A common pitfall here is to modify the code and try to run the tests right away, without compiling the changes first. + +* Did you expect variable or a [`Cell{:tact}`](/book/types#primitive-types) to contain a certain value (or a certain type of value) that is different from what was really there? Pay attention to your types and data layouts, especially their representation in [TL-B schemas](https://docs.ton.org/develop/data-formats/tl-b-language). + +* Do you know the intent of the code? It's often more difficult to debug someone else's code. If it's not your code, it's possible you might need to spend time learning exactly what the code does before you can debug it effectively. + + + + When writing contracts, start small and start with code that works! Sometimes, it is easier to fix a large or complicated set of code by starting with a small piece of code that demonstrates the core task you are trying to achieve. Then, you can modify or add code incrementally, testing at each point for errors. + + Here, it may be helpful to test your assumptions in a small [experimental playground](#lab) before rolling out a complete implementation. + + + +### Go over your code and observe the values [#approach-3] + +At the moment, Tact doesn't have a step-through debugger. Despite that, it's still possible to use the ["printf debugging"](https://en.wikipedia.org/wiki/Debugging#printf_debugging) approach. + +It involves actively placing [`dump(){:tact}`][dump] and [`dumpStack(){:tact}`](/ref/core-debug#dumpstack) function calls throughout your code and observing states of variables at a given point of time. Note, that those functions work only in a [debug mode](#debug-mode) and won't be executed otherwise. + +Once you found that some value isn't equal to what you've expected it to be, don't rush to fixing the issue on the spot. That's because what you're seeing may not be the root cause of it and merely a symptom, effect. Be very careful with cause-and-effect relationships and figure out which's which to resolve the cause and not introduce new mess for your future self. + + + + See how to use [`dump(){:tact}`][dump] for debugging: [Debug with `dump(){:tact}`](#tests-dump). + + + +In addition to dumping values, it's often helpful to use assertive functions like [`require(){:tact}`](/ref/core-debug#require), [`nativeThrowWhen(){:tact}`](/ref/core-debug#nativethrowwhen) and [`nativeThrowUnless(){:tact}`](/ref/core-debug#nativethrowunless). They help stating your assumptions clear, and are handy for setting up "trip wires" for catching issues in the future. + +And if you didn't find or cannot resolve the cause of your issues, try asking the community in Tact's [Telegram chat][tg] or, if your issue or question is generally related to TON more than it's related to Tact, hop into [TON Dev Telegram chat](https://t.me/tondev_eng). + + + +## Common debugging functions [#debug-functions] + +Tact provides a handful amount of various functions useful for debugging: [Core library → Debug](/ref/core-debug). + +## Enabling debug mode in compilation options [#debug-mode] + +In order to make certain functions like [`dump(){:tact}`][dump] or [`dumpStack(){:tact}`](/ref/core-debug#dumpstack) work, one needs to enable debug mode. + +The simplest and recommended approach is to modify a [`tact.config.json`](/book/config) file in the root of your project (or create it if it didn't exist yet), and [set the `debug` option to `true{:tact}`](/book/config#options-debug). + +If you're working on a [Blueprint][bp]-based project, you can enable debug mode in the compilation configs of your contracts, which are located in a directory named `wrappers/`: + +```typescript filename="wrappers/YourContractName.compile.ts" {7} +import { CompilerConfig } from '@ton/blueprint'; + +export const compile: CompilerConfig = { + lang: 'tact', + target: 'contracts/your_contract_name.tact', + options: { + debug: true, // ← that's the stuff! + } +}; ``` -## Using `@tact-lang/emulator` +Note, that versions of [Blueprint][bp] starting with 0.20.0 automatically enable debug mode in `wrappers/` for new contracts. -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. +In addition to that, [`tact.config.json`](/book/config) may still be used in [Blueprint][bp] projects. In such cases values specified in [`tact.config.json`](/book/config) act as default unless modified in the `wrappers/`. -```typescript -import { ContractSystem } from '@tact-lang/emulator'; -import { sample_Contract } from './output/sample_Contract'; + + + Read more about configuration and [`tact.config.json`](/book/config) file: [Configuration](/book/config).\ + See how to use [`dump(){:tact}`][dump] for debugging: [Debug with `dump(){:tact}`](#tests-dump). + + + +## Writing tests in Blueprint, with Sandbox and Jest [#tests] -// -// Init System -// +The [Blueprint][bp] is a popular development framework for writing, testing and deploying smart contracts on TON Blockchain. -// Contract System is a virtual environment that emulates the TON blockchain -const system = await ContractSystem.create(); +For testing smart contracts it uses the [Sandbox][sb], a local TON Blockchain emulator and [Jest][jest], a JavaScript testing framework. -// 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'); +Whenever you create a new [Blueprint][bp] project or use `blueprint create` command inside the existing project, it creates a new contract alongside with a test suite file for it. -// -// Open contract -// +Those files are placed in `tests/` folder and executed with [Jest][jest]. By default, all tests run, unless you specify specific group or test closure. For other options, refer to the brief documentation in the Jest CLI: `jest --help`. -// Contract itself -let contract = system.open(sample_Contract.fromInit(treasure.address)); +### Structure of test files [#tests-structure] -// This object would track all transactions in this contract -let tracker = system.track(contract.address); +Let's say that we have a contract named `Playground`, written in `contracts/playground.tact` file. If we've created that contract through [Blueprint][bp], then it also created a `tests/Playground.spec.ts` test suite file for us. -// This object would track all logs -let logger = system.log(contract.address); +The test file contains a single `describe(){:typescript}` [Jest][jest] function call, which denotes a test group. -// -// Sending a message -// +Inside that group, you'll have three variables, available in all tests within: -// 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" }); +* `blockchain` — local blockchain instance provided by [Sandbox][sb] +* `deployer` — a TypeScript wrapper used for deploying our `Playground` contract or any other we'd like to be deployed +* `playground` — a TypeScript wrapper for our `Playground` contract -// Run the system until there are no more messages -await system.run(); + -// -// Collecting results -// + It's a common mistake to update `.tact` code and run tests without making a build first. That's because tests in [Blueprint][bp] rely on TypeScript wrappers generated by a Tact compiler and work with the latest build made. -console.log(track.collect()); // Prints out all transactions in contract -console.log(logger.collect()); // Prints out all logs for each transaction + That's why every time you make a change to your Tact code, make sure to also build it with `npx blueprint build` before you execute the test suite. For your convenience, you may unite builds and tests into a single command, as shown in the [experimental lab setup](#lab-4). -// -// Invoking get methods -// + -let counter = await contract.getCounter(); -console.log(counter); // Prints out counter value +Then, a `beforeEach(){:tact}` [Jest][jest] function is called — it specifies all the code to be executed before each of the subsequent test closures. + + + It is strongly advised not to modify the contents of `beforeEach(){:tact}`, unless you really need some specific behavior for each test closure or parameters of your [`init(){:tact}`](/book/contracts#init-function) function have changed. + + + +Finally, each test closure is described with a call to `it(){:tact}` [Jest][jest] function — that's where tests are actually written. + +A simplest example of the test closure can look like that: + +```typescript +it('should deploy', async () => { + // The check is done inside beforeEach, so this can be empty +}); ``` -## Snapshot testing with `jest` +### Debug with `dump()` [#tests-dump] -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. +To see results of [`dump(){:tact}`][dump] function calls and use ["printf debugging"](#approach-3) approach, one has to: -### Initial setup +1. Put calls to [`dump(){:tact}`][dump] and other [common debugging functions](#debug-functions) in relevant places of the code. +2. Run [Jest][jest] tests, which would call target functions and send messages to target receivers. + +Assuming you've created a [new counter contract project](/#start), let's see how it works in practice. + +First, let's place a call to [`dump(){:tact}`][dump] in `contracts/simple_counter.tact`, which would output the `amount` passed in `msg{:tact}` [Struct](/book/structs-and-messages#structs) to contract's debug console: + +```tact filename="contracts/simple_counter.tact" {3} +// ... +receive(msg: Add) { + dump(msg.amount); + // ... +} +// ... +``` -Example of a minimal test file: +Next, let's comment out all existing `it(){:typescript}` test closures in `tests/SimpleCounter.spec.ts` file. And then add the following one: + +```typescript filename="tests/SimpleCounter.spec.ts" +it('should dump', async () => { + await playground.send( + deployer.getSender(), + { value: toNano('0.5') }, + { $$type: 'Add', queryId: 1n, amount: 1n }, + ); +}); +``` + +It sends a message to our contract's `receive(msg: Add){:tact}` [receiver](/book/receive) without storing the [results of such send](#tests-send). + +Now, if we build our contract with `yarn build{:shell}` and run our test suite with `yarn test{:shell}`, we'll see the following in the test logs: + +```txt +console.log + #DEBUG#: [DEBUG] File contracts/simple_counter.tact:17:9 + #DEBUG#: 1 + + at SmartContract.runCommon (node_modules/@ton/sandbox/dist/blockchain/SmartContract.js:221:21) +``` + +Which is produced by of our [`dump(){:tact}`][dump] call above. + + + + Read more about sending messages to contracts in tests: [Send messages to contracts](#tests-send). + + + +### State expectations with `expect()` [#tests-expect] + +The integral parts of writing tests is ensuring that your expectations match the observed reality. For that, [Jest][jest] provides a function `expect(){:tact}`, which is used as follows: + +1. First, an observed variable is provided. +2. Then, a specific method is called to check a certain property of that variable. + +Here's a more involved example, which uses `expect(){:tact}` function to check that counter contract actually properly increases the counter: ```typescript -import { ContractSystem } from '@tact-lang/emulator'; -import { sample_Contract } from './output/sample_Contract'; +it('should increase counter', async () => { + const increaseTimes = 3; + for (let i = 0; i < increaseTimes; i++) { + console.log(`increase ${i + 1}/${increaseTimes}`); + + const increaser = await blockchain.treasury('increaser' + i); -describe("contract", () => { - it("should deploy correctly", async () => { + const counterBefore = await simpleCounter.getCounter(); + console.log('counter before increasing', counterBefore); - // 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); + const increaseBy = BigInt(Math.floor(Math.random() * 100)); + console.log('increasing by', increaseBy); - // Send a message - await contract.send(treasure, { value: toNano(1) }, { $$type: "Deploy", queryId: 0n }); - await system.run(); + const increaseResult = await simpleCounter.send( + increaser.getSender(), + { value: toNano('0.05') }, + { $$type: 'Add', queryId: 0n, amount: increaseBy } + ); - // Testing output - expect(tracker.collect()).toMatchInlineSnapshot(); + expect(increaseResult.transactions).toHaveTransaction({ + from: increaser.address, + to: simpleCounter.address, + success: true, }); + + const counterAfter = await simpleCounter.getCounter(); + console.log('counter after increasing', counterAfter); + + expect(counterAfter).toBe(counterBefore + increaseBy); + } +}); +``` + + + + See more test examples in the [Sandbox][sb] documentation:\ + [Testing flow (FunC)](https://github.com/ton-org/sandbox/blob/main/docs/testing-key-points.md)\ + [Writing tests for Tact](https://github.com/ton-org/sandbox/blob/main/docs/tact-testing-examples.md) + + + +### Utility methods [#tests-jest-utils] + +Test files generated by [Blueprint][bp] import `@ton/test-utils` library, which provides access to a number of additional helper methods for the result type of `expect(){:typescript}` [Jest][jest] function. Note, that regular methods like `toEqual(){:typescript}` are still there and ready to be used. + +#### toHaveTransaction + +The method `expect(…).toHaveTransaction(){:typescript}` checks that the list of transactions has a transaction matching certain properties you specify: + +```typescript {2} +const res = await yourContractName.send(…); +expect(res.transactions).toHaveTransaction({ + // For example, let's check that a transaction to your contract was successful: + to: yourContractName.address, + success: true, }); ``` -### Generating snapshots +To know the full list of such properties, look at auto-completion options provided by your editor or IDE. + +#### toEqualCell + +The method `expect(…).toEqualCell(){:typescript}` checks equality of two [cells](/book/types#primitive-types): + +```typescript {3} +expect(oneCell).toEqualCell(anotherCell); +``` + +#### toEqualSlice + +The method `expect(…).toEqualSlice(){:typescript}` checks equality of two [slices](/book/types#primitive-types): + +```typescript {3} +expect(oneSlice).toEqualSlice(anotherSlice); +``` + +#### toEqualAddress + +The method `expect(…).toEqualAddress(){:typescript}` checks equality of two [addresses](/book/types#primitive-types): + +```typescript {3} +expect(oneAddress).toEqualAddress(anotherAddress); +``` + +### Send messages to contracts [#tests-send] -After running the `yarn jest` command, the line with `toMatchInlineSnapshot` of the test will be automatically updated with a snapshot of the output. +To send messages to contracts, use `.send(){:typescript}` method on their TypeScript wrappers like so: ```typescript -// ... - 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, - }, - ], - }, - ], - }, - ] - `); -// ... +// It accepts 3 arguments: +await yourContractName.send( + // 1. sender of the message + deployer.getSender(), // this is a default treasury, can be replaced + + // 2. value and (optional) bounce, which is true by default + { value: toNano('0.5'), bounce: false }, + + // 3. a message body, if any + 'Look at me!', +); +``` + +Message body can be a simple string, or an object specifying fields of the [Message](/book/structs-and-messages#messages) type: + +```typescript {4-8} +await yourContractName.send( + deployer.getSender(), + { value: toNano('0.5') }, + { + $$type: 'NameOfYourMessageType', + field1: 0n, // bigint zero + field2: 'yay', + }, +); +``` + +More often than not, it's important to store results of such sends, because they contain events occurred, transactions made and external messages sent: + +```typescript +const res = await yourContractName.send(…); +// res.events — array of events occurred +// res.externals — array of external-out messages +// res.transactions — array of transactions made +``` + +With that, we can easily filter or check certain transactions: + +```typescript +expect(res.transactions).toHaveTransaction(…); +``` + +### Observe the fees and values [#tests-fees] + +[Sandbox][sb] provides a helper function `printTransactionFees(){:typescript}`, which pretty-prints all the values and fees that went into transactions provided. It is quite handy for observing the flow of nanoToncoins. + +To use it, modify imports from `@ton/sandbox` on top of the test file: + +```typescript +import { Blockchain, SandboxContract, TreasuryContract, printTransactionFees } from '@ton/sandbox'; +// ^^^^^^^^^^^^^^^^^^^^ +``` + +Then, provide an array of transactions as an argument, like so: + +```typescript +printTransactionFees(res.transactions); +``` + +To work with individual values of total fees or fees from compute and action [phases](https://docs.ton.org/learn/tvm-instructions/tvm-overview#transactions-and-phases), inspect each transaction individually: + +```typescript {11,17,21} +// Storing the transaction handled by the receiver in a separate constant +const receiverHandledTx = res.transactions[1]; +expect(receiverHandledTx.description.type).toEqual('generic'); + +// Needed to please TypeScript +if (receiverHandledTx.description.type !== 'generic') { + throw new Error('Generic transaction expected'); +} + +// Total fees +console.log('Total fees: ', receiverHandledTx.totalFees); + +// Compute fee +const computeFee = receiverHandledTx.description.computePhase.type === 'vm' + ? receiverHandledTx.description.computePhase.gasFees + : undefined; +console.log('Compute fee: ', computeFee); + +// Action fee +const actionFee = receiverHandledTx.description.actionPhase?.totalActionFees; +console.log('Action fee: ', actionFee); + +// Now we can do some involved checks, like limiting the fees to 1 Toncoin +expect( + (computeFee ?? 0n) + + (actionFee ?? 0n) +).toBeLessThanOrEqual(toNano('1')); +``` + + + + [Sandbox][sb] has many more utility functions, which are often handy. For example, it provides `prettyLogTransaction(){:typescript}` and `prettyLogTransactions(){:typescript}`, which operate on a single or multiple transactions respectively and pretty-print flow of values between the addresses. + + + +### Transactions with intentional errors [#tests-errors] + +Sometimes it's useful to make negative tests, featuring intentional errors and throwing specific [exit codes](/book/exit-codes). + +Example of such [Jest][jest] test closure in [Blueprint][bp]: + +```typescript filename="tests/YourTestFileHere.spec.ts" {9,15} +it('throws specific exit code', async () => { + // Send a specific message to our contract and store the results + const res = await your_contract_name.send( + deployer.getSender(), + { + value: toNano('0.5'), // value in nanoToncoins sent + bounce: true, // (default) bounceable message + }, + 'the message your receiver expects', // ← change it to yours + ); + + // Expect the transaction to our contract fail with a certain exit code + expect(res.transactions).toHaveTransaction({ + to: your_contract_name.address, + exitCode: 5, // ← change it to yours + }); +}); +``` + +Note, that to track down transactions with a certain exit code, you only need to specify `exitCode` field in `toHaveTransaction(){:typescript}` method of `expect(){:typescript}`. + +However, it's useful to narrow the scope by specifying the recipient address `to`, such that Jest would look only at the transaction caused by our message to the contract. + +### Simulate passage of time [#tests-time] + +The Unix time in local blockchain instances provided by [Sandbox][bp] starts at the moment of the creation of those in `beforeEach(){:typescript}` block. + +```typescript {2} +beforeEach(async () => { + blockchain = await Blockchain.create(); // ← here + // ... +}); +``` + +Previously, we've been warned not to modify the `beforeEach(){:typescript}` block unless we really need to. And now, to override the time and time travel a little, we do. + +Let's add the following line by the end of it, setting `blockchain.now` explicitly to the time when deployment message was handled: +```typescript {3} +beforeEach(async () => { + // ... + blockchain.now = deployResult.transactions[1].now; +}); +``` + +Now, we can manipulate time in out test clauses. For example, let's make a transaction one minute after the deployment and another one after two: + +```typescript {2,4} +it('your test clause title', async () => { + blockchain.now += 60; // 60 seconds late + const res1 = await yourContractName.send(…); + blockchain.now += 60; // another 60 seconds late + const res2 = await yourContractName.send(…); +}); +``` + +## Logging via `emit` [#logging] + +A [global static function](/book/functions#global-static-functions) [`emit(){:tact}`](/ref/core-common#emit) sends a message to the outer world — it doesn't have a specific recipient. + +This function is very handy for logging and analyzing data off-chain — one just has to look at [external messages](/book/external) produced by the contract. + +### Logs in local Sandbox tests [#logging-local] + +When deploying in the [Sandbox][sb], you may call [`emit(){:tact}`](/ref/core-common#emit) from a [receiver function](/book/contracts#receiver-functions) and then observe the list of sent [external messages](/book/external): + +```typescript {9-10} +it('emits', async () => { + const res = await simpleCounter.send( + deployer.getSender(), + { value: toNano('0.05') }, + 'emit_receiver', // ← change to the message your receiver handles + ); + + console.log("Address of our contract: " + simpleCounter.address); + console.log(res.externals); // ← here one would see results of emit() calls, + // and all external messages in general +}); +``` + +### Logs of a deployed contract [#logging-deployed] + +Every transaction on TON Blockchain [contains `out_msgs`](https://docs.ton.org/develop/data-formats/transaction-layout#transaction) — a dictionary that holds the list of outgoing messages that were created while executing the transaction. + +To see logs from [`emit(){:tact}`](/ref/core-common#emit) in that dictionary, look for external messages without a recipient. In various TON Blockchain explorers, such transactions will be marked as `external-out` with destination specified as `-` or `empty`. + +Note, that some explorers deserialize the message body sent for you, while others don't. + +## Handling bounced messages [#bounced] + +When [sent](/book/send) with `bounce: true{:tact}`, messages can bounce back in case of errors. Make sure to write relevant [`bounced(){:tact}`](/book/bounced) message [receivers](/book/contracts#receiver-functions) and handle bounced messages gracefully: + +```tact +bounced(msg: YourMessage) { + // ...alright squad, let's bounce!... +} +``` + +Keep in mind that bounced messages in TON have only $224$ usable data bits in their message body and don't have any references, so one cannot recover much data from it. However, you still get to see whether the message has bounced or not, allowing you to create more robust contracts. + +Read more about bounced messages and receivers: [Bounced messages](/book/bounced). + +## Experimental lab setup [#lab] + +If you're overwhelmed by the testing setup of [Blueprint][bp] or just want to test some things quickly, worry not — there is a way to set up a simple playground as an experimental lab to test your ideas and hypotheses. + + + +### Create a new Blueprint project [#lab-1] + +That will prevent pollution of your existing one with arbitrary code and tests. + +The new project can be named anything, but I'll name it `Playground` to convey the right intent. + +To create it, run the following command: + + + + ```shell + npm create ton -- tact-playground --type tact-empty --contractName Playground + ``` + + + ```shell + # recommended + yarn create ton tact-playground --type tact-empty --contractName Playground + ``` + + + ```shell + pnpm create ton tact-playground --type tact-empty --contractName Playground + ``` + + + +Versions of [Blueprint][bp] starting with 0.20.0 automatically enable debug mode in `wrappers/` for new contracts, so we only have to adjust the testing suite and prepare our `Playground` contract for testing. + +### Update the test suite [#lab-2] + +Move into the newly created `tact-playground/` project and in the `tests/Playground.spec.ts`, change the `"should deploy"{:tact}` test closure to the following: + +```typescript filename="tests/Playground.spec.ts" +it('plays', async () => { + const res = await playground.send( + deployer.getSender(), + { value: toNano('0.5') }, // ← here you may increase the value in nanoToncoins sent + 'plays', + ); + + console.log("Address of our contract: " + playground.address); + console.log(res.externals); // ← here one would see results of emit() calls +}); +``` + +### Modify the contract [#lab-3] + +Replace the code in `contracts/playground.tact` with the following: + +```tact filename="contracts/playground.tact" {4-6} +import "@stdlib/deploy"; + +contract Playground with Deployable { + receive("plays") { + // NOTE: write your test logic here! + } +} +``` + +The basic idea of this setup is to place the code you want to test into the [receiver function](/book/contracts#receiver-functions) responding to the [string](/book/types#primitive-types) message `"plays"{:tact}`. + +Note, that you can still write any valid Tact code outside of that [receiver](/book/contracts#receiver-functions). But in order to test it you'll need to write related test logic inside of it. + +### Let's test! [#lab-4] + +With that, our experimental lab setup is complete. To execute that single test we've prepared for our `Playground` contract, run the following: + +```shell +yarn test -t plays +``` + +From now on, to test something you only need to modify the contents of the tested [receiver function](/book/contracts#receiver-functions) of your Tact contract file and re-run the command above. Rinse and repeat that process until you've tested what you wanted to test. + +For simplicity and cleaner output's sake, you may add a new field to `scripts` in your `package.json`, such that you'll only need to run `yarn lab{:shell}` to build and test in one. + +On Linux or macOS, it would look like: + +```json filename="package.json" {3} +{ + "scripts": { + "lab": "blueprint build 1>/dev/null && yarn test -t plays" + } +} +``` + +And here's how it may look on Windows: + +```json filename="package.json" {3-4} +{ + "scripts": { + "build": "blueprint build | out-null", + "lab": "yarn build && yarn test -t plays" + } +} +``` + +To run: + +```shell +yarn lab ``` -### 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. +[dump]: /ref/core-debug#dump +[tg]: https://t.me/tactlang +[bp]: https://github.com/ton-org/blueprint +[sb]: https://github.com/ton-org/sandbox +[jest]: https://jestjs.io diff --git a/pages/book/operators.mdx b/pages/book/operators.mdx index 6303c0e7..9d44bb14 100644 --- a/pages/book/operators.mdx +++ b/pages/book/operators.mdx @@ -471,7 +471,7 @@ two ^ 3; // 1 Binary bar (_bitwise OR_) operator `|{:tact}` applies a [bitwise OR](https://en.wikipedia.org/wiki/Bitwise_operation#OR), which performs the [logical OR](#binary-logical-or) operation on each pair of the corresponding bits of operands. This is useful when we want to apply a specific [bitmask](https://en.wikipedia.org/wiki/Mask_(computing)). -For example, _bitwise OR_ is commonly used in Tact to [combine base modes with optional flags](http://localhost:3000/book/message-mode#combining-modes-with-flags) by masking specific bits to $1$ in order to construct a target [message `mode`](/book/message-mode). +For example, _bitwise OR_ is commonly used in Tact to [combine base modes with optional flags](/book/message-mode#combining-modes-with-flags) by masking specific bits to $1$ in order to construct a target [message `mode`](/book/message-mode). Can only be applied to values of type [`Int{:tact}`][int]: diff --git a/pages/index.mdx b/pages/index.mdx index 3ed30d78..746f4f0f 100644 --- a/pages/index.mdx +++ b/pages/index.mdx @@ -18,7 +18,7 @@ To check it, run `node --version{:shell}` — it should show you the version 18. It will create a new project with the simple counter contract: - + ```shell npm create ton -- simple-counter --type tact-counter --contractName SimpleCounter @@ -26,6 +26,7 @@ It will create a new project with the simple counter contract: ```shell + # recommended yarn create ton simple-counter --type tact-counter --contractName SimpleCounter ``` diff --git a/pages/ref/core-common.mdx b/pages/ref/core-common.mdx index cf1da95b..b28a7999 100644 --- a/pages/ref/core-common.mdx +++ b/pages/ref/core-common.mdx @@ -235,22 +235,10 @@ emit("Catch me if you can, Mr. Holmes".asComment()); // asComment() converts a S - To analyze `emit(){:tact}` calls, one has to look at [external messages](http://localhost:3000/book/external) produced by the contract. For example, when deploying in [Sandbox](https://github.com/ton-org/sandbox), you may call `emit(){:tact}` from a [receiver function](/book/contracts#receiver-functions) and then observe the list of sent external messages: - - ```typescript {9-10} - it('emits', async () => { - const res = await simpleCounter.send( - deployer.getSender(), - { value: toNano('0.05') }, - 'emit_receiver', - ); - - console.log("Address of our contract: " + simpleCounter.address); - console.log(res.externals); // ← here one would see results of emit() calls, - // and all external messages in general - }); - ``` + To analyze `emit(){:tact}` calls, one has to look at [external messages](/book/external) produced by the contract. + Read more: [Logging via `emit(){:tact}`](/book/debug#logging). + [p]: /book/types#primitive-types