Atualizado em 15 de setembro de 2023: A Balancer publicou o post-mortem oficial, que fornece uma descrição detalhada sobre toda a história desse incidente, incluindo a experiência e as lições aprendidas. Este post-mortem, com sua narrativa intrincada e excelente, é convincente e certamente vale o seu tempo.
Do ponto de vista de segurança, este post-mortem revela a existência de dois bugs. O primeiro é o erro de arredondamento para baixo que discutimos em nosso relatório, e o segundo é o "resets rate on 0 supply" (reinicia a taxa com fornecimento 0), que ocorreu nas etapas de ataque 3.6 e 3.7, conforme descrito em nosso relatório. O relatório da Balancer considera o segundo como o problema mais crítico, sendo o primeiro contributivo. No entanto, acreditamos que ambos os bugs são igualmente importantes para uma exploração lucrativa:
O primeiro bug é usado para inflar a taxa do token, servindo como a causa raiz do lucro. Sem ele, gerar lucro seria inviável.
O segundo bug viabiliza o exploit ao equilibrar a dívida dos bb-a-tokens. Sem ele, o ataque falharia devido à baixa liquidez dos bb-a-tokens, dado que não há outras fontes para obter esses tokens (a menos que o atacante consiga obtê-los por algum meio).
Em 22 de agosto de 2023, a Balancer anunciou publicamente a presença de uma vulnerabilidade crítica afetando múltiplos boosted pools e pediu aos usuários que retirassem imediatamente seus LPs dos pools afetados. A Balancer havia iniciado procedimentos de mitigação de emergência para proteger a maior parte do TVL, mas alguns fundos permaneceram em risco. Infelizmente, em 27 de agosto, cinco dias depois, observamos vários ataques em produção. Desde então, ativos superiores a $2,12M foram roubados.
No momento da redação deste relatório (mais de três semanas após o anúncio, num ponto em que acreditamos ser seguro fazê-lo), a Balancer não divulgou nenhuma análise aprofundada dessa vulnerabilidade. Neste relatório, nosso objetivo é fornecer uma análise abrangente, baseada principalmente em uma das transações de ataque.
Principais conclusões (TL;DR)
- Nossa investigação indica que a causa raiz decorre de a manipulação de preço resultante da lógica de arredondamento para baixo no pool
linear. Isso, por sua vez, afeta inadequadamente a taxa de token em cache utilizada pelo poolboostedcorrespondente. - Este incidente enfatiza a necessidade crítica de notificações rápidas a projetos que realizaram fork de uma fonte vulnerável, o que de fato representa um desafio significativo para toda a comunidade.
- Os inúmeros ataques em andamento ressaltam a necessidade de prevenção proativa de ameaças, o que inevitavelmente poderia ajudar a mitigar perdas futuras.
Nas seções a seguir, primeiro forneceremos algumas informações básicas essenciais sobre a Balancer. Em seguida, realizaremos uma análise abrangente da vulnerabilidade e do ataque associado. Por fim, forneceremos um breve resumo dos ataques que observamos até agora, juntamente com os lucros correspondentes.
0x1 Contexto sobre a Balancer
A Balancer V2 [1] é um protocolo descentralizado de criador de mercado automatizado (AMM) que representa um bloco de construção flexível para liquidez programável. Ao contrário de outros AMMs onde a contabilização de tokens é pareada com a lógica do pool, a Balancer separa a contabilização e o gerenciamento de tokens da lógica do pool, o que pode melhorar a eficiência de swap ao reduzir muitas transferências de tokens.
A Balancer suporta vários tipos de pools. Cada pool está associado a um token LP denominado BPT (ou seja, Balancer Pool Token). Basicamente, o valor do BPT é calculado com base no valor total de todos os tokens subjacentes.
A Balancer suporta swaps multi-hop, também conhecidos como batch swaps, que aproveitam os melhores preços de todos os pools registrados no Vault. Especificamente, o Vault fornece a função batchSwap para facilitar swaps multi-hop.
Um flash swap nos pools da Balancer elimina a necessidade de manter qualquer um dos tokens de entrada tradicionalmente necessários para executar um swap. Em vez disso, ao identificar um desequilíbrio, você pode instruir o Vault a executar o swap e, posteriormente, receber a recompensa.
0x1.1 Vários Pools na Balancer
A seguir, apresentamos brevemente alguns conceitos de pools que são relevantes para essa vulnerabilidade.
-
Linear Pools: Os pools
Linear[2] são pools da Balancer que facilitam a troca de um ativo e sua contraparte encapsulada e geradora de rendimento a uma taxa de câmbio conhecida. Como o nome sugere, osLinearPools utilizam Matemática Linear. Um poollinearmanterá três tokens, incluindo:- dois ativos, ou seja, tokens
mainewrappedque possuem um token subjacente de valor igual; - o
BPTcorrespondente (Balancer Pool Token). Observe que osBPTsão tokens ERC-20.
- dois ativos, ou seja, tokens
-
Nesting Linear Pools: O BPT de um Linear Pool pode ser aninhado dentro de outro pool. Isso cria um caminho simples de
batchSwapentre os ativos base e os tokens no pool externo, pois os usuários podem fazer swap deBPTpara um dos tokens subjacentes do Linear Pool. -
Composable Stable Pools: Os Composable Stable Pools [3] são projetados para ativos que se espera que façam swap consistentemente próximo à paridade ou a uma taxa de câmbio conhecida. Os Composable Stable Pools utilizam Matemática Estável, que permite swaps de tamanho significativo antes de encontrar um impacto de preço substancial, aumentando enormemente a eficiência de capital para swaps de ativos semelhantes e correlacionados.
Um pool é composable quando permite swaps de e para seu próprio token LP. Colocar seu token LP em outros pools (ou "aninhar") permite um
batchSwapfácil de tokens de pools aninhados para tokens no pool externo. -
Boosted Pools: Os pools
Boosted[4] são projetados para melhorar a eficiência de capital da liquidez ociosa em grandes pools. Os poolsBoostedsão na verdade uma subclasse de outros pools. Por exemplo, um poolboostedpode ser construído sobre poolslinear.Os Boosted Pools são projetados para oferecer alta eficiência de capital, permitindo que os usuários forneçam liquidez de swap para tokens comuns enquanto encaminham tokens ociosos para protocolos externos. Isso oferece aos provedores de liquidez os benefícios de protocolos como o Aave, além das taxas de swap que coletam dos swaps.
0x1.2 Um Exemplo Concreto dos Boosted Pools Vulneráveis: Balancer Boosted Aave USD
Balancer Boosted Aave USD (símbolo: bb-a-USD) é um Composable Stable Pool que facilita swaps entre três stablecoins (ou seja, USDC, USDT e DAI) enquanto envia liquidez ociosa para o Aave. Os pools linear subjacentes são:
bb-a-USDC(composto por USDC e aUSDC encapsulado)bb-a-USDT(composto por USDT e aUSDT encapsulado)bb-a-DAI(composto por DAI e aDAI encapsulado)
Especificamente, bb-a-USD é uma coleção de um Composable Stable Pool que contém os tokens de pool de três pools linear diferentes, e cada um desses pools linear tem um token estável associado: DAI, USDC e USDT. A figura abaixo fornecida pelo documento oficial [5] mostra a estrutura do bb-a-USD:

0x1.3 Como Calcular o Preço do BPT
Uma questão importante que surge naturalmente é como determinar o preço do BPT ao trocar uma quantidade específica (ou seja, amountIn) de BPT por uma certa quantidade (ou seja, amountOut) de outro token.
A Balancer fornece uma descrição detalhada das fórmulas matemáticas adotadas [6, 7] por diferentes pools. Para simplificar, abstraímos e resumimos aqui os conceitos mais relevantes.
Tomando o pool linear como exemplo, o preço do BPT é calculado na função onSwap do contrato LinearPool.

O cálculo pode ser resumido da seguinte forma:

Aqui, tokenRate é calculado com a seguinte fórmula:

é um valor constante: .
Na fórmula acima, o numerador pode ser simplificado como a soma do saldo do token main e o saldo do token wrapped, enquanto o denominador é a diferença entre um valor predefinido (ou seja, _INITIAL_BPT_SUPPLY) e o saldo do BPT.
Vale ressaltar que os saldos de todos os tokens envolvidos precisam ser nominalizados antes de realizar o cálculo, pois diferentes tokens podem ter diferentes casas decimais. Especificamente, o saldo bruto de um determinado token será multiplicado por um fator de escala correspondente, que é determinado pela função _scalingFactors.
(1) Fatores de Escala dos Pools Linear
Tanto o BPT quanto o token main possuem um fator de escala regular e constante.

(2) Fatores de Escala dos Pools Boosted como bb-a-USD
O cálculo de um pool boosted é um pouco mais complexo. Especificamente, o fator de escala retornado é o produto do fator de escala bruto (por exemplo, 1e18) e a taxa do token, que é obtida da taxa de token em cache, se existir.

De onde vem a taxa de token em cache? Existe uma função privada denominada _updateTokenRateCache. Obviamente, essa função primeiro recuperará a taxa invocando a função getRate daquele token e, em seguida, a armazenará em cache.

Novamente, tomando bb-a-USDC como exemplo, a lógica central da função getRate correspondente segue a fórmula que discutimos anteriormente.

Observe que existem três caminhos possíveis que podem acionar a função _updateTokenRateCache:

Além disso, há uma verificação de expiração em vigor ao realizar atualizações pelos caminhos que passam pela função onSwap:

0x2 Análise da Vulnerabilidade
A causa raiz está na manipulação de preço causada pela lógica de arredondamento para baixo dentro da função onSwap do pool linear. Isso, por sua vez, afeta indevidamente a taxa de token em cache utilizada pelo pool boosted.
Especificamente, o amountOut é arredondado para baixo quando a função _downscaleDown é invocada. Portanto, se houver uma diferença de magnitude significativa entre amountOut e scalingFactors[indexOut], o valor de retorno da função _downscaleDown poderá ser zero.

Por exemplo, se usarmos bb-a-USDC (como BPT) para trocar por USDC (como token main) no pool bb-a-USDC, quando amountOut for menor que 1.000.000.000.000, o valor de retorno sempre será arredondado para zero. Isso aumentaria o saldo de bb-a-USDC, pois poderia ser interpretado como adição de liquidez unidirecional de bb-a-USDC.
Como resultado, se o BPT for o token utilizado no swap, sua taxa aumentará de acordo com a fórmula para calcular a taxa, dado que o numerador permanece o mesmo enquanto o denominador diminui. Esse bug poderia ser explorado para gerar uma (grande) diferença de preço.
0x3 Análise do Ataque
A transação de ataque consiste nas seguintes etapas de ataque:
- Empréstimo de 300.000 USDC via Flashloan da Aave.
- Troca de 1,067753 USDC por 0,970495 aUSDC no pool bb-a-USDC.
- Execução de
batchSwapnos poolsbb-a-USDCebb-a-USD, ou seja, obtendo 15.628bb-a-USDC, 139.431bb-a-DAIe 248.868bb-a-USDTcom 42.203USDC. As etapas detalhadas estão resumidas na tabela a seguir (com decimais):

- Troca de tokens LP pelos tokens estáveis subjacentes correspondentes:
- 139.431
bb-a-DAI-> 141.127DAIno poolbb-a-DAI - 15.628
bb-a-USDC-> 15.685USDCno poolbb-a-USDC - 248.868
bb-a-USDT-> 253.461USDTno poolbb-a-USDT
- Reembolso do flashloan, e o lucro final é:
- 114.324
DAI - 253.461
USDT - 0,970495
aUSDC
Vale destacar que o atacante drenou aUSDC com USDC do pool bb-a-USDC na etapa 2, o que tornaria a manipulação de preço na etapa 3 muito mais fácil, ou seja, o atacante precisava focar apenas em USDC e bb-a-USDC.
A etapa 3 desempenha o papel principal. Agora vamos nos aprofundar nos detalhes dessa etapa para entender por que o atacante conseguiu obter lucros. Especificamente,
- A etapa 3.1 é usada para drenar
USDCcombb-a-USDCdo poolbb-a-USDC; - As etapas 3.3 e 3.4 são usadas para trocar
bb-a-USDCporbb-a-DAI, enquanto a etapa 3.5 é usada para trocarbb-a-USDCporbb-a-USDT. - A etapa 3.7 é usada para trocar
USDCporbb-a-USDCno poolbb-a-USDC.
Aqui as etapas 3.2 e 3.6 não trocam de volta nenhum token alvo (ou seja,
USDC) devido ao arredondamento para baixo discutido anteriormente, portanto os saldos dos tokens alvo permanecem inalterados após o swap, o que pode ser interpretado como adição de liquidez extra debb-a-USDCno poolbb-a-USDC.
Obviamente, os swaps anormais ocorrem principalmente nas etapas 3.4, 3.5 e 3.7. A seguir, percorreremos os detalhes de cada uma dessas etapas.
(1) bb-a-USDC -> bb-a-DAI
Na etapa 3.3, a taxa de câmbio entre bb-a-USDC e bb-a-DAI é quase 1, enquanto na etapa 3.4, a taxa de câmbio torna-se 19:
- Etapa 3.3: 1.000.339.378.515.783.699 / 1.000.000.000.000.000.000 = 1,00
- Etapa 3.4: 139.430.482.942.020.211.267.110 / 7.300.000.000.000.000.000.000 = 19,10
Recordando a lógica do código que discutimos anteriormente, na etapa 3.3, após retornar a taxa de token previamente armazenada em cache para calcular o fator de escala (1.012.181.365.780.643.700), ela atualiza a taxa para calcular um novo valor (40.240.000.000.000.000.000). Esse valor atualizado é então utilizado na etapa 3.4 como o novo fator de escala. Como os fatores de escala brutos permanecem inalterados (ou seja, 1e18), isso implica que a nova taxa é aproximadamente 40 vezes maior do que a taxa antiga.

No entanto, de onde vem esse aumento significativo? Vamos revisitar a fórmula para calcular tokenRate. Como o saldo de aUSDC foi esgotado na etapa 2, o cálculo de tokenRate pode ser simplificado da seguinte forma:


Aqui o valor real do nominalMainBalance é devido ao arredondamento para baixo que ocorre na etapa 3.2.
(2) bb-a-USDC -> bb-a-USDT
A etapa 3.5 usa o mesmo truque para obter mais bb-a-USDT, e a taxa de câmbio entre bb-a-USDC e bb-a-USDT é superior a 12:
- 248.868.905.733.352.246.491.156 / 20.000.000.000.000.000.000.000 = 12,44
(3) USDC -> bb-a-USDC
Além disso, o bptBalance é aumentado na etapa 3.6, e então o bptSupply torna-se zero na etapa 3.7. Ao fazer isso, é possível trocar USDC por bb-a-USDC a uma taxa de câmbio de quase 1:1.

0x4 Resumo dos Ataques e Lucros
No momento da redação deste texto, observamos dezenas de ataques em produção, causando perdas superiores a $2,12M. Em resumo, esses ataques foram executados por três contas distintas, conforme segue:

A Balancer sofreu uma perda total de ~$1M devido a essa vulnerabilidade. Menos de 12 horas após o ataque inicial à Balancer, seu protocolo derivado por fork, Beethoven X, foi vítima de ataques semelhantes, resultando em uma perda estimada de ~$1,1M. O Beethoven X sofreu perdas ainda maiores do que a Balancer! A perda acumulada desse incidente de segurança totalizou ~$2,12M.
Uma lista completa dessas transações de ataque foi compilada em um documento que preparamos. Consulte-o para obter informações mais detalhadas.
Algumas Observações Sobre os Atacantes
Ao analisar as transações iniciadas por cada rede, encontramos uma divergência significativa no rastro das transações de ataque na Fantom em comparação com as da Ethereum e da Optimism.
Especificamente, além das diferenças notáveis nas funções principais, o atacante na Fantom utilizou dois truques únicos para evitar ser antecipado por MEV Bots. Além disso, os fundos utilizados para o ataque na Fantom foram preparados 163 dias antes do ataque.
Com base nas observações detalhadas acima, podemos inferir:
-
Pelo menos dois atacantes distintos estiveram envolvidos.
-
O atacante na Fantom é um infrator serial experiente.
0x5 Conclusão
Em resumo, esta é uma vulnerabilidade sutil enraizada na lógica de arredondamento para baixo. No entanto, explorar essa vulnerabilidade não é simples. Especificamente, o atacante conseguiu inflar a taxa de token em cache explorando o problema de arredondamento para baixo no pool linear, manipulando assim o preço do token no pool boosted correspondente.
Este incidente também enfatiza a importância da notificação oportuna aos projetos que realizaram fork da fonte vulnerável. Apesar do alerta da Balancer, ataques direcionados a protocolos derivados por fork continuam, destacando a necessidade de que esses projetos se mantenham informados sobre atualizações de segurança de seus projetos de origem. No entanto, garantir que esses projetos derivados por fork recebam notificações rápidas representa um desafio contínuo para a comunidade.
Além disso, a série contínua de ataques ressalta a importância da prevenção proativa de ameaças, o que poderia ajudar efetivamente a mitigar perdas potenciais.
Referências
- [1] https://docs-v2.balancer.fi/concepts/overview/basics.html
- [2] Linear Pools: https://docs-v2.balancer.fi/concepts/pools/linear.html
- [3] Composable Stable Pools
- [4] Boosted Pools
- [5] https://docs-v2.balancer.fi/concepts/pools/boosted.html#example
- [6] https://docs-v2.balancer.fi/reference/math/linear-math.html
- [7] https://docs-v2.balancer.fi/reference/math/stable-math.html



