DeFi AMM računovodstvene greške i eksploatacija keša virtuelnih balansa
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
Pregled
Yearn Finance-ov yETH pool (Nov 2025) otkrio je kako keševi koji štede gas unutar složenih AMM-ova mogu biti iskorišćeni kada se ne usklade tokom prelaza granica stanja. Weighted stableswap pool prati do 32 liquid staking derivata (LSDs), konvertuje ih u ETH-ekvivalentne virtuelne bilanse (vb_i = balance_i × rate_i / PRECISION), i čuva te vrednosti u pakovanom storage nizu packed_vbs[]. Kada su svi LP tokeni spaljeni, totalSupply ispravno pada na nulu, ali keširani slotovi packed_vbs[i] su zadržali ogromne istorijske vrednosti. Sledeći deponent je tretiran kao “prvi” liquidity provider iako je keš i dalje držao fantomsku likvidnost, što je napadaču omogućilo da mintuje ~235 septiliona yETH za samo 16 wei pre nego što isprazni ≈USD 9M u LSD kolateralu.
Ključni elementi:
- Keširanje izvedenog stanja: skupi zahtevi ka oracle-ima se izbegavaju tako što se virtuelni bilansi upisuju i inkrementalno ažuriraju.
- Nedostaje reset kada
supply == 0:remove_liquidity()proporcionalna smanjenja su ostavljala nenulte ostatke upacked_vbs[]nakon svakog ciklusa povlačenja. - Inicijalizaciona grana veruje kešu:
add_liquidity()poziva_calc_vb_prod_sum()i jednostavno čitapacked_vbs[]kada jeprev_supply == 0, pretpostavljajući da je keš takođe nula. - Trovanje stanja finansirano flash-lonom: petlje depozit/povlačenje su pojačavale ostatke zaokruživanja bez zaključavanja kapitala, omogućavajući katastrofalno prekomerno mintovanje u putanji “first deposit”.
Dizajn keša i nedostatak rukovanja granicama
Ranjiv tok je pojednostavljen u nastavku:
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
}
}
Pošto remove_liquidity() primenjivao samo proporcionalna umanjenja, svaka iteracija je ostavljala ostatke zaokruživanja u fiksnoj tački. Posle ≳10 ciklusa depozita/isplate ti ostaci su se akumulirali u ekstremno velike fantomske virtualne bilanse dok su on-chain token bilansi bili skoro prazni. Spaljivanjem poslednjih LP tokena totalSupply je postao nula, ali su keševi ostali popunjeni, pripremajući protokol za neispravnu inicijalizaciju.
Plan eksploatacije (studija slučaja yETH)
- Flash-loan working capital – Pozajmite wstETH, rETH, cbETH, ETHx, WETH, itd. sa Balancer/Aave da ne vezujete kapital dok manipulišete pool-om.
- Poison
packed_vbs[]– Ponavljajte depozite i isplate preko osam LSD asset-a. Svako parcijalno povlačenje flooruje/trimujepacked_vbs[i] − vb_share, ostavljajući >0 ostataka po tokenu. Ponavljanje petlje napuhuje fantomske ETH-ekvivalent bilanse bez podizanja sumnje jer se realni bilansi otprilike poništavaju. - Force
supply == 0– Spalite sve preostale LP tokene tako da pool misli da je prazan. Greška u implementaciji ostavlja zatrovanipacked_vbs[]netaknutim. - Dust-size “first deposit” – Pošaljite ukupno 16 wei podeljenih preko podržanih LSD slotova.
add_liquidity()vidiprev_supply == 0, pokreće_calc_vb_prod_sum(), i čita zastareli keš umesto da prerčuna iz stvarnih bilansa. Računica za minting zbog toga deluje kao da su ušli bilioni USD, emitujući ~2.35×10^26 yETH. - Drain & repay – Iskoristite napuhanu LP poziciju za sve vaulted LSD, swapujte yETH→WETH na Balanceru, konvertujte u ETH preko Uniswap v3, vratite flash loanove/takse, i operite profit (npr. kroz Tornado Cash). Neto profit ≈USD 9M dok su samo 16 wei sopstvenih sredstava ikada dotakli pool.
Generalizovani uslovi za eksploataciju
Možete zloupotrebiti slične AMM-ove kada su ispunjeni svi sledeći uslovi:
- Keširani izvodi balansa (virtual balances, TWAP snapshots, invariant helpers) opstaju između transakcija radi uštede gasa.
- Delimična ažuriranja skraćuju rezultate (floor division, fixed-point rounding), omogućavajući napadaču da akumulira stanje-vezane ostatke putem simetričnih ciklusa depozit/isplata.
- Granični uslovi ponovo koriste keševe umesto ponovnog izračunavanja iz stvarnih podataka, posebno kada
totalSupply == 0,totalLiquidity == 0, ili se kompozicija pool-a resetuje. - Minting logika nema provere razumnosti odnosa (npr. odsustvo granica
expected_value/actual_value), pa dust depozit može da mintuje praktično celu istorijsku ponudu. - Jeftin kapital je dostupan (flash loans ili interni kredit) za izvođenje desetina operacija menjanja stanja u jednoj transakciji ili u tesno orkestriranom bundlu.
Kontrolna lista za odbrambeni inženjering
- Explicit resets when supply/lpShares hit zero:
if (totalSupply == 0) {
for (uint256 i; i < tokens.length; ++i) packed_vbs[i] = 0;
}
Primijenite isto postupanje za svaki keširani akumulator izveden iz bilansa ili oracle podataka.
- Recompute on initialization branches – Kada je
prev_supply == 0, potpuno ignorišite keševe i obnovite virtual balances iz stvarnih token bilansa i aktuelnih oracle stopa. - Minting sanity bounds – Reverujte ako
lpToMint > depositValue × MAX_INIT_RATIOili ako jedna transakcija mintuje >X% istorijske ponude dok su ukupni depoziti ispod minimalnog praga. - Rounding-residue drains – Agregirajte per-token dust u sink (treasury/burn) kako ponovljena proporcionalna podešavanja ne bi odmakla keševe od stvarnih bilansa.
- Differential tests – Za svaku tranziciju stanja (add/remove/swap), ponovo izračunajte isti invariant off-chain koristeći visokopreciznu matematiku i potvrdite jednakost unutar uskog epsilona čak i nakon potpunog pražnjenja likvidnosti.
Nadzor & odgovor
- Multi-transaction detection – Pratite sekvence skoro-simetričnih deposit/withdraw događaja koji ostavljaju pool sa niskim balansima ali visokim keširanim stanjem, praćene
supply == 0. Detektori anomalija koji gledaju samo pojedinačne transakcije propuštaju ove kampanje zatrovanja. - Runtime simulations – Pre izvođenja
add_liquidity(), ponovo izračunajte virtual balances od nule i uporedite sa keširanim sumama; revert-ujte ili pauzirajte ako delte prelaze prag u basis-point-ima. - Flash-loan aware alerts – Označavajte transakcije koje kombinuju velike flash loans, iscrpna povlačenja iz pool-a i dust-veličine finalni depozit; blokirajte ili zahtevajte ručnu odobrenje.
References
Tip
Učite i vežbajte AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Učite i vežbajte Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
HackTricks

