FunC — низкоуровневый, строго типизированный язык программирования для смарт-контрактов в сети TON (The Open Network). Это «каноничный» язык ядра TON: многие базовые контракты экосистемы, ранние DApp и инфраструктура сети написаны именно на FunC.
Если Tact создан как высокоуровневый язык «в стиле TypeScript» для быстрой разработки, то FunC даёт:
- максимальный контроль над тем, как код исполняется на TVM;
- прямую работу с ячейками (cells), слайсами и битами;
- возможность реализовывать нестандартные модели состояния и оптимизировать газ до байта.
Цена за это — более высокий порог входа и необходимость хорошо понимать устройство TON и виртуальной машины TVM.
Зачем TON нужен язык FunC
При проектировании TON авторам нужен был язык, который:
- отображается на TVM почти один к одному — без лишних абстракций и неожиданностей;
- позволяет описывать сложную сериализацию данных и протоколы поверх ячеек;
- даёт строгую типизацию, но остаётся достаточно «близким к железу».
Так появился FunC:
- декларативный, но низкоуровневый;
- ориентирован на работу с ячейками, слайсами, tuрle-типами;
- хорошо подходит для реализации:
- базовых протоколов сети;
- стандартов токенов;
- системных контрактов и мостов;
- экспериментов с TVM и новым функционалом.
Позже к нему добавились более высокоуровневые языки (Tact, Tolk), но FunC остаётся важным элементом стека: многие фичи сначала реализуются и обкатываются именно на нём.
Ключевые особенности FunC
Основные свойства языка FunC:
- Сильная типизация.
Есть типы для целых чисел, буля, ячеек, слайсов, контекстов и т.д. Компилятор проверяет совместимость типов, но допускает достаточно низкоуровневые операции.
- Функциональный стиль.
Несмотря на то, что FunC не «чистый функциональный язык», многие конструкции:
- работают через функции без явного состояния;
- используют значения-результаты, а не мутабельные переменные.
- Близость к TVM.
Концепции FunC напрямую отображаются на инструкций TVM:
- работа с ячейками/слайсами;
- контроль стека;
- доступ к системным полям сообщения.
- Явная сериализация данных.
Разработчик сам описывает:
- как данные упаковываются в ячейки;
- как они читаются обратно;
- какие биты где лежат.
Это даёт максимальный контроль, но требует аккуратности.
- Интеграция с Fift.
FunC-компилятор генерирует Fift-код, который затем транслируется в байткод TVM. Fift можно использовать для низкоуровневой отладки и проверки.
- Минимальные абстракции.
В отличие от Tact и Tolk, FunC предлагает мало «сахара»: разработчик сам отвечает за многие детали протокола и оптимизации газа.
Где используется FunC в TON
FunC широко применялся и применяется для:
- Базовых системных контрактов TON.
Ключевые элементы инфраструктуры исторически писались на FunC.
- Стандартов токенов (Jetton, NFT и др.).
Ранние и референсные реализации стандартов Jetton и NFT были на FunC, до появления реализаций на Tact.
- Децентрализованных бирж и DeFi-протоколов.
Многие первые DEX, лендинги и сервисные контракты писались на FunC, чтобы максимально контролировать газ и структуру данных.
- Мостов и кросс-чейн-протоколов.
В связке с TON × CCIP и другими мостовыми решениями нужны точные и предсказуемые контракты — область, где FunC до сих пор актуален.
- Экспериментальных фич и исследований.
Разработчики, исследующие новые возможности TVM, часто начинают именно с FunC.
Базовые конструкции языка FunC
Минимальный контракт на FunC выглядит примерно так (упрощённый пример):
int counter;
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) {
;; разбор входящего сообщения и обновление состояния
}
int get_counter() method_id {
return counter;
}
Ключевые элементы:
- глобальные переменные (int counter;) — часть состояния контракта;
- функция recv_internal — обработчик внутренних сообщений;
- функция get_counter() с method_id — геттер, который можно вызывать off-chain.
На практике контракты на FunC гораздо сложнее:
- используют структуры / tuрle-тип;
- вручную разобирают и собирают сообщения;
- работают с картами, списками и другими структурами на основе ячеек.
Типы данных в FunC
FunC предоставляет набор типов, ориентированных на TVM:
- int — целые числа (знак, длина определяется контекстом);
- slice — слайс (прозрачное представление части ячейки);
- cell — ячейка (основной строительный блок хранения данных);
- builder — объект для конструирования ячейки;
- tuple — кортеж значений (часто используется для возврата нескольких результатов);
- cont — континуэшн (используется в продвинутых сценариях);
- логические значения (int с соглашениями, часто используются 0/1).
При работе с TVM важно понимать:
- ячейка (cell) может содержать до 1023 бит и до 4 ссылок на дочерние ячейки;
- slice — это «представление» части ячейки для чтения;
- builder — объект для записи данных в ячейку.
Пример работы с ячейками:
cell build_data(int value) {
builder b = begin_cell();
b = b.store_int(value, 64);
return b.end_cell();
}
(int, slice) load_data(slice s) {
int value = s~load_int(64);
return (value, s);
}
Структура контракта на FunC
Контракт на FunC обычно содержит:
- глобальные переменные / константы — часть состояния;
- функции для:
- обработки внутренних сообщений (recv_internal);
- обработки внешних сообщений (recv_external);
- геттеров (get_... с method_id);
- вспомогательных операций (парсинг, сериализация, расчёты).
Обработка сообщений
Стандартные сигнатуры:
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) {
;; логика для внутренних сообщений
}
() recv_external(slice in_msg_body) {
;; логика для внешних (external) сообщений
}
Внутри этих функций:
- извлекаются системные поля сообщения;
- проверяются адреса отправителей и параметры;
- разбирается тело сообщения (in_msg_body);
- вызываются нужные ветки бизнес-логики.
Геттеры
Геттер — это специальная функция, помеченная method_id:
int get_counter() method_id {
return counter;
}
Клиент может вызвать её через lite-клиент или SDK, получив результат без изменения состояния контракта.
Работа с ячейками, слайсами и сериализацией
Одна из ключевых особенностей FunC — необходимость самостоятельно описывать, как данные упаковываются и распаковываются.
Пример: запись структуры в ячейку
Допустим, нам нужно хранить в ячейке:
- баланс (64 бита);
- флаг блокировки (1 бит);
- timestamp (32 бита).
cell store_position(int balance, int is_frozen, int last_update) {
builder b = begin_cell();
b = b.store_int(balance, 64);
b = b.store_int(is_frozen, 1);
b = b.store_int(last_update, 32);
return b.end_cell();
}
Чтение обратно:
(tuple) load_position(slice s) {
int balance = s~load_int(64);
int is_frozen = s~load_int(1);
int last_update = s~load_int(32);
return (balance, is_frozen, last_update);
}
Здесь важно:
- порядок и размер полей должны совпадать при записи и чтении;
- длины (64, 1, 32) выбраны осознанно — под бизнес-ограничения;
- ошибка в одном месте легко ломает всё декодирование.
Компиляция и связка с Fift/TVM
Цепочка сборки FunC-контракта примерно такова:
- код на FunC → компилятор FunC → Fift-код;
- Fift-код → компиляция в байткод TVM;
- байткод + начальное состояние → готовый контракт для деплоя.
Существуют:
- CLI-инструменты для компиляции FunC;
- скрипты для упаковки байткода в .boc и деплоя;
- интеграция с TON Developer Toolbox и Blueprint, который может работать и с FunC, и с Tact, и с Tolk.
Fift остаётся полезным:
- для низкоуровневой отладки;
- для анализа байткода;
- для диагностики сложных ошибок сериализации и поведения TVM.
Инструменты и экосистема FunC
Несмотря на то, что экосистема вокруг Tact активно растёт, FunC сохраняет собственный набор инструментов:
- Официальный компилятор FunC.
Работает из CLI, интегрирован в множество тулов и CI.
- Fift и TVM-эмуляторы.
Позволяют:
- запускать байткод локально;
- исследовать состояние стека;
- проверять поведение контракта на конкретных входных данных.
- Blueprint.
Через TON Developer Toolbox можно создавать проекты и под FunC:
- удобная структура каталогов;
- базовые конфиги и примеры.
- Интеграции с IDE.
Поддержка подсветки синтаксиса FunC есть в:
- VS Code (через расширения сообщества);
- некоторых JetBrains IDE;
- других редакторах (через файлы синтаксиса).
- Референс-контракты.
В репозиториях TON можно найти:
- пример базового контракта;
- реализации Jetton/NFT на FunC;
- инструменты для тестирования и миграций.
Часть инфраструктуры исторически растёт именно вокруг FunC, поэтому многие команды до сих пор держат «ядро» на нём, а «обвязку» делают на высокоуровневых языках.
Безопасность и частые ошибки в FunC
FunC даёт много свободы, а значит — много места для ошибок. Частые классы проблем:
- Ошибки сериализации и TL-B.
- перепутали порядок полей;
- изменили схему хранения, не мигрировав существующие данные;
- неверные размеры полей (битовая длина).
Результат — данные читаются неправильно, баланс и состояние «ломаются».
- Неправильная обработка сообщений.
- недостаточная проверка отправителя;
- отсутствие проверки bounce-флага;
- игнорирование msg_value и других полей.
Это приводит к атакующим сценариям и некорректному поведению.
- Забытые проверки инвариантов.
- балансы могут уходить в минус;
- суммы переполняются;
- карты содержат противоречивые значения.
- Проблемы с газом.
- слишком тяжёлые циклы по большим структурам;
- рекурсия без жёстких ограничений;
- отсутствие оптимизации и раннего выхода.
- Ошибки в модели прав доступа.
- owner/admin не защищены;
- нет механизма паузы (emergency stop);
- ключи управления не разведены по ролям.
Рекомендации:
- использовать проверенные шаблоны и референс-контракты;
- документировать инварианты и критичные участки;
- проводить ревью с участием разработчиков, знающих TVM;
- обязательно проходить аудит для контрактов, которые держат значимые суммы и участвуют в инфраструктуре;
- проверять логику и риски через общие принципы из AI Security Hub и безопасности смарт-контрактов.
FunC vs Tact vs Tolk
В экосистеме TON фактически три основных направления языков:
- FunC — низкоуровневый язык, близкий к TVM.
- Tact — высокоуровневый, типобезопасный язык «в стиле TypeScript».
- Tolk — ещё один высокоуровневый язык с другим синтаксисом и подходом к компиляции.
Сравнение:
| Критерий | FunC | Tact | Tolk |
|---|---|---|---|
| Порог входа | Высокий: нужен TVM, ячейки, Fift | Средний: синтаксис ближе к TS, много примеров | Средний: компактный синтаксис, но экосистема моложе |
| Контроль над исполнением | Максимальный, вплоть до отдельных битов и инструкций | Часть деталей абстрагирована, но есть asm | Схожий баланс между удобством и контролем |
| Скорость разработки | Ниже, много ручной работы | Выше, проекты собираются бысторо, много тулов | Выше, если команда уже знакома с Tolk |
| Риск ошибок сериализации | Высокий, всё руками | Ниже, многое генерируется из типов | Ниже, чем в FunC, но требуется внимание |
| Производительность | Максимально возможная (если грамотно написать) | Чуть выше overhead, обычно не критично | Сопоставим с Tact для большинства задач |
| Области применения | Ядро протоколов, мосты, сложные кастомные модели | DApp, DeFi, игры, сервисные контракты | DApp, эксперименты, проекты с уже существующим кодом |
Практический вывод:
- если нужна максимальная предсказуемость и контроль, особенно для инфраструктуры и базовых протоколов — FunC продолжает быть хорошим выбором;
Типичные сценарии, где FunC до сих пор уместен
- Системные и базовые контракты.
Всё, что сидит «глубоко» в инфраструктуре сети, часто продолжают писать или поддерживать на FunC, чтобы:
- контролировать каждую инструкцию;
- понимать точный газ-профиль.
- Мосты и кросс-чейн-инфраструктура.
В связке с TON × CCIP и другими мостами:
- важно, чтобы сериализация, маршрутизация и лимиты были предельно прозрачны;
- FunC позволяет это реализовать максимально точно.
- Экспериментальные фичи TVM.
Когда нужно опробовать новую инструкцию или нетривиальную схему хранения:
- проще всего сделать это напрямую в FunC/TVM;
- а уже потом оборачивать в высокоуровневый язык.
- Оптимизация тяжёлых контрактов.
Иногда протоколы начинают на Tact, а узкие места переписывают на FunC, чтобы:
- сэкономить газ;
- сделать нестандартные трюки с ячейками.
Как начать работать с FunC разработчику
Минимальный план:
- Понять базовые концепции TON.
Прочитать:
- как устроены ячейки и сообщения;
- общие принципы TVM.
- Установить инструменты.
Через TON Developer Toolbox:
- компилятор FunC;
- Fift;
- эмуляторы/инструменты деплоя.
- Разобрать референс-контракт.
Пройтись по:
- простому счётчику;
- базовой реализации Jetton;
- небольшому сервисному контракту.
- Написать свой небольшой контракт.
Например:
- vault с одним параметром;
- простой регистр пользователей;
- контракт с геттером и одной командой.
- Отладить и задеплоить в тестнет.
Прогнать:
- сценарии с нормальными и ошибочными входами;
- edge-кейсы по сериализации и сообщениям.
После этого становится проще понимать большие кодовые базы на FunC и принимать решение, когда именно он нужен, а когда разумнее взять Tact.
FAQ по FunC
FunC «устаревает» на фоне Tact и Tolk? Нет. FunC — это язык, наиболее близкий к «сердцу» TON:
- новые высокоуровневые языки облегчают жизнь разработчикам;
- но ядро протоколов и эксперименты с TVM ещё долго будут опираться на FunC.
Скорее всего, стек будет таким: FunC + высокоуровневый язык сверху.
Сложнее ли проходить аудит кода на FunC, чем на Tact? Как правило, да:
- больше низкоуровневых деталей;
- сложнее отслеживать все места сериализации;
- TVM-контекст приходится держать в голове.
Но для опытных аудиторских команд FunC — привычная среда, с понятными паттернами.
Можно ли комбинировать FunC и Tact в одном проекте? Да. Типичная схема:
- критичные части (мосты, ядро) — на FunC;
- взаимодействие происходит через стандартные сообщения и форматы данных.
Насколько FunC подходит новичкам в блокчейн-разработке? Если вы только знакомитесь с TON, проще начать с Tact:
- порог входа ниже;
- меньше низкоуровневых деталей.
FunC логичнее осваивать, когда:
- вы уже пишете контракты и хотите больше контроля;
- нужно разбираться в чужом низкоуровневом коде;
- интересует инфраструктурный и исследовательский уровень.
Есть ли готовые шаблоны и стандарты на FunC? Да. В официальных и community-репозиториях TON есть:
- примеры базовых контрактов;
- реализации Jetton/NFT;
- утилиты для тестирования.
Лучше опираться на них, а не писать всё с нуля.
