El ataque se basó en una combinación inusual de particularidades técnicas del protocolo. Los atacantes crearon un pool de liquidez TUNA/USDC prácticamente vacío, tras lo cual canalizaron hacia él fondos prestados en USDC a través del agregador de intercambios Jupiter.
Dado que el intercambio produjo una cantidad ínfima de tokens TUNA, el sistema de valoración de activos de DeFiTuna, debido a una particularidad del redondeo, consideró que el valor de la posición era igual a cero. Después de esto, el protocolo reconoció erróneamente esa posición como financieramente sólida, permitiendo a los atacantes eludir la verificación de solvencia y retirar los fondos prestados a través de posiciones de liquidez bajo su control.
Las direcciones de los atacantes:
- 9ytGWP8tCRF1keREJ5VHqBpSuM9MZYwm3oFQQa1SvESb
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
- 7hiHL8AgDuLNVDQLfN3GHdLAEeCN1F7uz6nSANRvFJst
- BK9aTnKfPNnnj45Me5ACrky2vexzUrZHRzr4BjmQpH3c
- 8P3H7Hy98LWhw5QhuXdoigVdAzPCYRTYjdtQQKhGwvqD
- GrXBhM6Ty6YxubFp8zubtJF12WXmKj1dhMyakRPeaDpo
- F4xUroPaHro4gb2JAqa3e93E7DdZytRQJ4L2cHT5E53p
- ETGhosPrFApbiiKXDpxrhBz7J2MdTA3dxnL2Nkio9vEX
- 4ZKkGZuoXqgSHbmsJXUib3dgn5WNhzbPEXrxuMYJ5oQ3
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
La dirección comprometida:
- D76dDcSU5HnAGqVEZCDLyGgLpTp4xZuqeZyVDtUdDv55
Cómo se desarrolló el ataque: de un pool vacío al retiro de los fondos
El precio inicial del token se fijó lo más cerca posible del valor que mostraba el oráculo de precios oficial. Gracias a esto, el protocolo DeFiTuna superó con éxito la verificación de desviación de precio incorporada y no detectó nada sospechoso.
Luego, los atacantes colocaron en ese pool dos posiciones límite propias en un nivel de precio anómalamente alto, tick 208,640, añadiendo apenas 0.001052 TUNA (en dos partes iguales de 0.000526 TUNA cada una).
Después de esto, se invocó la función Open_and_increase_tuna_spot_position_jupiter del protocolo DeFiTuna. El análisis de los datos de la instrucción mostró que el atacante abrió una posición de trading sin ningún colateral propio.
Casi 570 mil USDC, obtenidos en préstamo del pool de liquidez (descontada la comisión), fueron canalizados automáticamente a través del enrutador integrado de Jupiter. En la instrucción de intercambio de 47 bytes se había especificado de antemano la ruta por la que debían pasar los fondos.
En la práctica, la totalidad de la suma, 569,601 USDC, se intercambió por tokens TUNA a través del pool de baja liquidez preparado de antemano. Dado que las únicas órdenes disponibles en ese pool eran las posiciones creadas por los atacantes en el nivel tick 208640, el intercambio pasó precisamente por ellas.
Teniendo en cuenta la comisión del 0.1%, el resultado final del intercambio fue:
Tras el redondeo, el protocolo recibió apenas 0.000494 TUNA.
Dónde se produjo el error
Al recibir una cantidad tan insignificante de tokens, DeFiTuna comenzó a calcular el valor de la posición usando el precio existente antes del intercambio, según los datos del oráculo de precios. En la función is_healthy() → compute_total_and_debt(), el valor se calculaba de la siguiente manera:
En la siguiente etapa, la función to_num() convertía ese número en un valor entero. Sin embargo, en esa conversión la parte fraccionaria se descarta por completo. Como resultado:
Justo aquí surgió el error crítico. Tras el redondeo, el sistema decidió que el valor total de la posición era igual a cero. A continuación, el protocolo aplicó una regla incorporada:
Partiendo de esta lógica, el sistema llegó a la conclusión errónea de que la posición era completamente segura y cumplía los requisitos de solvencia. En realidad, todos los fondos USDC ya pertenecían económicamente a las dos órdenes límite creadas de antemano por los atacantes. Tras eludir con éxito la verificación, ambas direcciones invocaron la función DecreaseLimitOrder y retiraron 284,280.483231 USDC cada una.
Por qué no funcionó la protección de DeFiTuna
El problema estaba en el mecanismo de verificación de solvencia TunaPosition.is_healthy(). La función compute_total_and_debt() calculaba el valor de los activos usando un precio de referencia y luego redondeaba el resultado hacia abajo con la función to_num(). Los atacantes construyeron deliberadamente un pool de liquidez extremadamente desequilibrado.
Después de que los USDC prestados fueran canalizados hacia ese pool a través de una ruta de intercambio arbitraria, regresó una cantidad de TUNA tan pequeña que el valor de la posición, tras el redondeo, se volvió igual a cero. Fue precisamente esto lo que permitió eludir por completo la verificación de solvencia y retirar los fondos prestados.
A dónde fueron los $570K robados
Tras el ataque, los activos robados se distribuyeron entre varias billeteras intermedias y luego se consolidaron en dos direcciones principales.
Después de esto, los fondos se transfirieron de la red Solana a Ethereum mediante el puente cross-chain Mayan. Ya en la red Ethereum, se enviaron 140 ETH al servicio de transferencias confidenciales Railgun desde la dirección: 0x6a2e244af2c17CFc64e8ca0EA4335318EdF20B0B.
Al mismo tiempo, cerca de 300K DAI, en el momento de la preparación de este material, seguían en la billetera: 0x509B9D094A6C26D716aaC131E8aDee5B16B86d3e.
