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

89304 bc high wormhole miner volume fee is credited without a zk leaf and can never be spent

Submitted on Aug 18th 2026 at 14:17:20 UTC by @Pyro for Audit Comp | Quantus

  • Report ID: #89304

  • Report Type: Blockchain/DLT

  • Report severity: High

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

  • Impacts:

    • Permanent freezing of unclaimed yield

Description

A wormhole address holds no spendable balance. process_exit_bundle never debits the source address when it settles an exit. What makes value at such an address usable is a ZK-tree leaf, and record_transfer is the pallet's only leaf writer.

The exit-credit path pairs the credit with the recorder:

				match <T::Currency as Unbalanced<_>>::increase_balance(
					exit_account,
					*exit_balance,
					frame_support::traits::tokens::Precision::Exact,
				) {
				Self::record_transfer(
					T::AssetId::default(),
					&from_account,
					&to_account,
					*exit_balance,
				);

The miner volume fee uses the same event-free primitive and stops at the event:

The author is qp_wormhole::extract_author_from_digest, which returns derive_wormhole_address(preimage). The miner holds the secret behind that address and can satisfy the exit circuit. It never receives a leaf, so there is nothing to prove against.

Nothing repairs the omission later:

  • increase_balance writes the balance without emitting a Balances event, so WormholeProofRecorderExtension cannot see it.

  • The settlement is an unsigned bare extrinsic, where that extension does not run at all.

  • The pallet exposes only verify_private_batch and verify_public_batch, so no admin path can insert the leaf.

  • Leaves are per (recipient, transfer_count) and each commits one fixed amount, so the mining-rewards leaf does not cover the fee.

Both failure arms of the same branch add the unpaid fee back to burn_amount. The branch's contract is pay or burn. The success path does neither.

1

A miner runs with a stable rewards_inner_hash

Its author account already exists from earlier block rewards.

2

A user settles an exit

Any user submits a valid unsigned verify_private_batch settling a 1,000 QUAN exit.

3

The volume fee is calculated

volume_fee_for_exit computes ceil(100_000 * 4 / 9996) = 41 quanta, so the total fee is 410,000,000,000 planck.

4

The miner fee is calculated

VolumeFeesBurnRate is 50 percent, so 205,000,000,000 planck burns and miner_fee is 205,000,000,000 planck.

5

The balance is credited

increase_balance(&author, 205_000_000_000, Exact) succeeds and MinerVolumeFeePaid is emitted.

6

No leaf is created

TransferCount for the author is unchanged and no NativeTransferred event fires, so no leaf exists for the credit.

7

The entitlement remains unchanged

The author's balance is 0.205 QUAN higher and its exitable entitlement is unchanged.

8

The issue repeats

Every later exit repeats this, so all wormhole volume-fee revenue accrues to block authors and none is deliverable.

Recommendation

Record the leaf inside the same success arm, exactly as the sibling exit-credit branch does.

Better, factor a helper that does increase_balance then record_transfer, and route every event-free credit in process_exit_bundle through it so no future branch can credit without recording.

Proof of Concept

Add to mod private_batch_proof_tests in pallets/wormhole/src/tests.rs. It reuses the committed proof fixture, so no node build is needed.

Output shape (the credited amount depends on the committed fixture's exit total):

Balance credited, leaf count unchanged. That pair is the whole finding.

Was this helpful?