Разработка смарт-контрактов на FunC (TON)
TON — это не ещё один EVM-клон. Разработчик, который приходит с опытом Solidity, первые две недели проводит в состоянии лёгкого шока: стек-ориентированная виртуальная машина TVM, ячеистая модель хранения данных через Cell, асинхронная передача сообщений между контрактами, и FunC — язык, который выглядит как функциональный C с элементами Lisp. Это не недостатки, это архитектурные решения, которые дают TON уникальные свойства масштабируемости. Но кривая обучения резкая.
Как устроен TON изнутри
TVM и Cell — не то, к чему вы привыкли
В EVM контракт — это байткод, storage — это key-value хранилище с 32-байтными слотами. В TVM контракт хранится как дерево Cell-объектов. Каждая Cell — это до 1023 бит данных и до 4 ссылок на другие Cell. Storage контракта — это тоже Cell-дерево, которое целиком загружается при каждом вызове и целиком сохраняется обратно.
Это влечёт конкретные следствия. Нет слот-коллизий как в EVM proxy. Нет проблемы unbounded storage growth в том же смысле. Но есть своя боль: чтение глубоко вложенной структуры данных требует последовательного разбора Cell через begin_parse() / load_uint() / load_ref(). Забудешь порядок полей при десериализации — получишь неверные данные без ошибки компилятора.
Асинхронная модель сообщений
В Solidity contractA.functionB() — это синхронный вызов в рамках одной транзакции. В TON всё иначе: контракты общаются через сообщения, каждое из которых обрабатывается в отдельной транзакции. Если контракт A отправляет сообщение контракту B, который отправляет сообщение контракту C — это три отдельные транзакции в трёх отдельных блоках.
Это ломает привычные паттерны. Атомарная операция «проверь баланс — переведи — обнови состояние» становится последовательностью сообщений с промежуточными состояниями. Нужна явная машина состояний в storage контракта. Нужны timeout-механизмы для незавершённых операций. Нужна обработка bounce-сообщений — когда целевой контракт не может принять сообщение и возвращает его отправителю.
Пропустить обработку bounce — типичная ошибка новичков в TON. Контракт отправляет TON другому контракту, тот реверсируется, coins возвращаются как bounce-сообщение. Если отправитель не обработал bounce — coins уходят в никуда (точнее, обрабатываются дефолтным receive-хендлером и теряются из логики).
Gas-модель в TON
В TON нет привычного gas limit на транзакцию в том же смысле. Есть storage fees — контракт платит за хранение своего состояния каждую секунду. Контракт с большим storage и нулевым балансом в какой-то момент будет заморожен. Это нужно учитывать при проектировании: контракты-хранилища данных (например, индивидуальные jetton-кошельки) должны иметь механизм пополнения или минимальный баланс.
Как мы пишем контракты на FunC
Стек разработки
Blueprint — стандартный инструмент для разработки и тестирования TON-контрактов. Предоставляет окружение для локального запуска TVM, написания тестов на TypeScript, деплоя через кастомизируемые скрипты.
toncli используем для быстрого прототипирования и работы с legacy-кодом. Для новых проектов — Blueprint предпочтительнее из-за лучшей интеграции с TypeScript.
Tact — высокоуровневый язык поверх FunC, который значительно снижает порог входа и количество ошибок при работе с Cell-структурами. Рассматриваем его для проектов, где скорость разработки важнее максимального контроля над байткодом. FunC — там, где нужна ручная оптимизация.
Официальные стандарты: TEP-74 (Jetton — аналог ERC-20), TEP-62 (NFT — аналог ERC-721), TEP-64 (метаданные токенов). OpenZeppelin-аналога в TON нет — есть референсные имплементации от команды TON Foundation, которые используем как базу.
Паттерны для асинхронных операций
State machine в storage. Каждая многошаговая операция имеет явный статус: pending, processing, completed, failed. Сообщения проверяют текущий статус перед переходом в следующее состояние. Без этого параллельные сообщения могут привести к непредсказуемому результату.
Timeout и cleanup. Операция в статусе processing более N секунд — автоматический rollback через отдельное сообщение от самого контракта (через send_raw_message с задержкой через внешние инструменты или хранение timestamp в storage).
Outgoing message routing. Все bounce-хендлеры явно задокументированы в коде. Паттерн: отдельная функция-диспетчер, которая по op-code определяет, что делать с bounce. Потеря средств из-за необработанного bounce — некомпенсируемая.
Типичная ошибка при работе с Jetton
Jetton-архитектура в TON шардированная: у каждого пользователя — отдельный контракт-кошелёк (Jetton Wallet), которым управляет Jetton Minter. Transfer — это два сообщения: от кошелька-отправителя к кошельку-получателю. Если получатель — контракт, который хочет «знать» о полученных jetton, он должен обработать transfer_notification сообщение.
Стандартная ошибка: контракт-получатель не реализует обработчик transfer_notification — jetton приходят, но контракт «не знает» об этом и не меняет состояния. Дальше — баг в бизнес-логике, который проявляется не сразу.
Сравнение TON и EVM для задач разработки
| Характеристика | TON / FunC | EVM / Solidity |
|---|---|---|
| Модель выполнения | Асинхронные сообщения | Синхронные вызовы |
| Хранение данных | Cell-деревья | Key-value slots |
| Язык | FunC / Tact | Solidity / Vyper |
| Стандарты токенов | TEP-74 (Jetton), TEP-62 (NFT) | ERC-20, ERC-721, ERC-1155 |
| Атомарность операций | Нет (несколько транзакций) | Да (одна транзакция) |
| Storage fees | Да (periodic) | Нет |
| Скорость транзакций | <5 секунд | 12-60 секунд (Ethereum) |
| Аудит-инструменты | Ограниченный набор | Slither, Mythril, Echidna |
Таблица показывает, что TON не «лучше» или «хуже» — он другой. Для задач с высоким TPS и дешёвыми транзакциями (payments, gamefi, miniapps в Telegram) — архитектура TON оптимальна. Для сложной DeFi-логики с atomic composability — EVM-экосистема проще в реализации.
Процесс разработки TON-контракта
Проектирование message flow (3-5 дней). До написания кода — полная диаграмма всех сообщений: op-code, направление, что происходит при bounce. Это критически важно именно для TON из-за асинхронности.
Разработка на FunC + тесты на TypeScript (1-3 недели). Blueprint-тесты покрывают happy path и все bounce-сценарии. Эмуляция TVM локально даёт полную трассировку транзакций.
Ревью storage layout. Проверяем порядок сериализации/десериализации Cell, корректность обработки bounce, отсутствие хендлеров-«пустышек» для неизвестных op-code.
Деплой на testnet (Testnet TON) → mainnet. Верификация исходного кода через TON Verifier.
Ориентиры по срокам
Базовый Jetton-контракт (TEP-74 совместимый) — 3-5 дней. NFT-коллекция с кастомной логикой (TEP-62) — 1-2 недели. Сложный протокол с несколькими взаимодействующими контрактами и стейт-машиной — от 4 недель. Интеграция с Telegram Mini App через tonconnect — добавляет 3-7 дней на клиентскую часть.
Стоимость рассчитывается индивидуально после разбора архитектуры.







