Основы FunC
FunC — это исторический язык разработки смарт-контрактов на TON, на котором написаны все системные контракты, стандарты токенов и большинство существующих DeFi-протоколов. Даже если вы пишете на Tact, умение читать FunC необходимо для аудита существующих контрактов, понимания стандартов и участия в экосистеме. Это как знание латыни для медика — язык не используется в повседневной практике, но вся терминология и документация на нём.
FunC — это язык программирования, который был создан для написания смарт-контрактов на блокчейне TON. Он компилируется в ассемблер Fift, который затем преобразуется в байт-код TVM. В этом уроке мы разберём базовые концепции FunC и его роль в экосистеме TON.
Зачем изучать FunC
Более 90% развёрнутых смарт-контрактов на TON написаны на FunC. Хотя для нового кода рекомендуется использовать Tact или Tolk, умение читать FunC критически важно для:
- Аудита существующих контрактов
- Понимания системных контрактов TON (кошельки, мультисиг, governance)
- Чтения документации и исследований безопасности
- Работы с legacy-кодом в production
FunC знания необходимы для чтения аудитов и понимания существующих контрактов, даже если новая разработка ведётся на Tact или Tolk.
Цепочка компиляции
FunC не исполняется напрямую. Код проходит несколько этапов:
FunC (.fc) → Fift assembler (.fif) → TVM bytecode (Cell)
- FunC — высокоуровневый язык с C-подобным синтаксисом
- Fift — ассемблер для TVM, работает со стеком
- TVM bytecode — упакован в Cell и хранится on-chain
В отличие от Tact, FunC не предоставляет автоматическую сериализацию данных — разработчик вручную управляет Cell, Slice и Builder.
Типы данных
FunC имеет небольшой набор примитивных типов, соответствующих структурам TVM:
| Тип | Описание | Пример |
|---|---|---|
int | 257-битное знаковое целое число | int x = 42; |
cell | Ячейка TVM (до 1023 бит данных + 4 ссылки) | cell c = begin_cell().end_cell(); |
slice | Указатель для чтения данных из Cell | slice s = c.begin_parse(); |
builder | Конструктор для записи данных в Cell | builder 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();
;; Обработка команды...
}
Вызов 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()
);
}
Порядок полей при чтении (load_) должен точно совпадать с порядком при записи (store_). Это одна из самых частых ошибок в FunC.
Частые ошибки
- Путают FunC с функциональными языками (Haskell, OCaml): несмотря на название, FunC — это скорее C-подобный язык с нестандартным синтаксисом.
- Забывают о модификаторе
impureдля функций с побочными эффектами: без него компилятор может оптимизировать вызов, полностью удалив его. - Не учитывают, что FunC работает напрямую со стеком TVM: порядок аргументов и возвращаемых значений зависит от их позиции на стеке.
- Путают символ
~(тильда) как оператор модификации и обычный вызов:var~method()модифицирует переменную in-place, аvar.method()— нет.
Проверка знанийWhy does FunC require the `impure` specifier on functions that send messages or modify storage, and what happens if you forget it?
Итоги
- FunC — язык с C-подобным синтаксисом, компилируемый в байт-код TVM через Fift
- Основные типы:
int,cell,slice,builder,tuple - Спецификатор
impureобязателен для функций с побочными эффектами recv_internalиrecv_external— точки входа для обработки сообщений- Состояние контракта хранится в Cell и управляется через
get_data()/set_data() - FunC требует ручного управления сериализацией — в отличие от Tact, где это автоматизировано
В следующем уроке мы научимся читать существующие FunC контракты — навык, необходимый для аудита и работы с production-кодом.
Проверьте понимание
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс
Войдите чтобы оценить урок