No dia 3 de novembro de 2025, os Composable Stable Pools do Balancer V2 e vários projetos em fork em várias blockchains sofreram um ataque coordenado, resultando em perdas totais superiores a 125 milhões de dólares. A BlockSec emitiu um alerta imediatamente e, em seguida, publicou uma análise preliminar.
Este é um ataque altamente complexo. A nossa investigação revela que a causa raiz é a perda de precisão nos cálculos de invariante, que levou à manipulação de preços, distorcendo assim o cálculo do preço do BPT (Token do Pool Balancer). Esta manipulação de invariante permite que os atacantes lucrem através de uma única troca em lote a partir de um determinado pool estável. Embora alguns pesquisadores tenham fornecido análises perspicazes, certas interpretações são enganosas, e a causa raiz e o processo de ataque ainda não foram totalmente esclarecidos. Este blog visa fornecer uma análise técnica abrangente e precisa do evento.
Pontos chave (TL;DR)
Causa raiz: inconsistência de arredondamento e perda de precisão
A operação de ampliação (upscaling) usa arredondamento unidirecional (arredondamento para baixo), enquanto a operação de redução (downscaling) usa arredondamento bidirecional (arredondamento para cima e para baixo).
Esta inconsistência resulta na perda de precisão e, quando explorada através de caminhos de troca cuidadosamente projetados, viola o princípio padrão de que “a arredondamento deve sempre favorecer o protocolo”.
Execução de Ataque
O atacante projetou cuidadosamente os parâmetros, incluindo o número de iterações e os valores de entrada, para maximizar o impacto da perda de precisão.
O atacante usa um método de duas fases para contornar a detecção: primeiro, executa o ataque principal em uma única transação, sem lucrar imediatamente, e depois, em outra transação, realiza o lucro ao retirar ativos.
Impacto operacional e ampliação
Devido a algumas limitações, o protocolo não pode ser pausado. Esta incapacidade de interromper a operação agravou o impacto do ataque e resultou em uma série de ataques subsequentes ou imitadores.
Na seção seguinte, iremos primeiro fornecer informações de fundo essenciais sobre o Balancer V2, e depois analisar detalhadamente os problemas identificados e os ataques relacionados.
0x1 Background
1. Pool de Estabilidade Combinada do Balancer V2
O componente afetado neste ataque é o pool de estabilidade combinada do protocolo Balancer V2. Estes pools são projetados para manter ativos próximos a uma ancoragem de 1:1 (ou para serem negociados a uma taxa de câmbio conhecida) e permitem grandes trocas com um impacto mínimo sobre o preço, aumentando significativamente a eficiência de capital entre ativos semelhantes ou relacionados. Cada pool tem seu próprio Token de Pool Balancer (BPT), que representa a participação dos provedores de liquidez no pool, bem como os ativos subjacentes correspondentes.
Este pool utiliza Stable Math (modelo StableSwap baseado em Curve), onde a invariável D representa o valor total virtual do pool.
O preço do BPT pode ser aproximado a:
A partir da fórmula acima, pode-se ver que, se D pode ser reduzido no balanço (mesmo sem qualquer perda real de fundos), o preço do BPT parecerá mais barato.
2. batchSwap() e onSwap()
O Balancer V2 fornece a função batchSwap(), que suporta trocas de múltiplos saltos (multi-hop swaps) dentro do Vault. De acordo com os parâmetros passados para esta função, existem dois tipos de troca:
GIVEN_IN(“entrada dada”):o chamador especifica o número exato de tokens de entrada, o pool calcula a quantidade correspondente de saída.
GIVEN_OUT(“dado de saída”):o chamador especifica a quantidade de saída desejada, o pool calcula a quantidade de entrada necessária.
Normalmente, o batchSwap() consiste em trocas entre vários Tokens executadas pela função onSwap(). Abaixo é apresentado o caminho de execução quando um SwapRequest é atribuído como tipo de troca GIVEN_OUT (note que o ComposableStablePool herda do BaseGeneralPool):
! [imagem] ()
Abaixo é mostrado o cálculo de amount_in no tipo de troca GIVEN_OUT, que envolve a invariável D.
// inGivenOut token x for y - equação polinomial para resolver
// ax = quantidade em que calcular
// bx = saldo token em
// x = bx + ax (finalBalanceIn)
// D = invariante
// A = coeficiente de amplificação
// n = número de tokens
// S = soma dos saldos finais mas x
// P = produto dos saldos finais mas x
D D^(n+1)
x^2 + ( S - ---------- - D) * x - ------------- = 0
(A * n^n) A * n^2n * P
3. Escalonar e arredondar
Para normalizar o cálculo entre diferentes saldos de Token, o Balancer executa as seguintes duas operações:
Ampliação (Upscaling): Antes de executar os cálculos, amplie o saldo e o montante para uma precisão interna uniforme.
! [imagem] ()
Reduzir (Downscaling): Converter o resultado de volta para a sua precisão nativa e aplicar arredondamento direcional (por exemplo, o montante de entrada é normalmente arredondado para cima para garantir que o fundo não seja cobrado a menos, enquanto o montante de saída é frequentemente arredondado para baixo).
! [imagem] ()
É evidente que aumentar e diminuir são operações que, em teoria, são pares - respectivamente, multiplicação e divisão. No entanto, existe uma inconsistência na implementação dessas duas operações. Especificamente, a operação de diminuição possui duas variantes ou direções: divUp e divDown. Em contraste, a operação de aumento tem apenas uma direção, que é mulDown.
A razão para essa inconsistência ainda não é clara. De acordo com os comentários na função _upscale(), os desenvolvedores acreditam que o impacto do arredondamento unilateral é insignificante.
// O arredondamento do aumento (Upscale) pode não ocorrer sempre na mesma direção: por exemplo, em uma troca,
// O saldo do Token de entrada deve ser arredondado para cima, enquanto o saldo do Token de saída deve ser arredondado para baixo. Este é o nosso único lugar onde todos os montantes são arredondados na mesma direção,
// Porque o impacto deste arredondamento é esperado ser mínimo
// (e a menos que _scalingFactor() seja sobrescrito, não há erro de arredondamento).
Análise de Vulnerabilidades 0x2
A questão fundamental reside na operação de arredondamento para baixo que ocorre ao executar a operação de ampliação (upscaling) na função BaseGeneralPool._swapGivenOut(). Em particular, _swapGivenOut() arredonda incorretamente o swapRequest.amount para baixo através da função _upscale(). Este valor arredondado é então utilizado como amountOut, que é usado ao calcular amountIn através de _onSwapGivenOut(). Este comportamento contradiz as práticas padrão que estipulam que “o arredondamento deve ser aplicado de maneira favorável ao protocolo”.
! [imagem] ()
Portanto, para o pool dado (wstETH/rETH/cbETH), o amountIn calculado subestima a entrada real necessária. Isso permite que os usuários troquem uma quantidade menor de um ativo subjacente (por exemplo, wstETH) por outro ativo (por exemplo, cbETH), resultando em uma diminuição do invariante D devido à redução da liquidez efetiva. Assim, o preço correspondente do BPT (wstETH/rETH/cbETH) torna-se artificialmente reduzido (deflacionado), uma vez que $\text{BPT price} = \frac{D}{\text{totalSupply}}$.
Análise de Ataque 0x3
O atacante executou
Ataque em duas fases, que pode ser para minimizar o risco de detecção:
Na primeira fase, o núcleo é utilizado para executar uma única transação, sem lucro imediato.
Na segunda fase, o atacante realiza lucro ao extrair ativos em outra transação.
A primeira fase pode ser dividida em duas etapas: cálculo de parâmetros e troca em lote. Abaixo, usamos um exemplo de uma transação de ataque (TX) na Arbitrum para ilustrar essas etapas.
Fase de cálculo de parâmetros
Nesta fase, o atacante combina cálculos fora da cadeia com simulações na cadeia, ajustando com precisão os parâmetros de cada salto na próxima fase (troca em lote) com base no estado atual do pool de estabilidade combinada (incluindo fator de escala, coeficiente de ampliação, taxa de BPT, taxas de troca e outros parâmetros). Curiosamente, o atacante também implantou um contrato auxiliar para ajudar nesses cálculos, o que pode ser para reduzir o risco de ser “atropelado”.
Primeiro, os atacantes coletam informações básicas sobre o pool alvo, incluindo o fator de escala de cada Token, parâmetros de ampliação, taxa de BPT e porcentagem de taxa de troca. Em seguida, eles calculam um valor chave trickAmt, que é a quantidade de Token alvo manipulada usada para provocar a perda de precisão.
Defina o fator de escala (scaling factor) do Token alvo como sF e calcule da seguinte forma:
Para determinar os parâmetros usados na etapa 2 da próxima fase (troca em massa), o atacante utiliza o seguinte calldata para realizar a chamada de simulação subsequente à função 0x524c9e20 do contrato auxiliar:
uint256[] balances; // Saldo do Token do pool (excluindo o BPT)
uint256[] scalingFactors; // Fatores de escala para cada token do pool
uint tokenIn; // Índice do Token de entrada simulado por esta opção
uint tokenOut; // Índice do Token de saída simulado por este salto
uint256 amountOut; // Quantidade esperada de Token de saída
uint256 amp; // Parâmetro de ampliação do bloco
uint256 fee; // percentual de taxa de troca do bloco
Os dados retornados são:
uint256[] balances; // Saldo dos Tokens do pool após a troca (excluindo BPT)
Especificamente, o saldo inicial e o número de iterações são calculados fora da cadeia e passados como parâmetros para o contrato do atacante (relatórios de 100,000,000,000 e 25, respectivamente). A cada iteração, são realizadas três trocas:
Trocar 1: Aumentar a quantidade do Token alvo para trickAmt + 1, assumindo que a direção da troca é 0 → 1.
Trocar 2: Continue a trocar trickAmt pela Token alvo, o que acionará o arredondamento para baixo na chamada _upscale().
320,000.
Por favor, note que, devido ao uso do método de Newton-Raphson no cálculo do StableMath, esta etapa pode ocasionalmente falhar. Para mitigar isso, o atacante implementou duas tentativas de reexame, cada uma usando 9/10 do valor original como valor de reserva.
O contrato auxiliar do atacante deriva da biblioteca StableMath do Balancer V2, o que pode ser provado pela inclusão de uma mensagem de erro personalizada no estilo “BAL”.
$d$ Fase de troca em massa
Então, a operação batchSwap###( pode ser dividida em três etapas:
Passo 1: O atacante troca BPT )wstETH/rETH/cbETH( por ativos subjacentes, ajustando precisamente o saldo de um Token (cbETH) para a borda do limite de arredondamento (amount = 9). Isso cria condições para a perda de precisão na próxima etapa.
Passo 2: Depois, o atacante utiliza uma quantidade cuidadosamente projetada (= 8) para trocar entre um outro ativo subjacente (wstETH) e cbETH. Como ao escalonar a quantidade de Token é arredondada para baixo, o Δx calculado torna-se ligeiramente menor (de 8.918 para 8), resultando em um Δy subestimado, o que faz com que a invariância (D, do modelo StableSwap da Curve) diminua. Como $\text{preço do BPT} = \frac{D}{\text{totalSupply}}$, o preço do BPT é artificialmente pressionado para baixo.
! [imagem] )(
3. Passo 3: O atacante reverte os ativos subjacentes de volta para BPT, restaurando o equilíbrio, enquanto lucra com o preço do BPT que foi pressionado para baixo.
0x4: Ataques e Perdas
Resumimos esses ataques e suas respectivas perdas na tabela abaixo, com uma perda total superior a 125 milhões de dólares.
! [imagem] )(
0x5 Conclusão
Este evento envolveu uma série de transações de ataque direcionadas ao protocolo Balancer V2 e seus projetos de fork, resultando em perdas financeiras significativas. Após o ataque inicial, foram observadas numerosas transações subsequentes e de imitação em várias cadeias. Este incidente destaca várias lições-chave sobre o design e a segurança dos protocolos DeFi:
Comportamento de arredondamento e perda de precisão: O arredondamento unilateral (arredondamento para baixo) utilizado na operação de ampliação (upscaling) é diferente do arredondamento bilateral (arredondamento para cima e para baixo) utilizado na operação de redução (downscaling). Para evitar vulnerabilidades semelhantes, o protocolo deve adotar aritmética de maior precisão e implementar verificações robustas. Deve-se respeitar o princípio padrão de que “o arredondamento deve sempre ser favorável ao protocolo”.
Evolução da Exploração de Vulnerabilidades: O atacante executou uma exploração de vulnerabilidade complexa em duas fases, com o objetivo de contornar a detecção. Na primeira fase, o atacante executou a exploração principal em uma única transação, sem obter lucro imediato. Na segunda fase, o atacante obteve lucro em outra transação ao extrair ativos. Isso destaca mais uma vez a contínua “corrida armamentista” entre pesquisadores de segurança e atacantes.
Consciência Operacional e Resposta a Ameaças: Este evento enfatiza a importância de alertas oportunos sobre estados de inicialização e operação, bem como mecanismos proativos de detecção e prevenção de ameaças para mitigar potenciais perdas causadas por ataques contínuos ou simulados.
Enquanto mantém a continuidade operacional e dos negócios, os participantes da indústria podem utilizar o BlockSec Phalcon como a última linha de defesa para proteger seus ativos. A equipe de especialistas da BlockSec está sempre pronta para realizar uma avaliação de segurança abrangente para o seu projeto.
Esta página pode conter conteúdos de terceiros, que são fornecidos apenas para fins informativos (sem representações/garantias) e não devem ser considerados como uma aprovação dos seus pontos de vista pela Gate, nem como aconselhamento financeiro ou profissional. Consulte a Declaração de exoneração de responsabilidade para obter mais informações.
A magia negra da manipulação de preços: Análise da vulnerabilidade de cálculo de invariantes do Balancer V2
Compilação: Blockchain em linguagem simples
No dia 3 de novembro de 2025, os Composable Stable Pools do Balancer V2 e vários projetos em fork em várias blockchains sofreram um ataque coordenado, resultando em perdas totais superiores a 125 milhões de dólares. A BlockSec emitiu um alerta imediatamente e, em seguida, publicou uma análise preliminar.
Este é um ataque altamente complexo. A nossa investigação revela que a causa raiz é a perda de precisão nos cálculos de invariante, que levou à manipulação de preços, distorcendo assim o cálculo do preço do BPT (Token do Pool Balancer). Esta manipulação de invariante permite que os atacantes lucrem através de uma única troca em lote a partir de um determinado pool estável. Embora alguns pesquisadores tenham fornecido análises perspicazes, certas interpretações são enganosas, e a causa raiz e o processo de ataque ainda não foram totalmente esclarecidos. Este blog visa fornecer uma análise técnica abrangente e precisa do evento.
Pontos chave (TL;DR)
Na seção seguinte, iremos primeiro fornecer informações de fundo essenciais sobre o Balancer V2, e depois analisar detalhadamente os problemas identificados e os ataques relacionados.
0x1 Background
1. Pool de Estabilidade Combinada do Balancer V2
O componente afetado neste ataque é o pool de estabilidade combinada do protocolo Balancer V2. Estes pools são projetados para manter ativos próximos a uma ancoragem de 1:1 (ou para serem negociados a uma taxa de câmbio conhecida) e permitem grandes trocas com um impacto mínimo sobre o preço, aumentando significativamente a eficiência de capital entre ativos semelhantes ou relacionados. Cada pool tem seu próprio Token de Pool Balancer (BPT), que representa a participação dos provedores de liquidez no pool, bem como os ativos subjacentes correspondentes.
Este pool utiliza Stable Math (modelo StableSwap baseado em Curve), onde a invariável D representa o valor total virtual do pool.
O preço do BPT pode ser aproximado a:
2. batchSwap() e onSwap()
O Balancer V2 fornece a função batchSwap(), que suporta trocas de múltiplos saltos (multi-hop swaps) dentro do Vault. De acordo com os parâmetros passados para esta função, existem dois tipos de troca:
Normalmente, o batchSwap() consiste em trocas entre vários Tokens executadas pela função onSwap(). Abaixo é apresentado o caminho de execução quando um SwapRequest é atribuído como tipo de troca GIVEN_OUT (note que o ComposableStablePool herda do BaseGeneralPool):
! [imagem] ()
Abaixo é mostrado o cálculo de amount_in no tipo de troca GIVEN_OUT, que envolve a invariável D.
// inGivenOut token x for y - equação polinomial para resolver // ax = quantidade em que calcular // bx = saldo token em // x = bx + ax (finalBalanceIn)
// D = invariante // A = coeficiente de amplificação // n = número de tokens // S = soma dos saldos finais mas x // P = produto dos saldos finais mas x
x^2 + ( S - ---------- - D) * x - ------------- = 0
(A * n^n) A * n^2n * P
3. Escalonar e arredondar
Para normalizar o cálculo entre diferentes saldos de Token, o Balancer executa as seguintes duas operações:
! [imagem] ()
! [imagem] ()
É evidente que aumentar e diminuir são operações que, em teoria, são pares - respectivamente, multiplicação e divisão. No entanto, existe uma inconsistência na implementação dessas duas operações. Especificamente, a operação de diminuição possui duas variantes ou direções: divUp e divDown. Em contraste, a operação de aumento tem apenas uma direção, que é mulDown.
A razão para essa inconsistência ainda não é clara. De acordo com os comentários na função _upscale(), os desenvolvedores acreditam que o impacto do arredondamento unilateral é insignificante.
Análise de Vulnerabilidades 0x2
A questão fundamental reside na operação de arredondamento para baixo que ocorre ao executar a operação de ampliação (upscaling) na função BaseGeneralPool._swapGivenOut(). Em particular, _swapGivenOut() arredonda incorretamente o swapRequest.amount para baixo através da função _upscale(). Este valor arredondado é então utilizado como amountOut, que é usado ao calcular amountIn através de _onSwapGivenOut(). Este comportamento contradiz as práticas padrão que estipulam que “o arredondamento deve ser aplicado de maneira favorável ao protocolo”.
! [imagem] ()
Portanto, para o pool dado (wstETH/rETH/cbETH), o amountIn calculado subestima a entrada real necessária. Isso permite que os usuários troquem uma quantidade menor de um ativo subjacente (por exemplo, wstETH) por outro ativo (por exemplo, cbETH), resultando em uma diminuição do invariante D devido à redução da liquidez efetiva. Assim, o preço correspondente do BPT (wstETH/rETH/cbETH) torna-se artificialmente reduzido (deflacionado), uma vez que $\text{BPT price} = \frac{D}{\text{totalSupply}}$.
Análise de Ataque 0x3
O atacante executou
Ataque em duas fases, que pode ser para minimizar o risco de detecção:
A primeira fase pode ser dividida em duas etapas: cálculo de parâmetros e troca em lote. Abaixo, usamos um exemplo de uma transação de ataque (TX) na Arbitrum para ilustrar essas etapas.
Fase de cálculo de parâmetros
Nesta fase, o atacante combina cálculos fora da cadeia com simulações na cadeia, ajustando com precisão os parâmetros de cada salto na próxima fase (troca em lote) com base no estado atual do pool de estabilidade combinada (incluindo fator de escala, coeficiente de ampliação, taxa de BPT, taxas de troca e outros parâmetros). Curiosamente, o atacante também implantou um contrato auxiliar para ajudar nesses cálculos, o que pode ser para reduzir o risco de ser “atropelado”.
Primeiro, os atacantes coletam informações básicas sobre o pool alvo, incluindo o fator de escala de cada Token, parâmetros de ampliação, taxa de BPT e porcentagem de taxa de troca. Em seguida, eles calculam um valor chave trickAmt, que é a quantidade de Token alvo manipulada usada para provocar a perda de precisão.
Defina o fator de escala (scaling factor) do Token alvo como sF e calcule da seguinte forma:
uint256[] balances; // Saldo do Token do pool (excluindo o BPT) uint256[] scalingFactors; // Fatores de escala para cada token do pool uint tokenIn; // Índice do Token de entrada simulado por esta opção uint tokenOut; // Índice do Token de saída simulado por este salto uint256 amountOut; // Quantidade esperada de Token de saída uint256 amp; // Parâmetro de ampliação do bloco uint256 fee; // percentual de taxa de troca do bloco
Os dados retornados são:
uint256[] balances; // Saldo dos Tokens do pool após a troca (excluindo BPT)
Especificamente, o saldo inicial e o número de iterações são calculados fora da cadeia e passados como parâmetros para o contrato do atacante (relatórios de 100,000,000,000 e 25, respectivamente). A cada iteração, são realizadas três trocas:
Por favor, note que, devido ao uso do método de Newton-Raphson no cálculo do StableMath, esta etapa pode ocasionalmente falhar. Para mitigar isso, o atacante implementou duas tentativas de reexame, cada uma usando 9/10 do valor original como valor de reserva.
O contrato auxiliar do atacante deriva da biblioteca StableMath do Balancer V2, o que pode ser provado pela inclusão de uma mensagem de erro personalizada no estilo “BAL”.
$d$ Fase de troca em massa
Então, a operação batchSwap###( pode ser dividida em três etapas:
! [imagem] )( 3. Passo 3: O atacante reverte os ativos subjacentes de volta para BPT, restaurando o equilíbrio, enquanto lucra com o preço do BPT que foi pressionado para baixo.
0x4: Ataques e Perdas
Resumimos esses ataques e suas respectivas perdas na tabela abaixo, com uma perda total superior a 125 milhões de dólares.
! [imagem] )(
0x5 Conclusão
Este evento envolveu uma série de transações de ataque direcionadas ao protocolo Balancer V2 e seus projetos de fork, resultando em perdas financeiras significativas. Após o ataque inicial, foram observadas numerosas transações subsequentes e de imitação em várias cadeias. Este incidente destaca várias lições-chave sobre o design e a segurança dos protocolos DeFi:
Enquanto mantém a continuidade operacional e dos negócios, os participantes da indústria podem utilizar o BlockSec Phalcon como a última linha de defesa para proteger seus ativos. A equipe de especialistas da BlockSec está sempre pronta para realizar uma avaliação de segurança abrangente para o seu projeto.
Este link:
Fonte: