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 V3
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?