The Tact smart contract programming language is a statically-typed smart contract programming language currently implemented as a transpiler into FunC, which compiles down to the TVM bitcode. This implementation strategy is likely to change in the future.
The Tact compiler parses the input source code into an abstract syntax tree, type-checks it, generates FunC code, and runs the FunC compiler, which produces the corresponding Fift code and a TVM binary in the BoC format.
Besides TVM binaries, the Tact compiler generates TypeScript "wrappers" to conveniently test Tact contracts locally in a simulated blockchain environment using, for instance, the standard de-facto Sandbox package.
Additionally, it generates summaries for humans and machines in Markdown and JSON formats. The summaries include information like
- binary code size,
- TL-B schemas for the program types, including contract storage and message formats,
- TVM exit codes,
- trait inheritance and contract dependency diagrams.
Currently, Tact does not have a (formal) language specification, so one needs to consult the Tact docs and the tests in this repository.
The list of known bugs can be obtained using the following GitHub request: https://github.com/tact-lang/tact/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22kind%3A%20bug%22.
The document outlines how you can contribute and provides information about different components of the compiler, including their entry points.
There are many ways to contribute to Tact, and many of them do not involve writing any code. Here are few ideas to get started:
- Simply start using Tact. Does everything work as expected? If not, we are always looking for improvements. Let us know by opening an issue.
- Look through the open issues. Share your ideas, provide workarounds, or ask for clarification. Tact is there to help.
- If you find an issue you would like to fix, open a pull request.
- Alternatively, we invite you to create new educational materials in any form, help foster the community, and build new Tact contracts and projects.
If your future pull request (PR) does not close a particular issue, please create the issue first and describe the whole context: what you're adding/changing and why you're doing so. Then, after some discussion, open a PR to close that issue.
If you are only fixing a bug, it is fine to submit a pull request right away, but we still recommend that you file an issue detailing what you're fixing. This is helpful in case we don't accept that specific fix but want to keep track of the issue. Additionally, by discussing the issue first, we might discover a better solution together, and there might not be any need for you to implement it yourself.
Furthermore, unrelated and/or undiscussed refactorings are less likely to be merged. Especially if they're made without introducing tests that demonstrate the contribution is correctly implemented. This usually includes positive and negative tests, showing the happy path(s), and featuring intentionally broken cases.
In general, small pull requests that do not perform any refactorings are much easier to review and more likely to get merged.
To pass review, the code has to conform to our styleguide.
To pass CI, one needs to have a warning-free build. To run all the lints described below, execute the following command in your terminal:
yarn lint:allRunning ESLint across the whole Tact codebase:
yarn lintTo spell-check the entire codebase with CSpell, run:
yarn spellThe Knip tool checks issues with the compiler dependencies and API. It can be run with
yarn knipTact is implemented in TypeScript. The minimum required version for Node.js is 22.
The rest of the build and development dependencies are specified, as usual, in the package.json file, and the most important ones are described in the present document.
Tact's pipeline uses a patched version of the FunC compiler vendored as a WASM binary with some JS wrappers; see the following files:
The message of the commit f777da3213e3b064a7f407b2569cfd546cca277e explains how the patched version was obtained. We had to patch the FunC compiler because the corresponding FunC compiler issue was unresolved at the time of writing.
The most up-to-date recipe to build Tact is described in the .github/workflows/tact.yml GitHub Actions file.
See: Using unreleased versions of tools early.
To run the Tact's CLI, use either of the following commands:
# If Tact is not built, but dependencies are installed via yarn install
npx ts-node src/cli/tact/index.ts
# If Tact is built via yarn build:fast
node bin/tact.jsTo run the BoC disassembly's CLI, use either of the following commands:
# If Tact is not built, but dependencies are installed via yarn install
npx ts-node src/cli/unboc/index.ts
# If Tact is built via yarn build:fast
node bin/unboc.jsUse the 0x tool to profile and visualize stack traces of the Tact compiler or other CLIs. 0x generates interactive flamegraphs to help identify performance bottlenecks in the code.
For example, let's profile the Tact compiler on compiling a certain contract from our benchmarks suite:
# Requires Tact compiler to be built beforehand
npx 0x ./bin/tact.js ./src/benchmarks/jetton/minter.tactAfter the compilation, a folder containing the profiling results will be created. It will include the self-contained interactive .html page with flamegraph visualization — open it in your browser of choice to see and filter the results.
The X-axis represents the profile population (not time), and the Y-axis represents the stack depth. Colors can help differentiate between functions, and the width of each block corresponds to the CPU time it took: the wider the block, the longer it took to compute that function call.
Using the search input on the top right, you can highlight certain functions on the flamegraph.
See npx 0x --help for more options, including:
-D/--output-dir, which is used to specify the artifact output directory.-o/--open, which automatically opens the flamegraph after finishing the run.
We use Jest as our testing framework. Jest supports a combination of snapshot and expectation tests.
Some tests are put in the same folder as the implementation and can be located in the *.spec.ts files; other tests are grouped into categories in the src/test folder. The project map section has more information on tests relevant to each compiler component.
To rebuild the compiler and run end-to-end tests, the usual workflow is as follows:
# build the compiler, but skip typechecking of the test files
yarn build:fast
# build the test contracts and benchmarks
yarn gen:contracts:fast
# run all tests except the slow property-based map tests
yarn test:fastUpdating all the test snapshots:
yarn test -uUpdating a subset of the test snapshots can be done like so:
yarn test -u spec-name-pattern1 spec-name-pattern2Beware that updating snapshots from Javascript Debug Terminal in VSCode might lead to unexpected results. E2E CLI tests check stderr of child tact processes, but JS Debug Terminal adds "Debugger attached" messages into it.
To compute coverage, run
yarn istanbulThe log will be generated into /coverage folder. For better UX, serve its contents locally with
cd coverage
npx http-server . -p 3000The benchmark system tracks gas consumption changes after making changes to the compiler.
To run benchmarks:
yarn benchTo run benchmarks and print comparison table
yarn benchThere are a few table print modes, you can set them with:
cross-env PRINT_MODE="first-last" yarn benchPrint modes:
first-last: Displays only the first and last benchmark results for comparison. This is useful for quickly identifying overall changes between the initial and most recent benchmarks.full: Displays all benchmark results in detail. This mode provides a comprehensive view of all historical benchmark data.last-diff: Displays the last three benchmark results to highlight recent changes. This mode is helpful for tracking incremental updates and their impact.
Default print mode is full.
To update historical benchmarks with results.json:
yarn bench:updateTo add new row to results.json:
yarn bench:addTo add a new benchmark:
- Create a new folder:
src/benchmarks/<your-benchmark-name>/ - Inside it, add
tact/andfunc/subfolders as needed. - Run
yarn gen:contracts:benchmarksto recompile benchmarks. - Add additional benchmark
We use Allure to generate reports for our tests.
To run the tests and generate the Allure report:
yarn test:allureDevelopers can use step(), parameter() and attachment() (and other) from src/test/allure/allure.ts to increase the readability of the test report
Tact's command-line interface (CLI) is located in bin/tact.js.
It can be run with:
# If Tact is not built, but dependencies are installed via yarn install
npx ts-node src/cli/tact/index.ts
# If Tact is built via yarn build:fast
node bin/tact.jsTact uses an in-house CLI arguments parser listed in src/cli/arg-parser.ts.
The main entry point for the Tact CLI is src/cli/tact/index.ts and src/pipeline/build.ts is the platform-independent compiler driver which contains the high-level compiler pipeline logic described above.
The Tact CLI gets Tact settings from a tact.config.json file or creates a default config for a single-file compilation mode. The format of tact.config.json files is specified in src/config/configSchema.json.
The so-called "pre-compilation" steps, which include imports resolution, type-checking, and building schemas for high-level Tact data structures to be serialized/deserialized as cells (this step is dubbed "allocation"), are located in src/pipeline/precompile.ts.
Besides the terminal, the Tact compiler is supposed to work in browser environments as well.
Some CLI tests can be found in .github/workflows/tact.yml GitHub Action file.
In addition to the compiler, Tact bundles the BoC disassembly from the @tact-lang/opcode package.
Its command-line interface (CLI) is located in bin/unboc.js.
It can be run with:
# If Tact is not built, but dependencies are installed via yarn install
npx ts-node src/cli/unboc/index.ts
# If Tact is built via yarn build:fast
node bin/unboc.jsFor more info, refer to the package's GitHub repository: tact-lang/ton-opcode.
The src/grammar/grammar.peggy file contains the Tact grammar expressed in the PEG language of the pgen parser generator.
The helper file src/grammar/index.ts contains the logic that transforms concrete syntax trees produced with the help of parser into abstract syntax trees (ASTs) defined in src/ast/ast.ts. The index.ts file also does grammar validation, like checking that function or constant attributes are not duplicated or that user identifiers do not start with specific reserved prefixes.
The src/grammar/test folder contains Tact to test the parse: .tact files are expected to parse without any issues, and .fail.tact files should result in parser errors. The parser error messages and the locations they point to are fixed in the src/grammar/**snapshots**/grammar.spec.ts.snap Jest snapshot file.
The src/stdlib/stdlib folder contains the source code of the standard library. It has two subfolders:
src/stdlib/stdlib/stdcontains ambient definitions that are present in every.tactfile. It's loaded automatically by the compiler and should never otherwise be included.src/stdlib/stdlib/libscontains standard library exports available via@stdlib/...imports from source code. The source code inside of these files is just a regular.tactsource and shouldn't import anything from the/stdfolder either.
The library is built by the yarn gen:stdlib script into a stdlib.ts file that holds the base64 of all the Tact and FunC sources in the library. Whenever the standard library's source code changes, a new stdlib.ts must be generated.
Whenever CLI (or external tooling) needs to access the source of the standard library, it should initialize the virtual filesystem from stdlib.ts. Accessing the source of the standard library directly is error-prone and discouraged. For example, the standard library might not be found if the compiler is included as a library.
The Tact type-checker implementation can be found mainly in the following files:
src/types/resolveDescriptors.tstakes care of checking at the level of module items, data type definitions, function signatures, etc., and it does not deal with statements (so it does not traverse function bodies);src/types/resolveStatements.tschecks statements and statements blocks;src/types/resolveExpression.tstype-checks the Tact expressions.
The current implementation of the typechecker is going to be significantly refactored, as per issue #458. The corresponding pull request will formally specify the Tact typing rules.
Until we have the Tact type system specified, the only source of information about it would be the aforementioned Tact docs and the tests in the following locations:
src/types/test: positive well-formedness tests at the level of data types, contracts, traits, and function signatures;src/types/test-failed: negative well-formedness tests at the level of data types, contracts, traits, and function signatures;src/types/stmts: positive type-checking tests at the level of function bodies;src/types/stmts-failed: negative type-checking tests at the level of function bodies;src/test/compilation-failed: negative type-checking tests that require full environment, for instance, the standard library (the other tests insrc/typesdon't have access to the full environment).
The constant evaluator is used as an optimizer to prevent some statically known expressions from being evaluated at run-time and increase the gas consumption of the contracts. It will later be extended to perform partial evaluation of contracts and use various simplification rules, such as applying some algebraic laws, to further reduce the gas consumption of contracts at run-time.
The constant evaluator supports a large subset of Tact and handles, for instance, constants defined in terms of other constants, built-in and user-defined functions, logical and arithmetic operations.
The main logic of the constant evaluator is in the file src/optimizer/interpreter.ts.
You can find the relevant tests in src/test/e2e-emulated/constants/constants.tact and the corresponding spec-file: src/test/e2e-emulated/constants/constants.spec.ts.
The negative tests for constant evaluation are in the Tact files prefixed with const-eval in the src/test/compilation-failed/contracts folder.
Some general information on how Tact code maps to FunC is described in the Tact docs: https://docs.tact-lang.org/book/func.
The code generator lives in the src/generator sub-folder with the entry point in src/generator/writeProgram.ts.
The implementation that we have right now is being refactored to produce FunC ASTs and then pretty-print those ASTs as strings instead of producing source FunC code in one step. Here is the relevant pull request: #559.
One can find the end-to-end codegen test spec files in the src/test/e2e-emulated folder. The test contracts are located in the subfolders of the src/test/e2e-emulated folder. Many of those spec files test various language features in relative isolation.
An important spec file that tests argument passing semantics for functions and assignment semantics for variables is here: src/test/e2e-emulated/functions/semantics.spec.ts.
Contracts with inline in the name of the file set experimental.inline config option to true.
Contracts with external in the name of the file set the external config option to true.
Note: If you add an end-to-end test contract, you must also run yarn gen to compile it and create TypeScript wrappers.
yarn gen also re-compiles test contracts, so running it when code generation is changed is essential.
Some other codegen tests are as follows:
src/test/gas-consumption: check gas consumption;src/test/exit-codes: test that certain actions produce the expected exit codes;src/test/codegen: test that these contracts compile just fine without running any dynamic tests: bug fixes for FunC code generation often add tests into this folder.
Benchmarks are located inside src/benchmarks/, one directory per benchmark:
| Path / file | Purpose |
|---|---|
tact/ |
Tact project that is being benchmarked |
func/ |
Equivalent FunC project that we compare against |
test.spec.ts |
Jest test spec for contract functionality testing |
bench.spec.ts |
Jest test spec for performance benchmarking |
gas.json |
Aggregated gas‑consumption results, updated by the CLI |
size.json |
Contract byte‑code size history, also updated by the CLI |
CLI support – All commands for creating, updating, or comparing benchmarks are documented in the Updating Benchmarks section.
The entry point to the Tact AST pretty-printer is src/ast/ast-printer.ts. It will be used for the Tact source code formatter once the parser keeps comments and other relevant information.
The project contains special TypeScript files with the .build.ts extension used for build processes and testing purposes. These files are not included in the published NPM package.
A typical example is test/contracts.build.ts which builds contract tests.
When adding new build or test scripts, make sure to use the .build.ts extension to keep them separate from the main compiler code.
To generate and inspect random Abstract Syntax Trees (ASTs), you can use the yarn random-ast command. This command generates a specified number of random Abstract Syntax Trees (ASTs) and pretty-prints them.
Note: At the moment, only Ast Expression will be generated
yarn random-ast <count>
Where <count> is the number of random expressions to generate.
yarn random-ast 42It will produce 42 random expressions and pretty-print them in the terminal.
The implementation can be found in random-ast.infra.ts.