Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 05.02 · 20 мин
Средний
FunCАудитКошелёкSmart Contracts

Чтение FunC кода

Умение читать FunC-код — практический навык, который понадобится каждому TON-разработчику. Все стандартные контракты (Jetton, NFT, кошельки) написаны на FunC, и при аудите или интеграции вам придётся разбираться в чужом коде. Это как умение читать чертежи для инженера — вы можете не рисовать их сами, но должны понимать каждую линию.

Умение читать FunC — ключевой навык для работы с экосистемой TON. Большинство системных контрактов (кошельки, governance, Elector) написаны на FunC. В этом уроке мы разберём основные паттерны чтения и пройдёмся по реальному контракту.


Идиомы Cell-операций

FunC использует характерные паттерны для работы с Cell, Slice и Builder. Узнавание этих идиом — первый шаг к пониманию любого контракта.

Чтение данных (Slice)

;; Парсинг ячейки: begin_parse() создаёт Slice для чтения
slice cs = cell_data.begin_parse();

;; Загрузка данных в порядке записи
int flags    = cs~load_uint(4);      ;; 4 бита без знака
int op       = cs~load_uint(32);     ;; 32-битный operation code
int query_id = cs~load_uint(64);     ;; 64-битный идентификатор запроса
slice addr   = cs~load_msg_addr();   ;; адрес (переменная длина)
int amount   = cs~load_coins();      ;; количество нанотонов (VarUInt16)

Оператор ~ (тильда) означает модифицирующий вызов: метод изменяет cs, продвигая указатель чтения вперёд.

Запись данных (Builder)

;; Создание ячейки: begin_cell() -> store_... -> end_cell()
cell msg = begin_cell()
  .store_uint(0x18, 6)          ;; флаги внутреннего сообщения
  .store_slice(dest_addr)       ;; адрес получателя
  .store_coins(amount)          ;; сумма в нанотонах
  .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)  ;; служебные поля
  .store_slice(body)            ;; тело сообщения
  .end_cell();
TIP

При аудите FunC кода обращайте внимание на recv_internal и layout хранилища (порядок store_/load_ полей). Это два самых важных элемента для понимания логики контракта.


Паттерн Operation Code (op)

Почти все контракты TON используют 32-битный operation code в начале тела сообщения:

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
  if (in_msg_body.slice_empty?()) {
    return ();  ;; Пустое сообщение -- просто получение TON
  }

  int op = in_msg_body~load_uint(32);
  int query_id = in_msg_body~load_uint(64);

  if (op == 0x7362d09c) {    ;; op::transfer_notification
    handle_transfer(in_msg_body);
    return ();
  }

  if (op == 0x595f07bc) {    ;; op::excesses
    return ();               ;; Возврат лишних средств
  }

  throw(0xffff);             ;; Неизвестный op
}

Распространённые op-коды в экосистеме TON:

Op-кодНазваниеОписание
0x0internal_transferПеревод внутри jetton
0xf8a7ea5transferЗапрос на перевод jetton
0x7362d09ctransfer_notificationУведомление о получении jetton
0x595f07bcexcessesВозврат лишнего газа
0xd53276dbchange_ownerСмена владельца

Работа со словарями

Dictionary (hashmap) — основная структура для хранения коллекций в TON:

;; Чтение из словаря
(slice value, int found?) = udict_get?(dict, 256, key);
if (found?) {
  int amount = value~load_coins();
}

;; Запись в словарь
dict~udict_set(256, key, begin_cell().store_coins(amount).end_cell().begin_parse());

;; Удаление из словаря
(dict, int deleted?) = udict_delete?(dict, 256, key);

;; Проверка, пуст ли словарь
if (dict_empty?(dict)) {
  ;; Словарь пуст
}

Типы словарей

  • udict_* — ключи — беззнаковые целые числа (uint)
  • idict_* — ключи — знаковые целые числа (int)
  • dict_* — ключи — произвольные Slice
NOTE

Словари в TON хранятся как деревья ячеек (Hashmap). Каждая операция поиска потребляет газ пропорционально глубине дерева. Это важно для оптимизации газа.


Обработка ошибок

FunC использует числовые коды ошибок с throw_if и throw_unless:

;; throw_unless(code, condition) -- бросает ошибку, если условие FALSE
throw_unless(401, equal_slices(sender_addr, owner_addr));  ;; Только владелец

;; throw_if(code, condition) -- бросает ошибку, если условие TRUE
throw_if(402, amount <= 0);  ;; Сумма должна быть положительной

;; throw(code) -- безусловное исключение
throw(0xffff);  ;; Неизвестная операция

Стандартные коды ошибок:

ДиапазонНазначение
0-255Зарезервированы TVM
256-65535Пользовательские коды контракта
0xffffСтандартный “неизвестная операция”

Разбор: контракт Wallet V4

Рассмотрим ключевые секции стандартного контракта кошелька V4 — одного из самых распространённых контрактов на TON.

Хранилище кошелька

;; Данные кошелька: seqno, subwallet_id, public_key, plugins
(int, int, int, cell) load_data() inline {
  slice ds = get_data().begin_parse();
  return (
    ds~load_uint(32),       ;; seqno -- счётчик транзакций (защита от replay)
    ds~load_uint(32),       ;; subwallet_id -- идентификатор подкошелька
    ds~load_uint(256),      ;; public_key -- Ed25519 публичный ключ
    ds~load_dict()          ;; plugins -- словарь установленных плагинов
  );
}

Обработка внешнего сообщения

() recv_external(slice in_msg) impure {
  var signature = in_msg~load_bits(512);   ;; Ed25519 подпись
  var cs = in_msg;
  var (subwallet_id, valid_until, msg_seqno) = (
    cs~load_uint(32),      ;; subwallet_id для проверки
    cs~load_uint(32),      ;; TTL сообщения
    cs~load_uint(32)       ;; seqno для защиты от replay
  );

  ;; Загружаем данные контракта
  var (stored_seqno, stored_subwallet, public_key, plugins) = load_data();

  ;; Проверки безопасности
  throw_unless(35, valid_until > now());               ;; Сообщение не просрочено
  throw_unless(33, msg_seqno == stored_seqno);         ;; Правильный seqno
  throw_unless(34, subwallet_id == stored_subwallet);  ;; Правильный subwallet
  throw_unless(35, check_signature(
    slice_hash(in_msg), signature, public_key          ;; Валидная подпись
  ));

  accept_message();  ;; Принимаем и оплачиваем газ из баланса

  ;; Обрабатываем действия (отправка сообщений)
  ;; ...

  ;; Увеличиваем seqno
  save_data(stored_seqno + 1, stored_subwallet, public_key, plugins);
}

Ключевые паттерны кошелька

  1. Seqno — защита от replay-атак. Каждое сообщение увеличивает счётчик.
  2. valid_until — TTL сообщения. Просроченные сообщения отклоняются.
  3. check_signature — Ed25519 подпись проверяется до accept_message().
  4. accept_message() — контракт соглашается оплатить газ из своего баланса.
WARNING

В recv_external все проверки безопасности (подпись, seqno, TTL) должны выполняться до accept_message(). Иначе злоумышленник может истощить баланс контракта, отправляя невалидные сообщения.

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

  1. Пытаются понять весь контракт сразу, вместо того чтобы начать с recv_internal — точки входа для обработки сообщений.
  2. Не обращают внимание на комментарии ;;, в которых часто содержится важная документация по TL-B схемам и ожидаемым форматам сообщений.
  3. Путают load_uint и preload_uint: load сдвигает курсор чтения, а preload читает без сдвига, что критично при парсинге сообщений.
  4. Игнорируют функции-хелперы в начале файла и сразу переходят к основной логике, хотя часто именно в хелперах скрыта критически важная бизнес-логика.

Проверка знанийKnowledge check
In a standard FunC contract, what does the `recv_internal` signature `(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body)` tell you about how to extract the sender's address?
ОтветAnswer
The sender's address is not a direct parameter -- it must be extracted from `in_msg_full` by parsing it as a slice: first load the 4-bit flags, then call `load_msg_addr()` on the remaining slice. The `in_msg_body` contains only the message payload (typically starting with a 32-bit op code).

Итоги

  • Оператор ~ обозначает модифицирующий вызов (продвигает указатель чтения)
  • Op-коды (32 бита) — стандартный паттерн маршрутизации операций в TON
  • Словари (udict_*, idict_*) — основа для хранения коллекций
  • throw_if / throw_unless — механизм обработки ошибок с числовыми кодами
  • Стандартный кошелёк V4 демонстрирует все основные паттерны: seqno, подписи, accept_message

Теперь, когда мы умеем читать FunC, перейдём к Tolk — современной замене FunC с более привычным синтаксисом.

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

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

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

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