58280 sc critical repayment s fee is charged from other users causing the contract to fail when the myt total balance of a user cannot cover the fee

Submitted on Oct 31st 2025 at 22:57:26 UTC by @Outliers for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58280

  • 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

Brief/Intro

There’s an accounting issue in the liquidation logic that occurs after a force repay. When a user’s account doesn’t have enough collateral left to cover the liquidator fee, the protocol still transfers the full fee to the liquidator using the Alchemist contract’s own balance. In short, the protocol ends up paying liquidators out of its own funds instead of the debtor’s

Vulnerability Details

   /// @dev Handles repayment fee calculation and account deduction
    /// @param accountId The tokenId of the account to force a repayment on.
    /// @param repaidAmountInYield The amount of debt repaid in yield tokens.
    /// @return fee The fee in yield tokens to be sent to the liquidator.
    function _resolveRepaymentFee(uint256 accountId, uint256 repaidAmountInYield) internal returns (uint256 fee) {
        Account storage account = _accounts[accountId];
        // calculate repayment fee and deduct from account
       
@audit>>>           fee = repaidAmountInYield * repaymentFee / BPS;
        emit balancebefore(account.collateralBalance,fee );

@audit>>>        account.collateralBalance -= fee > account.collateralBalance ? account.collateralBalance : fee;

@audit>>>          emit RepaymentFee(accountId, repaidAmountInYield, msg.sender, fee);                                  
@audit>>>          return fee;  // bug returned value is wrong // cap at collateral balance actually or ensure we remove from balance before 
    }


However, the issue arises when the user’s collateralBalance is less than the expected feeInYield. Even though the balance check is in place, the contract does not reduce or cap the fee based on what the account can actually cover. In scenarios where the user’s collateral has already been partially depleted (for example, through force repayment), the remaining balance may be insufficient to pay the full fee.

Despite that, the protocol still proceeds with the transfer to the liquidator — meaning the excess amount is effectively taken from the Alchemist contract’s token balance, not from the user’s account. This causes the system to subsidize liquidators using protocol funds.

In short:

  1. Force repay reduces the user’s balance.

  2. Liquidation logic calculates a repayment fee.

  3. Even if the user has little or no collateral left, the fee is still transferred out.

  4. The contract’s own MYT balance covers the shortfall.

Impact Details

This can occur in every liquidation occurring after a partial or complete force repayment. Protocol funds (MYT tokens) will be incorrectly reduced over time as liquidators receive more than what was available in the user’s account.

References

  • _doLiquidation()

  • Post-force-repay liquidation branch that handles feeInYield transfer

Proof of Concept

Proof of Concept

Was this helpful?