Обновлено 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): преобразование результатов обратно к их исходной точности с применением направленного округления (например, входные суммы обычно округляются вверх, чтобы гарантировать, что с пула не возьмут меньше положенного, в то время как выходные суммы часто округляются вниз).
Причина этого несоответствия неясна. Согласно комментарию в функции _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), где количество, подлежащее обмену, выводится из текущего баланса токена в пуле путем отсечения двух самых значимых десятичных цифр, то есть округления вниз до ближайшего значения, кратного , где 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



