Blog · 2026-06-21 · Michael Moffett

The txs the proof validated but settlement skipped: Aztec’s cross-layer conservation class.

On June 14, 2026, approximately $2.19M was extracted from Aztec’s deprecated RollupProcessor contract. The mechanism: the ZK proof validates the full batch of 32 transaction slots, but the L1 settlement loop processes only the first numRealTxs of them. The remaining slots pass proof validation and receive no settlement-side accounting. Tokens in those positions bypass the settlement fee entirely.

That is the bug in one sentence: proof scope and settlement scope diverged. The proof said 32 transactions were valid. Settlement processed fewer. The difference walked out.

This post is not a postmortem. SlowMist published the detailed root-cause analysis, and the incident was widely reported. That analysis is the authoritative artifact. What this covers is the invariant class the mechanism belongs to, and what a test that holds that class would assert.

The pattern

Conservation across layers.

The conservation class, broadly, asserts that units entering a system must be consistently accounted for. Every deposit must appear in the total. Every debit that gets processed must reduce the right balance. No unit slips between the layers unaccounted. Within a single contract, this fails when the accounting logic misses a credit or debit somewhere in the call graph. Across layers, it fails when the accounting view at one layer diverges from what the other layer actually processed.

The numRealTxs boundary is the cross-layer shape. The ZK proof layer and the L1 settlement layer are supposed to agree on which transactions have been finalized. The proof said “all 32 are valid.” Settlement said “I processed the first N.” Transactions at position N+1 through 32 existed in one accounting view and not the other. Conservation broke at the boundary.

The invariant that should hold is: for every transaction slot in a proof-validated batch, the L1 settlement loop processes exactly that slot, or the batch is invalid. Equivalently: the set of transactions the proof considers finalized must equal the set the settlement considers finalized. A test that carries a batch with numRealTxs < batch_size and checks that the accounting across both layers matches after settlement would have flagged this shape.

CI-verified coverage

What we cover, and where we stop.

For the conservation class, we ship reference artifacts on two runtimes: Cairo on Starknet and HyperEVM. Both carry the class with clean and planted-bug twins running in CI. The EVM-L1-ZK-settlement surface that Aztec used is not one of them.

cf-invariants, Cairo / Starknet (same-layer conservation). The 12-class reference suite includes two conservation-class references: erc4626_ref and staking_ref. The erc4626 conservation invariant (cf_invariant_share_asset_conservation) tracks the vault’s total_assets against an externally-maintained cumulative net-deposit count across every deposit and withdraw transition. The planted bug: withdraw decrements total_shares but not total_assets, so the vault’s reported total drifts from the real one after the first withdrawal. The invariant fires as soon as the two counts diverge. The staking conservation invariant (cf_invariant_stake_slash_conservation) asserts the same shape across a staking vault with a slashing path: every unit that enters as stake must appear either in total_staked or in slashed_pool or in completed withdrawals. The planted bug omits the slashed_pool credit on a slash, and the conservation assertion fails the moment a non-zero slash lands.

Both are single-layer conservation: the invariant lives inside one contract and the assertion compares internal accounting fields. They teach the conservation class shape. They are not the Aztec shape, which is a cross-layer divergence.

CI evidence for cf-invariants: PR #2 squash-merged 2026-06-07, commit 003e33c, CI run 27075213962 26/26 green. All 12 references source-verified on Starknet Sepolia. Repo: github.com/caliperforge/cf-invariants. Developer cookbook: github.com/caliperforge/cf-invariants/tree/main/docs/cookbook.

hyperevm-safety, HyperEVM / Solidity (cross-layer conservation window). D-5 (CoreWriterSolvencyWindow) is the cross-layer variant in our artifact set. The bug class it covers: a lending protocol on HyperEVM pre-credits a pending CoreWriter action on the EVM side before HyperCore has confirmed the settlement. The EVM-side solvency view shows inflated collateral that has not cleared the HyperCore layer yet. If HyperCore rejects the action, the borrower holds debt against collateral that never arrived. The CoreWriterSolvency library’s assertSettlementWindowInvariant reverts if the EVM-reported solvency would only hold because of unsettled credits. A protocol that calls this from every position-mutating entry point cannot be exploited via the pre-credit path.

The structural resemblance to the Aztec shape: in both cases, one layer’s accounting runs ahead of the other layer’s confirmed state. In Aztec, the proof layer validates transactions the settlement layer never processes. In D-5, the EVM layer counts collateral the HyperCore layer has not confirmed. The class identity is a cross-layer conservation window: two layers that should agree on settled state temporarily diverge, and the divergence window is exploitable.

CI evidence for hyperevm-safety: v0.1, commit 540464e, CI run 27379686747 6/6 jobs green. D-5 planted twin (CoreWriterSolvencyWindow.planted.t.sol) fires the INVARIANT VIOLATED CoreWriterSolvencyWindow marker and stays silent on the clean variant. Repo: github.com/caliperforge/hyperevm-safety.

What is not in our set. The EVM-L1-ZK-settlement surface, which is the Aztec attack surface, does not appear in our artifact set. We do not have a planted twin at the numRealTxs boundary or on any ZK-rollup-to-L1-settlement pair. A test that caught the Aztec shape directly would harness the RollupProcessor’s settlement loop, construct a batch with numRealTxs < batch_size, invoke settlement, and assert that the post-settlement accounting matches the full batch. That test does not exist in our CI today.

The teach

Why the class identity is the useful thing to say here.

The conservation class is not a single implementation pattern. It surfaces wherever there is a summary that two separate accounting views must agree. Single-layer conservation (erc4626, staking) fails when a contract’s internal bookkeeping has a gap. Cross-layer conservation (D-5, Aztec) fails when two systems that share a settlement boundary disagree on what has settled.

The useful output from an incident like Aztec’s is the class name, the invariant formulation, and the question: does anything in our harness already carry this shape? For teams building on Cairo or Starknet, the cf-invariants conservation references are the closest runnable starting point, with the caveat that they are single-layer. For teams building on HyperEVM or any EVM chain with a cross-layer settlement path, D-5 is the structural analog. For teams building the actual EVM-L1-ZK-settlement harness that the Aztec incident calls for, neither artifact covers it yet.

That is what the class-identity teach is doing: the method generalizes across runtimes; our coverage of this specific surface does not.

Sources

Primary analysis and CaliperForge artifacts.

CaliperForge artifacts cited:

Operator of record: Michael Moffett , michael@caliperforge.com , team@caliperforge.com. This writeup was drafted with AI assistance; the invariant framing, the coverage mapping, and this post were all reviewed by the operator. See caliperforge.com/ai-disclosure for the full disclosure register.