57476 sc high forcerepay fails to decrement global cumulativeearmarked

Submitted on Oct 26th 2025 at 14:45:14 UTC by @Paludo0x for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57476

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

During liquidations, AlchemistV3::_forceRepay() correctly reduces the account earmarked value account.earmarked but does not reduce the global earmarked cumulativeEarmarked.

Vulnerability Details

The buggy snippet is the following:

    function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) {
      ....
        // Repay debt from earmarked amount of debt first
        uint256 earmarkToRemove = credit > account.earmarked ? account.earmarked : credit;
        account.earmarked -= earmarkToRemove;

        creditToYield = creditToYield > account.collateralBalance ? account.collateralBalance : creditToYield;
        account.collateralBalance -= creditToYield;

        uint256 protocolFeeTotal = creditToYield * protocolFee / BPS;

        emit ForceRepay(accountId, amount, creditToYield, protocolFeeTotal);

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

        if (creditToYield > 0) {
            // Transfer the repaid tokens from the account to the transmuter.
            TokenUtils.safeTransfer(myt, address(transmuter), creditToYield);
        }
        return creditToYield;
    }

While in repay() call global earmarked is reduced:

This breaks the core invariant that the global earmark tracks the aggregate of user earmarks.

Because cumulativeEarmarked is used both as the denominator for redemption weighting and to compute liveUnearmarked = totalDebt - cumulativeEarmarked for future earmarks, the error propagates into worngly priced redemptions and persistent accounting drift.

These are the relevant snippet where cumulativeEarmarked is used:

Impact Details

These are the main impacts:

  • Users debts are reduced less than they should during redemptions

  • Protocol global accounting corruption: divergence between burned synthetics and aggregate debt reduction

  • Protocol risk over time: repeated liquidations via _forceRepay() can degrade solvency

Given the above impacts the severity is CRITICAL.

Proof of Concept

Proof of Concept

The PoC forces calling of _forceRepay() during a liquidation and demonstrates that global cumulativeEarmarked doesn't decrease after a liquidation.

This is the log:

Was this helpful?