58564 sc critical earmarked funds fail to accumulate when earmark is called in consecutive blocks

Submitted on Nov 3rd 2025 at 09:22:11 UTC by @SOPROBRO for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58564

  • Report Type: Smart Contract

  • Report severity: Critical

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol

  • Impacts:

    • Permanent freezing of unclaimed yield

    • Smart contract unable to operate due to lack of token funds

Description

Brief/Intro

When _earmark is called in consecutive blocks, the queryGraph interval becomes zero-length, causing no funds to be earmarked. As a result, transmuter redemptions are never paid and users debt won't be repaid.

Vulnerability Details

The function _earmark calculates the amount to earmark using:

uint256 amount = ITransmuter(transmuter).queryGraph(lastEarmarkBlock + 1, block.number);

At the end of _earmark, it sets

If _earmark is called in consecutive blocks (e.g., due to the permissionless poke function or frequent protocol activity via withdraw, mint, burn, repay, redeem), then:

  • lastEarmarkBlock + 1 == block.number

  • queryGraph returns 0 for a zero-length interval

  • No new funds are earmarked, preventing transmutation progress and debt repayment.

Example scenario (using poke)

  1. A user deposits and mints alAsset at block 0, setting lastEarmarkBlock = 0.

  2. At block 1, a user calls poke().

    • _earmark computes queryGraph(0 + 1, 1) → 0.

    • lastEarmarkBlock is updated to 1.

  3. If this continues each block, amount always equals 0, causing earmarked funds to never accumulate.

Impact Details

Because transmuter redemptions depend on earmarked debt, this behaviour leads to:

  • Users receiving no funds upon redemption.

  • The protocol failing to automatically repay debt.

This issue can occur naturally due to normal transaction flow, not just via malicious spam.

Recommendation

Add a guard to skip execution when the block interval is zero:

This ensures that _earmark only runs when there has been at least one full block of accumulation.

Proof of Concept

Proof Of Concept

In AlchemistV3.t.sol, in the setup function deployCoreContracts, change the timeToTransmute from 5_256_000 to 5000 (This is just for testing purposes, as if we attempt to poke 5_256_000) times in the test, it will result in a OOG error.

Then, add the following test, and run in the console forge test --mt test_loss_of_redeemed_funds_when_poking_every_block -vv. Feel free to change the value of rollAmount to see how the amount of consecutive _earmarked calls affects the amounts earmarked and redeemed.

Logs

We can clearly see that calling _earmarked consecutively results in a linearly loss of funds redeemed.

Was this helpful?