Back to Blog

Глубокий анализ: взлом Balancer V2

Code Auditing
5 ноября 2025 г.
7 min read

Обновлено 6 ноября 2025 г.: Balancer опубликовал официальный предварительный отчет [6], который подтверждает первопричину, выявленную в нашем анализе.

3 ноября 2025 г. Composable Stable Pools протокола Balancer V2, а также несколько форкнутых проектов в различных блокчейн-сетях подверглись скоординированной атаке, которая привела к общим убыткам на сумму более 125 миллионов долларов. Компания BlockSec выпустила предупреждение сразу же после обнаружения [1] и впоследствии опубликовала первоначальный анализ [2].

Это была крайне изощренная атака. Наше расследование показывает, что первопричиной стало манипулирование ценой в результате потери точности при вычислении инварианта, что, в свою очередь, исказило расчет цены BPT (Balancer Pool Token). Эта манипуляция инвариантом позволила злоумышленнику получить прибыль от конкретного стабильного пула с помощью одного пакетного обмена (batch swap). Хотя некоторые исследователи предложили свое видение ситуации, ряд интерпретаций вводит в заблуждение, а первопричина и процесс атаки до сих пор не были полностью прояснены. Цель этой статьи — представить всесторонний и точный технический анализ инцидента.

Основные выводы (TL;DR)

Первопричина: несоответствие правил округления и потеря точности

  • Операция масштабирования вверх (upscaling) использует однонаправленное округление (округление вниз), в то время как операция масштабирования вниз (downscaling) использует двунаправленное округление (округление вверх и вниз).
  • Это несоответствие создает потерю точности, которая при использовании специально подготовленного пути обмена нарушает стандартный принцип: округление всегда должно осуществляться в пользу протокола.

Исполнение эксплойта

  • Злоумышленник намеренно подобрал параметры, включая количество итераций и входные значения, чтобы максимизировать эффект потери точности.
  • Атакующий использовал двухэтапный подход, чтобы избежать обнаружения: сначала выполнил основной эксплойт в рамках одной транзакции без получения мгновенной прибыли, а затем реализовал прибыль, выводя активы в отдельной транзакции.

Операционное влияние и последствия

  • Протокол невозможно было приостановить из-за определенных ограничений [3]. Эта неспособность остановить операции усугубила последствия эксплойта и позволила провести множество последующих атак, включая копирование эксплойта.

В следующих разделах мы сначала предоставим ключевую справочную информацию о Balancer V2, а затем перейдем к углубленному анализу выявленных проблем и связанной с ними атаки.

0x1 Справочная информация

Composable Stable Pool протокола Balancer V2

Компонентом, затронутым в ходе этой атаки, был Composable Stable Pool [4] протокола Balancer V2. Эти пулы предназначены для активов, которые, как ожидается, будут поддерживать паритет 1:1 (или торговаться по известному обменному курсу), и позволяют проводить крупные свопы с минимальным влиянием на цену, что значительно повышает эффективность капитала для однородных или коррелирующих активов. Каждый пул имеет собственный токен пула Balancer (BPT), который представляет долю поставщика ликвидности в пуле, а также соответствующие базовые активы.

  • В этом пуле используется Stable Math (основанная на модели StableSwap от Curve), где инвариант D представляет виртуальную общую стоимость пула.
  • Цену BPT можно приблизительно выразить как:

Из приведенной выше формулы видно, что если D можно сделать меньше «на бумаге» (даже без фактической потери средств), цена BPT будет казаться ниже.

batchSwap() и onSwap()

Balancer V2 предоставляет функцию batchSwap(), которая позволяет выполнять многоходовые обмены в рамках Vault [5]. Существует два типа обмена, определяемых параметром, передаваемым в эту функцию:

  • GIVEN_IN ("Given In"): вызывающая сторона указывает точное количество входного токена, а пул рассчитывает соответствующее выходное количество.
  • GIVEN_OUT ("Given Out"): вызывающая сторона указывает желаемое выходное количество, а пул вычисляет требуемый входной объем.

Обычно batchSwap() состоит из нескольких обменов токенов, выполняемых через функцию onSwap(). Ниже приведен путь исполнения, когда SwapRequest назначается тип обмена GIVEN_OUT (обратите внимание, что ComposableStablePool наследуется от BaseGeneralPool):

Ниже показан расчет amount_in для типа обмена GIVEN_OUT, который включает в себя инвариант D.

Масштабирование и округление

Для нормализации расчетов между различными балансами токенов Balancer выполняет две следующие операции:

  • Масштабирование вверх (Upscaling): масштабирование балансов и сумм до унифицированной внутренней точности перед выполнением расчетов.
  • Масштабирование вниз (Downscaling): преобразование результатов обратно к их исходной точности с применением направленного округления (например, входные суммы обычно округляются вверх, чтобы гарантировать, что с пула не возьмут меньше положенного, в то время как выходные суммы часто округляются вниз).
Очевидно, что масштабирование вверх и вниз теоретически являются парными операциями — умножением и делением соответственно. Однако в реализации этих двух операций существует несоответствие. В частности, операция масштабирования вниз имеет два варианта или направления: divUp и divDown. Напротив, операция масштабирования вверх имеет только одно направление, а именно mulDown.

Причина этого несоответствия неясна. Согласно комментарию в функции _upscale(), разработчики считают, что влияние округления в одном направлении минимально.

// Округление при масштабировании вверх не обязательно всегда должно идти в одном направлении: например, в свопе баланс входящего // токена должен быть округлен вверх, а выходящего — вниз. Это единственное место, где мы округляем все суммы // в одном направлении, так как ожидается, что влияние этого округления минимально (и ошибки округления не возникнет, если _scalingFactor() не переопределен).

0x2 Анализ уязвимости

Основная проблема возникает из-за операции округления вниз, выполняемой во время масштабирования вверх в функции BaseGeneralPool._swapGivenOut(). В частности, _swapGivenOut() некорректно округляет вниз swapRequest.amount через функцию _upscale(). Полученное округленное значение впоследствии используется как amountOut при расчете amountIn через _onSwapGivenOut(). Такое поведение противоречит стандартной практике, согласно которой округление должно применяться таким образом, чтобы это было выгодно протоколу.

Таким образом, для данного пула (wstETH/rETH/cbETH) вычисленное amountIn недооценивает фактически требуемый входной объем. Это позволяет пользователю обменять меньшее количество одного базового актива (например, wstETH) на другой (например, cbETH), тем самым уменьшая инвариант D в результате снижения эффективной ликвидности. Следовательно, цена соответствующего BPT (wstETH/rETH/cbETH) становится заниженной, поскольку цена BPT = D / totalSupply.

0x3 Анализ атаки

Злоумышленник выполнил двухэтапную атаку, вероятно, для минимизации риска обнаружения:

  • На первом этапе основной эксплойт был проведен в рамках одной транзакции, не принесшей мгновенной прибыли.
  • На втором этапе злоумышленник реализовал прибыль, выводя активы в отдельной транзакции.

Первый этап можно разделить на две фазы: расчет параметров и пакетный обмен. Ниже мы иллюстрируем эти фазы на примере транзакции атаки (TX) в сети Arbitrum.

Фаза расчета параметров

На этой фазе злоумышленник объединил офчейн-расчеты с ончейн-симуляциями, чтобы точно настроить параметры каждого «прыжка» в следующей фазе (пакетного обмена), основываясь на текущем состоянии Composable Stable Pool (включая коэффициенты масштабирования, коэффициент усиления, ставку BPT, комиссии за своп и другие параметры). Примечательно, что злоумышленник также развернул вспомогательный контракт для помощи в этих расчетах, что, возможно, было сделано для уменьшения вероятности фронтраннинга.

Вначале злоумышленник собирает базовую информацию о целевом пуле, включая коэффициенты масштабирования каждого токена, параметр усиления, ставку BPT и процент комиссии за своп. Затем они вычисляют ключевое значение под названием trickAmt — манипулируемое количество целевого токена, используемое для вызова потери точности.

Обозначая коэффициент масштабирования целевого токена как sF, расчет выглядит так:

Чтобы определить параметры, используемые на шаге 2 следующей фазы (пакетного обмена), злоумышленник выполнил последующие вызовы симуляции функции 0x524c9e20 вспомогательного контракта со следующими calldata:

uint256[] balances; // Балансы токенов пула (исключая BPT)
uint256[] scalingFactors; // Коэффициенты масштабирования для каждого токена пула
uint tokenIn; // Индекс входного токена для симуляции этого этапа
uint tokenOut; // Индекс выходного токена для симуляции этого этапа
uint256 amountOut; // Желаемое количество выходного токена
uint256 amp; // Параметр усиления (amplification) пула
uint256 fee; // Процент комиссии пула за своп

И возвращаемые данные:

uint256[] balances; // Балансы токенов пула (исключая BPT) после обмена

В частности, начальный баланс и количество итераций цикла были вычислены вне сети и переданы в качестве параметров контракту злоумышленника (сообщается о значениях 100 000 000 000 и 25 соответственно). Каждая итерация выполняет три свопа:

  • Своп 1: Доведение суммы целевого токена до trickAmt + 1, при условии, что направление свопа 0 → 1.
  • Своп 2: Продолжение вывода целевого токена с суммой trickAmt, что вызывает округление вниз при вызове _upscale().
  • Своп 3: Выполнение операции обратного обмена (1 → 0), где количество, подлежащее обмену, выводится из текущего баланса токена в пуле путем отсечения двух самых значимых десятичных цифр, то есть округления вниз до ближайшего значения, кратного $10^{d-2}$, где d — количество десятичных цифр. Например, 324 816 -> 320 000.
    • Обратите внимание, что этот шаг иногда может завершаться неудачно из-за метода Ньютона-Рафсона, используемого в расчетах StableMath. Чтобы смягчить это, злоумышленник применяет две попытки повтора, каждая из которых использует 9/10 от исходного значения. Вспомогательный контракт злоумышленника основан на библиотеке StableMath от Balancer V2, что подтверждается наличием специфических сообщений об ошибках в стиле "BAL".

Фаза пакетного обмена (Batch Swap Phase)

Затем операцию batchSwap() можно разделить на три шага:

  • Шаг 1: Злоумышленник меняет BPT (wstETH/rETH/cbETH) на базовые активы, чтобы точно скорректировать баланс одного токена (cbETH) до границы округления (amount = 9). Это создает условия для потери точности на следующем шаге.

  • Шаг 2: Затем злоумышленник выполняет обмен между другим базовым активом (wstETH) и cbETH, используя подготовленную сумму (= 8). Из-за округления вниз при масштабировании суммы токенов, вычисленное Δx становится немного меньше (8,918 -> 8), что приводит к недооцененному Δy и, следовательно, к меньшему инварианту (D из модели StableSwap от Curve). Поскольку цена BPT = D / totalSupply, цена BPT искусственно занижается.

  • Шаг 3: Злоумышленник совершает обратный своп базовых активов обратно в BPT, восстанавливая баланс и получая прибыль от искусственно заниженной цены BPT.

0x4 Атаки и убытки

Мы суммировали атаки и соответствующие им убытки в таблице ниже, при этом общие убытки превысили 125 миллионов долларов.

0x5 Заключение

Этот инцидент включал серию транзакций атаки на протокол Balancer V2 и его форки, что привело к значительным финансовым потерям. После первоначальной атаки в нескольких сетях наблюдались многочисленные последующие транзакции и попытки копирования эксплойта. Это событие подчеркивает несколько критических уроков для проектирования и безопасности DeFi-протоколов:

  • Поведение при округлении и потеря точности: Однонаправленное округление (округление вниз), используемое при операции масштабирования вверх, отличается от двунаправленного округления (округление вверх и вниз), используемого при масштабировании вниз. Чтобы предотвратить подобные уязвимости, протоколы должны использовать арифметику с более высокой точностью и внедрять надежные проверочные механизмы. Крайне важно придерживаться принципа, согласно которому округление всегда должно осуществляться в пользу протокола.

  • Эволюция методов эксплуатации: Злоумышленник осуществил изощренный двухэтапный эксплойт, предназначенный для обхода систем обнаружения. На первом этапе он выполнил основной эксплойт без немедленного получения прибыли. На втором этапе злоумышленник реализовал прибыль, выводя активы в отдельной транзакции. Этот инцидент еще раз подчеркивает продолжающуюся «гонку вооружений» между исследователями безопасности и атакующими.

  • Операционная осведомленность и реагирование на угрозы: Этот случай подчеркивает важность своевременных оповещений об инициализации и операционном статусе, а также механизмов упреждающего обнаружения угроз и предотвращения для смягчения потенциальных убытков от продолжающихся атак или их копий.

Поддерживая операционную и бизнес-непрерывность, участники отрасли могут использовать BlockSec Phalcon в качестве последнего рубежа защиты своих активов. Команда экспертов BlockSec готова провести всестороннюю оценку безопасности вашего проекта.

Ссылки

[1] https://x.com/Phalcon_xyz/status/1985262010347696312

[2] https://x.com/Phalcon_xyz/status/1985302779263643915

[3] https://x.com/Balancer/status/1985390307245244573

[4] https://docs-v2.balancer.fi/concepts/pools/composable-stable.html

[5] https://docs-v2.balancer.fi/reference/swaps/batch-swaps.html

[6] https://x.com/balancer/status/1986104426667401241

Sign up for the latest updates
За пределами смарт-контракта: операционная безопасность доменов и DNS в Web3
Security Insights

За пределами смарт-контракта: операционная безопасность доменов и DNS в Web3

Аудит контрактов не проверяет сам контракт. Мы провели 800 SEAL-проверок DNS и регистраторов для 100 доменов из TVL Top 100 DefiLlama — лишь один домен прошёл все. Каких четырёх мер защиты не хватает большинству и почему это критично для пользователя.

Поверхности атаки Web3: обзор тестирования на проникновение

Поверхности атаки Web3: обзор тестирования на проникновение

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

Потеряно ~23 млн $: эксплойты Cosmos EVM и Moonwell | BlockSec Weekly
Security Insights

Потеряно ~23 млн $: эксплойты Cosmos EVM и Moonwell | BlockSec Weekly

За период (22.08.2026–30.08.2026): 5 инцидентов, ~$22,7 млн убытков; Tectonic — $74–119,5 млн, откачены на Cronos. Взлом Cosmos EVM (~$5,7 млн) на TAC Chain, Moonwell, Ajna, Rain Card Exploit (Ed25519) на Solana.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit