FunC: низкоуровневый язык смарт-контрактов TON

FunC — низкоуровневый, строго типизированный язык программирования для смарт-контрактов в сети TON (The Open Network). Это «каноничный» язык ядра TON: многие базовые контракты экосистемы, ранние DApp и инфраструктура сети написаны именно на FunC.

Если Tact создан как высокоуровневый язык «в стиле TypeScript» для быстрой разработки, то FunC даёт:

  • максимальный контроль над тем, как код исполняется на TVM;
  • прямую работу с ячейками (cells), слайсами и битами;
  • возможность реализовывать нестандартные модели состояния и оптимизировать газ до байта.

Цена за это — более высокий порог входа и необходимость хорошо понимать устройство TON и виртуальной машины TVM.

FunC: низкоуровневый язык смарт-контрактов TON

Зачем 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 продолжает быть хорошим выбором;
  • для 80% DApp (DeFi, игры, utility-контракты) выгоднее начинать на Tact или Tolk, а в FunC выносить только критичные низкоуровневые части.

Типичные сценарии, где FunC до сих пор уместен

  • Системные и базовые контракты.

Всё, что сидит «глубоко» в инфраструктуре сети, часто продолжают писать или поддерживать на FunC, чтобы:

  • контролировать каждую инструкцию;
  • понимать точный газ-профиль.
  • Мосты и кросс-чейн-инфраструктура.

В связке с TON × CCIP и другими мостами:

  • важно, чтобы сериализация, маршрутизация и лимиты были предельно прозрачны;
  • FunC позволяет это реализовать максимально точно.
  • Экспериментальные фичи TVM.

Когда нужно опробовать новую инструкцию или нетривиальную схему хранения:

  • проще всего сделать это напрямую в FunC/TVM;
  • а уже потом оборачивать в высокоуровневый язык.
  • Оптимизация тяжёлых контрактов.

Иногда протоколы начинают на Tact, а узкие места переписывают на FunC, чтобы:

  • сэкономить газ;
  • сделать нестандартные трюки с ячейками.

Как начать работать с FunC разработчику

Минимальный план:

  • Понять базовые концепции TON.

Прочитать:

  • Установить инструменты.

Через 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;
  • обвязка и бизнес-логика DApp — на Tact или Tolk;
  • взаимодействие происходит через стандартные сообщения и форматы данных.

Насколько FunC подходит новичкам в блокчейн-разработке? Если вы только знакомитесь с TON, проще начать с Tact:

  • порог входа ниже;
  • меньше низкоуровневых деталей.

FunC логичнее осваивать, когда:

  • вы уже пишете контракты и хотите больше контроля;
  • нужно разбираться в чужом низкоуровневом коде;
  • интересует инфраструктурный и исследовательский уровень.

Есть ли готовые шаблоны и стандарты на FunC? Да. В официальных и community-репозиториях TON есть:

  • примеры базовых контрактов;
  • реализации Jetton/NFT;
  • утилиты для тестирования.

Лучше опираться на них, а не писать всё с нуля.

См. также

Task Runner