Продакшен-контракты: разбор кейсов
Разбор реальных кейсов из продакшена — это бесценный опыт, который невозможно получить из теории. Каждый взлом, каждый потерянный миллион — это урок для всего сообщества. Изучение конкретных уязвимостей в развёрнутых контрактах TON показывает, как теоретические угрозы превращаются в реальные потери.
В предыдущих уроках мы изучили основные классы уязвимостей: bounced messages (урок 01), gas management (урок 02), целочисленные ловушки (урок 03) и race conditions (урок 04). Теперь применим эти знания к анализу реальных контрактов, работающих в mainnet TON.
Мы разберём три ключевых проекта:
- Jetton 2.0 — эталонная реализация токенов
- Getgems NFT — маркетплейс невзаимозаменяемых токенов
- TON DNS — система доменных имён
Для каждого контракта мы рассмотрим архитектуру и найдём паттерны безопасности, которые изучили ранее.
Кейс 1: Jetton 2.0 Reference Implementation
Jetton 2.0 (сентябрь 2025) — обновлённый стандарт взаимозаменяемых токенов TON. Это эталонная реализация, на которую равняются все токены экосистемы.
Архитектура
Jetton 2.0 состоит из двух типов контрактов:
- Jetton Master (
jetton-minter.fc): единственный экземпляр на токен. Хранитtotal_supply, метаданные (имя, символ, decimals), и код Jetton Wallet - Jetton Wallet (
jetton-wallet.fc): один экземпляр на каждого владельца. Хранит баланс владельца, адресowner, и ссылку на Master
Паттерны безопасности
1. Аутентификация по коду (Authentication by Code)
Jetton Wallet-ы не хранят список “доверенных адресов”. Вместо этого каждый кошелёк вычисляет ожидаемый адрес отправителя через state_init:
;; FunC (для чтения): проверка, что сообщение пришло от валидного Jetton Wallet
;; Адрес контракта = hash(state_init) = hash(code + data)
slice calculate_user_jetton_wallet_address(
slice owner_address, slice jetton_master_address, cell jetton_wallet_code
) inline {
return begin_cell()
.store_uint(0, 2) ;; 0b00 - addr_std
.store_uint(0, 1) ;; anycast: nothing
.store_int(0, 8) ;; workchain_id
.store_uint(
cell_hash(
calculate_jetton_wallet_state_init(
owner_address, jetton_master_address, jetton_wallet_code
)
), 256)
.end_cell()
.begin_parse();
}
В Tact этот паттерн реализован через contractAddress(StateInit{code, data}). Идея: если hash(code + data) совпадает с адресом отправителя, значит этот контракт был развёрнут с правильным кодом — ему можно доверять.
Authentication by Code — основной паттерн доверия в TON
В отличие от Ethereum, где контракты проверяют msg.sender по whitelist, в TON контракты верифицируют друг друга через совпадение кода и данных инициализации. Это позволяет безопасно взаимодействовать без централизованного реестра.
Проверка знанийIn Jetton 2.0, why does the Jetton Wallet verify the sender by computing `contractAddress(StateInit{code, data})` rather than maintaining a whitelist of trusted addresses?
2. Bounced handler (урок 01)
Jetton Wallet содержит bounced-handler для восстановления баланса:
;; FunC (для чтения): обработка bounced-сообщения в jetton-wallet.fc
() on_bounce(slice in_msg_body) impure {
in_msg_body~skip_bits(32); ;; пропускаем 0xFFFFFFFF
int op = in_msg_body~load_uint(32);
throw_unless(709, (op == op::internal_transfer) | (op == op::burn_notification));
int jetton_amount = in_msg_body~load_coins();
balance += jetton_amount; ;; восстанавливаем баланс
save_data(...);
}
Это именно тот паттерн, который мы разобрали в уроке 01: при неудачном переводе баланс отправителя восстанавливается.
3. Gas excess return (урок 02)
Каждый handler в Jetton Wallet завершается возвратом excess газа через сообщение excesses (опкод 0xd53276db). Это гарантирует, что неиспользованный газ возвращается инициатору операции, а не остаётся заблокированным на контракте.
Кейс 2: Getgems NFT
Getgems — крупнейший NFT-маркетплейс на TON. Архитектура включает несколько типов контрактов, каждый со своей ролью.
Архитектура
Паттерны безопасности
1. Атомарность передачи владения
При выставлении NFT на продажу, Item передаёт ownership контракту Sale. Sale становится временным владельцем и контролирует NFT до завершения сделки. Это предотвращает ситуацию, когда NFT одновременно “продаётся” и используется владельцем.
;; FunC (для чтения): nft-item.fc -- передача владения
() transfer_ownership(
int my_balance, int index, slice collection_address,
slice owner_address, cell content, slice sender_address,
int query_id, slice in_msg_body, int fwd_fees
) impure inline {
;; Проверяем, что отправитель == owner
throw_unless(401, equal_slices(sender_address, owner_address));
slice new_owner_address = in_msg_body~load_msg_addr();
force_chain(new_owner_address);
;; Обновляем владельца
store_data(index, collection_address, new_owner_address, content);
;; Уведомляем нового владельца
;; ...
}
2. Sale как эскроу
Контракт Sale выступает как эскроу: он принимает оплату покупателя, вычитает комиссию маркетплейса и royalty, передаёт оставшуюся сумму продавцу, и возвращает владение NFT покупателю. Все шаги — в рамках одного потока сообщений.
3. Расчёт комиссий (урок 03)
Комиссия маркетплейса и royalty вычисляются из цены продажи. Здесь критически важны проверки из урока 03: комиссия должна быть неотрицательной, а сумма комиссий не должна превышать цену:
price = 10 TON
marketplace_fee = 5% = 0.5 TON
royalty = 5% = 0.5 TON
seller_receives = 10 - 0.5 - 0.5 = 9 TON
Проверка: seller_receives > 0 [OK]
Кейс 3: TON DNS Contract
TON DNS — система доменных имён, построенная на основе NFT-стандарта. В модуле M09 (урок 01) мы разобрали архитектуру TON DNS — теперь посмотрим на неё через призму безопасности.
Архитектура
TON DNS реализует TEP-81 (DNS standard) и TEP-62 (NFT standard) одновременно:
- DNS Resolver (
nft-collection.fc): коллекция NFT + DNS-резолвер зоны .ton - Domain NFT (
nft-item.fc): каждый домен (.ton) является NFT item с DNS-записями - Домен
alice.ton— это NFT сindex = sha256("alice")
Паттерны безопасности
1. Делегирование резолвера
DNS-резолвер может делегировать разрешение субдоменов другим контрактам. При этом проверяется, что делегирование не создаёт циклов и что субдомен-резолвер — валидный контракт.
2. Ограничение обновления DNS-записей
Обновлять DNS-записи домена (адрес кошелька, ADNL-адрес, storage bag ID) может только владелец NFT:
;; FunC (для чтения): nft-item.fc -- только владелец меняет DNS-записи
() recv_internal(...) {
;; ...
if (op == op::change_dns_record) {
throw_unless(401, equal_slices(sender_address, owner_address));
;; Обновляем DNS-запись
int key = in_msg_body~load_uint(256); ;; sha256 имени записи
;; ...
}
}
Это предотвращает атаку, при которой злоумышленник перенаправляет домен foundation.ton на свой адрес.
3. Управление субдоменами
Владелец домена может создавать субдомены (shop.alice.ton), назначая их резолверами для поддоменной зоны. Каждый субдомен — отдельный контракт, что обеспечивает изоляцию (паттерн шардирования из урока 04).
Cell Builder: исследуем структуру данных контрактов
Все контракты хранят данные в ячейках (cells) — базовой единице данных TON. Jetton Master, например, организует данные так:
- Root Cell:
total_supply(120 бит),admin_address(267 бит) - Ref 1:
content(метаданные токена в формате TEP-64) - Ref 2:
jetton_wallet_code(код кошелька для деплоя)
Используйте Cell Builder, чтобы исследовать, как контракты организуют данные в ячейках. Попробуйте воссоздать структуру Jetton Master: корневая ячейка с двумя ссылками (content и wallet_code).
Частые ошибки
- Считают, что аудит покрывает все уязвимости: даже проаудированные контракты содержат баги; аудит снижает риск, но не устраняет его.
- Не проводят формальную верификацию критических контрактов (кошельки, DEX, мосты): формальная верификация доказывает корректность математически.
- Не мониторят контракты после деплоя: аномальная активность (необычные суммы, частые bounce) может указывать на начавшуюся атаку.
- Деплоят обновлённый контракт без полного регрессионного тестирования: исправление одной уязвимости может создать новую.
Максимум 1023 бита и 4 ссылки
Каждая ячейка TON ограничена 1023 битами данных и 4 ссылками на дочерние ячейки. Если данные не помещаются, контракт разбивает их по нескольким ячейкам, связанным ссылками. Это видно в структуре Jetton Master: основные данные в корне, метаданные и код — в ссылках.
Общие паттерны безопасности
Все три проекта используют одни и те же фундаментальные паттерны:
| Паттерн | Jetton 2.0 | Getgems NFT | TON DNS | Урок |
|---|---|---|---|---|
| Bounced handler | Восстановление баланса | Возврат NFT при ошибке | — | 01 |
| Gas excess return | 0xd53276db в каждом handler | Возврат после продажи | Возврат при обновлении | 02 |
| Валидация перед арифметикой | balance >= amount | price > fees | — | 03 |
| Шардирование состояния | Wallet per owner | Item per NFT, Sale per listing | Domain per NFT | 04 |
| Authentication by Code | state_init verification | Collection verifies Items | Resolver verifies domains | — |
Ключевые выводы
- Продакшен-контракты TON следуют единому набору паттернов — bounced handlers, gas return, authentication by code
- Шардирование — принцип проектирования, а не оптимизация: один контракт — одна единица данных
- FunC-код читается, чтобы понимать существующие контракты; Tact используется для написания новых
- Все три стандарта (Jetton, NFT, DNS) связаны через TEP-интерфейсы — DNS использует NFT-стандарт, Jetton использует аналогичное шардирование
В заключительном уроке модуля мы соберём все паттерны безопасности в единый чеклист аудита.
Проверьте понимание
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс
Войдите чтобы оценить урок