<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Bearby Blog - exploit</title>
    <subtitle>The official blog of Bearby — a non-custodial, quantum-resistant, multi-chain wallet.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://blog.bearby.io/tags/exploit/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://blog.bearby.io/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-07-23T00:00:00+00:00</updated>
    <id>https://blog.bearby.io/tags/exploit/atom.xml</id>
    <entry xml:lang="en">
        <title>Anatomy of the Balancer V2 ComposableStablePool Hack (Nov 2025)</title>
        <published>2026-07-23T00:00:00+00:00</published>
        <updated>2026-07-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Bearby
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://blog.bearby.io/blog/balancer-v2-stable-pool-hack-2025/"/>
        <id>https://blog.bearby.io/blog/balancer-v2-stable-pool-hack-2025/</id>
        
        <content type="html" xml:base="https://blog.bearby.io/blog/balancer-v2-stable-pool-hack-2025/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;&#x2F;strong&gt; — On &lt;strong&gt;2025-11-03 07:46:47 UTC&lt;&#x2F;strong&gt;, a single contract-deployment transaction drained two Balancer V2 ComposableStablePools of &lt;strong&gt;≈19,239 ETH&lt;&#x2F;strong&gt; (${\approx $71\text{M}}$ at the Chainlink spot of $$3{,}709.52$). The constructor fired a &lt;code&gt;batchSwap&lt;&#x2F;code&gt; of &lt;strong&gt;226 swaps&lt;&#x2F;strong&gt; that resolved into three phases: a geometric BPT-exit cascade, a WETH↔osETH ping-pong that drives a token balance into the sub-100-wei regime, and a geometric BPT-mint cascade that re-mints the BPT for &lt;strong&gt;~2.5% of fair value&lt;&#x2F;strong&gt;. The root cause is a rounding inconsistency between the rate-based balance scaling (&lt;code&gt;mulDown&lt;&#x2F;code&gt;, floor division) and the invariant $D$ recomputation at high amplification. Every number below is read directly from mainnet with &lt;code&gt;cast&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;cover.png&quot; alt=&quot;Cover&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-evidence-verified-on-chain&quot;&gt;The evidence, verified on-chain&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Theft tx&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;tx&#x2F;0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742&quot;&gt;&lt;code&gt;0x6ed07db1…23bc9742&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — block &lt;strong&gt;23,717,397&lt;&#x2F;strong&gt;, &lt;code&gt;07:46:47 UTC&lt;&#x2F;code&gt;, status ✅&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Extraction tx&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;tx&#x2F;0xd155207261712c35fa3d472ed1e51bfcd816e616dd4f517fa5959836f5b48569&quot;&gt;&lt;code&gt;0xd1552072…5b48569&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — block &lt;strong&gt;23,717,404&lt;&#x2F;strong&gt;, &lt;code&gt;07:48:11 UTC&lt;&#x2F;code&gt; (84 s later)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Exploit contract&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d&quot;&gt;&lt;code&gt;0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Deployer (EOA)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0x506D1f9EFe24f0d47853aDca907EB8d89AE03207&quot;&gt;&lt;code&gt;0x506D1f9EFe24f0d47853aDca907EB8d89AE03207&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Recipient (EOA)&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0xAa760D53541d8390074c61DEFeaba314675b8e3f&quot;&gt;&lt;code&gt;0xAa760D53541d8390074c61DEFeaba314675b8e3f&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Pool 1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0xdacf5fa19b1f720111609043ac67a9818262850c&quot;&gt;&lt;code&gt;0xdacf5fa1…2850c&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — &lt;em&gt;osETH&#x2F;wETH&lt;&#x2F;em&gt; StablePool, $A = 200{,}000$&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Pool 2&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd&quot;&gt;&lt;code&gt;0x93d19926…5f0bd&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; — &lt;em&gt;wstETH&#x2F;WETH&lt;&#x2F;em&gt; StablePool, $A = 5{,}000{,}000$&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Swap events in tx1&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;226 (121 on pool 1, 105 on pool 2)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Fee transfers&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;65 (to the Protocol Fees Collector)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Net stolen&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;6,587 WETH + 6,851 osETH + 4,260 wstETH (+ 64.6 BPT scraps)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;timeline.svg&quot; alt=&quot;Attack timeline: two transactions, 84 seconds apart&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Reproduce the headline figures yourself:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;https:&#x2F;&#x2F;ethereum.publicnode.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# both transactions succeeded&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; receipt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; grep&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; status&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;      # -&amp;gt; 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# the two pools&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; call&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0xdacf5fa19b1f720111609043ac67a9818262850c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; &amp;quot;name()(string)&amp;quot; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# -&amp;gt; &amp;quot;Balancer osETH&#x2F;wETH StablePool&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# ETH spot price at the attack block (Chainlink ETH&#x2F;USD, 8 decimals)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; call&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;latestRoundData()(uint80,int256,uint256,uint256,uint80)&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --block&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 23717397&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;   # answer 370951870300 -&amp;gt; $3,709.52&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;background-the-vault-and-composable-pools&quot;&gt;Background: the Vault and &quot;composable&quot; pools&lt;&#x2F;h2&gt;
&lt;p&gt;Balancer V2 centralizes all token custody in a single &lt;strong&gt;Vault&lt;&#x2F;strong&gt; (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;etherscan.io&#x2F;address&#x2F;0xBA12222222228d8Ba445958a75a0704d566BF2C8&quot;&gt;&lt;code&gt;0xBA12…BF2C8&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;). Pools are stateless pricing modules; the Vault moves the bytes. Two design choices matter here.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Internal balance.&lt;&#x2F;strong&gt; The Vault credits each account a per-token internal balance. A &lt;code&gt;batchSwap&lt;&#x2F;code&gt; settles &lt;em&gt;net&lt;&#x2F;em&gt; deltas against internal balance at the end — so a contract can trade in circles and walk away with the net surplus, never touching ERC-20 &lt;code&gt;transfer&lt;&#x2F;code&gt; until it withdraws.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Composable pools.&lt;&#x2F;strong&gt; A ComposableStablePool lists &lt;em&gt;its own BPT&lt;&#x2F;em&gt; as one of the pool&#x27;s tokens. Joining&#x2F;leaving is expressed as ordinary swaps against BPT, and the BPT &quot;price&quot; is derived from the invariant $D$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\text{BPT rate} ;=; \frac{D}{\text{totalSupply}}$$&lt;&#x2F;p&gt;
&lt;p&gt;Both targeted pools are 3-token composable stables whose non-BPT tokens are &lt;strong&gt;liquid-staking derivatives (LSDs)&lt;&#x2F;strong&gt; carrying an exchange rate $&amp;gt; 1$:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Pool&lt;&#x2F;th&gt;&lt;th&gt;Tokens&lt;&#x2F;th&gt;&lt;th&gt;Rate-bearing token&lt;&#x2F;th&gt;&lt;th&gt;Scaling factor (on-chain)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;osETH&#x2F;wETH&lt;&#x2F;td&gt;&lt;td&gt;WETH, BPT, osETH&lt;&#x2F;td&gt;&lt;td&gt;osETH&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;1.073369637857500505e18&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;wstETH&#x2F;WETH&lt;&#x2F;td&gt;&lt;td&gt;wstETH, BPT, WETH&lt;&#x2F;td&gt;&lt;td&gt;wstETH&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;1.239655095734259789e18&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;topology.svg&quot; alt=&quot;Actor topology and fund flow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-vulnerability-muldown-eats-precision&quot;&gt;The vulnerability: &lt;code&gt;mulDown&lt;&#x2F;code&gt; eats precision&lt;&#x2F;h2&gt;
&lt;p&gt;Before the invariant runs, balances are &lt;em&gt;upscaled&lt;&#x2F;em&gt; by their scaling factor so rate-bearing tokens enter the math at true ETH parity:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;solidity&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; Balancer FixedPoint — rounds DOWN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;function&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; mulDown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;uint256&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; uint256&lt;&#x2F;span&gt;&lt;span style=&quot;color: #EBA0AC;font-style: italic;&quot;&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; internal pure returns&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;uint256&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;a &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;*&lt;&#x2F;span&gt;&lt;span&gt; b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;span&gt; ONE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;          &#x2F;&#x2F; ONE = 1e18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; upscale path (simplified)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;balances&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; mulDown&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;balances&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;],&lt;&#x2F;span&gt;&lt;span&gt; scalingFactors&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For an 18-decimal token with no rate, &lt;code&gt;scalingFactor = 1e18&lt;&#x2F;code&gt; and &lt;code&gt;mulDown(b, 1e18) = b&lt;&#x2F;code&gt; — &lt;strong&gt;exact, zero loss&lt;&#x2F;strong&gt;. But osETH&#x27;s factor is &lt;code&gt;1.07337e18&lt;&#x2F;code&gt;. There, floor division discards a fraction of a wei:&lt;&#x2F;p&gt;
&lt;p&gt;$$\tilde b ;=; \text{mulDown}(b,, f) ;=; \left\lfloor \frac{b \cdot f}{10^{18}} \right\rfloor, \qquad f_{\text{osETH}} = 1.073369637857500505 \cdot 10^{18}$$&lt;&#x2F;p&gt;
&lt;p&gt;For a &lt;strong&gt;large&lt;&#x2F;strong&gt; balance this is rounding noise. For a balance of a &lt;strong&gt;few wei&lt;&#x2F;strong&gt; it is a 6–7% relative distortion:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th style=&quot;text-align: right&quot;&gt;$b$ (wei)&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;$\lfloor b f &#x2F;10^{18}\rfloor$&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;true $bf&#x2F;10^{18}$&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;rel. loss&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.0734&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;7.34%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;8&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;8.5870&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;6.83%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;9&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;9.6603&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;6.83%&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;17&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;18.2473&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1.36%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td style=&quot;text-align: right&quot;&gt;69&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;74&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;74.0625&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;0.08%&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;precision-loss.svg&quot; alt=&quot;Scaling-factor rounding: mulDown precision loss at small balances&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A 7% distortion on 8 wei is still 8 wei — negligible against a multi-million-ETH $D$ &lt;em&gt;if the invariant were well-conditioned&lt;&#x2F;em&gt;. It is not.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-the-distortion-becomes-catastrophic-amplification&quot;&gt;Why the distortion becomes catastrophic: amplification&lt;&#x2F;h2&gt;
&lt;p&gt;ComposableStablePools price with the Curve-style invariant. With amplification $\mathcal{A}$ and $n$ tokens, $D$ satisfies:&lt;&#x2F;p&gt;
&lt;p&gt;$$\mathcal{A} n^{n} \sum_{i} x_i ;+; D ;=; \mathcal{A} n^{n} D ;+; \frac{D^{,n+1}}{n^{n}\prod_{i} x_i}$$&lt;&#x2F;p&gt;
&lt;p&gt;solved by Newton iteration. Two facts conspire:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;High $\mathcal{A}$ flattens the curve.&lt;&#x2F;strong&gt; As $\mathcal{A} \to \infty$, the surface approaches the hyperplane $D \approx \sum x_i$ and the iteration becomes increasingly sensitive to the smallest balances.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;A near-zero balance is a singularity.&lt;&#x2F;strong&gt; The term $D^{,n+1}&#x2F;\prod x_i$ blows up as any $x_i \to 0$. With one balance pinned at a handful of wei &lt;em&gt;and&lt;&#x2F;em&gt; a 7% upscale rounding error layered on top, the iteration converges to a $D$ that &lt;strong&gt;systematically understates the true pool value&lt;&#x2F;strong&gt; — and therefore understates the BPT rate $D&#x2F;\text{supply}$.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The pool recomputes $D$ fresh on every swap. So the attack is not &quot;steal 7% of 8 wei&quot; — it is &lt;strong&gt;&quot;drive a balance into the ill-conditioned regime, then trade against a BPT price computed from a broken $D$.&quot;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;forensic-reconstruction-the-226-swaps&quot;&gt;Forensic reconstruction: the 226 swaps&lt;&#x2F;h2&gt;
&lt;p&gt;Decoding the &lt;code&gt;Swap&lt;&#x2F;code&gt; events of tx1 reveals the constructor did &lt;strong&gt;not&lt;&#x2F;strong&gt; run &quot;65 identical cycles.&quot; It ran a precise three-phase program per pool. Pool 1 (121 swaps):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;attack-phases.svg&quot; alt=&quot;The three-phase batchSwap structure&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 1 — BPT-exit cascade (swaps 0–21).&lt;&#x2F;strong&gt; Twenty-two swaps, alternating &lt;code&gt;BPT→WETH&lt;&#x2F;code&gt; and &lt;code&gt;BPT→osETH&lt;&#x2F;code&gt;, each &lt;strong&gt;÷100&lt;&#x2F;strong&gt; of the previous in its series:&lt;&#x2F;p&gt;
&lt;p&gt;$$5{,}258.03 \xrightarrow{\div100} 52.58 \xrightarrow{\div100} 0.526 \xrightarrow{\div100} \cdots$$&lt;&#x2F;p&gt;
&lt;p&gt;A geometric series with ratio $r = 0.01$. Sum burned:&lt;&#x2F;p&gt;
&lt;p&gt;$$\sum_{k=0}^{10} a,r^{k} = \frac{a}{1-r},\qquad a_{\text{WETH}} = 5258.03,;; a_{\text{osETH}} = 6472.01$$&lt;&#x2F;p&gt;
&lt;p&gt;$$\Rightarrow \text{BPT burned} \approx \frac{5258.03 + 6472.01}{0.99} \approx 11{,}848 \text{ BPT}$$&lt;&#x2F;p&gt;
&lt;p&gt;In return the contract receives &lt;strong&gt;4,922 WETH + 6,852 osETH&lt;&#x2F;strong&gt; — a near-fair exit at the &lt;em&gt;pre-distortion&lt;&#x2F;em&gt; BPT rate (≈ 1 ETH&#x2F;BPT). This is the &quot;loan&quot;: real value pulled out of the pool.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 2 — rounding-trigger ping-pong (swaps 22–111).&lt;&#x2F;strong&gt; Thirty repetitions of a 3-swap cycle: &lt;code&gt;WETH→osETH&lt;&#x2F;code&gt; (large), &lt;code&gt;WETH→osETH&lt;&#x2F;code&gt; (&lt;strong&gt;≈ 17–69 wei&lt;&#x2F;strong&gt;), &lt;code&gt;osETH→WETH&lt;&#x2F;code&gt;. The micro-swap is the scalpel — 31 of them land at exactly &lt;strong&gt;17 wei&lt;&#x2F;strong&gt; of osETH output, nudging the pool&#x27;s osETH balance into the sub-100-wei zone where the upscale floor and the singularity collude.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Phase 3 — BPT-mint cascade (swaps 112–120).&lt;&#x2F;strong&gt; Nine swaps, &lt;code&gt;{WETH,osETH}→BPT&lt;&#x2F;code&gt;, outputs being exact powers of ten (&lt;code&gt;1e4, 1e7, …, 1e22&lt;&#x2F;code&gt; wei of BPT), each step &lt;strong&gt;×1000&lt;&#x2F;strong&gt;. BPT is now priced from the broken $D$:&lt;&#x2F;p&gt;
&lt;p&gt;$$\frac{\text{underlying paid}}{\text{BPT minted}} ;\xrightarrow{\text{phase 3}}; \approx 0.025;\text{ETH&#x2F;BPT} \quad\text{vs.}\quad \underbrace{\approx 1.04;\text{ETH&#x2F;BPT}}_{\text{phase 1 rate}} ;; (\approx 41\times \text{ collapse})$$&lt;&#x2F;p&gt;
&lt;p&gt;The contract re-mints &lt;strong&gt;11,892 BPT for only 299 WETH + 0.46 osETH&lt;&#x2F;strong&gt; — roughly &lt;strong&gt;2.5% of the Phase-1 rate&lt;&#x2F;strong&gt;. The geometric exit of Phase 1 is &quot;repaid&quot; with a near-free geometric mint of Phase 3.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Net.&lt;&#x2F;strong&gt; BPT is roughly flat ($-44$ BPT), but the underlying from Phase 1 is never returned:&lt;&#x2F;p&gt;
&lt;p&gt;$$\underbrace{4{,}922\text{ WETH} + 6{,}852\text{ osETH}}&lt;em&gt;{\text{phase 1 (taken)}} ;-; \underbrace{299\text{ WETH} + 0.5\text{ osETH}}&lt;&#x2F;em&gt;{\text{phase 3 (paid)}} ;=; 4{,}623\text{ WETH} + 6{,}851\text{ osETH}$$&lt;&#x2F;p&gt;
&lt;p&gt;Pool 2 (wstETH&#x2F;WETH, 105 swaps) repeats the same program, yielding &lt;strong&gt;1,964 WETH + 4,260 wstETH&lt;&#x2F;strong&gt;. The accounting closes exactly against the &lt;code&gt;InternalBalanceChanged&lt;&#x2F;code&gt; events.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-exploit-contract&quot;&gt;The exploit contract&lt;&#x2F;h2&gt;
&lt;p&gt;The deployed bytecode decompiled (via &lt;code&gt;heimdall-rs&lt;&#x2F;code&gt;) to a contract with &lt;strong&gt;two entry points&lt;&#x2F;strong&gt; and a constructor that does all the stealing:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Constructor&lt;&#x2F;strong&gt; — builds the &lt;code&gt;batchSwap&lt;&#x2F;code&gt; calldata in memory (the 226 steps are &lt;em&gt;computed&lt;&#x2F;em&gt;, not stored literally) and calls &lt;code&gt;vault.batchSwap(...)&lt;&#x2F;code&gt; against both pools, crediting the surplus to its own internal balance.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;0x8a4f75d6&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; — the withdrawal. Gated by &lt;code&gt;require(msg.sender == _owner)&lt;&#x2F;code&gt;, it iterates the target pools, reads &lt;code&gt;getInternalBalance&lt;&#x2F;code&gt;, and pushes everything to the recipient via &lt;code&gt;manageUserBalance&lt;&#x2F;code&gt;:&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;solidity&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;&#x2F;&#x2F; decompiled &amp;amp; cleaned — selector 0x8a4f75d6(address[] pools)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;require&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;msg.sender&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; _owner&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;pool in pools&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;    (&lt;&#x2F;span&gt;&lt;span&gt;tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; vault&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getPoolTokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;pool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getPoolId&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;());&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    bals &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span&gt; vault&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;getInternalBalance&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt;address&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;),&lt;&#x2F;span&gt;&lt;span&gt; tokens&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ops &lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;UserBalanceOp&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;WITHDRAW&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tok&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; bal&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F38BA8;&quot;&gt; this&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; RECIPIENT&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #CBA6F7;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; ...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    vault&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;manageUserBalance&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;ops&lt;&#x2F;span&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The tx2 calldata confirms it verbatim — selector &lt;code&gt;0x8a4f75d6&lt;&#x2F;code&gt; followed by the two pool addresses:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0x8a4f75d6&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  0000...0020                                  &#x2F;&#x2F; offset&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  0000...0002                                  &#x2F;&#x2F; array length = 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  dacf5fa19b1f720111609043ac67a9818262850c     &#x2F;&#x2F; pool 1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  93d199263632a4ef4bb438f1feb99e57b4b5f0bd     &#x2F;&#x2F; pool 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The constructor is the entire attack surface. No privileged function, no re-entrancy, no oracle manipulation — pure arithmetic, committed in the deploy transaction.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-loot&quot;&gt;The loot&lt;&#x2F;h2&gt;
&lt;p&gt;Reading the six &lt;code&gt;InternalBalanceChanged(address,address,int256)&lt;&#x2F;code&gt; events (topic &lt;code&gt;0x18e1ea41…&lt;&#x2F;code&gt;) of tx1:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Pool&lt;&#x2F;th&gt;&lt;th&gt;Token&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Δ (exact)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;osETH&#x2F;wETH&lt;&#x2F;td&gt;&lt;td&gt;WETH&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+4,623.6015&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;osETH&#x2F;wETH&lt;&#x2F;td&gt;&lt;td&gt;osETH&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+6,851.1230&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;wstETH&#x2F;WETH&lt;&#x2F;td&gt;&lt;td&gt;WETH&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+1,963.8388&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;wstETH&#x2F;WETH&lt;&#x2F;td&gt;&lt;td&gt;wstETH&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;+4,259.8435&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.bearby.io&#x2F;blog&#x2F;balancer-v2-stable-pool-hack-2025&#x2F;loot.svg&quot; alt=&quot;Stolen funds breakdown&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Converting the LSDs to ETH at their on-chain rates ($1.0756$ for osETH, $1.2400$ for wstETH):&lt;&#x2F;p&gt;
&lt;p&gt;$$6{,}587.44 ;+; 6{,}851.12 \times 1.0756 ;+; 4{,}259.84 \times 1.2400 ;\approx; 19{,}239 \text{ ETH}$$&lt;&#x2F;p&gt;
&lt;p&gt;At the Chainlink spot of $$3{,}709.52$&#x2F;ETH that is &lt;strong&gt;≈ $71M&lt;&#x2F;strong&gt;. (Press coverage rounded to ~$63M using face-value LSD accounting and a slightly lower ETH print.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-audits-missed-it&quot;&gt;Why audits missed it&lt;&#x2F;h2&gt;
&lt;p&gt;The bug is invisible to every test that checks a swap in isolation — a single swap&#x27;s rounding is sub-wei and within tolerance. It only manifests as a &lt;strong&gt;cumulative, adversarial state trajectory&lt;&#x2F;strong&gt;: a balance driven to the ill-conditioned boundary &lt;em&gt;and then&lt;&#x2F;em&gt; a swap priced off the resulting $D$. Three blind spots aligned:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unit tests check correctness, not trajectory.&lt;&#x2F;strong&gt; &quot;Does &lt;code&gt;onSwap&lt;&#x2F;code&gt; return the right amount for these balances?&quot; passes. &quot;Can a 226-step adversarial &lt;code&gt;batchSwap&lt;&#x2F;code&gt; drive $D$ off-conservation?&quot; is never asked.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Amplification is treated as a tuning knob.&lt;&#x2F;strong&gt; High $\mathcal{A}$ feels safer (&quot;tighter peg&quot;) but sharpens the singularity at $x_i \to 0$.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The composable BPT makes joins&#x2F;exits cheap to spin.&lt;&#x2F;strong&gt; Flash-exit → distort → flash-mint is a one-transaction round trip with no capital requirement.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The honest fix is not &quot;round more carefully&quot; — it is to &lt;strong&gt;assert invariant conservation&lt;&#x2F;strong&gt; after every swap and revert if $D$ moves outside a tolerance band, plus bounds-check minimum balances before the Newton loop.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reproduce-everything&quot;&gt;Reproduce everything&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #CDD6F4; background-color: #1E1E2E;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;https:&#x2F;&#x2F;ethereum.publicnode.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 1. confirm the contract exists and is the attacker&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; code&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; |&lt;&#x2F;span&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt; wc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; -c&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 2. scaling factors (the rounding source)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; call&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0xdacf5fa19b1f720111609043ac67a9818262850c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;getScalingFactors()(uint256[])&amp;quot; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 3. amplification&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; call&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0xdacf5fa19b1f720111609043ac67a9818262850c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  &amp;quot;getAmplificationParameter()&amp;quot; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 4. the stolen amounts — decode the receipt logs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; receipt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742&lt;&#x2F;span&gt;&lt;span style=&quot;color: #F5C2E7;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt;  --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #9399B2;font-style: italic;&quot;&gt;# 5. decompile the bytecode yourself&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; code&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FAB387;&quot;&gt; 0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; --rpc-url&lt;&#x2F;span&gt;&lt;span&gt; $RPC&lt;&#x2F;span&gt;&lt;span style=&quot;color: #94E2D5;&quot;&gt; &amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exploit.hex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #89B4FA;font-style: italic;&quot;&gt;asm_to_sol&lt;&#x2F;span&gt;&lt;span style=&quot;color: #A6E3A1;&quot;&gt; exploit.hex&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The exploit is a masterclass in &lt;em&gt;small-errors-compounding&lt;&#x2F;em&gt;: a 7% rounding on 8 wei, amplified by a flat-curve singularity, repeated atomically until a $D$ that is &quot;close enough&quot; per-swap becomes catastrophically wrong in aggregate. The contract did nothing illegal — it called public, audited functions, in the order they were designed for, with parameters they were designed to accept. The math did the rest.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
