В начале сентября протокол Tectonic на Cronos потерял около 75 миллионов долларов. Блокчейн при этом работал штатно — консенсус не сломался, приватные ключи не утекли, в коде контракта не было переполнения. Одним словом — тишь да гладь. Атакующий просто сделал так, что контракт поверил в неправильную цену и после этого честно, как будто так и надо, по всем правилам, отдал ему чужие деньги. Это не единичный случай, а тренд года. По данным TRM Labs, за 2026-й на манипуляциях ценой протоколы поймали 32 атаки против 12 за весь 2025-й, а это, на минуточку, почти втрое больше, и год даже ещё не закончился. Доля таких атак среди всех крипто-взломов выросла примерно с одной семнадцатой в 2022-м до одной восьмой сейчас. В списке пострадавших за последние недели — ($8.7млн), More Markets на Flow EVM ($410 тыс.), Cozy Finance на Optimism ($170 тыс.), Full Sail на Sui ($91 тыс.).

Почему у контракта вообще есть слабое место

Смарт-контракт живёт в детерминированной клетке. У него нет fetch(), нет часов, нет источника случайности. Так задумано, иначе ноды не смогли бы ни о чём договориться, исполняя один и тот же код. У кого-то миллисекунды не совпали, кто-то сфетчил данные с сайта центробанка и они разошлись и т.д. и т.п. Это фундаментальное свойство, а не косяк создателей. Но каждый контракт кредитного протокола хочет знать, сколько стоит залог в каждый конкретный промежуток времени, а курс живёт снаружи, в старом мире, в web2. Так вот, этот мостик между мирами и называется Оракул: кто-то приносит данные внутрь обычной транзакцией, и с этого момента они детерминированы для всех. И вот здесь спрятан парадокс, ради которого всё затевалось. Мы построили систему, где не нужно доверять никому, и поставили на входе компонент, которому приходится доверять. Собственно, это и есть «проблема оракула». Вся безопасность протокола на миллионы долларов упирается в один-единственный вопрос. Откуда взялось это число, этот курс, эти данные, и можно ли на них повлиять.

Атака первая: спот-цена из пула

Самый частый способ узнать цену «ончейн, без посредников» — заглянуть в пул ликвидности DEX. Выглядит красиво: данные из самого блокчейна, никаких внешних сервисов.

// ТАК ДЕЛАТЬ НЕЛЬЗЯ
function getPrice() public view returns (uint256) {
    (uint112 reserve0, uint112 reserve1, ) = pair.getReserves();
    return (uint256(reserve1) * 1e18) / uint256(reserve0);
}

Три простых строки, читается как справочник, ничего лишнего, но проблема в том, что это не «цена токена» — это соотношение резервов в конкретном пуле прямо сейчас, а соотношение резервов меняет любой, у кого есть деньги на своп. Дальше вступает механизм, которого нет в web2, так называемый флеш-займ. Можно занять миллионы без залога, но только при одном условии, вернуть нужно в той же транзакции, не раньше и не позже, одна транзакция на получение и возврат средств. Если не вернул, транзакция откатывается целиком, как будто ничего и не было. В этой ситуации кредитор ничем не рискует, поэтому занимать может кто угодно. Теперь складываем 2+2:

  1. Занимаем флеш-займом крупную сумму

  2. Выкупаем один из токенов пула — соотношение резервов перекашивается, «цена» взлетает

  3. В том же блоке дёргаем протокол-жертву, который читает getReserves() и видит раздутую цену

  4. Закладываем подорожавший токен и берём под него настоящий кредит

  5. Возвращаем флеш-займ, уходим с разницей

Вот и всё, одна транзакция — одна атака, миллионы в кармане. У арбитражников нет ни одного блока, чтобы вернуть цену на место, потому что манипуляция и её эксплуатация происходят атомарно. Поэтому в индустрии есть жёсткое правило, что протокол, который берёт спот-цену из пула ликвидности, будет взломан. Не «может быть», а будет.

Разбор: Mango Markets, $110 миллионов за десять минут

Урок истории из октября 2022-го. Аврахам Айзенберг зашёл на Mango Markets с 10 миллионами долларов, которые предусмотрительно разложил на два аккаунта. На одном аккаунте он открыл шорт на 488 миллионов MNGO, на другом просто их скупал. Его задача была не заработать на сделке, а нарисовать цену. MNGO — низколиквидный токен, и объёма хватило, чтобы поднять его цену примерно на 2000% относительно десятидневной средней. Протокол считал стоимость залога по этой нарисованной цене, увидел у аккаунта гигантское обеспечение, недолго думая, выдал под него займов на сумму, которая выкосила протокол. Итог: свыше 110 миллионов долларов. Дальше было самое интересное. Айзенберг публично назвал это «прибыльной торговой стратегией» и предложил вернуть часть, оставив себе десятки миллионов как «баг-баунти». Его аргумент был прост, как лапоть: я не взламывал код, я совершал сделки, разрешённые правилами. CFTC, SEC и Минюст США с такой трактовкой не согласились, и он был признан виновным в мошенничестве и манипулировании рынком. Техническая мораль такова: код отработал ровно так, как написан. Ни одной строчки не было нарушено, потому что уязвимость была не в логике займов, а в том, что источником истины о цене назначили то, на что можно влиять деньгами.

TWAP: помогает, но не спасает

Первый рефлекс, который сразу же срабатывает у среднестатистического технаря — усреднить. Time-Weighted Average Price берёт цену не «сейчас», а среднюю за определённый промежуток времени, например полчаса или час. Тогда однотранзакционный флеш-займ перестаёт работать, потому что для сдвига средней за 30 минут нужно держать перекошенную цену минимум эти 30 минут, отбиваясь от арбитражников и всех тех, кто увидел возможность заработать. Это уже не бесплатно, а дорого и рискованно. Но TWAP выставляет сразу два чека за эту защиту. Первый чек: он по определению отстающий, потому что на реальном обвале рынка TWAP ещё несколько минут показывает вчерашний мир, и протокол в это время принимает решения по устаревшим данным, а это, на минуточку, не сработавшие ликвидации или сработавшие, но не на тех и не там. Второй чек: TWAP может защитить только от дешёвой атаки, а вот дорогая атака ему не по зубам. Если протокол держит сотни миллионов, а атака на низколиквидный пул стоит несколько миллионов, тогда экономика всё ещё на стороне атакующего. Именно поэтому в списке жертв 2026 года так много протоколов на молодых сетях. В них просто тонкая ликвидность, и сдвинуть цену дёшево.

Правильный путь — и вторая ловушка

Промышленный ответ — децентрализованная сеть оракулов. У Chainlink цену приносит не одна нода, а десятки независимых, и каждая берёт данные с нескольких бирж, результат агрегируется и публикуется, когда цена отклонилась больше заданного порога (deviation threshold) или истёк срок (heartbeat). Чтобы это подделать, нужно скомпрометировать большинство нод, а это намного дороже, чем украсть. Читается это так:

(, int256 answer, , , ) = feed.latestRoundData();
uint256 price = uint256(answer);

И вот здесь девять из десяти туториалов заканчиваются — а зря. Потому что так писать тоже нельзя. 13 мая 2022 года цена LUNA летела в пропасть, как электросамокатчик, падающий в ад. У фида Chainlink была зашита нижняя граница minAnswer на уровне примерно$0.10, так сказать, страховка от «мусорных» значений. Когда рынок ушёл ниже, фид упёрся в эту границу и был приостановлен из-за беспрецедентной волатильности. Ну а дальше начинается арифметика. Оракул показывал ~$0.107, в то же время рынок — ~$0.01. Разница в десять раз, и она держалась. Атакующие завели на Venus Protocol 230 миллионов LUNA по цене оракула, а это, примерно, больше 24 миллионов долларов «обеспечения», в то время как реальная цена этого беспредела составляла около 2 миллионов. Взяли под этот залог активов на ~13.5 миллиона и ушли. Venus потерял около 11 миллионов долларов. Blizz Finance, работавший по той же схеме, потерял свыше 8.3 миллиона и закрылся, потому что его выпотрошили в ноль. Оракул не был взломан. Он работал ровно по спецификации. Взломаны были протоколы, которые не проверяли, что им пришло.

Безопасное чтение цены

Собираем всё в один рабочий пример. Это не «красивее», это минимально достаточно.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface AggregatorV3Interface {
    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
    function decimals() external view returns (uint8);
}

contract PriceReader {
    error BadPrice(int256 answer);
    error StalePrice(uint256 updatedAt, uint256 nowTs);
    error PriceOutOfBounds(uint256 price);

    AggregatorV3Interface public immutable feed;

    /// heartbeat фида + запас. Берётся из документации КОНКРЕТНОГО фида,
    /// а не копируется из чужого проекта: у разных пар она разная.
    uint256 public immutable maxDelay;

    /// границы здравого смысла для этого актива
    uint256 public immutable minPrice;
    uint256 public immutable maxPrice;

    constructor(address feed_, uint256 maxDelay_, uint256 minPrice_, uint256 maxPrice_) {
        feed = AggregatorV3Interface(feed_);
        maxDelay = maxDelay_;
        minPrice = minPrice_;
        maxPrice = maxPrice_;
    }

    function getPrice() public view returns (uint256) {
        (, int256 answer, , uint256 updatedAt, ) = feed.latestRoundData();

        // 1. цена вообще положительная
        if (answer <= 0) revert BadPrice(answer);

        // 2. данные свежие: фид не завис и не приостановлен
        if (updatedAt == 0 || block.timestamp - updatedAt > maxDelay) {
            revert StalePrice(updatedAt, block.timestamp);
        }

        uint256 price = uint256(answer);

        // 3. цена в пределах здравого смысла — страховка от упора
        //    в minAnswer/maxAnswer самого фида (кейс LUNA)
        if (price < minPrice || price > maxPrice) revert PriceOutOfBounds(price);

        return price;
    }
}

Три проверки, каждая закрывает конкретный способ потерять деньги. Проверка на свежесть — против зависшего или приостановленного фида. Величину maxDelay берут из heartbeat конкретной пары: у ETH/USD он один, у экзотики совсем другой, и подставлять сюда круглое число «на глаз» означает либо ложные срабатывания, либо дыру, в которую даже слон пролезет. Границы — тот самый урок LUNA. Если фид упёрся в свой внутренний minAnswer, формально он отдаёт валидное число, и единственный способ это заметить — сравнивать с диапазоном, в котором цена вообще имеет смысл. Тут есть неприятный трейд-офф: слишком узкие границы сами становятся отказом в обслуживании на резком движении рынка, поэтому границы ставят широкие «физически осмысленные значения». Отдельно про L2. Если контракт живёт на Base, Optimism, Arbitrum, тогда обязательно читайте ещё и sequencer uptime feed. Когда секвенсер лежит и снова поднимается, цены прилетают пачкой, и в этот момент ликвидации работают по данным из прошлого. Chainlink отдаёт отдельный фид «жив ли секвенсер и сколько времени прошло с подъёма», очень простое правило, которое спасёт: после восстановления выдержать grace period и только потом принимать решения по цене. Показательно, что два из свежих инцидентов 2026-го случились именно на L2.

Чек-лист: аудит собственного кода

Пройдитесь по своему проекту прямо сейчас.

  • Цена берётся из getReserves() или любого «сейчас в пуле»? Это не оракул, это дыра

  • Результат latestRoundData() используется без проверки answer > 0?

  • Проверяется ли свежесть через updatedAt, и взят ли maxDelay из документации именно этого фида?

  • Есть ли границы на цену на случай упора фида в minAnswer/maxAnswer?

  • Контракт на L2? Читается ли sequencer uptime feed?

  • Один источник цены на весь протокол? Подумайте о втором, независимом, и о расхождении между ними как о стоп-кране

  • Можно ли в одной транзакции сдвинуть цену и тут же ею воспользоваться? Если да, то атака стоит ровно флеш-займ

  • Что происходит, если оракул вернул revert? Протокол встаёт или на всякий случай берёт последнее известное значение? Второе опаснее, чем кажется

Что из этого следует

Забавный парадокс индустрии: мы тратим огромные усилия на то, чтобы сделать вычисления недоверенными, а потом сводим всю безопасность к одному числу, приехавшему снаружи. Блокчейн действительно почти невозможно взломать, поэтому взламывают не его. Тридцать две атаки за неполный год и больше миллиарда суммарных потерь в DeFi за 2026-й говорят, что это не экзотика для аудиторов, а рабочая рутина. И почти каждая из них разбирается на одно предложение. Контракт поверил числу, которое не проверил. Если пишете контракт, который трогает деньги, выпишите на бумажке один вопрос: откуда я узнаю цену и сколько стоит на неё повлиять. Пока у вас нет численного ответа на вторую половину, протокол не готов.

Такие разборы — раз в неделю. Между ними я веду серию «Web3 для JS-разработчиков»: двадцать постов о том, как вся эта кухня устроена изнутри, сегодня вышел шестнадцатый. Если хочешь понимать блокчейн системно, а не от взлома к взлому — начни с него: blockwright Telegram

Это Райт. Ни бага, ни костыля тебе, друг!

Комментарии (2)


  1. lazarus_net
    22.09.2026 08:51

    Что-то коллега у вас АИ духом пахнет.

    Попросите переписать в нормальном стиле, классические ИИ обороты всех достали.

    Далее: за что боролись за то и напоролись. Кому было не понятно когда смарт контракты вводили что это точка отказа и работать не может В приципе, так как система должна независимо получить данные из внешнего источника- который нельзя проверить и гарантировать достоверность и непротиворечивость данных.

    Люди в свои системы встроили механизм защиты от ошибок и алгоритмы разрешения конфликтов. Данные публикуются например независимыми агентствами и если что можно пойти в суд. Плюс есть возможность задержать транзакцию и начать проверки/юридические разбирательства.

    Но нам сказали что все это плохо и можно лучше - пока не получается….


    1. Blockwright Автор
      22.09.2026 08:51

      Спасибо, что нашли время на комментарий.

      Статистику и анализ я правда гоняю через ИИ - это эффективнее, удобнее и несомненно быстрее в десятки раз. Но текст, структуру и код пишу сам. Если обороты режут глаз, прошу простить, я не профессиональный писатель, я только учусь.

      А по сути текста вы попали в самую точку и статья ровно про это и написана. Оракул и есть ахиллесова пята. Честный контракт, которому нужно использовать внешние данные.

      Только вывод я бы сделал другой - смарт-контракты не отменяют суды и арбитраж - они убирают их из цепочки обязательных доверенных лиц. И насчет «нельзя проверить» я бы не согласился, оракул = доверие, «проверяемыми» данные он не делает. Он сужает, а не закрывает дыру недоверия - об этом и написано в статье. Сотни миллионов утекают не потому, что «децентрализация не работает», а потому что проверки выкидывают ради скорости. Это ровно та часть, которую можно и нужно построить правильно.