57345 sc high missing cumulativeearmarked decrement in forcerepay breaks earmarking invariant leading to unfair redemption burden distribution

Submitted on Oct 25th 2025 at 11:57:20 UTC by @Smartkelvin for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57345

  • Report Type: Smart Contract

  • Report severity: High

  • 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

The _forceRepay() function, called during liquidations to repay earmarked debt using a user's collateral, fails to decrement the global cumulativeEarmarked state variable despite reducing the user's local account.earmarked. This breaks the critical accounting invariant that cumulativeEarmarked should equal the sum of all users' account.earmarked values. This causes the protocol to understate available unearmarked debt, throttling new earmarking operations, and creates an unfair distribution of redemption burdens where non-liquidated users bear disproportionate losses during transmuter redemptions

Vulnerability Details

The Accounting Invariant The protocol maintains a critical invariant:

cumulativeEarmarked = Σ(all account.earmarked values) This invariant ensures accurate tracking of how much debt has been earmarked for redemption across all position.

In _forceRepay() (in AlchemistV3.sol)

Compare this with the correct implementation in repay()

The _subDebt() function includes a safety clamp:

However, this only prevents cumulativeEarmarked from exceeding totalDebt, not from being overstated relative to actual user earmarks. This persists and accumulates with each liquidation.

Impact Details

Throttled Earmarking of New Debt The _earmark() function calculates available unearmarked debt as: uint256 liveUnearmarked = totalDebt - cumulativeEarmarked;

When cumulativeEarmarked is overstated, liveUnearmarked is understated, causing:

  • Less new debt gets earmarked than should be

  • New borrowers receive inadequate earmark coverage

  • The transmuter queue doesn't grow proportionally with new debt

Example:

Actual State:

  • Total debt: 200e18

  • Actual sum of earmarks: 50e18

  • Should earmark: 150e18

  • cumulativeEarmarked: 140e18 (overstated by 90e18 from liquidations)

  • liveUnearmarked: 200 - 140 = 60e18

  • Only 60e18 will be earmarked instead of 150e18

  • 90e18 of debt is incorrectly excluded from earmarking

2. Unfair Redemption Burden Distribution

3. Compounding Effect

Each liquidation that triggers _forceRepay adds to the desynchronization:

  • First liquidation: +90e18

  • Second liquidation: +50e18

  • Total : 140e18

Proof of Concept

Proof of Concept

add the test function to the test file of the contract

Was this helpful?