58447 sc critical unfair collateral loss through socialized redemption costs
Submitted on Nov 2nd 2025 at 12:36:43 UTC by @MahdiKarimi for Audit Comp | Alchemix V3
Report ID: #58447
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Protocol insolvency
Description
Overview
In AlchemistV3, the redemption process is designed such that the cost of redemption (collateral and protocol fees) is shared among all debt holders, while the benefit (debt reduction) is granted only to users with previously earmarked debt.
This creates an imbalance: if a user has no earmarked debt, their collateral still decays during a redemption, even though neither their debt nor earmark changes. This happens because, during the earmarking phase, earmarked debt is distributed across all existing debt holders. If a new user later joins the system by depositing collateral and minting debt, they start with zero earmarked balance. When a redemption then occurs, the collateral decay (representing redemption costs) is applied globally, causing the new user’s collateral to decrease—despite having no debt reduced or earmarked cleared.
As a result, users can lose collateral value without receiving any benefit, effectively subsidizing the redemptions of others.
Technical Description
Flawed Accounting Relationship
The issue stems from the contract’s global weight-based decay model. Two key weights control balance updates during redemptions:
_collateralWeight
Tracks collateral and fee removal after redemptions
Global (applies to all users)
_redemptionWeight, _earmarkWeight
Track debt earmarking and redemption decay
Per-user (applies only to earmarked users)
When a redemption occurs, redeem() reduces total collateral and updates _collateralWeight based on the redeemed amount. During the next _sync(), every user’s collateral balance is scaled down using this updated weight — even if they had no earmarked debt and thus no debt relief.
Meanwhile, the actual debt reduction in _sync() depends entirely on a user’s personal earmarked balance. If a user’s earmarked == 0, their debt remains unchanged, despite their collateral being reduced through the global decay.
The outcome:
Collateral losses are socialized across all users, but the debt benefit is privatized to earmarked users.
Demonstration (PoC)
A minimal test, test_PoC_SocializedDecay_UnfairCollateralLoss() which I have included can be added toAlchemistV3.t.sol, verifies this behavior.
Scenario
User A
Deposits
100e18collateralMints
90e18debt20e18of this debt is earmarked
User B (victim)
Deposits
100e18collateralMints
90e18debtHas no earmarked debt
Redemption phase
The transmuter redeems
20e18of User A’s debt._collateralWeightincreases to reflect total collateral withdrawn (principal + fee).
Sync phase
User B calls
poke(), triggering_sync()His collateral balance is reduced due to global
_collateralWeight, but his debt remains unchanged (sinceearmarked = 0).
Output Summary
User B’s debt is constant, yet they lose 10e18 collateral. That 10e18 was effectively seized to fund the redemption of User A’s earmarked debt.
Impact
This is direct loss of collateral for users
Root Cause Summary
_collateralWeight (global)
Applies redemption costs to all users
Collateral loss for unrelated users
_redemptionWeight / _earmarkWeight (per-user)
Restrict debt relief to earmarked accounts
Benefit limited to selected users
_sync()
Uses global decay for collateral but per-user weights for debt
Accounting mismatch between loss and benefit
Recommended Fix
During
_sync(), calculatecollateralToRemoveas a function of each user’s personal redeemedTotal (the portion of their earmarked cleared).Apply decay locally, ensuring:
Users without earmarked debt do not lose collateral.
Debt and collateral adjustments remain symmetrical.
Proof of Concept
Proof of Concept
Was this helpful?