Розробка смарт-контрактів на 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. Зберігання контракту — це також дерево Cell, яке повністю завантажується при кожному виклику і повністю зберігається назад.
Це має конкретні наслідки. Немає колізій слотів як у EVM proxy. Немає проблеми необмеженого зростання сховища в тому ж смислі. Але є своя біль: читання глибоко вкладеної структури даних вимагає послідовного розбору Cell через begin_parse() / load_uint() / load_ref(). Забудете порядок полів під час десеріалізації — отримаєте неправильні дані без помилки компілятора.
Асинхронна модель повідомлень
У Solidity contractA.functionB() — це синхронний виклик в межах однієї транзакції. У TON все інакше: контракти спілкуються через повідомлення, кожне з яких обробляється в окремій транзакції. Якщо контракт A надсилає повідомлення контракту B, який надсилає повідомлення контракту C — це три окремі транзакції в трьох окремих блоках.
Це порушує знайомі паттерни. Атомарна операція «перевір баланс — переведи — оновіть стан» стає послідовністю повідомлень з проміжними станами. Вам потрібна явна машина стану у сховищі контракту. Вам потрібні механізми timeout для незавершених операцій. Вам потрібна обробка bounce-повідомлень — коли цільовий контракт не може прийняти повідомлення і повертає його відправнику.
Пропустити обробку bounce — типова помилка новачків у TON. Контракт надсилає TON іншому контракту, який повертається, монети повертаються як bounce-повідомлення. Якщо відправник не обробив bounce — монети зникають (точніше, обробляються обробником за умовченням і втрачаються з логіки).
Gas-модель у TON
У TON немає звичного gas limit на транзакцію в тому ж смислі. Є storage fees — контракт платить за зберігання свого стану кожну секунду. Контракт з великим сховищем і нульовим балансом в якийсь момент буде заморожений. Це потрібно враховувати при проектуванні: контракти-сховища даних (наприклад, окремі гаманці 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, які ми використовуємо як базу.
Паттерни для асинхронних операцій
Машина стану у сховищі. Кожна багатокрокова операція має явний статус: pending, processing, completed, failed. Повідомлення перевіряють поточний статус перед переходом у наступний стан. Без цього паралельні повідомлення можуть привести до непередбачуваного результату.
Timeout і cleanup. Операція зі статусом processing більше N секунд — автоматичний откат через окреме повідомлення від самого контракту (через send_raw_message з затримкою через зовнішні інструменти або зберігання timestamp у сховищі).
Маршрутизація вихідних повідомлень. Усі 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 слоти |
| Мова | FunC / Tact | Solidity / Vyper |
| Стандарти токенів | TEP-74 (Jetton), TEP-62 (NFT) | ERC-20, ERC-721, ERC-1155 |
| Атомарність операцій | Ні (кілька транзакцій) | Так (одна транзакція) |
| Storage fees | Так (періодично) | Ні |
| Швидкість транзакцій | <5 секунд | 12-60 секунд (Ethereum) |
| Інструменти аудиту | Обмежений набір | Slither, Mythril, Echidna |
Таблиця показує, що TON не «краще» або «гірше» — він інший. Для завдань з високим TPS і дешевими транзакціями (платежі, gamefi, miniapps у Telegram) — архітектура TON оптимальна. Для складної DeFi-логіки з atomic composability — екосистема EVM простіша у реалізації.
Процес розробки TON-контракту
Проектування потоку повідомлень (3-5 днів). До написання коду — повна діаграма всіх повідомлень: op-codes, напрямок, що відбувається при bounce. Це критично важливо саме для TON через асинхронність.
Розробка на FunC + тести на TypeScript (1-3 тижня). Blueprint-тести покривають happy path і всі bounce-сценарії. Емуляція TVM локально дає повне трасування транзакцій.
Перевірка storage layout. Перевіряємо порядок серіалізації/десеріалізації Cell, правильність обробки bounce, відсутність обробників-«заглушок» для невідомих op-codes.
Деплой на testnet (Testnet TON) → mainnet. Верифікація вихідного коду через TON Verifier.
Орієнтири за сроками
Базовий Jetton-контракт (TEP-74 сумісний) — 3-5 днів. NFT-колекція з користувацькою логікою (TEP-62) — 1-2 тижня. Складний протокол з кількома взаємодіючими контрактами і стейт-машиною — від 4 тижнів. Інтеграція з Telegram Mini App через tonconnect — додає 3-7 днів на клієнтську частину.
Вартість розраховується індивідуально після розгляду архітектури.







