57582 sc critical calling earmark one block apart skips the block s earmark value

Submitted on Oct 27th 2025 at 10:42:57 UTC by @hashbug for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57582

  • Report Type: Smart Contract

  • Report severity: Critical

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

  • Impacts:

    • Theft of unclaimed royalties

Description

Intro

The Transmuter uses StakingGraph to maintain a record of the value to be earmarked for each block. This mechanism is used to set aside debt and collateral for 1:1 redemption claims.

Due to an early return in Transmuter.queryGraph, two subsequent blocks containing an AlchemistV3._earmark call will lose the earmarking information from the second block.

This makes the Transmuter unable to fulfill its 1:1 obligation to the user. The user will lose their synthetic tokens and silently receive less yield tokens in return.

Vulnerability Details

In the case of two subsequent blocks containing an _earmark call, the following will hold in the second call:

This means that the call

will result in startBlock == endBlock in Transmuter.queryGraph.

This satisfies the early return condition

Therefore, no matter what the StakingGraph stores in the block slot, the amount earmarked in _earmark will be 0.

Impact Details

Since Transmuter earmarking is suppressed when _earmark is called frequently, the 1:1 obligation will be degraded naturally during times of higher volume resulting in value loss for the transmuting users.

The Transmuter uses balanceOf to adjust the amount withdrawn from it, thus the burden will be shifted entirely to the last claimant instead of the loss being socialized and the call will succeed even though it will silently return less value.

Thus, it is possible to borrow synthetics at the expense of the transmuting users. If yield can be generated on the synthetics, e.g. by trading them for yield tokens, this yield is afforded without having the loan consume the collateral over time. This can be achieved by calling poke on an arbitrary CDP (because we only need the _earmark call, not the CDP-dependent _sync) in every block.

Once redemption claimants notice that their claims are not being fulfilled, the soft peg will be lost (at least temporarily, until the contract gets upgraded) and synthetics to burn the loan might be obtainable at a fraction of their original price, thus freeing the original collateral needed for the loan.

Proof of Concept

Proof of Concept

We will show that calling poke in every block reduces the transmutation value from 1:1 to 1:0.

The new test is derived from AlchemistV3Test and the main code is in test_blockEarmarkLoss.

Was this helpful?