58443 sc critical incorrect consumption of yield cover in redeem leading to reuse of accrued yield

Submitted on Nov 2nd 2025 at 12:06:01 UTC by @w3llyc4de20Ik2nn1 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58443

  • Report Type: Smart Contract

  • Report severity: Critical

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

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

The AlchemistV3 redeem function incorrectly updates lastTransmuterTokenBalance by subtracting usedYield from the current transmuterBal instead of adding it to the prior balance, causing previously consumed yield to be reused as debt cover in subsequent redemptions without actually burning or transferring the tokens.

Vulnerability Details

In the AlchemistV3.sol: redeem () subtracts the usedYield (portion of accrued yield applied as debt cover) from the current transmuter balance to "consume" it, but since transmuterBal already includes the full deltaYield (new accrual), this sets lastTransmuterTokenBalance to the old balance plus only the unused yield; as a result, the next redemption's deltaYield calculation re-includes the previously used yield, allowing it to be double-dipped as cover without actually transferring or burning the tokens.

In the redeem function, the logic intended to "consume" the observed yield delta (to prevent reuse as cover in future redemptions) is flawed. Specifically:

// observed transmuter pre-balance -> potential cover 

uint256 transmuterBal = TokenUtils.safeBalanceOf(myt, address(transmuter));  // <-- DEFINED HERE: Current balance fetched from transmuter contract 

deltaYield represents the new yield accrued in the transmuter since the last update to lastTransmuterTokenBalance (the "old" last balance). Note: lastTransmuterTokenBalance is a state variable (previously set, e.g., from prior calls to redeem or _earmark). It's used here as the "old" balance.

usedYield is the portion of that delta applied as cover (in yield token units).

The code attempts to consume this by setting lastTransmuterTokenBalance = transmuterBal - usedYield

Definition of usedYield (Portion of Delta Applied as Cover)

This is where the bug manifests: It incorrectly updates the state variable lastTransmuterTokenBalance, affecting the next call's deltaYield calculation

However, since transmuterBal = lastTransmuterTokenBalance (old) + deltaYield, this simplifies to old + deltaYield - usedYield = old + unusedYield.

As a result, the next redemption's deltaYield becomes futureBal - (old + unusedYield) = (futureBal - transmuterBal) + usedYield, effectively re-adding the consumed usedYield back into the available delta. This allows the same yield accrual to be reused as cover across multiple redemptions, potentially enabling attackers to redeem more debt than intended without consuming the underlying yield tokens.

Impact Details

This enables attackers to redeem more debt than intended by reusing the same yield accrual multiple times, inflating redemptions beyond the actual collateral available.

To fix the yield reuse issue, update lastTransmuterTokenBalance

References

https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi

Proof of Concept

Proof of Concept

Practical example: Denote old_last = lastTransmuterTokenBalance (before redemption). -vvvv

Was this helpful?