Back to Blog

#3: Incidente KyberSwap: Exploração Magistral de Erros de Arredondamento com Cálculos Extremamente Sutis

Code Auditing
February 10, 2024
7 min read

Em 23 de novembro de 2023, ocorreu uma série de ataques direcionados ao KyberSwap. Esses ataques resultaram em uma perda total de mais de $48 milhões. O problema fundamental originou-se de uma direção de arredondamento incorreta durante o processo de reinvestimento do KyberSwap. Isso subsequentemente levou a cálculos de tick impróprios e, em última análise, à contagem dupla de liquidez.

Publicamos um relatório extenso, "Yet Another Tragedy of Precision Loss: An In-Depth Analysis of the KyberSwap Incident", que aprofunda os detalhes do evento. Para uma compreensão mais aprofundada, recomendamos a consulta à análise completa. A seguir, fornecemos uma introdução sucinta a este incidente, destacando-o como um dos dez principais incidentes de segurança de 2023.

Contexto

KyberSwap é uma plataforma descentralizada de formador de mercado automatizado (CLAMM). Para atender à demanda de mercado de liquidez concentrada, o KyberSwap Elastic foi lançado com base no Uniswap V3, com diversas melhorias, incluindo a curva de reinvestimento para permitir o auto-compounding dos rendimentos de provisão de liquidez.

1. Tick e Preço de Raiz Quadrada

O Tick em CLAMMs similares ao Uniswap V3 é utilizado para marcar o preço de forma discreta, de modo que os LPs possam fornecer liquidez dentro de um intervalo fixo em vez de todo o intervalo (daí o termo "concentrada").

A liquidez pode ser colocada em um intervalo entre quaisquer dois ticks (que não precisam ser adjacentes), ou seja, um par de índices de tick (um tick inferior e um tick superior). Especificamente, o preço de cada tick (em um índice inteiro i) é definido da seguinte forma:

Na prática, o preço de raiz quadrada (denotado como sqrtP ou sqrtPrice) é utilizado:

Também é possível calcular o tick atual com base no preço de raiz quadrada atual:

Obviamente, enquanto apenas um único preço de raiz quadrada é calculado para um dado tick, múltiplos preços de raiz quadrada podem apontar para o mesmo tick. Para uma explicação mais detalhada, consulte a documentação do Uniswap V3 e do KyberSwap.

2. Curva de Reinvestimento

O CLAMM baseado no Uniswap V3 sofre com a baixa utilização do pool pelas taxas de LP e com as significativas taxas de gas necessárias para o reinvestimento. Portanto, o KyberSwap adotou a curva de reinvestimento para resolver o problema.

A chave da curva de reinvestimento é que as taxas coletadas em cada swap são acumuladas como liquidez adicional no pool como a liquidez de reinvestimento dentro de um intervalo infinito. Os tokens de reinvestimento são emitidos para os LPs e a liquidez de reinvestimento acumulada é alocada aos LPs de acordo. Além disso, a liquidez de reinvestimento também participa do processo de swap e cálculo de preço.

O código correspondente aos cálculos introduzidos acima é exibido na função computeSwapStep no seguinte trecho de código do pool correspondente.

Deve-se observar que, devido à liquidez de reinvestimento, a liquidity nesta função é a soma de dois componentes: baseL para a liquidez base e reinvestL para a liquidez acumulada para o reinvestimento.

3. Swap no KyberSwap

A implementação da função swap do pool do KyberSwap discutida anteriormente pode ser abstraída no diagrama abaixo:

A lógica crucial relacionada ao cálculo do tick reside dentro do loop while de swap, destacado pelo retângulo azul. Especificamente, a lógica principal envolve a função computeSwapStep e a função _updateLiquidityAndCrossTick. A primeira calcula estados-chave, como quantidades de entrada e saída para o swap dado e nextSqrtP, enquanto a segunda lida com casos em que um cross-tick ocorre.

Tradicionalmente, quando o preço aumenta, nos referimos a isso como deslocar o tick para a direita/para cima; caso contrário, dizemos que o tick se move para a esquerda/para baixo.

Para melhor compreender a vulnerabilidade que será discutida adiante, é essencial explorarmos a lógica de código relevante da função computeSwapStep, conforme ilustrado na figura a seguir:

Especificamente, a função calcReachAmount calcula os tokens de entrada necessários para o preço alvo targetSqrtP (linhas 50-57). Se o usedAmount for maior que specifiedAmount, o tick não é cruzado e nextSqrtP é calculado a partir de deltaL (ou seja, o delta de liquidez, linhas 59-62). O deltaL é determinado usando a função estimateIncrementalLiquidity, e o preço final nextSqrtP é calculado com a função calcFinalPrice (linhas 70-79). Se menos entrada for necessária, nextSqrtP é definido como o preço do próximo tick, mas esse caso não é usado no ataque.

Os passos descritos acima deixam claro que, se o tick não for cruzado, o nextSqrtP retornado por computeSwapStep não deve ser maior que o sqrtP do próximo tick. No entanto, devido à dependência do preço em relação à liquidez (liquidez base e delta de liquidez) e à perda de precisão, os atacantes conseguem manipular o nextSqrtP para ser maior enquanto o tick não é cruzado.

Análise de Vulnerabilidade

A causa raiz reside no cálculo de tick defeituoso causado pela direção de arredondamento incorreta dentro do cálculo do delta de liquidez (ou seja, a função estimateIncrementalLiquidity) do contrato SwapMath (que é invocado pela função computeSwapStep). Isso, por sua vez, afeta impropriamente o cálculo do tick posteriormente.

Curiosamente, ao examinar o comentário na linha 188 (destacado pelo retângulo azul), descobrimos que deltaL deve ser arredondado para cima a fim de arredondar para baixo o nextSqrtP. No entanto, deltaL é erroneamente arredondado para baixo devido ao uso da função mulDivFloor na linha 189. Consequentemente, nextSqrtP é incorretamente arredondado para cima.

Análise do Ataque

Os atacantes iniciaram múltiplas transações de ataque, com cada transação drenando múltiplos pools. Por simplicidade, a discussão a seguir é baseada no primeiro ataque dentro da transação de ataque.

A lógica central do ataque consiste nas seguintes seis etapas:

  1. Empréstimo de 2.000 WETH via flash loan do AAVE.

  2. Troca de 6,850 WETH por 6,371 frxETH no pool vítima 0xfd7b. Esta etapa é usada para empurrar o tick atual e o currentSqrtP para uma posição onde atualmente não há liquidez presente.

  • currentSqrtP parece ser escolhido aleatoriamente pelo atacante, e o swap para exatamente nesse preço.
  • A liquidez base (baseL) é zero após esta etapa, mas a liquidez de reinvestimento (reinvestL) é diferente de zero.
  1. Adição de liquidez ao pool e em seguida remoção de parte da liquidez. Esta etapa é usada para controlar o intervalo e a liquidez total para um valor desejado.
  • O intervalo de tick é escolhido com base no currentSqrtP.
  • A liquidez desejada para o ataque pode ser derivada do intervalo de tick, embora a lógica de cálculo correspondente requeira exploração adicional.
  1. Troca de 387,170 WETH por 0,06 frxETH no pool. Esta etapa é usada para manipular o tick atual de modo que nextTick == currentTick. Especificamente, o swap na etapa 4 engana astuciosamente o pool para acreditar que o tick 111.310 não é cruzado. No entanto, na realidade, o currentSqrtP é de fato maior que o sqrtP do tick 111.310.
  1. Troca de 0,06 frxETH por 396,244 WETH no pool. Observe que a direção do swap é oposta em comparação com a etapa anterior. Nesta etapa, a liquidez é contada duplamente para tornar o swap lucrativo e, consequentemente, drenar o pool.
  1. Repagamento do flash loan e coleta de 6,364 WETH e 1,117 frxETH.

Para um exame aprofundado, consulte nossa análise abrangente, que inclui mais detalhes com cálculos e figuras.

Resumo

O problema fundamental neste incidente decorre do arredondamento incorreto durante o processo de reinvestimento do KyberSwap, levando a cálculos de tick imprecisos e, em última análise, à contagem dupla de liquidez. Este incidente destaca a natureza intrincada e evasiva dos problemas de perda de precisão dentro dos protocolos DeFi, representando um sério desafio para toda a comunidade.

Este ataque de 2023 se destaca por sua complexidade, com cálculos excepcionalmente refinados, servindo como um exemplo primordial dos muitos incidentes de segurança relacionados à precisão que testaram significativamente a comunidade. Além disso, após extensas negociações com as autoridades, o atacante emitiu uma mensagem de tom provocador ao público, afirmando uma exigência de controle total sobre o protocolo.

Leia outros artigos desta série:

Best Security Auditor for Web3

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

BlockSec Audit