Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 11.05 · 30 мин
Продвинутый
SecurityProduction ContractsJettonNFTTON DNSTON

Продакшен-контракты: разбор кейсов

Разбор реальных кейсов из продакшена — это бесценный опыт, который невозможно получить из теории. Каждый взлом, каждый потерянный миллион — это урок для всего сообщества. Изучение конкретных уязвимостей в развёрнутых контрактах TON показывает, как теоретические угрозы превращаются в реальные потери.

В предыдущих уроках мы изучили основные классы уязвимостей: bounced messages (урок 01), gas management (урок 02), целочисленные ловушки (урок 03) и race conditions (урок 04). Теперь применим эти знания к анализу реальных контрактов, работающих в mainnet TON.

Мы разберём три ключевых проекта:

  1. Jetton 2.0 — эталонная реализация токенов
  2. Getgems NFT — маркетплейс невзаимозаменяемых токенов
  3. 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
Архитектура Jetton 2.0
Jetton Master
total_supply: 1,000,000
admin_address: EQA...
content: метаданные (TEP-64)
jetton_wallet_code: Cell (код)
Деплоит кошельки через state_init
Wallet A
bal: 500
owner: A
Wallet B
bal: 300
owner: B
Wallet C
bal: 200
owner: C

Паттерны безопасности

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) совпадает с адресом отправителя, значит этот контракт был развёрнут с правильным кодом — ему можно доверять.

TIP

Authentication by Code — основной паттерн доверия в TON

В отличие от Ethereum, где контракты проверяют msg.sender по whitelist, в TON контракты верифицируют друг друга через совпадение кода и данных инициализации. Это позволяет безопасно взаимодействовать без централизованного реестра.

Проверка знанийKnowledge check
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?
ОтветAnswer
A whitelist would require updating every wallet whenever a new wallet is created -- impossible at scale. Instead, each wallet computes the expected address of the sender using the same wallet code and the sender's init data (owner address + master address). If the computed address matches the actual sender, it proves the sender was deployed with the correct code -- a trustless verification that works without any central registry or storage overhead.

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. Архитектура включает несколько типов контрактов, каждый со своей ролью.

Архитектура

Архитектура Getgems NFT
NFT Collection
Владелец: создатель | Хранит: метаданные, код NFT Item
Деплоит через state_init
Item 1
own: A
Item 2
own: B
Item 3
own: A
NFT Sale (эскроу)
price: 10 TON | fee: 5% | royalty: 5%
Владеет NFT на время продажи
Marketplace
Координирует продажи

Паттерны безопасности

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).

Cell Builder
Root
512/1023 бит
0/4 ссылок
Нажмите на ячейку, чтобы выбрать её. Добавляйте дочерние ячейки (до 4 ссылок), настраивайте биты (до 1023) и перетаскивайте ячейки для изменения структуры дерева.
Каждая ячейка TON хранит до 1023 бит данных и до 4 ссылок на дочерние ячейки. Все данные в блокчейне TON организованы как деревья ячеек (Bag of Cells).

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

  1. Считают, что аудит покрывает все уязвимости: даже проаудированные контракты содержат баги; аудит снижает риск, но не устраняет его.
  2. Не проводят формальную верификацию критических контрактов (кошельки, DEX, мосты): формальная верификация доказывает корректность математически.
  3. Не мониторят контракты после деплоя: аномальная активность (необычные суммы, частые bounce) может указывать на начавшуюся атаку.
  4. Деплоят обновлённый контракт без полного регрессионного тестирования: исправление одной уязвимости может создать новую.

TIP

Максимум 1023 бита и 4 ссылки

Каждая ячейка TON ограничена 1023 битами данных и 4 ссылками на дочерние ячейки. Если данные не помещаются, контракт разбивает их по нескольким ячейкам, связанным ссылками. Это видно в структуре Jetton Master: основные данные в корне, метаданные и код — в ссылках.


Общие паттерны безопасности

Все три проекта используют одни и те же фундаментальные паттерны:

ПаттернJetton 2.0Getgems NFTTON DNSУрок
Bounced handlerВосстановление балансаВозврат NFT при ошибке01
Gas excess return0xd53276db в каждом handlerВозврат после продажиВозврат при обновлении02
Валидация перед арифметикойbalance >= amountprice > fees03
Шардирование состоянияWallet per ownerItem per NFT, Sale per listingDomain per NFT04
Authentication by Codestate_init verificationCollection verifies ItemsResolver verifies domains

Ключевые выводы

  1. Продакшен-контракты TON следуют единому набору паттернов — bounced handlers, gas return, authentication by code
  2. Шардирование — принцип проектирования, а не оптимизация: один контракт — одна единица данных
  3. FunC-код читается, чтобы понимать существующие контракты; Tact используется для написания новых
  4. Все три стандарта (Jetton, NFT, DNS) связаны через TEP-интерфейсы — DNS использует NFT-стандарт, Jetton использует аналогичное шардирование

В заключительном уроке модуля мы соберём все паттерны безопасности в единый чеклист аудита.

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

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

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

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