56714 sc high accounting invariant violation in forcerepay leads to protocol insolvency

Submitted on Oct 19th 2025 at 20:44:55 UTC by @HandsomeEarthworm6 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56714

  • 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

The _forceRepay function in AlchemistV3 fails to update the global cumulativeEarmarked counter when repaying a user's earmarked debt during liquidation. This breaks a critical accounting invariant that the protocol depends on for all debt management operations.

The Invariant: cumulativeEarmarked = Σ(all users' account.earmarked)

This invariant is fundamental to the protocol's earmarking and redemption system, which manages how debt transitions from normal to earmarked status over time, and how redemptions are distributed fairly across all borrowers.

Vulnerability Details

In the _forceRepay function , when earmarked debt is repaid, only the user's individual account.earmarked is decremented, but the global cumulativeEarmarked counter is not updated:

function _forceRepay(uint256 accountId, uint256 amount) internal returns (uint256) { if (amount == 0) { return 0; } _checkForValidAccountId(accountId); Account storage account = _accounts[accountId];

Impact Details

Once the invariant is broken, the protocol suffers cascading failures:

1.Incorrect Unearmarked Debt Calculation: The _earmark() function calculates available unearmarked debt as:

uint256 liveUnearmarked = totalDebt - cumulativeEarmarked; If cumulativeEarmarked is inflated (not decremented during force repay), liveUnearmarked becomes artificially low or even underflows to zero, preventing any new debt from being earmarked.

2.Broken Weight System: The earmark weight is calculated as:

_earmarkWeight += PositionDecay.WeightIncrement(amount, liveUnearmarked); With incorrect liveUnearmarked, the entire weight system (earmark weight, redemption weight, collateral weight, survival accumulator) becomes corrupted, affecting all CDP sync operations.

3.Redemption Failures: The redeem() function caps redemptions at:

uint256 liveEarmarked = cumulativeEarmarked; if (amount > liveEarmarked) amount = liveEarmarked;

Was this helpful?