Tolk: новый язык смарт-контрактов TON в 2026
Tolk — преемник FunC и нативный язык Acton. TypeScript-подобный синтаксис, сильная типизация, pattern matching.
- Автор
- TON Adoption Team · исследовательская группа проекта
- Опубликовано
Содержание22разделов
- Краткая история: как мы пришли к Tolk
- Зачем ещё один язык
- Синтаксис Tolk: ключевые элементы
- Типы данных
- Функции и контракты
- Сообщения и pattern matching
- Storage
- Tolk vs FunC: что упрощено
- Tolk vs Tact: что осталось низкоуровневым
- Пример: counter-контракт целиком
- Линт-правила Tolk: 29 проверок из коробки
- Тестирование: unit + coverage + fuzz + mutation
- Fuzzing через @test.fuzz
- Mutation testing
- Деплой Tolk-контракта
- Совместимость: можно ли мешать Tolk и FunC
- acton func2tolk: автоматическая конвертация
- Mixed-language проекты
- Когда что выбирать: матрица решения
- Реальный опыт первых недель
- Будущее Tolk: что обещано
- Итог
TL;DR. Tolk — это следующее поколение языка смарт-контрактов TON, нативный язык Acton v1.0 (релиз 11 мая 2026). Он наследует семантику FunC и компилируется напрямую в TVM-байткод, но синтаксически ближе к TypeScript: structs, enums, pattern matching, нормальная типизация. Цель — закрыть зазор между низкоуровневым FunC и высокоуровневым Tact: дать читаемый код без потери контроля над gas и cell-layout. Эта статья — карта местности для разработчика, который знает FunC или Tact и хочет понять, что нового в Tolk и стоит ли мигрировать.
Краткая история: как мы пришли к Tolk
TON-стек прошёл три поколения языков:
- FunC — первый язык, написан командой Николая Дурова. Низкоуровневый, статически типизированный, очень близко к TVM. Контроль над стеком — почти ручной. Большинство существующих mainnet-контрактов (jetton-минтеры, NFT-коллекции, AMM-пары STON.fi и DeDust) написаны на FunC.
- Tact — появился в 2023. Высокоуровневый, синтаксис похож на TypeScript/Kotlin, OOP-стиль, автоматическая сериализация. Цель — дать «Solidity для TON» и снизить порог входа. Платой стало частичное скрытие модели TVM: разработчик меньше знает, что именно происходит на уровне cell/slice.
- Tolk — разработан TON Core team как следующая итерация FunC. Сохраняет прямую компиляцию в TVM-байткод и низкоуровневую модель, но добавляет современный синтаксис, паттерн-матчинг и сильную типизацию. По данным TON Foundation, gas-оверхед на типичных контрактах падает на 30-50% по сравнению с эквивалентным FunC.
Tolk не отменяет FunC — он его развивает. Это не отдельный compiler-target, а скорее «FunC 2.0» с человеческим синтаксисом.
Зачем ещё один язык
Чего не дали FunC + Tact:
- FunC слишком низкоуровневый. Простой counter-контракт на FunC — это ~30 строк со stack-manipulation, ручной сериализацией и
begin_parse() / load_uint(32)для каждого поля. Новому разработчику нужен месяц только чтобы перестать пугаться. - Tact слишком далеко от TVM. Автоматический сериализационный слой и OOP-абстракции экономят строки, но прячут реальную стоимость операций. Edge-case баги в gas-расходе или в encoding’е сообщений ловить сложнее.
- Нет общего стандарта. До Tolk проект на FunC и проект на Tact — это два разных мира с разной тулзой, разным дебагом, разной библиотечной экосистемой.
Tolk берёт читаемость Tact (structs, pattern matching, нормальные типы) и оставляет прозрачность FunC (явные cell/slice, контролируемый gas, прямая компиляция в TVM). Это компромисс, но компромисс полезный.
Синтаксис Tolk: ключевые элементы
Типы данных
Базовые TVM-типы доступны напрямую и пишутся в нижнем регистре (как примитивы TypeScript):
int— знаковое число (по умолчанию 257 бит, можно указатьint32,int64,uint32и т.д.).coins— специальный тип для TON-сумм с собственной сериализацией.address— TON-адрес (MsgAddressв TL-B).cell— корневая ячейка TON-данных.slice— указатель на данные внутри ячейки (для чтения).builder— конструктор новой ячейки (для записи).
Composite-типы:
struct Storage {
id: uint32
owner: address
counter: int32
}
enum MessageType {
Increase = 0x12345678
Decrease = 0x87654321
Reset = 0xAABBCCDD
}
struct и enum — главные «лёгкие» абстракции. Они компилируются в обычную cell-сериализацию, но в коде вы работаете с ними как с обычными типами.
Функции и контракты
Функции объявляются с ключевым словом fun, тип возвращаемого значения после двоеточия:
@inline
fun loadStorage(): Storage {
return Storage.fromCell(contract.getData())
}
@inline
fun saveStorage(storage: Storage) {
contract.setData(storage.toCell())
}
Контракт — это набор top-level функций плюс хендлеры сообщений (onInternalMessage, onBouncedMessage, get-методы). Никакого отдельного contract MyContract { ... }-блока, как в Tact: контракт — это файл.
Сообщения и pattern matching
Входящие сообщения объявляются как struct с static OPCODE:
struct IncreaseCounter {
static OPCODE = 0x12345678
queryId: uint64
delta: int32
}
Внутри хендлера используется matches<T>() для проверки типа и parseAs<T>() для разбора:
fun onInternalMessage(in: InMessage) {
val storage = loadStorage()
val body = in.body
if (body.matches<IncreaseCounter>()) {
val msg = body.parseAs<IncreaseCounter>()
assert (in.senderAddress == storage.owner) throw Errors.NotOwner
storage.counter += msg.delta
saveStorage(storage)
}
}
Это заменяет всю простыню begin_parse() / load_uint(32) / load_uint(64) / load_int(32), которую во FunC писали руками для каждого сообщения.
Storage
Storage сериализуется прямой парой toCell() / fromCell() на struct. Контракт читает состояние через contract.getData(), пишет — через contract.setData(cell). Никакого скрытого слоя, как в Tact: вы видите ровно ту ячейку, которая попадёт в c4.
Tolk vs FunC: что упрощено
| Что во FunC | Что в Tolk |
|---|---|
Ручной begin_parse() / load_uint(N) для каждого поля | body.parseAs<MyStruct>() одной строкой |
throw_if(err, condition) | assert(!condition) throw err |
() как unit-тип в сигнатурах | Просто отсутствие возвращаемого типа |
~load_msg_addr() с тильдой-мутацией | slice.loadAddress() обычным dot-вызовом |
| TVM opcodes как магические числа | UPPER_SNAKE_CASE константы |
Имена типа storage::owner_address | Поддерживаются через backtick-escape, но обычно — camelCase |
Tolk vs Tact: что осталось низкоуровневым
| Что в Tact | Что в Tolk |
|---|---|
| Автоматический сериализационный слой со скрытыми allocations | Явные toCell() / fromCell(), видимые в коде |
receive("message")-обработчики | matches<T>() по OPCODE — ближе к TVM-логике |
self.field в стиле OOP | Явная struct-переменная Storage, передаваемая в функции |
as uint32 после имени поля | Тип сразу: counter: int32 |
| Скрытый gas-cost | Полный контроль над cell-layout и операциями |
Это не «Tact с другим синтаксисом» — это другая модель. Tolk не прячет TVM, он делает работу с ним приятной.
Пример: counter-контракт целиком
Канонический counter из шаблона Acton (acton new my_counter --template counter):
import "@stdlib/tvm-dicts"
struct Storage {
id: uint32
owner: address
counter: int32
}
@inline
fun loadStorage(): Storage {
return Storage.fromCell(contract.getData())
}
@inline
fun saveStorage(storage: Storage) {
contract.setData(storage.toCell())
}
struct IncreaseCounter {
static OPCODE = 0x12345678
queryId: uint64
delta: int32
}
struct ResetCounter {
static OPCODE = 0xAABBCCDD
queryId: uint64
}
fun onInternalMessage(in: InMessage) {
val storage = loadStorage()
val body = in.body
if (body.matches<IncreaseCounter>()) {
val msg = body.parseAs<IncreaseCounter>()
assert (in.senderAddress == storage.owner) throw Errors.NotOwner
storage.counter += msg.delta
saveStorage(storage)
} else if (body.matches<ResetCounter>()) {
assert (in.senderAddress == storage.owner) throw Errors.NotOwner
storage.counter = 0
saveStorage(storage)
}
}
get fun currentValue(): int32 {
return loadStorage().counter
}
Что здесь происходит, по строкам:
- Storage — три поля, сериализуются в одну cell.
loadStorage/saveStorage— синтаксический сахар надcontract.getData()иsetData().@inlineподсказывает компилятору заинлайнить и сэкономить gas.IncreaseCounter/ResetCounter— две формы сообщений с уникальными opcodes.onInternalMessage— единая точка входа, внутри которойmatches<T>()разводит ветки.get fun— get-метод, доступный через внешние RPC-запросы без отправки транзакции.
Эквивалент на FunC занял бы 30-40 строк со stack-magic. На Tact — примерно столько же строк, но с скрытыми allocations и без явного контроля над cell-layout.
Линт-правила Tolk: 29 проверок из коробки
В Acton встроен acton check — линтер, который покрывает security- и quality-классы багов. Особенно полезны для аудита и code review:
- E007
no-bounce-handler— контракт безonBouncedMessage(классический источник тихих потерь). - E013
unauthorized-access—storage.save()без предшествующей проверки sender’а. Использует CFG + dataflow analysis уровня Slither/Mythril. - E018
random-requires-initialization— вызовrandom()безrandomize_lt(). - E019
divide-before-multiply— паттерн(a / b) * c, типичный источник precision loss. - E007/E016 — dangerous send-modes (
DESTROY_IF_ZERO,CARRY_ALL_BALANCE) без safety-комментария.
Команда:
acton check
acton check --explain E013 # подробное описание правила
acton check --fix # авто-фикс простых случаев
29 правил — не маркетинг. Каждое реально срабатывает на production-коде. На официальном counter-template TON Foundation линтер чистый, но как только начинается реальный код — warnings появляются быстро.
Тестирование: unit + coverage + fuzz + mutation
Тесты пишутся на самом Tolk, используют @acton/testing. Базовый паттерн:
import "@acton/testing/expect"
import "@acton/testing/blockchain"
test("counter increases by sender owner", () => {
val blockchain = TestBlockchain.create()
val deployer = blockchain.treasury("deployer")
val counter = blockchain.deploy(Counter, deployer.address)
val res = counter.sendIncrease(deployer, 5)
expect(res).toHaveSuccessfulTx({
from: deployer.address,
to: counter.address,
success: true
})
expect(counter.currentValue()).toEqual(5)
})
Запускается через acton test. На каноническом counter-проекте проходит 8 тестов за менее 30 мс — порядок на два быстрее, чем @ton/sandbox на Node.js.
Fuzzing через @test.fuzz
Аннотация над get-функцией превращает её в fuzz-тест:
import "@acton/testing/expect"
import "@acton/testing/fuzz"
@test.fuzz
get fun `test balance stays bounded`(value: int) {
val bounded = fuzz.bound(value, 0, 100)
fuzz.assume(bounded != 13) // discard input 13
expect(bounded >= 0).toBeTrue()
}
Хелперы:
fuzz.bound(value, min, max)— clamp значения в диапазон (аналогvm.assumeв Foundry).fuzz.assume(condition)— отбросить input, если условие не выполнено.
Используется для проверки инвариантов вида «total_supply == sum(balances)» или «overflow на swap-функции не возможен».
Mutation testing
Главное оружие для аудита:
acton test --mutate --mutate-contract Counter
Acton автоматически модифицирует код (меняет += на -=, == на !=, убирает assert, переворачивает граничные условия) и прогоняет тесты на каждой мутации. Если все тесты прошли — мутация survived — значит покрытие в этой точке недостаточно.
На официальном counter-template TON Foundation mutation testing находит 3 survivors из 15 — даже у вендоров TVM есть пробелы. Для bug-hunting’а это означает: каждый survivor = потенциальный bug-вектор.
Деплой Tolk-контракта
Деплой пишется как Tolk-скрипт через acton script:
// contracts/scripts/deploy.tolk
import "@acton/emulation/scripts"
import "@wrappers/Counter.gen"
fun main() {
val deployer = scripts.wallet("deployer")
val counter = Counter.create({ owner: deployer.address, counter: 0 })
counter.deploy(deployer.address, { value: 0.05 TON }).waitForFirstTransaction()
println("Deployed at: {}", counter.address)
}
Запуск:
acton script contracts/scripts/deploy.tolk # эмуляция (без отправки)
acton script contracts/scripts/deploy.tolk --fork-net testnet # эмуляция с live testnet state
acton script contracts/scripts/deploy.tolk --net testnet # реальный send
acton script contracts/scripts/deploy.tolk --net testnet --tonconnect # через Tonkeeper
Авто-генерация TypeScript-обёрток для frontend:
acton wrapper Counter --ts
Создаёт wrappers-ts/Counter.gen.ts — типизированный wrapper, который вы импортируете в React/Vite-приложение и вызываете методы контракта с полной IDE-подсказкой.
Совместимость: можно ли мешать Tolk и FunC
Да, и это важная фича для миграции с legacy-кодовой базы.
acton func2tolk: автоматическая конвертация
acton func2tolk path/to/vault.fc
Под капотом — npx @ton/convert-func-to-tolk@1.0.0. На вход .fc, на выход .tolk:
() storage::load() impure inline { ... }→@inline fun `storage::load`() { ... }ds~load_msg_addr()→ds.loadAddress()throw_if(err, condition)→assert(!condition) throw err- TVM opcodes автоматически в UPPER_SNAKE_CASE-константы
Имена с :: сохраняются через backtick-escaped identifiers. Конвертация лексическая, не семантическая — после неё всё ещё нужно прогнать acton check и тесты, но 80% работы делается автоматически.
Mixed-language проекты
Один Acton.toml может содержать одновременно FunC и Tolk модули. Это позволяет:
- Оставить аудированное FunC-ядро (например, AMM-пары STON.fi v2) нетронутым.
- Новые модули писать на Tolk.
- Постепенно конвертировать старые модули через
func2tolkпо мере появления времени.
Когда что выбирать: матрица решения
| Сценарий | Рекомендуемый язык | Почему |
|---|---|---|
| Новый проект, команда без TON-опыта | Tolk | Самый низкий порог входа, современный синтаксис, нативный в Acton |
| Новый проект, команда с FunC-опытом | Tolk | Сохраняется ментальная модель TVM, синтаксис проще |
| Простой бизнес-логичный контракт без жёстких gas-ограничений | Tact | Самый высокий уровень абстракции, быстро |
| Финансовое ядро (AMM, lending, vault) с критичным gas | Tolk или FunC | Прозрачный gas-cost, аудируемая модель |
| Расширение существующего FunC-проекта | FunC + точечно Tolk | Не ломать рабочий код |
| Миграция legacy FunC | func2tolk + ручной cleanup | Автоконвертация → тесты → линт |
| Учебный проект | Tolk | Acton-шаблоны на Tolk, документация свежая |
Краткое правило: по умолчанию Tolk, FunC оставляйте для legacy и максимально критичного gas-кода, Tact — для прототипов без production-нагрузки.
Реальный опыт первых недель
Tolk + Acton доступны публично с 11 мая 2026 — релиз свежий, экосистема ещё формируется. Из наблюдаемого:
- Conversion работает. Перевод реальных FunC-контрактов (vault.fc STON.fi на 84 строки) через
func2tolkпроходит без ручных правок. Синтаксис на выходе читается. - Mutation testing продуктивен. Даже на официальном template TON Foundation находятся survivors (отсутствие проверки sender’а в Decrease/Reset-хендлерах, граничное условие
>=vs>). - Линт ловит реальные баги. На production-FunC коде через прослойку конвертации
acton checkсрабатывает на E013 (unauthorized-access) и E007 (no-bounce-handler). - Linux Node.js обязателен для
func2tolk— Windows-native nodejs ломается на WSL UNC-путях. Решение:~/.local/nodeчерез ручную установку tarball’а.
Это — первые недели. Через 3-6 месяцев экосистема библиотек, обучающих материалов и production-проектов вырастет — но окно first-mover прямо сейчас.
Будущее Tolk: что обещано
В публичных коммуникациях TON Foundation и команды Acton упоминается:
- Расширение стандартной библиотеки (
@stdlib/*) — больше готовых helpers’ов для jetton/NFT/DNS-паттернов. - Generic-функции — типовые шаблоны без дублирования кода.
- Более богатый pattern matching — switch-выражения, exhaustiveness check.
- Интеграция on-chain библиотек через
acton library publish— переиспользование кода между контрактами на уровне TVM.
Конкретных дат roadmap нет. Tolk развивается через минорные релизы Acton CLI: acton up подтягивает свежую версию компилятора, stdlib и линтера. Следить лучше через CHANGELOG в github.com/ton-blockchain/acton.
Итог
Tolk — это точка перехода TON-стека к зрелому языку смарт-контрактов. Он сохраняет прозрачность FunC и берёт читаемость от Tact, не становясь полностью ни тем, ни другим. Для нового проекта в 2026 году выбор Tolk — рациональный дефолт. Для legacy FunC-кодовых баз — постепенная миграция через func2tolk без переписывания с нуля.
Сама по себе Tolk-разработка имеет смысл только в связке с Acton — toolchain даёт линт, mutation testing, debugger и deploy. Установка и quickstart описаны там же.
Частые вопросы
Чем Tolk отличается от FunC?
Чем Tolk отличается от Tact?
Зачем нужен ещё один язык, если уже есть FunC и Tact?
Можно ли смешивать Tolk и FunC в одном проекте?
Какой синтаксис в Tolk для входящих сообщений?
Сколько линт-правил из коробки в Tolk?
Как тестировать Tolk-контракты?
Что обещано в Tolk-roadmap после v1.0?
Похожие материалы
- Основы14 мая 2026 г.
Acton v1.0 — Foundry для TON: полный гайд (2026)
Acton v1.0 от TON Foundation вышел 11 мая 2026: один Rust-CLI, который заменяет blueprint, sandbox, Misti и func.
- Основы15 дек. 2025 г.
TON для разработчиков: знакомство с FunC, Tact и Tolk
Какие языки используются для смарт-контрактов TON в 2026 — FunC, Tact, Tolk. Что выбрать новичку, чем отличаются, какие инструменты нужны и где учиться.
- Основы11 февр. 2026 г.
Mainnet vs Testnet TON: для чего нужны и как переключаться
Чем отличается mainnet от testnet TON, как переключить Tonkeeper в тестовую сеть, где взять бесплатный testnet TON и зачем это нужно разработчикам и.
- Основы30 дек. 2025 г.
Почему TON не EVM-совместим и что это значит
TON использует TVM вместо Ethereum Virtual Machine — почему так задумано, чем это плохо и хорошо для пользователя, и что есть EVM-мосты в экосистеме TON в 2026.
- Новости9 мар. 2026 г.
TON Foundation: кто стоит за развитием блокчейна 2026
Разбор TON Foundation — структура, ключевые люди, история отделения от Telegram, роль Max Crown и Steve Yun, появление TON Strategy Co.