57483 sc medium fees could be skipped when there is not enough collateral

Submitted on Oct 26th 2025 at 15:52:02 UTC by @a16 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57483

  • Report Type: Smart Contract

  • Report severity: Medium

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

  • Impacts:

    • Theft of unclaimed yield

Description

Brief/Intro

In multiple places throughout the code base, fee transfers are skipped entirely when the remaining amount to pay from is less than the calculated fee.

Vulnerability Details

When calling _liquidate(), _forceRepay() is first being called, which only transfers the fee to the protocolFeeReceiver if the remaining collateral is larger than the calculated owed fee.

    if (account.collateralBalance > protocolFeeTotal) {
        account.collateralBalance -= protocolFeeTotal;
        // Transfer the protocol fee to the protocol fee receiver
        TokenUtils.safeTransfer(myt, protocolFeeReceiver, protocolFeeTotal);
    }

A similar pattern also exists in _doLiquidation() where feeInYield is supposed to be transferred to msg.sender:

This means that if the remaining collateral is smaller than the calculated fee, no fee is taken at all, not even the remaining collateral (which would be a partial amount of the calculated fee).

Impact Details

The impacts of this behavior include:

  1. A loss of fees generated by the protocol

  2. A broken incentive mechanism for liquidations, where users might not be incentivized to liquidate certain positions because it would result in them not receiving liquidation fees, potentially leading to those positions eventually going underwater, resulting in undercollateralization and protocol loss of funds.

  3. The creation of a paradoxical situation where a lower fee rate could actually lead to more fees taken and a higher user collateral could actually mean more fees taken from the user.

Suggestion

Use the remaining collateral to partially pay the calculated fees. For example, in the first case, do something like this:

Proof of Concept

The following function should be added to AlchemistV3.t.sol. It demonstrates that the protocolFeeReceiver receives no fees from the liquidation, but when the ProtocolFee parameter is changed to 0.5%, the protocolFeeReceiver suddenly receives fees, and the test fails.

function test_ForceRepay_Skips_ProtocolFee_When_RemainingCollateralTooSmall() external { console.log("Step 1: Setup protocol fee and raise deposit caps");

}

Was this helpful?