2 сентября 2025 года протокол Bunni V2 подвергся сложной хакерской атаке [1]. Злоумышленник воспользовался критической уязвимостью в механизме учета ликвидности, чтобы вывести около 8,4 миллиона долларов США из двух пулов ликвидности: пула USDC/USDT в сети Ethereum [2] и пула weETH/ETH в сети Unichain [3].
Первопричиной стала ошибка округления при обновлении протоколом неиспользуемых балансов пула во время вывода ликвидности. Эта ошибка привела к значительной недооценке общего объема ликвидности в контракте, создав несоответствие между теоретической и фактической ликвидностью, которое можно было эксплуатировать. Затем злоумышленник провел точную атаку типа «сэндвич», чтобы получить прибыль от этого расхождения.
Этот инцидент привел к серьезным финансовым потерям для протокола Bunni, который впоследствии объявил о банкротстве 23 октября 2025 года [4].
Общая информация
Bunni V2 — это протокол автоматизированного маркет-мейкера (AMM), построенный на базе Uniswap V4. Он реализует свою основную логику через механизм хуков и внедряет инновации в алгоритм концентрированной ликвидности Uniswap V3, стремясь обеспечить повышенную эффективность капитала для поставщиков ликвидности (LP) [5].
В частности, протокол повышает доходность LP в основном за счет функции регипотеки (Rehypothecation) и механизма ребалансировки (Rebalancing). Первая распределяет ликвидность в сторонние протоколы для получения доходности, обеспечивая базовую ликвидность при извлечении дополнительной внешней прибыли. Второй постоянно оптимизирует распределение ликвидности по ценовым диапазонам, повышая активное использование капитала для увеличения доходов от комиссий. Эти два механизма составляют ключевые инновации протокола, построенные на фундаментальной модели концентрированной ликвидности.
Регипотека (Rehypothecation)
Чтобы повысить доходность для поставщиков ликвидности, Bunni V2 использует стратегию регипотеки. Эта стратегия распределяет средства по различным позициям:
- rawBalance (первичный баланс): часть резервов пула для токена хранится непосредственно внутри
contract PoolManagerUniswap V4. Это служит мгновенно доступной ликвидностью для обеспечения обменов (свопов). - reserves (резервы): остаток депонируется в указанное хранилище ERC4626. Это позволяет пользователям получать дополнительную внешнюю доходность на эти активы.
Таким образом, общие активы пула определяются как: Активы пула = rawBalance + базовая сумма reserves.
Ребалансировка (Rebalancing)
Для увеличения дохода от комиссий Bunni V2 внедряет механизм ребалансировки, который отслеживает средневзвешенную по времени цену. Когда изменение цены превышает пороговое значение, ликвидность перераспределяется по различным ценовым диапазонам в соответствии с функцией распределения ликвидности (LDF).
Это перераспределение может изменить соотношение токенов, требуемое LDF, оставляя излишек в одном из токенов. Этот излишек определяется как «неиспользуемый баланс» (idle balance).
Таким образом, ликвидность делится на две части:
- Активный баланс (Active Balance): часть, распределенная LDF, которая участвует в расчете ликвидности.
- Неиспользуемый баланс (Idle Balance): излишек, не используемый для активной ликвидности.
Следовательно, Активы пула = Активный баланс + Неиспользуемый баланс.
Ключевые функции: расчет ликвидности и вывод средств
Эта атака эксплуатирует две критические функции: queryLDF() и withdraw(). Функция queryLDF() рассчитывает ликвидность пула для проведения обменов, а функция withdraw() позволяет пользователям изымать пропорциональную долю ликвидности.
Функции queryLDF()
Из-за стратегии регипотеки количество базовых активов динамично, и Bunni V2 не хранит фиксированное значение «итоговой ликвидности». Вместо этого протокол предоставляет функцию queryLDF() для получения информации о ликвидности в реальном времени при совершении обмена [6]. Процесс выполнения этой функции состоит из следующих четырех этапов:
-
Запрос плотности ликвидности:
-
Вызывается функция плотности ликвидности
ldf.query(), которая получает плотность ликвидности вне текущего ценового тика. -
Вызывается
LiquidityAmounts.getAmountsForLiquidity()для получения плотности внутри текущего ценового тика. -
Рассчитывается общая плотность ликвидности токена0 и токена1 в обоих направлениях, обозначенные как
totalDensity0иtotalDensity1.
Примечательно, что функция
LiquidityAmounts.getAmountsForLiquidity()использует округление в большую сторону, чтобы гарантировать, что рассчитанные количества токенов консервативно не превышают теоретические значения.
-
-
Расчет доступного баланса
Доступные балансы, используемые для расчетов ликвидности, обозначаются как
balance0иbalance1. Неиспользуемый баланс вычитается из общего баланса соответствующего токена за вычетом средств, которые не участвуют в расчетах ликвидности.В этой атаке, где неиспользуемые средства пула состояли из
token0, формулы расчета были следующими: -
-
Оценка эффективной ликвидности
-
Оценивается ликвидность, которую может поддержать каждый токен на основе его фактического доступного баланса (
balance0илиbalance1) и рассчитанной общей плотности (totalDensity0илиtotalDensity1). -
Выбирается меньшее из двух значений в качестве итоговой эффективной общей ликвидности.
Формула выглядит следующим образом:
-
-
Расчет активных балансов
На основе определенной общей ликвидности протокол рассчитывает фактическое количество токенов, доступных для торговли. Это определяется как активный баланс.
Функция withdraw()
Bunni V2 предоставляет функцию withdraw() для вывода ликвидности. Пользователи изымают ликвидность пропорционально своей доле в общих средствах пула. Протокол обновляет rawBalance, reserves и idleBalance в той же пропорции. Формула корректировки выглядит следующим образом:
Где:
shares— количество долей ликвидности, которые выводит пользователь;totalSupply— общее количество токенов ликвидности для этого пула.
Анализ уязвимости
Уязвимость возникает в функции withdraw() при расчете суммы корректировки для неиспользуемого баланса, где используется округление вниз (floor rounding). Это приводит к переоценке значения неиспользуемого баланса.
Вспоминая формулу доступного баланса: . Переоцененный неиспользуемый баланс напрямую приводит к недооценке доступного баланса (balance0), используемого для расчетов ликвидности. Следовательно, оценочная эффективная общая ликвидность также оказывается заниженной. Согласно отчету о расследовании взлома Bunni [7], такое направление округления при расчетах ликвидности использовалось преднамеренно. Меньшее значение рассчитанной ликвидности приводит к более сильному проскальзыванию цены при обмене.
Эта конструкция опирается на критическое допущение: соотношение балансов между двумя токенами остается относительно сбалансированным. В нормальных условиях при достаточной ликвидности значения итоговой ликвидности, рассчитанные отдельно для каждого токена, обычно близки. Поэтому влияние ошибки округления ограничено. Однако, когда доступный баланс токена, содержащего неиспользуемый остаток, становится крайне низким, возникает проблема. В этом сценарии ошибка округления вниз значительно усиливается.
Злоумышленник использовал эту уязвимость, выполнив серию мелких операций вывода средств, округлив доступный баланс token0 с 28 вэй до 4 вэй. Это падение намного превысило пропорцию фактически сгоревших долей ликвидности. Тем временем доступный баланс token1 оставался на относительно нормальном уровне. Этот дисбаланс создал значительное окно для арбитража. В следующей главе представлен подробный численный анализ.
Анализ атаки
Взяв за пример транзакцию в сети Ethereum [2], злоумышленник осуществил атаку в три этапа:
- На первом этапе злоумышленник манипулировал ценой, чтобы значительно истощить доступный баланс USDC (token0). Это создало начальные условия, необходимые для усиления последующей ошибки округления.
- На втором этапе основная эксплуатация была проведена через серию небольших выводов средств, из-за чего протокол недооценил фактическую ликвидность пула.
- На третьем этапе злоумышленник совершил два направленных обмена для арбитража несоответствия между недооцененной ликвидностью протокола и фактической ликвидностью пула, в конечном итоге получив прибыль.
Этап 1: Манипуляция ценой и сокращение баланса целевого токена
Злоумышленник выполнил три транзакции обмена, манипулируя ценой USDC (token0) по отношению к USDT (token1), сдвинув ее с начального тика -1 до тика 5000. Основная цель заключалась в том, чтобы истощить активный баланс USDC в пуле, снизив его до чрезвычайно низкого уровня в 28 вэй. Это создало необходимые начальные условия для усиления последующей ошибки округления на следующем этапе.
Этап 2: Использование вывода средств для усиления расхождений в ликвидности
Злоумышленник инициировал 44 небольших вывода средств через функцию withdraw(). Из-за округления вниз, используемого этой функцией при обновлении idleBalance, неиспользуемый баланс протокола оказался завышен. Это привело к еще большей недооценке доступного баланса USDC в функции queryLDF(). После этих повторяющихся операций доступный баланс USDC был аномально подавлен с 28 вэй до 4 вэй. Это составило фактическое сокращение на 85,7%, что намного превышает теоретическую пропорцию, соответствующую изъятым долям ликвидности (т. е. 8,998105442969973e-07%). К этому моменту оценочная ликвидность USDC в пуле была сильно недооценена.
Этап 3: Осуществление арбитража и получение прибыли
Затем злоумышленник выполнил два направленных обмена, совершив операцию, похожую на сэндвич-атаку.
Шаг 1: Злоумышленник использовал большое количество USDT для обмена на USDC. В это время внутренняя оценка ликвидности была сильно занижена из-за недооцененного баланса USDC. Этот крупный обмен поднял цену до экстремального уровня, сдвинув тик с 5000 до 839 189.
Шаг 2: После формирования экстремальной цены злоумышленник немедленно перевернул операцию, обменяв часть USDC обратно на USDT. Поскольку цена пула теперь была сильно искажена, возвращаемое значение функции queryLDF() для плотности ликвидности USDC упало до 1. Это сделало значение ликвидности, рассчитанное на основе USDC, больше, чем значение, рассчитанное на основе USDT.
Согласно логике протокола по выбору меньшего значения, общая ликвидность определяется балансом USDT. Это привело к тому, что рассчитанная ликвидность немедленно вернулась из недооцененного состояния до нормального уровня, что вызвало внезапный рост. Злоумышленник использовал этот сдвиг, обменяв минимальное количество USDC на большое количество USDT, тем самым завершив арбитраж и получив прибыль.
Резюме
Этот инцидент был окончательно вызван ошибками округления при корректировке неиспользуемых балансов во время вывода ликвидности. Хотя проектирование такой функции округления вниз было задумано как стратегия защиты при расчетах ликвидности, оно не смогло адекватно учесть критические граничные условия. В частности, ошибки округления усиливаются нелинейно, когда балансы токенов сильно разбалансированы.
Этот инцидент выявляет риски сопряжения между несколькими модулями в сложных DeFi-протоколах. Даже если правила округления отдельных компонентов разработаны консервативно, отсутствие последовательной проверки безопасности во всей системе может привести к критическим уязвимостям, которые могут быть использованы при определенных обстоятельствах.
Ссылки
- https://x.com/bunni_xyz/status/1962833866277744953
- https://etherscan.io/tx/0x1c27c4d625429acfc0f97e466eda725fd09ebdc77550e529ba4cbdbc33beb97b
- https://uniscan.xyz/tx/0x4776f31156501dd456664cd3c91662ac8acc78358b9d4fd79337211eb6a1d451
- https://x.com/bunni_xyz/status/1981160279871558114
- https://docs.bunni.xyz/docs/v2/overview
- https://github.com/Bunniapp/bunni-v2/blob/2b303b8c1b9f8afbb169d62ba52da93d6d2171fe/src/lib/QueryLDF.sol#L40
- https://blog.bunni.xyz/posts/exploit-post-mortem/
О компании BlockSec
BlockSec — это провайдер комплексных решений по безопасности блокчейнов и крипто-комплаенсу. Мы создаем продукты и услуги, которые помогают клиентам проводить аудит кода (включая смарт-контракты, блокчейны и кошельки), перехватывать атаки в реальном времени, анализировать инциденты, отслеживать незаконные средства и соблюдать требования AML/CFT на протяжении всего жизненного цикла протоколов и платформ.
BlockSec опубликовала множество статей по безопасности блокчейнов на престижных конференциях, сообщила о нескольких атаках «нулевого дня» на DeFi-приложения, заблокировала многочисленные взломы, предотвратив потерю более 20 миллионов долларов, и обеспечила безопасность миллиардов долларов в криптовалюте.
- Официальный сайт: https://blocksec.com/
- Официальный аккаунт в Twitter: https://twitter.com/BlockSecTeam
- 🔗 Сервис аудита BlockSec: Отправить запрос
- 🔗 Приложение безопасности Phalcon: Записаться на демо
- 🔗 Phalcon Compliance



