58338 sc critical alchemistv3 repayment fee can exceed remaining collateral leading to position insolvency
Submitted on Nov 1st 2025 at 11:23:00 UTC by @T0nraq for Audit Comp | Alchemix V3
Report ID: #58338
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Description
Summary
When liquidating an account with earmarked debt, the repayment fee may be larger than the account’s remaining collateral after the force repayment and protocol fee are deducted. The helper _resolveRepaymentFee caps the deduction from collateralBalance, but it still returns the raw, larger fee value. The caller then transfers this larger amount to the liquidator, overpaying them from protocol-held tokens (i.e., other users’ deposits), leaving the liquidated position at zero collateral and creating an accounting shortfall. This shortfall also can/is not be added to earmarkable debt and cannot be shared globally to restore accounting parity
Vulnerability Details
_resolveRepaymentFee returns raw fee instead of the actually deducted amount
File: src/AlchemistV3.sol
function _resolveRepaymentFee(uint256 accountId, uint256 repaidAmountInYield) internal returns (uint256 fee) {
Account storage account = _accounts[accountId];
fee = repaidAmountInYield * repaymentFee / BPS; // raw fee
// Deduct only what the account can afford (caps at remaining collateral)
account.collateralBalance -= fee > account.collateralBalance ? account.collateralBalance : fee;
emit RepaymentFee(accountId, repaidAmountInYield, msg.sender, fee);
return fee; // BUG: returns raw fee, not deducted amount
}Liquidation uses the returned (raw) value for transfer
File: src/AlchemistV3.sol
Simple example
Start with a position that, after force repayment and protocol fee, has 4 MYT collateral left.
Repayment fee calculation yields 9 MYT.
_resolveRepaymentFeededucts only 4 MYT from the account (capped at remaining collateral) but still RETURNS 9 MYT.Liquidation code transfers the returned 9 MYT to the liquidator.
Result: liquidator receives 9 MYT while the account could only afford 4 MYT. The 5 MYT shortfall is taken from protocol-held tokens (others’ deposits), position ends with 0 collateral, and the system is overpaying fees.
Impact
Overpayment to liquidators using protocol-held tokens (socializes losses across depositors).
Liquidated positions end at zero collateral even when they could not afford the full fee.
Accounting shortfall that compounds over multiple liquidations; can lead to insolvency risk and misleading system metrics.
Recommended fix (return the actual deducted amount)
Return the amount actually deducted from collateralBalance, not the raw fee. Keep calculation simple and safe by capping first, then subtracting, then returning what was paid.
Proof of Concept
Proof of Concept
Test file:
src/test/AlchemistV3.t.solAdd test function (snippet below):
testLiquidate_Undercollateralized_Position_With_Earmarked_Debt_Insufficient_Collateral_For_RepaymentFee()
Run the POC:
Was this helpful?