Разработка смарт-контрактов на Tact (TON)
TON — это не ещё один EVM-клон. Асинхронная модель сообщений, actor model, бесконечный шардинг — всё это делает разработку контрактов принципиально другой задачей по сравнению с Solidity. Tact появился как попытка сделать эту разработку более доступной: строгая типизация, знакомый синтаксис, встроенные safety-примитивы. Но за удобством скрываются специфические паттерны, незнание которых ведёт к потере средств или неработающей логике.
Асинхронность как главная архитектурная проблема
В EVM вызов контракта — это синхронная операция. В TON — каждое взаимодействие между контрактами это отдельное сообщение, которое обрабатывается в следующем блоке. Контракт A отправляет сообщение контракту B, получает ответ через один или несколько блоков. В промежутке состояние A может измениться.
Это порождает паттерн, который разработчики с EVM-фоном пропускают: optimistic state update. Логика такова: контракт A обновляет своё состояние до получения подтверждения от B — иначе при параллельных вызовах возникает race condition. Если B вернёт ошибку, A должен откатить состояние через обработчик bounced message.
В Tact bounce-handler выглядит так:
bounced(msg: bounced<TokenTransfer>) {
self.balance += msg.amount; // откатываем списание
}
Не реализовать bounce-handler — значит потерять средства при любом отказе дочернего контракта.
Управление газом: forward_ton и carry-value паттерн
В TON газ оплачивается в TON (nanoton). При отправке сообщения между контрактами нужно явно указать, сколько TON пересылается вместе с сообщением на оплату газа следующего контракта. В Tact это параметр value в send().
Типичная ошибка: отправить сообщение с value: 0 — контракт-получатель не сможет обработать его из-за нехватки газа. Сообщение зависает или bounced. Правильный паттерн — carry-value: пересылать достаточно TON через цепочку контрактов, рассчитывая газ на каждый шаг.
Tact упрощает это через SendRemainingValue mode — остаток от входящего сообщения пересылается дальше:
send(SendParameters{
to: nextContract,
value: 0,
mode: SendRemainingValue + SendIgnoreErrors,
body: NextMessage{...}.toCell()
});
Но SendIgnoreErrors — опасный флаг. Он игнорирует ошибки при отправке, что может привести к silent failure. Используем его только там, где потеря сообщения некритична.
Как строим TON-контракты на Tact
Стек: Tact 1.x, Blueprint (тестовый фреймворк от TON Foundation), sandbox для локального запуска, @ton/core для работы с cells/slices в тестах. TypeScript для тест-кода и скриптов деплоя.
Структура проекта следует Blueprint-конвенциям: контракты в contracts/, тесты в tests/, скрипты деплоя в scripts/. Каждый контракт — отдельный файл с явным указанием contract MyContract with Deployable.
Тесты через sandbox покрывают:
- нормальный flow (happy path)
- bounce scenarios (что происходит при revert дочернего контракта)
- граничные значения газа (достаточно ли value на каждый шаг)
- параллельные вызовы (не возникает ли race condition в состоянии)
Верификация. TON верифицирует контракты через ton-verify — сравнивает хэш скомпилированного байткода с тем, что задеплоено. Верификация на tonscan.org и tonviewer.com — стандарт для любого публичного контракта.
Сравнение FunC и Tact
| Критерий | FunC | Tact |
|---|---|---|
| Синтаксис | Низкоуровневый, C-подобный | Высокоуровневый, TypeScript-подобный |
| Безопасность типов | Ручная | Встроенная |
| Скорость разработки | Медленно | Быстро |
| Контроль над ячейками | Полный | Ограниченный |
| Оптимизация газа | Ручная, максимальная | Автоматическая, достаточная |
Для большинства задач — DeFi примитивы, NFT контракты, jetton стандарт — Tact достаточен и безопаснее. FunC используем когда нужна максимальная оптимизация газа или нестандартная работа с cell layout.
Процесс работы
Начинаем с проектирования message flow: рисуем граф контрактов и сообщений между ними. На TON архитектурные решения на этом этапе дороже переделок, чем на EVM — потому что async model влияет на все паттерны.
Разработка занимает 3-5 дней для одного контракта средней сложности, до 2 недель для системы из нескольких взаимодействующих контрактов. Деплой в mainnet — через Blueprint npx blueprint run, с проверкой состояния через tonapi.io.







