58464 sc critical repayment fee paid from protocol funds when user collateral is depleted

Submitted on Nov 2nd 2025 at 14:06:02 UTC by @auditagent for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58464

  • 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

Brief/Intro

  • _resolveRepaymentFee calculates the full fee (repaidAmount * repaymentFee / BPS) and transfers that amount to the liquidator.

  • The collateral is only debited up to their remaining balance.

  • Liquidator receives full repayment fee from pool even when user's collateral couldn't cover it.

The docs claims that the Liquidator Fee Vault only covers fees when the user's collateral can’t pay the liquidator. https://keenanlukeom.github.io/alchemix-v3-docs/user/concepts/liquidations. However, this is not how the protocol behaves currently

Vulnerability Details

  1. _liquidate() calls _forceRepay() to clear earmarked debt using the user's collateral.

  2. If the user's debt is fully cleared or position becomes healthy after repayment, the function takes an early-return path that pays a repayment fee.

  3. _resolveRepaymentFee() computes the fee and deducts only what's available from user collateral:

  1. _liquidate() then transfers the full returned fee:

The docs mention expected behavior:

Should the user’s collateral not be sufficient on its own to pay a liquidator, there is a separate fee vault that may be funded by any entity (including the DAO) that may be drawn from to pay liquidators.

However currently the returned feeInYield is transferred to the liquidator unconditionally. There is no check to verify the user's collateral was sufficient.

Impact Details

  • Each earmark-only liquidation on an undercollateralized position drains protocol reserves equal to the fee shortfall

References

  • https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol#L900

  • https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol#L840

  • https://keenanlukeom.github.io/alchemix-v3-docs/user/concepts/liquidations/

Proof of Concept

Proof of Concept

Add the following PoC in src/test/AlchemistV3.t.sol and run using forge test --match-test Liquidate_EarmarkedRepaymentFeeShortfall_ComesFromPool

Was this helpful?