Σφάλματα λογιστικής σε DeFi AMM & Εκμετάλλευση Virtual Balance Cache
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Επισκόπηση
Η πισίνα yETH του Yearn Finance (Νοέμ 2025) αποκάλυψε πώς οι gas-saving caches μέσα σε σύνθετα AMMs μπορούν να μετατραπούν σε όπλο όταν δεν εξομαλύνονται κατά τις μεταβάσεις οριακών καταστάσεων. Η weighted stableswap pool παρακολουθεί έως και 32 liquid staking derivatives (LSDs), τα μετατρέπει σε ETH-ισοδύναμα virtual balances (vb_i = balance_i × rate_i / PRECISION), και αποθηκεύει αυτές τις τιμές σε έναν packed storage πίνακα packed_vbs[]. Όταν όλα τα LP tokens καούν, το totalSupply μειώνεται σωστά σε μηδέν αλλά οι cached packed_vbs[i] θέσεις διατήρησαν τεράστιες ιστορικές τιμές. Ο επόμενος καταθέτης αντιμετωπίστηκε ως ο «πρώτος» liquidity provider παρόλο που η cache ακόμα κρατούσε φανταστική ρευστότητα, επιτρέποντας σε έναν επιτιθέμενο να mintάρει ~235 septillion yETH με μόλις 16 wei πριν αποστραγγίσει ≈USD 9M σε LSD collateral.
Κύρια συστατικά:
- Derived-state caching: αποφεύγονται ακριβές oracle lookups με την επίμονη αποθήκευση virtual balances και την βαθμιαία ενημέρωσή τους.
- Missing reset when
supply == 0: τα proportional decrements στοremove_liquidity()άφησαν μη μηδενικά υπολείμματα σταpacked_vbs[]μετά από κάθε κύκλο ανάληψης. - Initialization branch trusts the cache: το
add_liquidity()καλεί_calc_vb_prod_sum()και απλώς διαβάζει ταpacked_vbs[]ότανprev_supply == 0, υποθέτοντας ότι η cache έχει επίσης μηδενιστεί. - Flash-loan financed state poisoning: loops κατάθεσης/ανάληψης μεγέθυναν τα στρογγυλοποιητικά υπολείμματα χωρίς lockup κεφαλαίου, επιτρέποντας μια καταστροφική over-mint διαδρομή στο “first deposit” path.
Σχεδίαση cache & έλλειψη χειρισμού οριακών καταστάσεων
Η ευάλωτη ροή απλοποιείται παρακάτω:
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
}
}
Because remove_liquidity() only applied proportional decrements, every loop left fixed-point rounding dust. After ≳10 deposit/withdraw cycles those residues accumulated into extremely large phantom virtual balances while the on-chain token balances were almost empty. Burning the final LP shares set totalSupply to zero yet caches stayed populated, priming the protocol for a malformed initialization.
Exploit playbook (yETH case study)
- Flash-loan working capital – Δανειστείτε wstETH, rETH, cbETH, ETHx, WETH, κ.λπ. από Balancer/Aave για να αποφύγετε το δεσμευμένο κεφάλαιο ενώ χειρίζεστε την pool.
- Poison
packed_vbs[]– Εκτελέστε επαναλαμβανόμενους κύκλους καταθέσεων και αναλήψεων σε οκτώ LSD assets. Κάθε μερική ανάληψη περικόπτειpacked_vbs[i] − vb_share, αφήνοντας >0 υπολείμματα ανά token. Η επανάληψη του βρόχου διογκώνει φανταστικά υπόλοιπα ισοδύναμου ETH χωρίς να προκαλεί υποψία επειδή τα πραγματικά υπόλοιπα περίπου συμψηφίζονται. - Force
supply == 0– Κάντε burn κάθε εναπομείναν LP token ώστε η pool να πιστέψει ότι είναι άδεια. Σφάλμα στην υλοποίηση αφήνει το μολυσμένοpacked_vbs[]ανεπηρέαστο. - Dust-size “first deposit” – Στείλτε συνολικά 16 wei κατανεμημένα στα υποστηριζόμενα LSD slots. Το
add_liquidity()βλέπειprev_supply == 0, τρέχει_calc_vb_prod_sum()και διαβάζει την ξεπερασμένη cache αντί να αναπροσαρμόσει από τα πραγματικά υπόλοιπα. Επομένως ο υπολογισμός του mint συμπεριφέρεται σαν να εισήλθαν τρισεκατομμύρια USD, εκπέμποντας ~2.35×10^26 yETH. - Drain & repay – Εξαγοράστε τη φουσκωμένη θέση LP για όλα τα vaulted LSDs, swap yETH→WETH στο Balancer, μετατρέψτε σε ETH μέσω Uniswap v3, αποπληρώστε flash loans/fees, και ξεπλύνετε το κέρδος (π.χ. μέσω Tornado Cash). Καθαρό κέρδος ≈USD 9M ενώ μόνο 16 wei ιδίων κεφαλαίων άγγιξαν ποτέ την pool.
Generalized exploitation conditions
You can abuse similar AMMs when all of the following hold:
- Cached derivatives of balances (virtual balances, TWAP snapshots, invariant helpers) διατηρούνται μεταξύ συναλλαγών για εξοικονόμηση gas.
- Partial updates truncate αποτελέσματα (floor division, fixed-point rounding), επιτρέποντας σε επιτιθέμενο να σωρεύσει υπολείμματα κατάστασης μέσω συμμετρικών κύκλων κατάθεσης/ανάληψης.
- Boundary conditions reuse caches αντί να επαναυπολογίζουν από την ground-truth, ειδικά όταν
totalSupply == 0,totalLiquidity == 0, ή όταν η σύνθεση της pool μηδενίζεται. - Minting logic lacks ratio sanity checks (π.χ. απουσία ορίων
expected_value/actual_value) οπότε μια dust κατάθεση μπορεί να mint-άρει ουσιαστικά ολόκληρη την ιστορική προσφορά. - Cheap capital is available (flash loans ή internal credit) για την εκτέλεση δεκάδων λειτουργιών που τροποποιούν κατάσταση μέσα σε μία συναλλαγή ή σε ένα αυστηρά συγχρονισμένο bundle.
Defensive engineering checklist
- Explicit resets when supply/lpShares hit zero:
if (totalSupply == 0) {
for (uint256 i; i < tokens.length; ++i) packed_vbs[i] = 0;
}
Εφαρμόστε την ίδια μεταχείριση σε κάθε cached accumulator που προκύπτει από υπόλοιπα ή δεδομένα oracle.
- Recompute on initialization branches – Όταν
prev_supply == 0, αγνοήστε εντελώς τις caches και ανακατασκευάστε τα virtual balances από τα πραγματικά υπόλοιπα token + live oracle rates. - Minting sanity bounds – Κάνετε revert αν
lpToMint > depositValue × MAX_INIT_RATIOή αν μια μεμονωμένη συναλλαγή κάνει mint >X% της ιστορικής προσφοράς ενώ οι συνολικές καταθέσεις είναι κάτω από ένα ελάχιστο όριο. - Rounding-residue drains – Συγκεντρώστε το dust ανά token σε έναν sink (treasury/burn) ώστε οι επαναλαμβανόμενες αναλογικές προσαρμογές να μην εκτρέπουν τις caches από τα πραγματικά υπόλοιπα.
- Differential tests – Για κάθε μετάβαση κατάστασης (add/remove/swap), επαναυπολογίστε το ίδιο invariant off-chain με αριθμητική υψηλής ακρίβειας και επιβεβαιώστε ισότητα εντός στενού epsilon ακόμα και μετά από πλήρη αποστράγγιση ρευστότητας.
Monitoring & response
- Multi-transaction detection – Παρακολουθείστε ακολουθίες σχεδόν συμμετρικών γεγονότων deposit/withdraw που αφήνουν την pool με χαμηλά υπόλοιπα αλλά υψηλή cached κατάσταση, ακολουθούμενες από
supply == 0. Οι ανιχνευτές ανωμαλιών μιας μίας συναλλαγής χάνουν αυτές τις εκστρατείες μόλυνσης. - Runtime simulations – Πριν εκτελέσετε το
add_liquidity(), επαναυπολογίστε τα virtual balances από την αρχή και συγκρίνετέ τα με τα cached sums· κάντε revert ή παύση αν οι αποκλίσεις υπερβαίνουν ένα όριο basis-point. - Flash-loan aware alerts – Σημάνετε συναλλαγές που συνδυάζουν μεγάλα flash loans, εξαντλητικές αναλήψεις της pool, και ένα dust-sized τελικό deposit· μπλοκάρετε ή απαιτήστε χειροκίνητη έγκριση.
References
Tip
Μάθετε & εξασκηθείτε στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
HackTricks

