Falha de contabilidade no pool yETH da Yearn permitiu criação massiva de tokens com 16 wei

Falha de contabilidade no pool yETH da Yearn permitiu criação massiva de tokens com 16 wei

Exploração em contrato DeFi no Ethereum abusou de saldos virtuais em cache não zerados para emitir 235 septilhões de yETH e retirar cerca de US$ 9 milhões em ativos líquidos.

ComponentePool yETH da Yearn Finance no Ethereum, com lógica de AMM ponderado e saldos virtuais armazenados em packed_vbs[].
VetorSequência de depósitos e retiradas com fundos obtidos por flash loan, esvaziamento completo do pool e novo depósito mínimo de 16 wei quando supply == 0.
ImpactoEmissão indevida de 235 septilhões de tokens yETH, controle econômico do pool e retirada de aproximadamente US$ 9 milhões em ativos subjacentes.
PrioridadeRevisar transições de estado em contratos que tratam supply == 0, bloquear emissões desproporcionais e validar limpeza explícita de caches contábeis.
ArtefatosVariáveis packed_vbs[], funções remove_liquidity() e add_liquidity(), ativos wstETH, rETH, WETH, ETHx, cbETH, sfrxETH, apxETH, wOETH e mETH.
MitigaçãoZerar saldos virtuais quando todos os LP tokens forem queimados, testar ciclos completos de entrada e saída de liquidez e simular transações antes da execução.
Resumo técnico

Em 30 de novembro de 2025, o pool yETH da Yearn Finance no Ethereum foi explorado por meio de uma falha de contabilidade interna ligada a saldos virtuais mantidos em cache. O ataque resultou na retirada de aproximadamente US$ 9 milhões em ativos, depois que o contrato emitiu uma quantidade incompatível de tokens yETH para um depósito de apenas 16 wei. A falha não dependia de comprometimento de chave privada, invasão de servidor ou manipulação externa de interface: o fluxo abusou de regras válidas do próprio contrato quando o estado do pool chegou a supply == 0 sem que variáveis auxiliares fossem reinicializadas de forma consistente.

O ponto central da vulnerabilidade estava no uso de packed_vbs[], estrutura usada para armazenar informações de saldos virtuais e reduzir custo de gás. O contrato tratava a condição supply == 0 como se ela representasse um pool recém-inicializado, mas essa suposição deixou de ser verdadeira depois de ciclos de depósito e retirada. Quando todos os LP tokens foram queimados, o contador principal de fornecimento foi zerado, porém os valores residuais de packed_vbs[] permaneceram preenchidos. Ao realizar um depósito mínimo após esse esvaziamento, o atacante fez a rotina de criação de liquidez ler saldos fantasmas acumulados e emitir 235 septilhões de yETH, um número de 41 dígitos, sem correspondência com o valor real depositado.

Fluxo técnico

O pool yETH opera como um AMM de stable swap ponderado para derivativos líquidos de staking baseados em Ethereum. A construção combina mecânica de pesos semelhante à de pools ponderados com saldos virtuais no estilo usado para otimização de cálculo. Em vez de recalcular todos os valores em cada operação, o contrato mantém informações persistidas em variáveis de estado. Essa decisão reduz custo operacional em cenários normais, mas aumenta a necessidade de consistência em transições extremas, especialmente quando a liquidez total chega a zero e o contrato precisa distinguir um primeiro depósito real de uma reinicialização após uso anterior.

A exploração foi montada em etapas encadeadas. Primeiro, o atacante obteve ativos por flash loans em Balancer e Aave, incluindo wstETH, rETH, WETH, ETHx e cbETH, sem precisar manter capital próprio equivalente antes da transação. Em seguida, executou mais de dez ciclos de depósito e retirada para alterar gradualmente os saldos virtuais internos. Cada ciclo depositava ativos nos cofres e no pool, retirava parte da liquidez e deixava pequenos resíduos contábeis em packed_vbs[] por causa do modo como a redução proporcional e arredondamentos eram tratados.

Depois de acumular esses resíduos, o atacante queimou os LP tokens remanescentes e fez self.supply chegar a zero. Essa etapa era crítica: o estado principal indicava ausência de fornecimento, mas os slots de armazenamento associados aos saldos virtuais continuavam carregando valores anteriores. Na etapa final, o atacante depositou quantias mínimas em wei nos tokens suportados. A função add_liquidity() entrou no caminho lógico reservado à primeira entrada de liquidez e consultou packed_vbs[] como base de cálculo. Como a função não partiu de saldos zerados, a emissão foi calculada sobre valores fantasmas em vez do depósito efetivo.

Com a emissão indevida de yETH, o atacante passou a ter poder econômico suficiente para retirar os ativos subjacentes do pool. A sequência incluiu troca de yETH por WETH em pools da Balancer, retirada de ativos como sfrxETH, wstETH, ETHx, cbETH, rETH, apxETH, wOETH e mETH, conversão posterior para ETH por meio da Uniswap V3 e outras DEXs, pagamento dos flash loans com taxas e envio de parte dos valores ao Tornado Cash. O lucro decorreu da diferença entre o valor quase nulo usado para acionar a emissão e o valor real dos ativos drenados após a criação artificial dos tokens.

Superfície afetada

A superfície exposta era o contrato do pool yETH e qualquer fluxo que dependesse da correspondência entre fornecimento total de LP tokens e saldos virtuais em cache. O problema aparece quando duas funções com responsabilidades complementares, remove_liquidity() e add_liquidity(), compartilham premissas diferentes sobre o estado persistente. A primeira reduzia saldos virtuais de forma proporcional ao remover liquidez, mas não garantia que todos os slots de packed_vbs[] fossem explicitamente zerados quando a liquidez total desaparecia. A segunda assumia que supply == 0 equivalia a um ambiente limpo para cálculo inicial.

Protocolos DeFi que utilizam invariantes matemáticos complexos, múltiplos ativos, rate providers e otimizações persistidas em armazenamento têm risco ampliado quando estados derivados são tratados como se fossem sempre coerentes com contadores principais. O caso do yETH mostra que a vulnerabilidade não estava apenas em uma fórmula isolada, mas na transição entre estados: pool usado, pool esvaziado e pool reabastecido. Para operadores e auditores, o ativo exposto não é somente o token LP, mas toda a cesta de derivativos líquidos de staking que pode ser retirada quando a emissão indevida altera a relação entre participação e lastro.

  • Contratos que interpretam supply == 0 como condição suficiente para executar lógica de primeiro depósito.
  • Caches contábeis persistidos em packed_vbs[] que não são sincronizados com a queima total de LP tokens.
  • Fluxos com múltiplos ativos e arredondamento proporcional durante remove_liquidity().
  • Pools integrados a Balancer, Aave, Uniswap V3 ou outras DEXs que permitam capital temporário e saída rápida de ativos.
Hunting e telemetria

A detecção precisa ir além de alertas por transação isolada. A preparação do ataque envolveu vários ciclos de depósito e retirada destinados a moldar o estado interno antes da etapa de drenagem. Monitoramento on-chain deve correlacionar sequências em uma mesma janela, observar oscilações repetidas de liquidez, identificar quando o fornecimento total de LP tokens chega a zero e comparar esse evento com a persistência de saldos virtuais ou variáveis derivadas. Um sinal forte é a combinação de múltiplas operações financiadas por flash loan, mudanças pequenas e repetidas em saldos internos e uma emissão final que excede de forma extrema o valor depositado.

Em contratos DeFi, hunting eficaz deve calcular razão entre entrada econômica e tokens emitidos, e não apenas verificar eventos de depósito ou retirada de forma nominal. Uma emissão de 235 septilhões de yETH para 16 wei representa quebra direta de invariantes econômicos. Operadores de protocolo devem instrumentar simulações de pré-execução para estimar o resultado esperado de add_liquidity() sob o estado atual do contrato e comparar a emissão projetada com limites definidos por lastro, saldo real e variação aceitável. Em paralelo, equipes de resposta devem revisar transferências subsequentes para WETH, retiradas de ativos LSD, conversões em DEXs e uso de serviços de mistura quando houver drenagem confirmada.

  • Sequências de dez ou mais ciclos de depósito e retirada com ativos obtidos por flash loan antes de uma retirada total.
  • Eventos em que supply == 0 ocorre sem limpeza verificável de variáveis derivadas como packed_vbs[].
  • Emissão de LP tokens ou yETH em proporção incompatível com o depósito efetivo em wei.
  • Trocas rápidas de yETH por WETH, retiradas de sfrxETH, wstETH, ETHx, cbETH, rETH, apxETH, wOETH ou mETH e conversão para ETH.
  • Fluxos de saída para Tornado Cash após consolidação de ativos.
Mitigação

A correção principal é tratar o esvaziamento completo do pool como uma transição de estado própria, não como detalhe colateral de uma retirada. Quando todos os LP tokens forem queimados, os saldos virtuais associados ao pool precisam ser zerados de maneira explícita, incluindo todos os slots relevantes de packed_vbs[]. Também é necessário revisar qualquer caminho que use prev_supply == 0 ou supply == 0 para selecionar lógica especial, porque essa condição sozinha não prova que o contrato nunca foi usado. O código deve diferenciar inicialização original, reinicialização após liquidez zerada e reentrada após operações parciais com resíduos de arredondamento.

A validação defensiva deve incluir testes unitários e testes baseados em propriedades para ciclos completos de adicionar liquidez, remover liquidez parcial, remover liquidez total e adicionar liquidez novamente com quantias mínimas. Esses testes devem verificar invariantes econômicos, como relação entre valor depositado e tokens emitidos, conservação aproximada de valor, ausência de saldos virtuais quando não houver fornecimento e comportamento correto em múltiplos ativos com casas decimais diferentes. Para produção, controles de runtime podem bloquear transações com emissão anômala, principalmente quando a operação é precedida por flash loans ou por sequências repetitivas de alteração de liquidez.

Na resposta a incidente, a ordem prática é pausar fluxos afetados quando o protocolo tiver mecanismo de emergência, capturar estado de contrato e eventos antes de qualquer atualização, identificar endereços que participaram da cadeia de flash loans, mapear ativos retirados e preparar correção com validação independente. Após a atualização, a equipe deve reexecutar a sequência explorada em ambiente de simulação para confirmar que packed_vbs[] é limpo no esvaziamento total e que um depósito mínimo não consegue acionar emissão desproporcional. A revisão também deve abranger rate providers, wrappers e pools externos usados na liquidação, porque a drenagem se completa por integrações, não apenas pela função vulnerável.

  • Zerar packed_vbs[] e demais estados derivados sempre que a queima de LP tokens levar supply a zero.
  • Adicionar testes que reproduzam depósitos e retiradas repetidas com arredondamento e posterior depósito mínimo.
  • Bloquear ou pausar emissões quando a razão entre entrada depositada e tokens criados ultrapassar limites econômicos do pool.
  • Correlacionar flash loans, ciclos de liquidez, emissão anômala, swaps em DEXs e retiradas de ativos subjacentes.
  • Revisar premissas de inicialização em add_liquidity() e efeitos residuais de remove_liquidity() antes de reativar o pool.

Postar um comentário

0 Comentários