57977 sc high inconsistent rawlocked state of a user after subdebt leads to irrecoverable user collateral loss

Submitted on Oct 29th 2025 at 18:43:24 UTC by @damdam0249 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57977

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

Description

Brief/Intro

A logic flaw exists in the collateral management system where a user's locked collateral (rawLocked) is not properly recalculated after their debt is fully extinguished via liquidation/subdebt. This state inconsistency leads to the user being incorrectly charged collateral during subsequent synchronization events, resulting in a direct, permanent loss of user funds.

Vulnerability Details

The root cause is a missing state synchronization between a user's debt and their locked collateral. The liquidation logic correctly caps the amount of collateral to free (toFree) to prevent underflows but fails to ensure that the rawLocked value is updated to reflect the new debt state. This can leave the accounting in an inconsistent state where account.debt == 0 but account.rawLocked > 0.

The problematic flow occurs in two stages:

Stage 1: Incomplete State Update during Liquidation In the liquidate() function, the following code handles the debt and collateral update in the subdebt function:

While this prevents underflows, it uses a delta-based update for rawLocked instead of recalculating it from the remaining debt. If the liquidation clears the user's debt (account.debt becomes 0), the rawLocked value should also be zero. However, the current logic leaves a non-zero rawLocked and a 0 total debt value.

Stage 2: Incorrect Collateral Deduction during Synchronization Later, when _sync() is called (e.g., after a redemption), it calculates collateral to remove based on the outdated rawLocked value:

Because account.rawLocked is non-zero despite the user having no debt, the ScaleByWeightDelta function returns a positive collateralToRemove value. This unfairly reduces the user's free collateralBalance.

Impact Details

Primary Impact: Direct loss of user collateral. Users who have had their debt fully liquidated will have further collateral incorrectly deducted from their balance during the next sync operation.

Secondary Impacts: * Protocol accounting inconsistency between total locked collateral and actual user debt. * In severe cases, this could contribute to protocol insolvency by incorrectly removing collateral from the system that is not backed by any debt.

References

Proof of Concept

Proof of Concept

  1. User A has a position with debt = 100 and rawLocked = 120.

  2. A liquidation event clears User A's entire debt (debt becomes 0). The capped logic sets rawLocked = 20 (for example), instead of recalculating it to 0.

  3. Later, a transmuter redemption triggers a _sync() for User A's position.

  4. _sync() calculates collateralToRemove based on rawLocked = 20.

  5. User A's collateralBalance is decremented by collateralToRemove, even though they have no outstanding debt, resulting in a permanent loss of funds.

Result of test

Was this helpful?