O ataque baseou-se em uma combinação incomum de particularidades técnicas do protocolo. Os invasores criaram um pool de liquidez TUNA/USDC praticamente vazio e, em seguida, canalizaram para ele fundos emprestados em USDC por meio do agregador de intercâmbios Jupiter.
Como o intercâmbio produziu uma quantidade ínfima de tokens TUNA, o sistema de avaliação de ativos da DeFiTuna, devido a uma particularidade do arredondamento, considerou que o valor da posição era igual a zero. Depois disso, o protocolo reconheceu erroneamente essa posição como financeiramente sólida, permitindo aos invasores contornar a verificação de solvência e retirar os fundos emprestados por meio de posições de liquidez sob seu controle.
Os endereços dos invasores:
- 9ytGWP8tCRF1keREJ5VHqBpSuM9MZYwm3oFQQa1SvESb
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
- 7hiHL8AgDuLNVDQLfN3GHdLAEeCN1F7uz6nSANRvFJst
- BK9aTnKfPNnnj45Me5ACrky2vexzUrZHRzr4BjmQpH3c
- 8P3H7Hy98LWhw5QhuXdoigVdAzPCYRTYjdtQQKhGwvqD
- GrXBhM6Ty6YxubFp8zubtJF12WXmKj1dhMyakRPeaDpo
- F4xUroPaHro4gb2JAqa3e93E7DdZytRQJ4L2cHT5E53p
- ETGhosPrFApbiiKXDpxrhBz7J2MdTA3dxnL2Nkio9vEX
- 4ZKkGZuoXqgSHbmsJXUib3dgn5WNhzbPEXrxuMYJ5oQ3
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
O endereço comprometido:
- D76dDcSU5HnAGqVEZCDLyGgLpTp4xZuqeZyVDtUdDv55
Como o ataque se desenrolou: de um pool vazio ao retiro dos fundos
O preço inicial do token foi definido o mais próximo possível do valor exibido pelo oráculo de preços oficial. Graças a isso, o protocolo DeFiTuna passou com sucesso pela verificação de desvio de preço incorporada e não detectou nada suspeito.
Em seguida, os invasores colocaram nesse pool duas posições limite próprias em um nível de preço anormalmente alto, tick 208,640, adicionando apenas 0.001052 TUNA (em duas partes iguais de 0.000526 TUNA cada).
Depois disso, foi invocada a função Open_and_increase_tuna_spot_position_jupiter do protocolo DeFiTuna. A análise dos dados da instrução mostrou que o invasor abriu uma posição de trading sem nenhuma garantia própria.
Quase 570 mil USDC, obtidos como empréstimo do pool de liquidez (descontada a taxa), foram canalizados automaticamente por meio do roteador integrado da Jupiter. Na instrução de intercâmbio de 47 bytes, havia sido especificada de antemão a rota pela qual os fundos deveriam passar.
Na prática, a totalidade do valor, 569,601 USDC, foi trocada por tokens TUNA por meio do pool de baixa liquidez preparado de antemão. Como as únicas ordens disponíveis nesse pool eram as posições criadas pelos invasores no nível tick 208640, o intercâmbio passou justamente por elas.
Levando em conta a taxa de 0.1%, o resultado final do intercâmbio foi:
Após o arredondamento, o protocolo recebeu apenas 0.000494 TUNA.
Onde ocorreu o erro
Ao receber uma quantidade tão insignificante de tokens, a DeFiTuna começou a calcular o valor da posição usando o preço existente antes do intercâmbio, conforme os dados do oráculo de preços. Na função is_healthy() → compute_total_and_debt(), o valor era calculado da seguinte forma:
Na etapa seguinte, a função to_num() convertia esse número em um valor inteiro. No entanto, nessa conversão a parte fracionária é totalmente descartada. Como resultado:
Foi justamente aqui que surgiu o erro crítico. Após o arredondamento, o sistema decidiu que o valor total da posição era igual a zero. Em seguida, o protocolo aplicou uma regra incorporada:
Partindo dessa lógica, o sistema chegou à conclusão errônea de que a posição era completamente segura e atendia aos requisitos de solvência. Na realidade, todos os fundos USDC já pertenciam economicamente às duas ordens limite criadas de antemão pelos invasores. Após contornar com sucesso a verificação, ambos os endereços invocaram a função DecreaseLimitOrder e retiraram 284,280.483231 USDC cada um.
Por que a proteção da DeFiTuna não funcionou
O problema estava no mecanismo de verificação de solvência TunaPosition.is_healthy(). A função compute_total_and_debt() calculava o valor dos ativos usando um preço de referência e, em seguida, arredondava o resultado para baixo com a função to_num(). Os invasores construíram deliberadamente um pool de liquidez extremamente desequilibrado.
Depois que os USDC emprestados foram canalizados para esse pool por meio de uma rota de intercâmbio arbitrária, retornou uma quantidade de TUNA tão pequena que o valor da posição, após o arredondamento, tornou-se igual a zero. Foi justamente isso que permitiu contornar por completo a verificação de solvência e retirar os fundos emprestados.
O problema estava no mecanismo de verificação de solvência TunaPosition.is_healthy(). A função compute_total_and_debt() calculava o valor dos ativos usando um preço de referência e, em seguida, arredondava o resultado para baixo com a função to_num(). Os invasores construíram deliberadamente um pool de liquidez extremamente desequilibrado. Depois que os USDC emprestados foram canalizados para esse pool por meio de uma rota de intercâmbio arbitrária, retornou uma quantidade de TUNA tão pequena que o valor da posição, após o arredondamento, tornou-se igual a zero. Foi justamente isso que permitiu contornar por completo a verificação de solvência e retirar os fundos emprestados.
Após o ataque, os ativos roubados foram distribuídos entre várias carteiras intermediárias e depois consolidados em dois endereços principais.
Depois disso, os fundos foram transferidos da rede Solana para a Ethereum por meio da ponte cross-chain Mayan. Já na rede Ethereum, 140 ETH foram enviados ao serviço de transferências confidenciais Railgun a partir do endereço: 0x6a2e244af2c17CFc64e8ca0EA4335318EdF20B0B.
Ao mesmo tempo, cerca de 300K DAI, no momento da preparação deste material, continuavam na carteira: 0x509B9D094A6C26D716aaC131E8aDee5B16B86d3e.
