56545 sc high force repayment leaves stale global earmarks freezing transmuter redemptions

Submitted on Oct 17th 2025 at 13:35:15 UTC by @vivekd for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56545

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

_forceRepay clears a borrower's earmarked debt locally but never subtracts the same amount from the global cumulativeEarmarked. After a handful of liquidations that hit _forceRepay, the protocol believes almost all debt is already earmarked; _earmark then stops allocating new debt to the Transmuter, so future redemptions can't be serviced and user funds become permanently stuck.

Vulnerability Details

  • During liquidation the contract calls _forceRepay (src/AlchemistV3.sol:819-823).

  • _forceRepay reduces the account's earmarked balance (src/AlchemistV3.sol:760-763), but unlike repay() or redeem() it never subtracts the removed amount from cumulativeEarmarked.

  • Consequently, cumulativeEarmarked only grows. Once it equals totalDebt, every call to _earmark() sees liveUnearmarked = totalDebt - cumulativeEarmarked == 0 (src/AlchemistV3.sol:1114), so no new debt is earmarked for the Transmuter queue.

  • When the next user tries to create or claim a redemption, the Transmuter finds no earmarked debt available and the system stalls even though borrowers are still solvent.

  • The issue does not require global insolvency; a single liquidation that enters _forceRepay is enough to corrupt the global counter.

Impact Details

  • Earmarking debt is the only way the protocol reserves collateral for Transmuter claims. Once cumulativeEarmarked stays stuck at its old value, new earmarks cease and redemption requests back up forever.

  • All alAsset holders waiting in the Transmuter queue are unable to exit, amounting to a permanent freeze of user funds.

  • Because the global counter is never corrected, the freeze persists across liquidations and redemptions until the contract is upgraded.

Proof of Concept

Proof of Concept

Logs:

The test performs the following:

  • Creates three borrowers (A, B, C), earmarks A and B by opening Transmuter redemptions.

  • Liquidates borrower A, which triggers _forceRepay.

  • Reads state afterwards: borrower-level earmarks are zero (attackerEarmarkAfter = 0, bystanderEarmarkAfter = 0) yet the global counter is still globalAfter = 8561643835616438357021.

Because the global counter is larger than the sum of account earmarks, future _earmark calls see no "live" debt and Transmuter redemptions cannot proceed, proving the permanent-freeze condition.

Was this helpful?