58762 sc insight manipulation of feeinunderlying through front running during liquidations on ethereum

Submitted on Nov 4th 2025 at 12:15:35 UTC by @sol_4th05 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58762

  • Report Type: Smart Contract

  • Report severity: Insight

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

  • Impacts:

    • Permanent freezing of unclaimed royalties

Description

Brief/Intro

During the liquidation process on the Ethereum blockchain in case alchemistCurrentCollateralization < globalMinimumCollateralization the feeInUnderlying value can be manipulated (reduced to 0) by an attacker at no costs just paying transaction gas fee. This way the attacker prevent the user who started the liquidation process from receiving its feeInUnderlying for that liquidation.

Vulnerability Details

When all the conditions are met and a user calls the function AlchemistV3::Liquidate, it calls in turn these functions to liquidate a position _liquidate, _doLiquidation, calculateLiquidation. Considering the latter, it returns among the others the following value: outsourcedFee. Then that value is used for the calculation of the feeInUnderlying that is sent to the user.

function _doLiquidation(uint256 accountId, uint256 collateralInUnderlying, uint256 repaidAmountInYield)
        internal
        returns (uint256 amountLiquidated, uint256 feeInYield, uint256 feeInUnderlying)
    {
        Account storage account = _accounts[accountId];

@>      (uint256 liquidationAmount, uint256 debtToBurn, uint256 baseFee, uint256 outsourcedFee) = calculateLiquidation(
            collateralInUnderlying,
            account.debt,
@>          minimumCollateralization,
@>          normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt,
@>          globalMinimumCollateralization,
            liquidatorFee
        );
                    .
                    .
                    .

        // Handle outsourced fee from vault
        if (outsourcedFee > 0) {
            uint256 vaultBalance = IFeeVault(alchemistFeeVault).totalDeposits();
            if (vaultBalance > 0) {
@>              uint256 feeBonus = normalizeDebtTokensToUnderlying(outsourcedFee);
@>              feeInUnderlying = vaultBalance > feeBonus ? feeBonus : vaultBalance;
@>              IFeeVault(alchemistFeeVault).withdraw(msg.sender, feeInUnderlying);
            }
        }

        emit Liquidated(accountId, msg.sender, amountLiquidated + repaidAmountInYield, feeInYield, feeInUnderlying);
        return (amountLiquidated + repaidAmountInYield, feeInYield, feeInUnderlying);
    }

The alchemistCurrentCollateralization is a parameter given to the function AlchemistV3::calculateLiquidation.

alchemistCurrentCollateralization = (_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt

However, looking inside the function AlchemistV3::calculateLiquidation, the value above is used in order to determine whether outsourcedFee = (debt * feeBps) / BPS; or it will be 0 otherwise.

Thus, it is possible for an attacker to front-run liquidations of positions being in this specific condition.

  • collateralizationRatio <= collateralizationLowerBound

  • alchemistCurrentCollateralization < globalMinimumCollateralization

  • collateral > debt

For these positions, the attacker can front-run the liquidation transaction depositing additional myt tokens. The attacker can front-run liquidations depositing an amount of myt increasing the _mytSharesDeposited value so that alchemistCurrentCollateralization >= globalMinimumCollateralization.

By doing this simple front run action, the attacker can set the value of feeInUnderlying to 0.

Impact Details

Due to this attack the user starting the liquidation process will not get any feeInUnderlying even if it should have been >0.

The impact of the attack is twofold:

From one hand the feeInUnderlying that should have gone to the user it is reset by the attacker and on the other hand the protocol can't incentivize users to make liquidations with the feeInUnderlying as yield running this way the risk to leave active bad debt positions for a long time.

The amount of the feeInUnderlying as yield lost by the users, varies depending on what is the value of feeBps and the value of the debt. However, it should always be relevant and never negligible because of the reason explained before.

References

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

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

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

Mitigation

A possible mitigation action could be the one below:

https://gist.github.com/0x4th05/dc0e99b72696e2e3a579d5e27d21b170

Proof of Concept

Proof of Concept

Add the following test to the test suite (AlchemistV3.t.sol) of the project, and run this: forge test --mt testManipulationUnderlyingFeeInLiquidations -vvvvv and it should pass

As an additional proof of the issue, you can run the same test (without the front-running action) noting that the feeInUnderlying value it's != 0 as it should always be.

forge test --mt testUnderlyingFeeInLiquidationsWithoutManipulation -vvvvv

The result of the 2 tests:

Was this helpful?