Atualizado em 6 de nov. de 2025: A Balancer publicou seu relatório preliminar oficial [6], que confirma a causa raiz identificada em nossa análise.
Em 3 de novembro de 2025, os Composable Stable Pools da Balancer V2, juntamente com diversos projetos derivados (forks) em múltiplas redes, sofreram um exploit coordenado que resultou em perdas totais superiores a $125 milhões. A BlockSec emitiu um alerta no menor tempo possível [1] e posteriormente publicou uma análise inicial [2].
Este foi um ataque altamente sofisticado. Nossa investigação revela que a causa raiz foi a manipulação de preço resultante de perda de precisão no cálculo do invariante, o que por sua vez distorceu o cálculo do preço do BPT (Balancer Pool Token). Essa manipulação do invariante permitiu que o atacante lucrasse em um pool estável específico por meio de um único batch swap. Embora alguns pesquisadores tenham fornecido análises pertinentes, certas interpretações são equivocadas, e a causa raiz e o processo do ataque ainda não foram totalmente esclarecidos. Este blog tem como objetivo apresentar uma análise técnica abrangente e precisa do incidente.
Principais Conclusões (TL;DR)
Causa raiz: inconsistência de arredondamento e perda de precisão
- A operação de upscaling utiliza arredondamento unidirecional (arredondamento para baixo), enquanto a operação de downscaling utiliza arredondamento bidirecional (arredondamento para cima e para baixo).
- Essa inconsistência cria uma perda de precisão que, quando explorada por meio de um caminho de swap cuidadosamente elaborado, viola o princípio padrão de que o arredondamento deve sempre favorecer o protocolo.
Execução do exploit
- O atacante elaborou deliberadamente os parâmetros, incluindo o número de iterações e os valores de entrada, para maximizar o efeito da perda de precisão.
- O atacante utilizou uma abordagem em dois estágios para escapar da detecção: primeiro executando o exploit principal dentro de uma única transação sem lucro imediato e, em seguida, realizando os lucros ao retirar os ativos em uma transação separada.
Impacto operacional e amplificação
- O protocolo não pôde ser pausado devido a certas restrições [3]. Essa incapacidade de interromper as operações agravou o impacto do exploit e permitiu numerosos ataques subsequentes ou cópias.
Nas seções a seguir, forneceremos primeiramente informações essenciais sobre a Balancer V2, seguidas de uma análise aprofundada dos problemas identificados e do ataque associado.
0x1 Contexto
Composable Stable Pool da Balancer V2
O componente afetado neste ataque foi o Composable Stable Pool [4] do protocolo Balancer V2. Esses pools são projetados para ativos que devem manter paridade próxima de 1:1 (ou negociar a uma taxa de câmbio conhecida) e permitem grandes swaps com impacto mínimo no preço, melhorando significativamente a eficiência de capital entre ativos semelhantes ou correlacionados. Cada pool possui seu próprio Balancer Pool Token (BPT), que representa a participação do provedor de liquidez no pool, juntamente com os ativos subjacentes correspondentes.
- Este pool adota a Stable Math (baseada no modelo StableSwap da Curve), onde o invariante D representa o valor total virtual do pool.
- O preço do BPT pode ser aproximado como:
A partir da fórmula acima, pode-se observar que, se D puder ser reduzido no papel (mesmo sem nenhuma perda real de fundos), o preço do BPT parecerá mais barato.
batchSwap() e onSwap()
A Balancer V2 fornece a função batchSwap(), que permite swaps com múltiplos saltos (multi-hop) dentro do Vault [5]. Existem dois tipos de swap determinados por um parâmetro passado a esta função:
- GIVEN_IN ("Dado na Entrada"): o chamador especifica o valor exato do token de entrada, e o pool calcula o valor de saída correspondente.
- GIVEN_OUT ("Dado na Saída"): o chamador especifica o valor de saída desejado, e o pool calcula o valor de entrada necessário.
Tipicamente, um batchSwap() consiste em múltiplos swaps de token para token executados via função onSwap(). A seguir, descreve-se o caminho de execução quando um SwapRequest recebe o tipo de swap GIVEN_OUT (observe que ComposableStablePool herda de BaseGeneralPool):
A seguir, é mostrado o cálculo de amount_in para o tipo de swap GIVEN_OUT, que envolve o invariante D.
Escalonamento e Arredondamento
Para normalizar os cálculos entre diferentes saldos de tokens, a Balancer realiza as duas operações a seguir:
- Upscaling: Eleva os saldos e valores a uma precisão interna unificada antes de realizar os cálculos.
- Downscaling: Converte os resultados de volta à sua precisão nativa, aplicando arredondamento direcional (por exemplo, valores de entrada são geralmente arredondados para cima para garantir que o pool não cobre menos do que o devido, enquanto valores de saída são frequentemente arredondados para baixo).
O motivo dessa inconsistência não é claro. De acordo com o comentário na função _upscale(), os desenvolvedores consideram o impacto do arredondamento em uma única direção como mínimo.
// O arredondamento no upscale não necessariamente seguiria sempre a mesma direção: em um swap, por exemplo, o saldo do
// token de entrada deve ser arredondado para cima, e o do token de saída para baixo. Este é o único lugar onde arredondamos na
// mesma direção para todos os valores, pois o impacto desse arredondamento é esperado como mínimo (e não há
// erro de arredondamento a menos que_scalingFactor()seja sobrescrito).
0x2 Análise da Vulnerabilidade
O problema subjacente surge da operação de arredondamento para baixo realizada durante o upscaling na função BaseGeneralPool._swapGivenOut(). Em particular, _swapGivenOut() arredonda incorretamente para baixo swapRequest.amount por meio da função _upscale(). O valor arredondado resultante é posteriormente utilizado como amountOut ao calcular amountIn via _onSwapGivenOut(). Esse comportamento contradiz a prática padrão de que o arredondamento deve ser aplicado de forma a beneficiar o protocolo.
Portanto, para um determinado pool (wstETH/rETH/cbETH), o amountIn calculado subestima a entrada real necessária. Isso permite que um usuário troque uma quantidade menor de um ativo subjacente (por exemplo, wstETH) por outro (por exemplo, cbETH), reduzindo assim o invariante D como resultado da liquidez efetiva reduzida. Consequentemente, o preço do BPT correspondente (wstETH/rETH/cbETH) torna-se artificialmente deflacionado, já que preço do BPT = D / totalSupply.
0x3 Análise do Ataque
O atacante executou um ataque em dois estágios, provavelmente para minimizar o risco de detecção:
- No primeiro estágio, o exploit principal foi realizado dentro de uma única transação, sem lucro imediato.
- No segundo estágio, o atacante realizou os lucros ao retirar os ativos em uma transação separada.
O primeiro estágio pode ser dividido em duas fases: cálculo de parâmetros e batch swap. A seguir, ilustramos essas fases usando um exemplo de transação (TX) de ataque na Arbitrum.
A Fase de Cálculo de Parâmetros
Nesta fase, o atacante combinou cálculos off-chain com simulações on-chain para ajustar com precisão os parâmetros de cada salto na próxima fase (batch swap), com base no estado atual do Composable Stable Pool (incluindo fatores de escalonamento, coeficiente de amplificação, taxa do BPT, taxas de swap e outros parâmetros). Curiosamente, o atacante também implantou um contrato auxiliar para auxiliar nesses cálculos, o que pode ter sido destinado a reduzir a exposição a ataques de front-running.
No início, o atacante coleta informações básicas sobre o pool alvo, incluindo os fatores de escalonamento de cada token, o parâmetro de amplificação, a taxa do BPT e o percentual de taxa de swap. Em seguida, calcula um valor-chave chamado trickAmt, que é o valor manipulado do token alvo usado para induzir a perda de precisão.
Denotando o fator de escalonamento do token alvo como sF, o cálculo é:
Para determinar os parâmetros usados na etapa 2 da próxima fase (batch swap), o atacante realizou chamadas de simulação subsequentes à função 0x524c9e20 do contrato auxiliar com os seguintes dados de chamada:
uint256[] balances; // Saldos dos tokens do pool (excluindo BPT)
uint256[] scalingFactors; // Fatores de escalonamento para cada token do pool
uint tokenIn; // Índice do token de entrada para a simulação deste salto
uint tokenOut; // Índice do token de saída para a simulação deste salto
uint256 amountOut; // Valor desejado do token de saída
uint256 amp; // Parâmetro de amplificação do pool
uint256 fee; // Percentual de taxa de swap do pool
E os dados de retorno são:
uint256[] balances; // Saldos dos tokens do pool (excluindo BPT) após o swap
Especificamente, o saldo inicial e o número de iterações foram calculados off-chain e passados como parâmetros ao contrato do atacante (reportados como 100.000.000.000 e 25, respectivamente). Cada iteração realiza três swaps:
- Swap 1: Empurra o valor do token alvo para trickAmt + 1, assumindo que a direção do swap é 0 → 1.
- Swap 2: Continua trocando o token alvo com trickAmt, o que aciona o arredondamento para baixo na invocação de _upscale().
- Swap 3: Executa uma operação de swap reverso (1 → 0), onde o valor a ser trocado é derivado do saldo atual do token no pool ao truncar os dois dígitos decimais mais significativos, ou seja, arredondando para baixo ao múltiplo mais próximo de , onde d é o número de dígitos decimais. Por exemplo, 324.816 -> 320.000.
- Observe que esta etapa pode ocasionalmente falhar devido ao método de Newton–Raphson utilizado no cálculo do StableMath. Para mitigar isso, o atacante implementa duas tentativas de repetição, cada uma usando um fallback de 9/10 do valor original. O contrato auxiliar do atacante é derivado da biblioteca StableMath da Balancer V2, como evidenciado pela inclusão das mensagens de erro personalizadas no estilo "BAL".
A Fase de Batch Swap
Em seguida, a operação batchSwap() pode ser dividida em três etapas:
-
Etapa 1: O atacante troca BPT (wstETH/rETH/cbETH) por ativos subjacentes para ajustar com precisão o saldo de um token (cbETH) ao limite de um arredondamento (amount = 9). Isso configura as condições para a perda de precisão na próxima etapa.
-
Etapa 2: O atacante então realiza swaps entre outro ativo subjacente (wstETH) e cbETH usando um valor elaborado (= 8). Devido ao arredondamento para baixo ao escalar os valores dos tokens, o Δx calculado torna-se ligeiramente menor (de 8,918 para 8), levando a um Δy subestimado e, portanto, a um invariante menor (D do modelo StableSwap da Curve). Como preço do BPT = D / totalSupply, o preço do BPT torna-se artificialmente deflacionado.
- Etapa 3: O atacante realiza o swap reverso dos ativos subjacentes de volta para BPT, restaurando o equilíbrio enquanto lucra com o preço deflacionado do BPT.
0x4 Ataques e Perdas
Resumimos os ataques e suas perdas correspondentes na tabela abaixo, com perdas totais superiores a $125 milhões.
0x5 Conclusão
Este incidente envolveu uma série de transações de ataque direcionadas ao protocolo Balancer V2 e seus projetos derivados, resultando em perdas financeiras significativas. Após o ataque inicial, inúmeras transações subsequentes e cópias foram observadas em múltiplas redes. Este evento destaca várias lições críticas para o design e a segurança de protocolos DeFi:
-
Comportamento de Arredondamento e Perda de Precisão: O arredondamento unidirecional (arredondamento para baixo) utilizado na operação de upscaling difere do arredondamento bidirecional (arredondamento para cima e para baixo) utilizado na operação de downscaling. Para prevenir vulnerabilidades semelhantes, os protocolos devem empregar aritmética de maior precisão e implementar verificações de validação robustas. É essencial manter o princípio padrão de que o arredondamento deve sempre favorecer o protocolo.
-
Evolução da Exploração: O atacante realizou um exploit sofisticado em dois estágios projetado para escapar da detecção. No primeiro estágio, o atacante executou o exploit principal dentro de uma única transação sem lucro imediato. No segundo estágio, o atacante realizou os lucros ao retirar os ativos em uma transação separada. Este incidente destaca mais uma vez a corrida armamentista contínua entre pesquisadores de segurança e atacantes.
-
Consciência Operacional e Resposta a Ameaças: Este incidente sublinha a importância de alertas oportunos sobre o status de inicialização e operacional, bem como mecanismos proativos de detecção e prevenção de ameaças para mitigar perdas potenciais de ataques em andamento ou cópias.
Ao manter a continuidade operacional e de negócios, os participantes do setor podem aproveitar o BlockSec Phalcon como a última linha de defesa para proteger seus ativos. A equipe de especialistas da BlockSec está pronta para realizar uma avaliação de segurança abrangente para o seu projeto.
Referências
[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



