Back to Blog

№3: Инцидент в KyberSwap: мастерское использование ошибок округления посредством крайне тонких вычислений

Code Auditing
February 10, 2024
6 min read

23 ноября 2023 года произошла серия атак на KyberSwap. Эти атаки привели к совокупному ущербу на сумму более 48 миллионов долларов. Фундаментальная проблема возникла из-за некорректного направления округления в процессе реинвестирования KyberSwap. Это впоследствии привело к неправильным расчетам тиков и, в конечном итоге, к двойному учету ликвидности.

Мы выпустили подробный отчет "Еще одна трагедия потери точности: углубленный анализ инцидента с KyberSwap", в котором подробно рассматриваются детали этого события. Для более глубокого понимания мы рекомендуем ознакомиться с полным анализом. Ниже представлено краткое введение в данный инцидент, который мы включили в десятку главных инцидентов безопасности 2023 года.

Фон

KyberSwap — это децентрализованная платформа автоматизированного маркет-мейкера (CLAMM). Чтобы удовлетворить рыночный спрос на концентрированную ликвидность, KyberSwap Elastic была запущена на базе Uniswap V3 с рядом улучшений, включая кривую реинвестирования для обеспечения автоматического реинвестирования доходности от предоставления ликвидности.

1. Тик и цена квадратного корня

Тик в CLAMM, подобных Uniswap V3, используется для дискретного обозначения цены, что позволяет поставщикам ликвидности (LP) предоставлять ее в фиксированном диапазоне, а не во всем диапазоне (отсюда термин "концентрированная").

Ликвидность может быть размещена в диапазоне между любыми двумя тиками (которые не обязательно должны быть соседними), т.е. парой индексов тиков (нижний тик и верхний тик). В частности, цена каждого тика (с целочисленным индексом i) определяется следующим образом:

На практике используется цена квадратного корня (обозначается как sqrtP или sqrtPrice):

Также возможно вычислить текущий тик на основе текущей цены квадратного корня:

Очевидно, что хотя для данного тика рассчитывается только одна цена квадратного корня, несколько цен квадратного корня могут указывать на один и тот же тик. Для получения более подробного объяснения, пожалуйста, обратитесь к документации Uniswap V3 и KyberSwap.

2. Кривая реинвестирования

CLAMM на базе Uniswap V3 страдает от низкой эффективности использования пула для комиссий LP и значительных затрат газа, необходимых для реинвестирования. Поэтому KyberSwap приняла кривую реинвестирования для решения этой проблемы.

Ключ к кривой реинвестирования заключается в том, что комиссии, собранные в каждом обмене, накапливаются в качестве дополнительной ликвидности в пуле как реинвестируемая ликвидность в бесконечном диапазоне. Реинвестируемые токены выпускаются для LP, и накопленная ликвидность распределяется между ними соответствующим образом. Кроме того, реинвестируемая ликвидность также участвует в процессе обмена и расчета цены.

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

Следует отметить, что из-за ликвидности реинвестирования liquidity (ликвидность) в этой функции является суммой двух компонентов: baseL для базовой ликвидности и reinvestL для накопленной ликвидности для реинвестирования.

3. Обмен в KyberSwap

Реализацию функции swap пула KyberSwap, рассмотренную ранее, можно представить в виде следующей диаграммы:

Ключевая логика, относящаяся к расчету тиков, находится внутри цикла обмена, что выделено синим прямоугольником. В частности, основная логика включает функцию computeSwapStep и функцию _updateLiquidityAndCrossTick. Первая рассчитывает ключевые состояния, такие как объемы ввода и вывода для данного обмена и nextSqrtP, в то время как вторая обрабатывает случаи, когда происходит пересечение тика.

Традиционно, когда цена растет, мы называем это сдвигом тика вправо/вверх; в противном случае мы говорим, что тик движется влево/вниз.

Чтобы лучше понять уязвимость, которая будет обсуждаться позже, необходимо изучить соответствующую логику кода функции computeSwapStep, как показано на следующем рисунке:

В частности, функция calcReachAmount вычисляет входные токены, необходимые для целевой цены targetSqrtP (строки 50-57). Если usedAmount больше, чем specifiedAmount, тик не пересекается, и nextSqrtP рассчитывается из deltaL (т.е. дельта-ликвидности, строки 59-62). deltaL определяется с помощью функции estimateIncrementalLiquidity, а конечная цена nextSqrtP рассчитывается с помощью функции calcFinalPrice (строки 70-79). Если требуется меньше входных данных, nextSqrtP устанавливается на цену следующего тика, но этот случай не используется в атаке.

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

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

Первопричина заключается в ошибочном расчете тика, вызванном неправильным направлением округления при расчете дельта-ликвидности (т.е. функции estimateIncrementalLiquidity) контракта SwapMath (который вызывается функцией computeSwapStep). Это, в свою очередь, неправильно влияет на последующий расчет тика.

Интересно, что при изучении комментария в строке 188 (выделено синим прямоугольником) мы обнаруживаем, что deltaL должна округляться в большую сторону для округления в меньшую сторону nextSqrtP. Однако deltaL по ошибке округляется в меньшую сторону из-за использования функции mulDivFloor в строке 189. Следовательно, nextSqrtP неточно округляется в большую сторону.

Анализ атаки

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

Основная логика атаки состоит из следующих шести шагов:

  1. Заимствование 2000 WETH через флеш-кредит от AAVE.

  2. Обмен 6.850 WETH на 6.371 frxETH в целевом пуле 0xfd7b. Этот шаг используется для перевода текущего тика и currentSqrtP в положение, где в настоящее время ликвидность отсутствует.

  • currentSqrtP, по-видимому, был выбран злоумышленником случайным образом, и обмен останавливается именно на этой цене.
  • Базовая ликвидность (baseL) после этого шага равна нулю, но ликвидность реинвестирования (reinvestL) не равна нулю.
  1. Добавление ликвидности в пул, а затем удаление части ликвидности. Этот шаг используется для контроля диапазона и общей ликвидности до желаемого значения.
  • Диапазон тиков выбирается на основе currentSqrtP, хотя соответствующая логика расчета требует дальнейшего изучения.
  1. Обмен 387.170 WETH на 0.06 frxETH в пуле. Этот шаг используется для манипулирования текущим тиком так, чтобы nextTick == currentTick. В частности, обмен на этапе 4 хитро вводит пул в заблуждение, заставляя его поверить, что тик 111,310 не пересечен. Однако в действительности currentSqrtP действительно больше, чем sqrtP тика 111,310.
  1. Обмен 0.06 frxETH на 396.244 WETH в пуле. Обратите внимание, что направление обмена противоположно предыдущему шагу. На этом этапе ликвидность учитывается дважды, чтобы сделать обмен прибыльным и, следовательно, опустошить пул.
  1. Погашение флеш-кредита и получение 6.364 WETH и 1.117 frxETH.

Для углубленного изучения, пожалуйста, обратитесь к нашему комплексному анализу, который включает больше деталей с расчетами и рисунками.

Резюме

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

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

Читайте другие статьи из этой серии:

Best Security Auditor for Web3

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

BlockSec Audit