L'attaque reposait sur une combinaison inhabituelle de particularités techniques du protocole. Les attaquants ont créé un pool de liquidité TUNA/USDC pratiquement vide, puis y ont acheminé des fonds empruntés en USDC via l'agrégateur d'échanges Jupiter.
Étant donné que l'échange n'a produit qu'une quantité infime de tokens TUNA, le système d'évaluation des actifs de DeFiTuna, en raison d'une particularité de l'arrondi, a considéré que la valeur de la position était égale à zéro. Après cela, le protocole a considéré à tort cette position comme financièrement solide, permettant aux attaquants de contourner la vérification de solvabilité et de retirer les fonds empruntés via des positions de liquidité sous leur contrôle.
Les adresses des attaquants:
- 9ytGWP8tCRF1keREJ5VHqBpSuM9MZYwm3oFQQa1SvESb
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
- 7hiHL8AgDuLNVDQLfN3GHdLAEeCN1F7uz6nSANRvFJst
- BK9aTnKfPNnnj45Me5ACrky2vexzUrZHRzr4BjmQpH3c
- 8P3H7Hy98LWhw5QhuXdoigVdAzPCYRTYjdtQQKhGwvqD
- GrXBhM6Ty6YxubFp8zubtJF12WXmKj1dhMyakRPeaDpo
- F4xUroPaHro4gb2JAqa3e93E7DdZytRQJ4L2cHT5E53p
- ETGhosPrFApbiiKXDpxrhBz7J2MdTA3dxnL2Nkio9vEX
- 4ZKkGZuoXqgSHbmsJXUib3dgn5WNhzbPEXrxuMYJ5oQ3
- 917DKTphW3rhBG5gsJpwKsNGisNV2dx74uUFd8HBEjtg
L'adresse compromise:
- D76dDcSU5HnAGqVEZCDLyGgLpTp4xZuqeZyVDtUdDv55
Comment l'attaque s'est déroulée: d'un pool vide au retrait des fonds
Le prix initial du token a été fixé au plus près possible de la valeur affichée par l'oracle de prix officiel. Grâce à cela, le protocole DeFiTuna a passé avec succès la vérification d'écart de prix intégrée et n'a rien détecté de suspect.
Ensuite, les attaquants ont placé dans ce pool deux positions limites leur appartenant, à un niveau de prix anormalement élevé, tick 208,640, en ajoutant seulement 0.001052 TUNA (en deux parts égales de 0.000526 TUNA chacune).
Après cela, la fonction Open_and_increase_tuna_spot_position_jupiter du protocole DeFiTuna a été appelée. L'analyse des données de l'instruction a montré que l'attaquant avait ouvert une position de trading sans aucune garantie propre.
Près de 570 mille USDC, empruntés au pool de liquidité (déduction faite de la commission), ont été automatiquement acheminés via le routeur intégré de Jupiter. Dans l'instruction d'échange de 47 octets, l'itinéraire que les fonds devaient emprunter avait été spécifié à l'avance.
Dans les faits, la totalité de la somme, 569,601 USDC, a été échangée contre des tokens TUNA via le pool de faible liquidité préparé à l'avance. Comme les seuls ordres disponibles dans ce pool étaient les positions créées par les attaquants au niveau tick 208640, l'échange est passé précisément par elles.
Compte tenu de la commission de 0.1%, le résultat final de l'échange a été:
Après arrondi, le protocole n'a reçu que 0.000494 TUNA.
Où l'erreur s'est produite
Ayant reçu une quantité de tokens aussi négligeable, DeFiTuna a commencé à calculer la valeur de la position en utilisant le prix qui existait avant l'échange, selon les données de l'oracle de prix. Dans la fonction is_healthy() → compute_total_and_debt(), la valeur était calculée de la manière suivante:
À l'étape suivante, la fonction to_num() convertissait ce nombre en une valeur entière. Toutefois, lors d'une telle conversion, la partie fractionnaire est entièrement supprimée. Résultat:
C'est précisément là qu'est survenue l'erreur critique. Après arrondi, le système a décidé que la valeur totale de la position était égale à zéro. Le protocole a ensuite appliqué une règle intégrée:
Partant de cette logique, le système a conclu à tort que la position était parfaitement sûre et satisfaisait aux exigences de solvabilité. En réalité, tous les fonds USDC appartenaient déjà économiquement aux deux ordres limites créés à l'avance par les attaquants. Après avoir contourné la vérification avec succès, les deux adresses ont appelé la fonction DecreaseLimitOrder et ont retiré 284,280.483231 USDC chacune.
Pourquoi la protection de DeFiTuna n'a pas fonctionné
Le problème résidait dans le mécanisme de vérification de solvabilité TunaPosition.is_healthy(). La fonction compute_total_and_debt() calculait la valeur des actifs en utilisant un prix de référence, puis arrondissait le résultat à la baisse à l'aide de la fonction to_num(). Les attaquants ont délibérément construit un pool de liquidité extrêmement déséquilibré.
Après que les USDC empruntés ont été acheminés vers ce pool via un itinéraire d'échange arbitraire, il est revenu une quantité de TUNA si faible que la valeur de la position, après arrondi, est devenue égale à zéro. C'est précisément cela qui a permis de contourner entièrement la vérification de solvabilité et de retirer les fonds empruntés.
Où sont allés les $570K dérobés
Après l'attaque, les actifs dérobés ont été répartis entre plusieurs portefeuilles intermédiaires, puis regroupés sur deux adresses principales.
Après cela, les fonds ont été transférés du réseau Solana vers Ethereum au moyen du pont cross-chain Mayan. Une fois sur le réseau Ethereum, 140 ETH ont été envoyés au service de transferts confidentiels Railgun depuis l'adresse: 0x6a2e244af2c17CFc64e8ca0EA4335318EdF20B0B.
Dans le même temps, environ 300K DAI, au moment de la préparation de ce document, se trouvaient toujours dans le portefeuille: 0x509B9D094A6C26D716aaC131E8aDee5B16B86d3e.
