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 V3
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.
Recommended Mitigation
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?