Bugs comptables des AMM DeFi & Exploitation du cache de virtual balances
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Vue d’ensemble
Le pool yETH de Yearn Finance (Nov 2025) a montré comment des caches économiseurs de gas à l’intérieur d’AMM complexes peuvent être exploités lorsqu’ils ne sont pas réconciliés lors des transitions d’état aux frontières. Le stableswap pondéré suit jusqu’à 32 liquid staking derivatives (LSDs), les convertit en ETH-equivalent virtual balances (vb_i = balance_i × rate_i / PRECISION), et stocke ces valeurs dans un tableau de stockage packé packed_vbs[]. Lorsque tous les LP tokens sont brûlés, totalSupply tombe correctement à zéro mais les slots cachés packed_vbs[i] ont conservé d’énormes valeurs historiques. Le déposant suivant a été traité comme le “premier” fournisseur de liquidité alors que le cache contenait toujours de la liquidité fantôme, permettant à un attaquant de mint ~235 septillion yETH pour seulement 16 wei avant de drainer ≈USD 9M en collatéral LSD.
Éléments clés :
- Derived-state caching : les consultations d’oracles coûteuses sont évitées en persistant les virtual balances et en les mettant à jour de façon incrémentale.
- Missing reset when
supply == 0: les décréments proportionnels dansremove_liquidity()ont laissé des résidus non nuls danspacked_vbs[]après chaque cycle de retrait. - Initialization branch trusts the cache :
add_liquidity()appelle_calc_vb_prod_sum()et lit simplementpacked_vbs[]quandprev_supply == 0, en supposant que le cache est également remis à zéro. - Flash-loan financed state poisoning : des boucles dépôt/retrait ont amplifié les résidus d’arrondi sans verrouillage de capital, permettant un over-mint catastrophique dans le chemin “first deposit”.
Conception du cache et absence de gestion des états limites
Le flux vulnérable est simplifié ci-dessous:
function remove_liquidity(uint256 burnAmount) external {
uint256 supplyBefore = totalSupply();
_burn(msg.sender, burnAmount);
for (uint256 i; i < tokens.length; ++i) {
packed_vbs[i] -= packed_vbs[i] * burnAmount / supplyBefore; // truncates to floor
}
// BUG: packed_vbs not cleared when supply hits zero
}
function add_liquidity(Amounts calldata amountsIn) external {
uint256 prevSupply = totalSupply();
uint256 sumVb = prevSupply == 0 ? _calc_vb_prod_sum() : _calc_adjusted_vb(amountsIn);
uint256 lpToMint = pricingInvariant(sumVb, prevSupply, amountsIn);
_mint(msg.sender, lpToMint);
}
function _calc_vb_prod_sum() internal view returns (uint256 sum) {
for (uint256 i; i < tokens.length; ++i) {
sum += packed_vbs[i]; // assumes cache == 0 for a pristine pool
}
}
Parce que remove_liquidity() appliquait seulement des décréments proportionnels, chaque boucle laissait poussière d’arrondi en virgule fixe. Après ≳10 cycles dépôt/retrait ces résidus se sont accumulés en soldes virtuels fantômes extrêmement élevés alors que les soldes on-chain des tokens étaient presque vides. La combustion des dernières parts LP a mis totalSupply à zéro mais les caches sont restés peuplés, préparant le protocole à une initialisation malformée.
Plan d’exploitation (étude de cas yETH)
- Flash-loan working capital – Emprunter wstETH, rETH, cbETH, ETHx, WETH, etc. depuis Balancer/Aave pour éviter d’immobiliser du capital pendant la manipulation du pool.
- Poison
packed_vbs[]– Boucler des dépôts et retraits sur huit actifs LSD. Chaque retrait partiel tronquepacked_vbs[i] − vb_share, laissant des résidus >0 par token. La répétition de la boucle gonfle des soldes virtuels fantômes équivalents en ETH sans éveiller les soupçons parce que les soldes réels se compensent approximativement. - Force
supply == 0– Brûler tous les tokens LP restants pour que le pool croie être vide. Un oubli d’implémentation laisse lepacked_vbs[]empoisonné intact. - Dépôt initial de taille poussière – Envoyer un total de 16 wei réparti entre les slots LSD supportés.
add_liquidity()voitprev_supply == 0, exécute_calc_vb_prod_sum(), et lit le cache obsolète au lieu de recomposer à partir des soldes réels. Le calcul de mint agit donc comme si des trillions de USD étaient entrés, émettant ~2.35×10^26 yETH. - Drain & repay – Racheter la position LP gonflée pour tous les LSD en coffre, swapper yETH→WETH sur Balancer, convertir en ETH via Uniswap v3, rembourser les flash loans/frais, et blanchir le profit (p. ex. via Tornado Cash). Profit net ≈USD 9M alors que seulement 16 wei de fonds propres ont touché le pool.
Conditions générales d’exploitation
Vous pouvez abuser des AMM similaires lorsque toutes les conditions suivantes sont réunies :
- Dérivés mis en cache des soldes (virtual balances, TWAP snapshots, invariant helpers) persistent entre transactions pour économiser du gas.
- Les mises à jour partielles tronquent les résultats (floor division, fixed-point rounding), permettant à un attaquant d’accumuler des résidus d’état via des cycles symétriques dépôt/retrait.
- Les conditions aux limites réutilisent les caches au lieu d’une recomputation à partir de la vérité terrain, surtout lorsque
totalSupply == 0,totalLiquidity == 0, ou que la composition du pool est réinitialisée. - La logique de minting manque de vérifications de cohérence de ratio (p.ex. absence de bornes
expected_value/actual_value) de sorte qu’un dépôt de type dust peut minter essentiellement la totalité de l’offre historique. - Du capital bon marché est disponible (flash loans ou crédit interne) pour exécuter des dizaines d’opérations ajustant l’état dans une seule transaction ou un bundle étroitement chorégraphié.
Liste de contrôle d’ingénierie défensive
- Explicit resets when supply/lpShares hit zero:
if (totalSupply == 0) {
for (uint256 i; i < tokens.length; ++i) packed_vbs[i] = 0;
}
Appliquer le même traitement à chaque accumulateur mis en cache dérivé des soldes ou des données oracle.
- Recompute on initialization branches – Quand
prev_supply == 0, ignorer complètement les caches et reconstruire les virtual balances à partir des soldes réels des tokens + taux oracle en direct. - Minting sanity bounds – Revert si
lpToMint > depositValue × MAX_INIT_RATIOou si une seule transaction minte >X% de l’offre historique alors que les dépôts totaux sont en dessous d’un seuil minimal. - Rounding-residue drains – Agréger la poussière par token dans un puits (treasury/burn) afin que des ajustements proportionnels répétés n’éloignent pas les caches des soldes réels.
- Differential tests – Pour chaque transition d’état (add/remove/swap), recalculer le même invariant off-chain avec des mathes haute précision et assert l’égalité dans un epsilon serré même après des drains de liquidité complets.
Surveillance et réponse
- Multi-transaction detection – Suivre les séquences d’événements dépôt/retrait quasi-symétriques qui laissent le pool avec de faibles soldes mais un état cache élevé, suivi de
supply == 0. Les détecteurs d’anomalies mono-transaction manquent ces campagnes de poisoning. - Runtime simulations – Avant d’exécuter
add_liquidity(), recalculer les virtual balances depuis zéro et comparer avec les sommes mises en cache ; revert ou mettre en pause si les deltas dépassent un seuil en points de base. - Flash-loan aware alerts – Signaler les transactions combinant de gros flash loans, des retraits exhaustifs du pool, et un dépôt final de taille poussière ; bloquer ou exiger une approbation manuelle.
Références
Tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks

