Aztec Connect processRollup() numRealTxs proof-settlement boundary drain (2026-06-14)
- Name: Aztec Connect
processRollup()numRealTxsproof-settlement boundary drain - Date: 2026-06-14
- Protocol: Aztec Connect
- Chain: Ethereum
- Category: proof-to-effects binding failure / zk-rollup settlement-boundary mismatch / unbacked balance drain
- Loss: about 88,000 of residual bridge-position value in follow-on exploitation on 2026-06-15
- Summary: Aztec Connect’s deprecated rollup contracts split one security promise across two subsystems that no longer agreed about what a batch meant. The validity proof examined rows in fixed groups of 32 and accepted the whole authenticated group, while the onchain settlement path trusted
numRealTxsand only processed that many rows for real asset movement. The attacker abusedprocessRollup()by submitting batches where row0was an innocuous settled action and row1was a large deposit credited by the proof but skipped by settlement becausenumRealTxs = 1. That created internal Aztec balances backed by no real deposits. Later rows inside the same exploit sequence used ordinary withdrawals at row0to cash those fabricated balances out into ETH, DAI, wstETH, Yearn vault tokens, and LUSD. A second attacker then reused the same flaw to pull out leftover bridge-wrapper positions and redeem them externally. - Trigger / entry point:
- The exploit entry point was Aztec Connect’s rollup settlement function
processRollup()on the rollup processor proxy at0xFF1F2B4ADb9dF6FC8eAFecDcbF96A2B351680455. - Aztec Connect’s proof system authenticated full row groups, but the settlement code executed only the first
numRealTxsrows for deposit / withdrawal side effects. - That mismatch let a row be accepted by the proof, mutate internal balances, and still avoid the settlement checks that should have required real funds to enter the rollup.
- The exploit entry point was Aztec Connect’s rollup settlement function
- Root cause:
- The proof system and the Solidity settlement code did not bind the same economically relevant side effects.
- The proof accepted all rows in the authenticated group, but settlement trusted a declared boundary (
numRealTxs) instead of reasserting that every credited row was also one it would economically settle. - Aztec’s own disclosure is crisp: the problem was not a break in the proof system or trusted setup, but an issue in the smart contracts where the side effects of a proven rollup were not asserted to match the side effects processed by the deposit-withdrawal system.
- Once an attacker could create internal balances from rows beyond the settlement boundary, every later ordinary withdrawal path became a reserve-drain primitive.
- Exploit path:
- On 2026-06-14, the attacker funded fresh wallet
0x0F18D8b44a740272f0be4d08338d2b165b7EdD17via Tornado Cash and deployed helper contracts, including0x06f585F74e0DA633Ae813A0f23Fb9900B61d0fcD. - At
12:26 UTC, the attacker executed transaction0x074ec9317d8336db37e8c348fbdd7515573ff4088239c77ab429f522509aeeb1, which packed 14 crafted rollup submissions (rollupId13277through13290) into one transaction. - In each crafted batch,
numRealTxswas set to1, so the Solidity public-effect loop processed only row0, but the proof-side commitment still covered row1because Aztec’s1024-row rollups were hashed in32inner-rollup chunks of32rows each andceil(1 / 32) = 1still committed the first whole chunk. - For balance-creation calls, row
0was a harmless internal transfer that settlement processed normally, while row1was a large deposit. The proof credited that deposit inside Aztec Connect, but settlement never collected the external assets or decremented pending deposits because row1sat beyondnumRealTxsand therefore never reached the L1 deposit-accounting loop. - After repeating that pattern, the attacker held large internal balances backed by no real incoming funds.
- In later rollups of the same exploit sequence, the attacker placed ordinary withdrawals at row
0, and the rollup contract paid real assets out to the attacker wallet. - The first attack extracted about
908.9867 ETH,270,513.0542 DAI,167.8904 wstETH,4,873.8567 yvDAI,16.5696 yvWETH,9,273.7343 LUSD, and359.0475 yvLUSD, about$2.19Mtotal at the time. - Beginning around
04:00 UTCon 2026-06-15, a second attacker funded fresh wallet0x7ec9…06db, continued fromrollupId13291, and used 14 more craftedprocessRollup()calls (13291through13304) to extract residual bridge-position value. - That second round moved
wa2WETH,wa2DAI,wcDAI,weWETH, andTB-275out of Aztec Connect. The attacker then redeemed the Aave and Compound wrappers externally into WETH / ETH and DAI, and used the returnedTB-275path to force the rollup to unwind TroveBridge value back into ETH for them. - Aztec Labs’ postmortem adds the cash-out chain that matters for future incident comparisons: extracted ETH and DAI from the second round moved through disposable wallets
0x7ec9…06db -> 0xc431…c1f5 -> 0x0af7…9912and then into high-volume service contract0xd90e…f31b, while the residual Euler-wrapped WETH stayed stranded because it had no redeemable underlying at the time checked.
- On 2026-06-14, the attacker funded fresh wallet
- What the attacker needed to be true:
- The proof layer had to authenticate more rows than the settlement layer would economically process.
- A row beyond
numRealTxshad to be able to credit internal balance without the settlement layer independently proving that the matching assets were really received. - Ordinary withdrawal rows still had to honor those fabricated internal balances as if they were genuine Aztec balances.
- Residual wrapper and bridge positions held by the rollup contract had to remain redeemable after the first drain, so a second exploit round could monetize them.
- No admin, pause, or upgrade authority could remain to halt the vulnerable contracts once the exploit began.
- Technical anatomy worth remembering:
- The proof system was not broken. The bug sat in the boundary between
proof says this batch is internally consistentandSolidity settlement says these are the rows that count for real assets. - This was not a generic
bad proofstory; it was aproof and consumer authenticated different side-effect setsstory. - The exploit path is memorable because it used one benign settled row to satisfy the runtime boundary and one hidden balance-creating row to exploit the proof boundary.
- The crucial mechanics were more specific than
proof checked more rows than settlement. WithrollupSize = 1024andnumInnerRollups = 32, the verifier-side public-input hash still committed the first32-row inner-rollup chunk whennumRealTxs = 1, so row1remained part of the accepted state transition even though the L1 accounting loop stopped before it. - The skipped side effect was also concrete: uncounted deposit-shaped rows never hit the
decreasePendingDepositBalance(...)path, so they could create private balance without pulling corresponding ETH/ERC-20 value into the backed pending-deposit ledger. - The second wave matters analytically: after the obvious reserves were drained, the same mismatch still reached leftover bridge-wrapper positions that could be redeemed outside Aztec Connect.
- Deprecation did not neutralize the contract. It only ensured nobody retained the authority to patch or pause a still-funded, still-withdrawable liability surface.
- The proof system was not broken. The bug sat in the boundary between
- Representative transactions and addresses:
- First exploit tx:
0x074ec9317d8336db37e8c348fbdd7515573ff4088239c77ab429f522509aeeb1 - First attacker EOA:
0x0F18D8b44a740272f0be4d08338d2b165b7EdD17 - First attack helper contract:
0x06f585F74e0DA633Ae813A0f23Fb9900B61d0fcD - Vulnerable rollup processor proxy:
0xFF1F2B4ADb9dF6FC8eAFecDcbF96A2B351680455 - First-wave crafted rollup IDs:
13277–13290 - Second-wave crafted rollup IDs:
13291–13304 - Second-wave attacker path, as summarized by Aztec Labs:
0x7ec9…06db -> 0xc431…c1f5 -> 0x0af7…9912 -> 0xd90e…f31b - Residual sidecar assets monetized in the second wave:
wa2WETH,wa2DAI,wcDAI,TB-275 - Residual sidecar asset extracted but not redeemably monetized at review time:
weWETH
- First exploit tx:
- Response / mitigation:
- Aztec Labs disclosed that Aztec Connect had been deprecated in 2023 and that all administrative and upgrade authority had been renounced in April 2024 after a year of withdrawal notice.
- Because Aztec Labs no longer controlled the contracts, there was no admin key, upgrade path, or pause mechanism available to stop the exploit once it began.
- Aztec Labs emphasized that the live Aztec Network and current systems were separate and unaffected.
- Blockaid independently flagged the pre-transaction setup activity roughly six minutes before the first exploit transaction landed, but monitoring and warnings were the only realistic levers left for a renounced contract still holding user assets.
- The follow-on drain through
wa2*,wcDAI,weWETH, andTB-275also made the residual-service lesson harsher: once the obvious reserves were gone, the remaining liability surface still included redeemable bridge and wrapper sidecars, not just the balances most users would have recognized on first inspection. - Aztec Labs’ appendix is also useful for taxonomy purposes because it shows the second-wave monetization path distinctly: some wrappers (
wa2WETH,wa2DAI,wcDAI,TB-275) still had external redemption or unwind value, whileweWETHno longer did. That distinction is exactly why residual-service inventories need to track redeemability and sidecar unwind rights, not merely token symbols left on the deprecated rollup.
Pattern tags
- proof-to-effects-binding-failure
- proof-effect-truth-security-object
- effect-ledger-commitment
- exit-right-provenance-binding
- deprecation-without-deauthorization
- One proof-authenticated inner-rollup chunk, one narrower
numRealTxssettlement projection, and one later withdrawal path were all allowed to behave as if they were the same balance-sheet object.
Three proof-settlement promotions this incident collapsed
-
A proof-authenticated inner-rollup chunk existed at all
- Aztec Connect’s proof system really did authenticate the first
32-row inner-rollup chunk even whennumRealTxs = 1. - That only answered the cryptographic-validity question:
some chunked batch transition satisfied the proof statement.
- Aztec Connect’s proof system really did authenticate the first
-
That authenticated chunk was treated as the canonical economically settled ledger for this batch
- The live bug lived here. Aztec Connect quietly promoted
row 1 sat inside the proof-authenticated first chunkintorow 1 therefore counted as a real settled depositeven thoughprocessRollup()only charged deposits and decremented pending balances for row0. - In other words, one proof-authenticated object and one narrower
numRealTxssettlement projection were allowed to behave as if they were the same balance-sheet truth.
- The live bug lived here. Aztec Connect quietly promoted
-
That accepted ledger still deserved live withdrawal and sidecar-unwind authority
- Once the protocol recorded proof-accepted-but-unsettled internal balances, later ordinary withdrawals, wrapper redemptions, and bridge-unwind paths still honored those balances as if the backing had already arrived.
- The second-wave drains through
wa2*,wcDAI, andTB-275make the last promotion especially clear: even after the first reserve basket was gone, the same counterfeit ledger still inherited real exit authority against residual sidecar value.
Why the exploit worked
- Aztec Connect let the proof layer and the Solidity settlement layer answer different questions about the same batch. The proof still authenticated the first 32-row inner-rollup chunk when
numRealTxs = 1, butprocessRollup()only charged deposits and honored public effects for row0. - That split made
accepted by proofstronger thaneconomically settled. A row could still create internal Aztec balance while sitting outside the L1 loop that should have collected the corresponding ETH or ERC-20 value and decremented pending deposits. - The exploit shape stayed simple enough to repeat fourteen times in one transaction: keep row
0harmless so the settlement boundary looks legitimate, hide the balance-creating deposit in row1, then later place an ordinary counted withdrawal back in row0and let the rollup pay real assets out. - The second wave matters because it proves the bug was not only a first-drain reserve bug. Once fabricated balance could reach wrapper and bridge sidecars too, the real security object was the whole exit surface, not just the first basket of visible tokens.
- Deprecation only removed intervention authority. It did not shrink the economic meaning of a proof-accepted-but-unsettled balance, and it did not make residual bridge positions any less monetizable.
Detection and audit pivots
- Diff the exact effect set committed by the proof against the exact effect set the L1 settlement loop charges, releases, or reconciles. If one side works in 32-row chunks while the other works off
numRealTxs, reviewers should assume a solvency boundary exists there until equivalence is proved. - Fuzz the
last counted row / first uncounted rowboundary with mixed batches: harmless row0, state-changing row1,numRealTxs = 1, and public amounts chosen to match later withdrawals. The safe outcome is revert or complete charge-and-settle parity, notproof passed and one row silently skipped. - Treat deposit-consumption calls such as
decreasePendingDepositBalance(...)as treasury invariants, not bookkeeping details. If a credited deposit can avoid the exact path that proves pending funds were consumed, the protocol is already printing redeemable internal balance. - Reconcile every batch across three ledgers, not one: proof-credited internal balances, settlement-side asset inflows/outflows, and later withdrawal / bridge-unwind authorizations. Any path where one ledger can advance without the others is the live exploit surface.
- For sunset systems, inventory the residual service by redeemability rather than token symbol. Wrapper tokens, bridge sidecars, and helper unwind rights need to be modeled as live liabilities if the same old proof / settlement boundary can still cash them out.
Failure-prevention checklist
- Make the proof-facing batch object and the settlement-facing batch object literally the same security object. If the circuit authenticates 32-row chunks, then every counter, padding rule, and
numRealTxs-style selector that narrows economic meaning must be part of the authenticated object rather than post-proof runtime metadata. - Treat
numRealTxs, inner-rollup counts, and loop-bound variables as treasury inputs. Review them the same way you would review withdrawal authorization, because they decide which rows actually collect backing assets and which rows can only mutate internal state. - Reassert backing at the settlement boundary for every credited deposit row. A rollup should not let internal balance increase unless the same authenticated effect set also proves the matching ETH/ERC-20 value entered and the pending-deposit ledger was consumed.
- Prefer an explicitly committed settled-effects ledger over a later runtime projection. The contract should settle the exact proved effect list, not derive a narrower
rows that countsubset after verification. - Add impossible-boundary regression tests from the real exploit shape: benign settled row
0, hidden deposit row1,numRealTxs = 1, and a later counted withdrawal that tries to cash out the fabricated credit. - Keep residual bridge and wrapper positions on the same liability sheet as obvious token balances. If a deprecated rollup still holds
wa2*,wcDAI,TB-*, or similar sidecars that can be redeemed externally, they are still part of the funded blast radius of any proof/settlement mismatch. - Decommission deprecated proof systems as residual custody systems, not just abandoned products. If the protocol cannot patch or pause anymore, it has to narrow the live exit surface before governance walks away.
- Require one reconciliation view per batch that proves proof-credited balance creation, settlement-side asset movement, and later withdrawal authority all still refer to the same economically backed rows.
Audit prompts
- What exact rows did the proof authenticate, and what exact rows did
processRollup()or its equivalent actually charge, settle, or release against real assets? - Which runtime variables decide
these are the real rows, and are those variables themselves authenticated by the proof or merely trusted as batch metadata? - Can one row still call the balance-crediting logic while skipping the asset-collection or pending-deposit-consumption path because it sits just outside a settlement loop bound?
- If the proof commits in fixed-size chunks, have reviewers fuzzed the first row beyond the runtime boundary inside the same authenticated chunk rather than only the last counted row?
- Where exactly does the system prove that an internal balance later withdrawn at row
0was backed by a deposit row that was also economically settled, rather than just proof-accepted? - Which bridge wrappers, sidecar positions, or helper unwind rights remain cash-outable if the obvious reserves are drained first?
- For deprecated immutable systems, what concrete artifact shows the remaining service is truly exit-only and bounded, rather than merely unowned but still economically wide open?
- If a batch-validity proof and the asset-moving contract disagree today, what monitoring or reconciliation would detect the mismatch before later withdrawals monetize it?
Lessons that should already be standard
Valid proofis not a solvency statement. The system is only safe when the proof and the asset-moving consumer still agree on the same economically relevant rows.- Batch-width counters and settlement loop bounds are custody logic, not harmless throughput plumbing. They deserve the same suspicion as withdrawal checks.
- Internal balances are already money when later code can withdraw them. Any row that can create them without proving matching backing value is a live reserve-printing surface.
- Deprecated immutable rollups are still balance-sheet systems until every reachable reserve, bridge sidecar, and helper unwind path is either drained, bounded, or deliberately defended as an exit-only service.
- Reconciliation has to span the whole lifecycle: proof-authenticated rows, settlement-side collection, and later exit rails. Looking at only one of those ledgers is how underbacked credit survives long enough to cash out.
- The second attacker matters analytically because they proved the real liability surface was wider than the first basket of visible reserves. Sidecar redeemability and bridge unwind rights are part of the same exploit object, not postscript trivia.
Why this incident matters
- It is a very clean example of why
the proof is validis not enough. A proof system can remain cryptographically sound while a settlement boundary around it still leaks real assets. - The incident sharpens a reusable design lesson for rollups, bridges, and batch-settlement systems: the exact effect set the proof authenticates must be the same effect set the asset-moving consumer executes.
- It is also a clean residual-service failure. Once Aztec Connect was immutable and deprecated, the safe lifecycle question was no longer
is the old product supported?buthas the remaining withdrawal surface been narrowed into a bounded, explicitly defended exit-only service? - The two-wave exploitation matters because it shows that residual risk inventory must include sidecar positions and helper unwind paths, not just the headline token balances left on the old contract.
- As a design reference, it is the branch’s cleanest reminder that healthy proof-driven settlement needs one authenticated effect object, one committed settled-effects object, and one later exit path that only cashes out balance whose backing provenance was already proven.
Internal linkages
- Core recurring pattern: proof-to-effects-binding-failure.
- Broader positive-design companion: proof-effect-truth-security-object — the sharper question here is what one healthy proof-driven settlement object should have kept fused together before any Aztec balance became withdrawable.
- Narrower committed-object companion: effect-ledger-commitment — the more concrete design question is what exact committed settled-effects ledger
processRollup()should have consumed so proof-valid rows and settlement-valid rows could not drift apart. - Downstream exit-rail companion: exit-right-provenance-binding — the cash-out side lesson is that later withdrawals and bridge unwinds should not have been able to treat ambient internal balance as redeemable value once the credited deposit rows had never been economically settled.
- Lifecycle control frame: deprecation-without-deauthorization — Aztec Connect had been deprecated and governance-renounced, but the old settlement path still held withdrawable value and therefore still needed exploit-grade security assumptions.
- Broader decommissioning companion: decommissioning-authority-extinguishment-discipline — the healthier retirement boundary was not
old rollup, no admin, users can still leave; it wasauthority and exposed value stripped down far enough that the remaining path was a consciously owned residual service rather than a socially abandoned production liability. - Residual-service companion: exit-only-residual-surface-discipline — this is the narrower positive-design question for what Aztec Connect’s remaining withdrawal surface should have looked like once the product was retired but legacy exits still existed, including wrapper and bridge sidecars that could still be unwound into cash.
- Adjacent authenticated-object boundary: proof-to-message-binding-failure.
Control surface
-
The leverage sat in the split between fixed-width row groups accepted by the proof, the narrower
numRealTxsboundary honored by settlement, and later withdrawal / bridge-unwind paths that treated internally credited balance as fully backed value. -
The residual balance sheet was wider than the first-wave reserve inventory:
wa2*,wcDAI,weWETH, andTB-275proved that wrapper positions and bridge unwind rights were still part of the live exit surface. -
The decisive security question was not
is the proof valid?butdoes the settlement consumer execute and charge for the exact same economically relevant rows the proof authenticated? -
Put another way, the exploitable boundary was the full path from proof-authenticated row groups, through the object
processRollup()actually settled, into the later exit rails and sidecar redemption paths that never re-asked where those internal balances got their backing. -
Sources:
-
Last reviewed: 2026-08-03 UTC