57825 sc high forced repay cover enables double counted debt reduction in redeem
Submitted on Oct 29th 2025 at 04:28:50 UTC by @zcai for Audit Comp | Alchemix V3
Report ID: #57825
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Description
Brief/Intro
The _forceRepay() function reduces an account's earmarked debt and transfers MYT to the Transmuter but fails to decrement the global cumulativeEarmarked counter. This oversight allows the redeem() function to treat previously repaid funds as fresh cover, enabling double-counting where totalDebt is reduced twice for the same underlying repayment. This is a High severity vulnerability that corrupts core system accounting under normal liquidation and redemption operations.
Vulnerability Details
When _forceRepay() processes a liquidation, it correctly reduces the account's earmarked debt and transfers the corresponding MYT tokens to the Transmuter. However, it does not decrement the global cumulativeEarmarked variable, leaving it inflated relative to the actual sum of individual account earmarks.
The redeem() function calculates available cover by examining the Transmuter's MYT balance increase (deltaYield) and applies this cover to reduce both cumulativeEarmarked and totalDebt. Since the global earmarked counter was never decremented during forced repayment, redeem() sees the forced-repaid MYT as legitimate cover and reduces totalDebt a second time for the same underlying repayment.
During liquidation,
_forceRepay()reducesaccount.earmarked(lines 761-762) but leavescumulativeEarmarkedunchanged.MYT tokens are transferred to the Transmuter (line 779).
In the same block, Transmuter calls
redeem();_earmark()early-returns (same block), solastTransmuterTokenBalanceis not refreshed to include the transfer from step 2.redeem()computesdeltaYieldagainst the stale snapshot and treats the newly transferred MYT as fresh cover.Because
liveEarmarked = cumulativeEarmarkedstill includes the previously repaid amount, the cover is applied against inflated earmarked debt.Both
cumulativeEarmarkedandtotalDebtare decremented again (lines 613-616), double-counting the same repayment.
The root cause is the missing global earmark adjustment in _forceRepay(), contrasting with the correct implementation in the regular repay() function which properly decrements cumulativeEarmarked (lines 525-526). does exist.
Impact Details
This vulnerability breaks core solvency accounting by causing totalDebt to be understated relative to outstanding synthetic tokens. The double-counted debt reduction corrupts fundamental system invariants that underpin liquidation thresholds, redemption calculations, and global collateralization ratios. This accounting desynchronization can lead to unfair value distribution, incorrect liquidation triggers, and systematic tracking errors that compound over time as more forced repayments and redemptions occur
References
https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L738-L782
https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L589-L641
Link to Proof of Concept
https://gist.github.com/i-am-zcai/57c87d99bc1fc262c99c1053a7551516
Proof of Concept
Proof of Concept
Command:
Impact (demonstrated by the PoC):
totalDebtdecreases once during liquidation (via_forceRepay) and again duringredeem(0)in the same block, for the same underlying repayment.cumulativeEarmarkedis reduced duringredeem()despite the corresponding account earmark already being cleared during_forceRepay().No additional collateral leaves the Alchemist on
redeem(0), yettotalDebtfalls, breaking solvency accounting invariants.
Was this helpful?