Aktualisiert am 6. Nov. 2025: Balancer hat seinen offiziellen vorläufigen Bericht veröffentlicht [6], der die in unserer Analyse identifizierte Grundursache bestätigt.
Am 3. November 2025 waren Balancer V2s Composable Stable Pools zusammen mit mehreren abgeleiteten Projekten über mehrere Ketten hinweg einem koordinierten Exploit ausgesetzt, der zu Gesamtverlusten von über 125 Millionen US-Dollar führte. BlockSec gab frühzeitig eine Warnung [1] heraus und veröffentlichte anschließend eine erste Analyse [2].
Dies war ein hochintelligenter Angriff. Unsere Untersuchung ergab, dass die Grundursache eine Preismanipulation war, die aus Präzisionsverlusten bei der Berechnung der Invariante resultierte, was wiederum die Berechnung des BPT (Balancer Pool Token)-Preises verzerrte. Diese Invariantenmanipulation ermöglichte es dem Angreifer, durch einen einzigen Batch-Swap von einem bestimmten Stable Pool zu profitieren. Während einige Forscher aufschlussreiche Analysen geliefert haben, sind bestimmte Interpretationen irreführend, und die Grundursache sowie der Angriffsablauf sind noch nicht vollständig geklärt. Dieser Blogbeitrag zielt darauf ab, eine umfassende und genaue technische Analyse des Vorfalls zu präsentieren.
Wichtigste Erkenntnisse (TL;DR)
Grundursache: Rundungsinkonsistenz und Präzisionsverlust
- Die Hochskalierungsoperation verwendet unidirektionale Rundung (Abrundung), während die Herunterskalierungsoperation bidirektionale Rundung (Auf- und Abrundung) verwendet.
- Diese Inkonsistenz erzeugt Präzisionsverlust, der, wenn er durch einen sorgfältig konstruierten Swap-Pfad ausgenutzt wird, das Grundprinzip verletzt, dass Rundungen immer dem Protokoll zugutekommen sollten.
Ausführung des Exploits
- Der Angreifer konstruierte absichtlich Parameter, einschließlich der Anzahl der Iterationen und der Eingabewerte, um die Auswirkung des Präzisionsverlusts zu maximieren.
- Der Angreifer nutzte einen zweistufigen Ansatz, um einer Entdeckung zu entgehen: Zuerst wurde der Kern-Exploit innerhalb einer einzigen Transaktion ohne sofortigen Gewinn ausgeführt, anschließend wurden die Gewinne durch den Rückzug von Vermögenswerten in einer separaten Transaktion realisiert.
Betriebliche Auswirkungen und Verstärkung
- Das Protokoll konnte aufgrund bestimmter Einschränkungen [3] nicht angehalten werden. Diese Unfähigkeit, den Betrieb einzustellen, verschärfte die Auswirkungen des Exploits und ermöglichte zahlreiche nachfolgende oder Nachahmerangriffe.
In den folgenden Abschnitten geben wir zunächst wichtige Hintergrundinformationen zu Balancer V2 und anschließend eine detaillierte Analyse der identifizierten Probleme und des damit verbundenen Angriffs.
0x1 Hintergrund
Balancer V2s Composable Stable Pool
Die betroffene Komponente bei diesem Angriff war der Composable Stable Pool [4] des Balancer V2 Protokolls. Diese Pools sind für Vermögenswerte konzipiert, die voraussichtlich nahe 1:1 Parität beibehalten (oder zu einem bekannten Wechselkurs gehandelt werden) und ermöglichen große Swaps mit minimalem Preiseinfluss, wodurch die Kapitaleffizienz zwischen gleichartigen oder korrelierten Vermögenswerten erheblich verbessert wird. Jeder Pool verfügt über seinen eigenen Balancer Pool Token (BPT), der den Anteil des Liquiditätsanbieters am Pool repräsentiert, zusammen mit den entsprechenden zugrunde liegenden Vermögenswerten.
- Dieser Pool nutzt Stable Math (basierend auf Curve's StableSwap-Modell), wobei die Invariante D den virtuellen Gesamtwert des Pools darstellt.
- Der BPT-Preis kann approximiert werden als:
Aus der obigen Formel ist ersichtlich, dass, wenn D theoretisch verkleinert werden kann (auch ohne tatsächlichen Verlust von Geldern), der BPT-Preis günstiger erscheint.
batchSwap() und onSwap()
Balancer V2 stellt die batchSwap() Funktion bereit, die Multi-Hop-Swaps innerhalb des Vaults [5] ermöglicht. Es gibt zwei Swap-Typen, die durch einen an diese Funktion übergebenen Parameter bestimmt werden:
- GIVEN_IN ("Gegeben Ein"): Der Aufrufer gibt den genauen Betrag des Eingabe-Tokens an, und der Pool berechnet den entsprechenden Ausgabe-Betrag.
- GIVEN_OUT ("Gegeben Aus"): Der Aufrufer gibt den gewünschten Ausgabe-Betrag an, und der Pool berechnet den erforderlichen Eingabe-Betrag.
Typischerweise besteht ein batchSwap() aus mehreren Token-zu-Token-Swaps, die über die onSwap() Funktion ausgeführt werden. Das Folgende beschreibt den Ausführungspfad, wenn einem SwapRequest der Swap-Typ GIVEN_OUT zugewiesen wird (beachten Sie, dass ComposableStablePool von BaseGeneralPool erbt):
Das Folgende zeigt die Berechnung von amount_in für den GIVEN_OUT-Swap-Typ, die die Invariante D beinhaltet.
Skalierung und Rundung
Um die Berechnungen über verschiedene Token-Guthaben hinweg zu normalisieren, führt Balancer die folgenden zwei Operationen durch:
- Hochskalierung: Skalieren Sie Guthaben und Beträge auf eine einheitliche interne Präzision hoch, bevor Berechnungen durchgeführt werden.
- Herunterskalierung: Konvertieren Sie die Ergebnisse zurück in ihre native Präzision und wenden Sie eine gerichtete Rundung an (z. B. werden Eingabebeträge normalerweise aufgerundet, damit der Pool nicht zu wenig berechnet, während Ausgabebeträge oft abgerundet werden).
Der Grund für diese Inkonsistenz ist unklar. Laut dem Kommentar in der _upscale()-Funktion halten die Entwickler die Auswirkung der Rundung in einer einzigen Richtung für minimal.
// Rundung beim Hochskalieren würde nicht notwendigerweise immer in die gleiche Richtung gehen: bei einem Swap zum Beispiel sollte der Saldo des
// eingehenden Tokens aufgerundet und der ausgehende Token abgerundet werden. Dies ist der einzige Ort, an dem wir in der gleichen Richtung für alle Beträge runden, da die Auswirkungen dieser Rundung als minimal eingeschätzt werden (und kein Rundungsfehler auftritt, es sei denn,_scalingFactor()wird überschrieben).
0x2 Schwachstellenanalyse
Das zugrunde liegende Problem ergibt sich aus der Abrundungsoperation, die bei der Hochskalierung in der Funktion BaseGeneralPool._swapGivenOut() durchgeführt wird. Insbesondere rundet _swapGivenOut() den swapRequest.amount über die Funktion _upscale() fälschlicherweise ab. Der resultierende abgerundete Wert wird anschließend als amountOut bei der Berechnung von amountIn über _onSwapGivenOut() verwendet. Dieses Verhalten widerspricht der gängigen Praxis, dass die Rundung so erfolgen sollte, dass sie dem Protokoll zugutekommt.
Daher unterschätzt der berechnete amountIn für einen gegebenen Pool (wstETH/rETH/cbETH) die tatsächlich benötigte Eingabe. Dies ermöglicht es einem Benutzer, eine geringere Menge eines zugrunde liegenden Vermögenswerts (z. B. wstETH) gegen einen anderen (z. B. cbETH) einzutauschen, wodurch die Invariante D aufgrund der reduzierten effektiven Liquidität sinkt. Folglich wird der Preis des entsprechenden BPT (wstETH/rETH/cbETH) deflationiert, da BPT-Preis = D / totalSupply.
0x3 Angriffsanalyse
Der Angreifer führte einen zweistufigen Angriff durch, wahrscheinlich um das Entdeckungsrisiko zu minimieren:
- In der ersten Stufe wurde der Kern-Exploit innerhalb einer einzigen Transaktion durchgeführt, ohne sofortigen Gewinn zu erzielen.
- In der zweiten Stufe realisierte der Angreifer Gewinne durch den Rückzug von Vermögenswerten in einer separaten Transaktion.
Die erste Stufe kann weiter in zwei Phasen unterteilt werden: Parameterberechnung und Batch-Swap. Nachfolgend illustrieren wir diese Phasen anhand einer Beispiel-Angriffstransaktion (TX) auf Arbitrum (angriffstransaktion (TX) auf Arbitrum).
Die Phase der Parameterberechnung
In dieser Phase kombinierte der Angreifer Off-Chain-Berechnungen mit On-Chain-Simulationen, um die Parameter jedes Hops in der nächsten (Batch-Swap-) Phase präzise abzustimmen, basierend auf dem aktuellen Zustand des Composable Stable Pools (einschließlich Skalierungsfaktoren, Verstärkungskoeffizient, BPT-Rate, Swap-Gebühren und anderer Parameter). Interessanterweise setzte der Angreifer auch einen Hilfskontrakt zur Unterstützung dieser Berechnungen ein, was möglicherweise dazu diente, die Anfälligkeit für Front-Running zu reduzieren.
Zu Beginn sammelt der Angreifer grundlegende Informationen über den Zielpool, einschließlich der Skalierungsfaktoren jedes Tokens, des Verstärkungsparameters, der BPT-Rate und des Swap-Gebührenprozentsatzes. Anschließend berechnet er einen Schlüsselwert namens trickAmt, der den manipulierten Betrag des Zieltokens darstellt, der verwendet wird, um Präzisionsverluste zu verursachen.
Bezeichnet man den Skalierungsfaktor des Zieltokens mit sF, so lautet die Berechnung:
Um die Parameter für Schritt 2 der nächsten (Batch-Swap-) Phase zu bestimmen, führte der Angreifer nachfolgende Simulationsaufrufe an die Funktion 0x524c9e20 des Hilfskontrakts mit den folgenden calldata durch:
uint256[] balances; // Guthaben von Pool-Tokens (ohne BPT) uint256[] scalingFactors; // Skalierungsfaktoren für jedes Pool-Token uint tokenIn; // Index des Eingabe-Tokens für die Simulation dieses Hops uint tokenOut; // Index des Ausgabe-Tokens für die Simulation dieses Hops uint256 amountOut; // Gewünschter Ausgabe-Token-Betrag uint256 amp; // Verstärkungsparameter des Pools uint256 fee; // Pool-Swap-Gebührenprozentsatz
Und die Rückgabedaten sind:
uint256[] balances; // Pool-Token-Guthaben (ohne BPT) nach dem Swap
Speziell wurden das anfängliche Guthaben und die Anzahl der Schleifeniterationen Off-Chain berechnet und als Parameter an den Kontrakt des Angreifers übergeben (angegeben als 100.000.000.000 und 25). Jede Iteration führt drei Swaps durch:
- Swap 1: Pusht den Betrag des Zieltokens auf
trickAmt + 1, unter der Annahme, dass die Swap-Richtung 0 → 1 ist. - Swap 2: Tauscht weiterhin das Zieltoken mit
trickAmtaus, was zu einer Abrundung bei der_upscale()-Aufrufung führt. - Swap 3: Führt eine Rücktauschoperation (1 → 0) durch, bei der der zu tauschende Betrag aus dem aktuellen Token-Guthaben im Pool durch Abschneiden der beiden signifikantesten Dezimalstellen ermittelt wird, d. h. Abrundung auf das nächste Vielfache von $10^{d-2}$, wobei
ddie Anzahl der Dezimalstellen ist. Zum Beispiel 324.816 -> 320.000.- Beachten Sie, dass dieser Schritt aufgrund der Newton-Raphson-Methode bei der StableMath-Berechnung gelegentlich fehlschlagen kann. Um dies zu mildern, implementiert der Angreifer zwei Wiederholungsversuche, von denen jeder eine 9/10-Fallback des ursprünglichen Werts verwendet. Der Hilfskontrakt des Angreifers leitet sich von der StableMath-Bibliothek von Balancer V2 ab, wie der Einbezug der benutzerdefinierten Fehlermeldungen im "BAL"-Stil zeigt.
Die Batch-Swap-Phase
Dann kann die batchSwap()-Operation in drei Schritte unterteilt werden:
-
Schritt 1: Der Angreifer tauscht BPT (wstETH/rETH/cbETH) gegen zugrunde liegende Vermögenswerte, um das Guthaben eines Tokens (cbETH) präzise an die Grenze einer Rundungsgrenze anzupassen (Betrag = 9). Dies schafft die Voraussetzungen für Präzisionsverluste im nächsten Schritt.
-
Schritt 2: Der Angreifer tauscht dann zwischen einem anderen zugrunde liegenden Vermögenswert (wstETH) und cbETH mit einem konstruierten Betrag (= 8). Aufgrund der Abrundung bei der Skalierung von Token-Beträgen wird der berechnete
Δxetwas kleiner (8,918 auf 8), was zu einem unterschätztenΔyund somit zu einer geringeren Invariante (D aus dem Curve's StableSwap-Modell) führt. Da BPT-Preis = D / totalSupply ist, wird der BPT-Preis künstlich deflationiert.
- Schritt 3: Der Angreifer tauscht die zugrunde liegenden Vermögenswerte zurück in BPT und stellt so das Gleichgewicht wieder her, während er von dem deflationierten BPT-Preis profitiert.
0x4 Angriffe und Verluste
Wir haben die Angriffe und ihre entsprechenden Verluste in der folgenden Tabelle zusammengefasst, wobei die Gesamtverluste 125 Millionen US-Dollar übersteigen.
0x5 Schlussfolgerung
Dieser Vorfall beinhaltete eine Reihe von Angriffstransaktionen, die auf das Balancer V2 Protokoll und seine abgeleiteten Projekte abzielten und zu erheblichen finanziellen Verlusten führten. Nach dem ersten Angriff wurden zahlreiche nachfolgende und Nachahmer-Transaktionen über mehrere Ketten hinweg beobachtet. Dieses Ereignis unterstreicht mehrere kritische Lektionen für das Design und die Sicherheit von DeFi-Protokollen:
-
Rundungsverhalten und Präzisionsverlust: Die unidirektionale Rundung (Abrundung) bei der Hochskalierungsoperation unterscheidet sich von der bidirektionalen Rundung (Auf- und Abrundung) bei der Herunterskalierungsoperation. Um ähnliche Schwachstellen zu vermeiden, sollten Protokolle eine Arithmetik mit höherer Präzision verwenden und robuste Validierungsprüfungen implementieren. Es ist unerlässlich, das Grundprinzip aufrechtzuerhalten, dass Rundungen immer dem Protokoll zugutekommen sollten.
-
Evolution der Ausnutzung: Der Angreifer führte einen ausgeklügelten zweistufigen Exploit durch, der darauf ausgelegt war, einer Entdeckung zu entgehen. In der ersten Phase führte der Angreifer den Kern-Exploit innerhalb einer einzigen Transaktion ohne sofortigen Gewinn aus. In der zweiten Phase realisierte der Angreifer Gewinne, indem er Vermögenswerte in einer separaten Transaktion zurückzog. Dieser Vorfall unterstreicht erneut den andauernden Wettrüstung zwischen Sicherheitsexperten und Angreifern.
-
Operative Wachsamkeit und Bedrohungsreaktion: Dieser Vorfall unterstreicht die Bedeutung von rechtzeitigen Warnungen bezüglich Initialisierung und Betriebsstatus sowie proaktiver Bedrohungserkennung und -präventionsmechanismen zur Minimierung potenzieller Verluste durch laufende oder Nachahmer-Angriffe.
Während die Aufrechterhaltung des Betriebs und der Geschäftskontinuität können Branchenteilnehmer BlockSec Phalcon als letzte Verteidigungslinie nutzen, um ihre Vermögenswerte zu schützen. Das Expertenteam von BlockSec steht bereit, eine umfassende Sicherheitsbewertung für Ihr Projekt durchzuführen.
Referenz
[1] https://x.com/Phalcon_xyz/status/1985262010347696312
[2] https://x.com/Phalcon_xyz/status/1985302779263643915
[3] https://x.com/Balancer/status/1985390307245244573
[4] https://docs-v2.balancer.fi/concepts/pools/composable-stable.html
[5] https://docs-v2.balancer.fi/reference/swaps/batch-swaps.html



