57590 sc critical double counted transmuter cover in redeem allows overstated redemptions and potential over withdraw over borrow

Submitted on Oct 27th 2025 at 11:16:27 UTC by @algiz for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57590

  • 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

When repay() and redeem() execute in the same block, AlchemistV3.redeem() subtracts too much from totalDebt and cumulativeEarmarked. It reduces both the actual redeemed amount and the coverToApplyDebt portion, even though that cover was already accounted for earlier in repay(). This clears the same debt twice. The extra “forgiven” debt then propagates to every borrower’s account on their next sync(), making their recorded debt and earmark lower than it should be.

With artificially low debt, borrowers can withdraw more collateral or borrow more alAssets than they should be allowed to, which directly extracts value from the protocol and drives it toward insolvency.

Vulnerability Details

The bug becomes exploitable when repay() and redeem() are both executed in the same block. In that case, the call to _earmark() inside redeem() returns early and does not refresh lastTransmuterTokenBalance to reflect the new MYT that was just transferred to the Transmuter during repay(). As a result, when redeem() runs later in the same block, it observes a higher current Transmuter balance than the stale lastTransmuterTokenBalance, and treats the difference as newly available “cover.” This produces a nonzero coverToApplyDebt, which is included in redeemedDebtTotal.

redeemedDebtTotal is then subtracted from both totalDebt and cumulativeEarmarked. The problem is that the portion of redeemedDebtTotal coming from coverToApplyDebt corresponds to debt that was already accounted for during repay() in the same block: that debt was already deducted from totalDebt and cumulativeEarmarked when the borrower repaid and transferred MYT to the Transmuter. Subtracting it again in redeem() clears the same debt twice.

This double-clearing also feeds into the global decay variables (_survivalAccumulator, _redemptionWeight), which means the reduced debt and reduced earmark are treated as legitimate system-wide progress. On subsequent sync() calls, that state propagates to all borrowers. Their positions will reflect less outstanding debt, which lets them withdraw more collateral or mint more debt than they should be allowed to.

Impact Details

The issue lets an attacker repeatedly front-run redemption claims in the same block as their own repayments to artificially erase system debt. This allows borrowers across the system to withdraw excess collateral / mint additional alAssets they shouldn’t be allowed to, and can leave future redeemers underpaid, pushing the protocol toward insolvency.

References

Debt state updates during repay():

Early return in earmark() - LINKarrow-up-right

Second subtraction of totalDebt / cumulativeEarmark - [LINK](https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L596-L616]

Proof of Concept

Proof of Concept

  1. Inject the following test into the AlchemistV3.t.sol:

  1. Run the test with the following command:

  1. Output:

Was this helpful?