58507 sc critical repayment fee after forcerepay could result in socialized loss during global undercollateralization
Submitted on Nov 2nd 2025 at 21:19:12 UTC by @algizsec for Audit Comp | Alchemix V3
Report ID: #58507
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
When a user gets liquidated, the system first attempts to heal his position by clearing earmarked debt (by forcing a repay). When all debt is cleared a repaymentFee should be paid. In case the remaining account.collateralBalance cannot cover the full repaymentFee, the fee gets socialized between the other users, even though the system could be globally undercollateralized. Instead of covering the liquidation fees from the feeVault, AlchemistV3 uses its own MYT balance to pay the liquidator. Subsequently, this could result in insolvency for withdraws/redemptions from other users.
Furthermore, the behaviour is inconsistent with the way the system pays feeInYield during an actual liquidation. During actual liquidation, feeInYield is only paid to liquidators if there is enough account.collateralBalance. There is an additional bug in this part of the logic which checks the account.collateralBalance >= feeInYield after the balance has already been reduced with the fee.
Vulnerability Details
Inside
_resolveRepaymentFee():
fee = repaidAmountInYield * repaymentFee / BPS;
account.collateralBalance -= fee > account.collateralBalance ?account.collateralBalance : fee;However, after _resolveRepaymentFee() we have:
which deduces MYT from the overall balance, but total effective mytSharesDeposited remain higher. (Note: there is another unrelated issue where the mytSharesDeposited does not get subtracted during resolveRepaymentFee(). )
In this scenario, the user could not cover the repayment fee. Even if the system is globally under the minimumCollateralization, instead of covering the fee using the feeVault, the fees are covered by the other users. This could result in insolvency for late withdraws / redemptions (unless the protocol injects liquidity from the feeVault).
Inside
_doLiquidation():
amountLiquidated includes debtToBurn and baseFee (liquidator's fee). The code first deducts the entire amountLiquidated from the user, then sends the net (amountLiquidated - feeInYield) to the Transmuter. The remaining "slice" corresponds to feeInYield and is supposed to be paid to the liquidator. However, the payment of that fee is incorrectly conditioned on the user's post-deduction from collateralBalance. If after the deduction, the user's balance is < feeInYield, the liquidator gets nothing, even though the fee slice was already taken from the user's funds.
The unpaid fee remains stranded inside the Alchemist contract - accounting-wise removed from the user, but not transferred to anyone.
Impact Details
During market stress,(e.g when MYT price falls), liquidations can yield repayment fees, which might not be payable by the liquidated users. The shortfall is socialized, which would be acceptable if the system is above the minimumCollateralization. If the system is globally undercollateralized, it will let the users bear the losses instead of covering them from the feeVault.
Furthermore, the way the system pays liquidators is inconsistent depending on if there is earmark or not. In some edge scenarios without earmarks, liquidators could end up receiving no fees due to a flaw in the logic.
References
[resolve repayment fees 1] (https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L825-L826)
[resolve repayment fees 2] (https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L903-L904)
[feeInYield flaw] (https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L871-L880)
Proof of Concept
The PoC demonstrates how a price drop of 10% for MYT could easily result in a socialized loss of magnitude the repayment fee for a force repaid position (2% in this case). For bigger positions, this amount could be significant, amplifying the effect on other users. The PoC finally demonstrates the inability of a user without any debt to withdraw his collateral fully.
Add the following test to the
AlchemistV3.t.sol:
Make
_mytSharesDepositedin AlchemistV3 public for the sake of the PoCRun the test with:
forge test --mt testLiquidate_Undercollateralized_Position_With_Earmarked_Debt_Liquidation_10Percent_Yield_Price_Drop -vvOutput:
Was this helpful?