Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 05.01 · 25 мин
Средний
FunCSmart ContractsTVMTON

Основы FunC

FunC — это исторический язык разработки смарт-контрактов на TON, на котором написаны все системные контракты, стандарты токенов и большинство существующих DeFi-протоколов. Даже если вы пишете на Tact, умение читать FunC необходимо для аудита существующих контрактов, понимания стандартов и участия в экосистеме. Это как знание латыни для медика — язык не используется в повседневной практике, но вся терминология и документация на нём.

FunC — это язык программирования, который был создан для написания смарт-контрактов на блокчейне TON. Он компилируется в ассемблер Fift, который затем преобразуется в байт-код TVM. В этом уроке мы разберём базовые концепции FunC и его роль в экосистеме TON.


Зачем изучать FunC

Более 90% развёрнутых смарт-контрактов на TON написаны на FunC. Хотя для нового кода рекомендуется использовать Tact или Tolk, умение читать FunC критически важно для:

  • Аудита существующих контрактов
  • Понимания системных контрактов TON (кошельки, мультисиг, governance)
  • Чтения документации и исследований безопасности
  • Работы с legacy-кодом в production
NOTE

FunC знания необходимы для чтения аудитов и понимания существующих контрактов, даже если новая разработка ведётся на Tact или Tolk.


Цепочка компиляции

FunC не исполняется напрямую. Код проходит несколько этапов:

FunC (.fc) → Fift assembler (.fif) → TVM bytecode (Cell)
  1. FunC — высокоуровневый язык с C-подобным синтаксисом
  2. Fift — ассемблер для TVM, работает со стеком
  3. TVM bytecode — упакован в Cell и хранится on-chain

В отличие от Tact, FunC не предоставляет автоматическую сериализацию данных — разработчик вручную управляет Cell, Slice и Builder.


Типы данных

FunC имеет небольшой набор примитивных типов, соответствующих структурам TVM:

ТипОписаниеПример
int257-битное знаковое целое числоint x = 42;
cellЯчейка TVM (до 1023 бит данных + 4 ссылки)cell c = begin_cell().end_cell();
sliceУказатель для чтения данных из Cellslice s = c.begin_parse();
builderКонструктор для записи данных в Cellbuilder b = begin_cell();
tupleУпорядоченная коллекция значенийtuple t = empty_tuple();
contПродолжение (continuation) TVMИспользуется для управления потоком
;; Объявление переменных
int counter = 0;
cell data = begin_cell().store_uint(42, 32).end_cell();
slice cs = data.begin_parse();
int value = cs~load_uint(32);  ;; value = 42

Объявление функций

Функции в FunC объявляются с указанием возвращаемого типа, имени и параметров:

;; Простая функция: сложение двух чисел
int add(int a, int b) {
  return a + b;
}

;; Функция, возвращающая несколько значений (тензор)
(int, int) swap(int a, int b) {
  return (b, a);
}

;; Функция без возвращаемого значения
() log_event(int event_id) impure {
  ;; side effect: emit event
}

Спецификаторы функций

FunC использует несколько важных спецификаторов:

  • impure — функция имеет побочные эффекты (отправка сообщений, изменение хранилища). Без impure компилятор может удалить вызов при оптимизации.
  • inline / inline_ref — подстановка тела функции в место вызова для оптимизации газа.
  • method_id — определяет функцию как get-метод, доступный для внешних вызовов.
;; Get-метод: вызывается извне для чтения состояния
int seqno() method_id {
  slice ds = get_data().begin_parse();
  return ds~load_uint(32);
}

;; Impure-функция: отправляет сообщение
() send_coins(slice to_addr, int amount) impure inline {
  var msg = begin_cell()
    .store_uint(0x10, 6)
    .store_slice(to_addr)
    .store_coins(amount)
    .store_uint(0, 107)
    .end_cell();
  send_raw_message(msg, 64);
}

Точки входа контракта

Каждый смарт-контракт на FunC имеет две основные точки входа:

recv_internal — обработка внутренних сообщений

Вызывается при получении сообщения от другого контракта или кошелька:

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
  ;; Парсим заголовок сообщения
  slice cs = in_msg_full.begin_parse();
  int flags = cs~load_uint(4);

  ;; Проверяем bounced-сообщения
  if (flags & 1) {
    return ();  ;; Игнорируем bounced
  }

  ;; Читаем адрес отправителя
  slice sender_addr = cs~load_msg_addr();

  ;; Обрабатываем тело сообщения
  int op = in_msg_body~load_uint(32);

  if (op == 1) {
    ;; Операция "инкремент"
    slice ds = get_data().begin_parse();
    int counter = ds~load_uint(32);
    counter += 1;
    set_data(begin_cell().store_uint(counter, 32).end_cell());
  }
}

recv_external — обработка внешних сообщений

Вызывается при получении сообщения извне блокчейна (от dApp или CLI):

() recv_external(slice in_msg) impure {
  ;; Внешние сообщения требуют accept_message()
  ;; для оплаты газа из баланса контракта
  accept_message();

  ;; Обработка команды...
}
WARNING

Вызов accept_message() обязателен в recv_external. Без него контракт отклонит внешнее сообщение, так как нет отправителя, оплачивающего газ.


Работа с хранилищем

Контракт хранит своё состояние в одной Cell, доступной через get_data() и set_data():

;; Чтение состояния контракта
(int, slice) load_data() inline {
  slice ds = get_data().begin_parse();
  int counter = ds~load_uint(32);
  slice owner = ds~load_msg_addr();
  return (counter, owner);
}

;; Запись состояния контракта
() save_data(int counter, slice owner) impure inline {
  set_data(
    begin_cell()
      .store_uint(counter, 32)
      .store_slice(owner)
    .end_cell()
  );
}
TIP

Порядок полей при чтении (load_) должен точно совпадать с порядком при записи (store_). Это одна из самых частых ошибок в FunC.

Частые ошибки

  1. Путают FunC с функциональными языками (Haskell, OCaml): несмотря на название, FunC — это скорее C-подобный язык с нестандартным синтаксисом.
  2. Забывают о модификаторе impure для функций с побочными эффектами: без него компилятор может оптимизировать вызов, полностью удалив его.
  3. Не учитывают, что FunC работает напрямую со стеком TVM: порядок аргументов и возвращаемых значений зависит от их позиции на стеке.
  4. Путают символ ~ (тильда) как оператор модификации и обычный вызов: var~method() модифицирует переменную in-place, а var.method() — нет.

Проверка знанийKnowledge check
Why does FunC require the `impure` specifier on functions that send messages or modify storage, and what happens if you forget it?
ОтветAnswer
The `impure` specifier tells the FunC compiler that the function has side effects. Without it, the compiler may optimize away the function call entirely during dead code elimination, silently removing critical operations like send_raw_message() or set_data().

Итоги

  • FunC — язык с C-подобным синтаксисом, компилируемый в байт-код TVM через Fift
  • Основные типы: int, cell, slice, builder, tuple
  • Спецификатор impure обязателен для функций с побочными эффектами
  • recv_internal и recv_external — точки входа для обработки сообщений
  • Состояние контракта хранится в Cell и управляется через get_data() / set_data()
  • FunC требует ручного управления сериализацией — в отличие от Tact, где это автоматизировано

В следующем уроке мы научимся читать существующие FunC контракты — навык, необходимый для аудита и работы с production-кодом.

FunC показывает, как один контракт обрабатывает входящее сообщение. Но проектирование системы контрактов TON начинается раньше — с message flow design: какие сообщения летят между акторами, как обрабатываются bounce-ответы, как выстраиваются параллельные цепочки сообщений. Это уровень System Design TON.

System Design: actor model и message flow

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 6