58306 sc critical repayment fee not adjusted for insufficient collateral

Submitted on Nov 1st 2025 at 06:28:12 UTC by @JoeMama for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58306

  • Report Type: Smart Contract

  • Report severity: Critical

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

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

Description

Brief/Intro

During liquidation, _forceRepay will try to reduce the debt. If the debt is cleared or the debt ratio becomes healthy again, the repayment fee for the caller is recalculated based on the new debt. However, this fee could be larger than the remaining collateral balance, meaning the collateral cannot fully cover the fee.

Vulnerability Details

The problem lies in _resolveRepaymentFee. It calculates the repayment fee from the debt and tries to deduct it from the collateral balance. However, even if the collateral isn’t enough to cover the full fee, the function still returns the total fee amount.

    function _resolveRepaymentFee(uint256 accountId, uint256 repaidAmountInYield) internal returns (uint256 fee) {
        Account storage account = _accounts[accountId];
        // calculate repayment fee and deduct from account
        fee = repaidAmountInYield * repaymentFee / BPS; // 1%

        account.collateralBalance -= fee > account.collateralBalance ? account.collateralBalance : fee;
        emit RepaymentFee(accountId, repaidAmountInYield, msg.sender, fee);
        return fee;
    }

Impact Details

If the full fee cannot be deducted from the collateral, the collateral balance becomes insufficient to cover it. As a result, other users may end up paying part of the fee.

https://gist.github.com/hexens-joe/564f4927c57da0d0dacb776c4a5a5733

Proof of Concept

Proof of Concept

please run the gist with forge test --mt testRepaymentFeeTakenFromAlchemist -vv

During this poc, console log was added to the relevant _resolveRepaymentFee function

The outputs:

Meaning the fee was more than collateralBalance, but still returned the full fee amount to be send to the caller.

Was this helpful?