For the complete documentation index, see llms.txt. This page is also available as Markdown.

88300 bc high missing wormhole zk leaf permanently freezes all miner exit fee yield

Submitted on Aug 12th 2026 at 17:11:50 UTC by @Porcupine17126 for Audit Comp | Quantus

  • Report ID: #88300

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/immunefi-team/audit-comp-quantus-chain

  • Impacts:

    • Permanent freezing of unclaimed yield

Description

Brief / Introduction

Every successful Wormhole exit allocates part of its volume fee to the current block author. The runtime increases the author's native balance and emits MinerVolumeFeePaid, but it does not call Wormhole::record_transfer. Production block-author reward accounts are keyless Wormhole addresses, so a committed ZK-tree leaf is the only usable claim to the credited value. The miner therefore receives an on-chain balance that can never be spent or withdrawn.

The production parameters charge 4 bps on Wormhole exit volume and allocate 50% to the miner. Approximately 2 bps of every successful Wormhole exit is permanently frozen (with the circuit's one-quantum minimum affecting small exits).

Scoped commit: 3b243b870a5442a6e5f443056f67bbba87d472f5

Vulnerability Details

The block author is a Wormhole address

Production validators must provide --rewards-inner-hash. The node validates it as a canonical Poseidon digest and stores that inner digest in the QPoW pre-runtime log. The runtime derives the author account as:

This is a Wormhole address, not an ordinary ML-DSA signing account:

Settlement credits a balance but omits the leaf

The exit settlement credits the miner with event-free Unbalanced::increase_balance and emits MinerVolumeFeePaid:

The success branch never invokes Self::record_transfer. No other mechanism repairs the omission:

  1. increase_balance emits no Balances::Minted or transfer event for WormholeProofRecorderExtension to scan.

  2. verify_private_batch and verify_public_batch are bare unsigned extrinsics, so the signed-transaction extension does not run for settlement.

The adjacent production paths demonstrate the required invariant:

record_transfer increments TransferCount, inserts (recipient, transfer_count, asset_id, amount) into the Poseidon Merkle tree, and exposes the leaf index needed for a proof:

Knowing the Wormhole secret cannot compensate for a missing leaf. The secret proves ownership of a leaf already committed to a historical root; it cannot synthesize an omitted leaf or witness.

Impact Details

Any valid private- or public-batch Wormhole exit included by a normal production validator reaches this path. No malicious validator, privilege, malformed proof, Sybil behavior, or race is required.

The post-state is inconsistent:

The miner cannot sign from the derived Wormhole address and cannot construct a Wormhole withdrawal proof without the omitted leaf. The entire miner share is permanently frozen as unclaimed mining yield.

At the scoped runtime parameters:

See runtime/src/configs/mod.rs#L728-L752.

Later correctly recorded mining rewards do not repair the missing credit: those leaves commit only their own amounts and transfer counts, not the stranded earlier fee.

Suggested Remediation

After successful miner increase_balance, explicitly record the native-token credit before emitting MinerVolumeFeePaid:

Update settlement weights for the additional ZK-tree insertion. Retaining event-free increase_balance is appropriate; changing only to mint_into would rely on an event-scanning extension that does not execute for these bare unsigned settlement extrinsics.

Note on commit 60ec991

Commit 60ec991 added MinerVolumeFeePaid because the event-free credit was ambiguous to indexers. That change improves visibility but does not create a ZK-tree leaf. It consequently reports the keyless address as paid while the value remains unclaimable; it does not fix or document this permanent-freeze impact.

Proof of Concept

This PoC extends the repository's existing valid-private-batch test. The original test already loads a real valid proof fixture, sets the matching block hash and QPoW author digest, executes verify_private_batch, and verifies both the nonzero fee balance increase and MinerVolumeFeePaid event.

The added invariant captures Wormhole::transfer_count(author) before settlement and proves it remains unchanged afterward, even though the balance was credited.

1

Clone the scoped repository

Clone the scoped chain repository and check out the exact scoped commit:

2

Apply the patch

3

Run the focused PoC

Observed result:

4

Run the full pallet regression suite

Observed result:

What the PoC proves

The test verifies all of the following in one successful valid-proof execution:

  • expected_miner_fee > 0.

  • The QPoW-derived author's free balance increases by exactly that fee.

  • MinerVolumeFeePaid reports the exact author and fee.

  • The same author's Wormhole::TransferCount does not advance.

Since record_transfer is the only path that advances the counter and inserts the spendable ZK leaf, the credited fee has no witness in the committed tree and is permanently unclaimable.

Was this helpful?