58456 sc medium account can enter unliquidatable state with residual debt
Submitted on Nov 2nd 2025 at 13:38:14 UTC by @gizzy for Audit Comp | Alchemix V3
Report ID: #58456
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Description
Summary
The liquidation flow in AlchemistV3._liquidate() has a critical flaw in its ordering of operations. After _forceRepay() repays earmarked debt and potentially brings an account back to healthy collateralization, the function deducts a repayment fee that can consume ALL remaining collateral. This leaves the account with residual debt but ZERO collateral, creating an unliquidatable state where the account cannot be liquidated again, repaid, or burned.
Additionally, if the user claims their redemption from the Transmuter, totalSyntheticsIssued drops to 0 while the account still has debt, breaking the protocol's core accounting invariant.
Vulnerability Details
Root Cause
In AlchemistV3.sol:798-851, the _liquidate() function follows this sequence:
Line 828: Call
_forceRepay()to repay earmarked debtLine 831-835: If debt is fully cleared (debt == 0), calculate and deduct repayment fee, then return
Line 838-844: Otherwise, recalculate collateralization ratio and check if liquidation is needed
Line 845-850: If ratio is now healthy (>
collateralizationLowerBound), calculate and deduct repayment fee, then return
The bug occurs in step 4: The repayment fee is calculated based on the full amount repaid by _forceRepay(), but the collateralization check in step 3 does NOT account for this fee deduction.
Repayment Fee Calculation
From AlchemistV3.sol:909-916:
Key Issue: If fee > account.collateralBalance, the entire remaining collateral is wiped to 0, while still with debt .
Step-by-Step Attack Vector
Initial Setup
Alice: Deposits 1,000 MYT shares worth $1,000 USD
Bob: Deposits 1,000 MYT shares worth $1,000 USD (provides liquidity, doesn't borrow)
Alice: Mints maximum debt = $900 (90% LTV at 110% minimum collateralization)
Alice: Creates redemption in Transmuter with all 900 alTokens
Attack Execution
Step 1: Wait for Full Transmutation
Time passes: (5,256,000 blocks)
Earmarked debt decays to ≈$899 due to rounding
Step 2: Price Crash
MYT price drops 9.5%
Alice's collateral value: 1,000 shares × $0.905 = $905
Alice's debt: $900
Alice's earmarked: $899
Collateralization ratio: $905 / $900 = 100.5% (below 105%
collateralizationLowerBound)
Step 3: Liquidator Calls liquidate(aliceTokenId)
The function executes as follows:
this makes
Cannot liquidate again:
liquidate()reverts withLiquidationError()because:collateralInUnderlying = 0collateralizationRatio = 0 / debt = 0%Check passes (0% < 105%), proceeds to repay
_forceRepay()tries to repay butaccount.earmarked = 0, returns 0debtis still > 0, so continues to_doLiquidation()calculateLiquidation()returnsliquidationAmount = 0(no collateral to seize)Function returns
(0, 0, 0)which triggers revert at line 556
Cannot burn/repay: If Alice tries to burn alTokens to repay the debt:
Protocol accounting breaks: If Alice claims her Transmuter redemption:
Transmuter calls
alchemist.redeem(899 alTokens)This burns 900 alTokens from circulation
totalSyntheticsIssued -= 900→ becomes 0BUT Alice's account still has debt
Broken invariant:
totalDebt > totalSyntheticsIssued(debt exists with 0 synthetics in circulation)
Impact
Denial of Service (DOS)
Account becomes permanently unliquidatable,cant burn and repay. Protocol Accounting Corruption
totalDebt>totalSyntheticsIssuedbreaks core invariantFunctions relying on this invariant may malfunction
_earmark()could enter undefined behavior
Bad Debt Accumulation
Residual debt becomes uncollectable "ghost debt"
Scales across multiple users during market volatility
Likelihood
High - This occurs in common scenarios:
Any price drop of 5-15% for high-LTV positions
Repayment fee > 1% (default is 3%, making it very likely)
##Reference
https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L818C7-L842C10
Recommended Fix
Solution: Deduct Repayment Fee BEFORE Health Check
File: src/AlchemistV3.sol:798-851
Modify the _liquidate() function to deduct the repayment fee immediately after _forceRepay(), then perform the health check on the adjusted collateral balance.
Proof of Concept
Proof of Concept
Add this test to src/test/AlchemistV3.t.sol (https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/test/AlchemistV3.t.sol):
Expected Output
Was this helpful?