57585 sc high alchemistv3 does not properly update cdp collateralbalance when redemptions exceed totallocked which enables some cdps to over withdraw collateral on account of others

Submitted on Oct 27th 2025 at 11:06:05 UTC by @niroh for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57585

  • 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

AlchemistV3 implements a mechanism to distribute redemption collateral payments pro-rata beween CDPs. This mechanism can be described as follows:

  1. Each CDP tracks its rawLocked amount, which, with each change in the CDP debt, gets added/removed the amount of collateral required to cover the added/removed debt with minimumCR (based on collateral price at the time the debt was added or removed).

  2. A totalLocked state variable maintains a sum of the rawLocked for all CDPs in the system.

  3. When collateral is paid out in redeem, the part of that collateral each CDP pays is tracked with the help of the _collateralWeight accumulator that accumulates the ratio of collateral payment to totalLocked of each redeem(). When a CDP is synced, its share of collateral payments is calculated from the colateralWeight diff since last sync and its rawLocked. That share is then subtracted from the CDP collateralBalance.

//FROM ALCHEMISTV3 Redeem

 // move only the net collateral + fee
uint256 collRedeemed  = convertDebtTokensToYield(amount);
uint256 feeCollateral = collRedeemed * protocolFee / BPS;
uint256 totalOut      = collRedeemed + feeCollateral;

// update locked collateral + collateral weight
uint256 old = _totalLocked;
_totalLocked = totalOut > old ? 0 : old - totalOut;
_collateralWeight += PositionDecay.WeightIncrement(totalOut > old ? old : totalOut, old);

Vulnerability Details

The problem occurs in situations where a redeem() collateral payment exceeds totalLocked. Since rawLocked/totalLocked only accounts for minimumCR above debt at the time the debt was added (and does not update backwards), if collateral price decreases significantly, redeem payments may exceed totalLocked.

Note in the code snippet above, that in the case of the paid amount exceeding totalLocked, the collateralWeight is only updated up to totalLocked, meaning the amount of collateral paid above totalLocked is not reduced pro-rata from CDPs collateralBalance.

This creates a situation where the sum of all CDP.collateralBalance is below the actual collateral available in AlchemistV3. This is because each CDP holds a collateralBalance amount higher then they should actually have (and that the system can cover).

A CDP owner who is aware of this issue can track the system for such a scenario, and immediately (after burning any remaining debt they may have) redeem their inflated collateralBalance. This will enable them to withdraw more collateral than they should actually have, on account of other CDP holders who will not be able to withdraw even their fair share of collateral (ignoring the excessive part of their collateralBalance) because of the over-withdrawal of faster-to-act CDP owners. (see POC for example)

Impact Details

Loss of funds (deposited collateral) for CDP owners who are last to withdraw in this scenario

References

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

Proof of Concept

Proof of Concept

To run:

  1. Copy the code below into the IntegrationTest contract is test/IntergrationTest.t.sol

  2. Comment out the line addDepositsToMYT(); in the setup() function (to enable simulating myt price drop )

  3. Run with FOUNDRY_PROFILE=default forge test --fork-url https://mainnet.gateway.tenderly.co --match-test testTotalLockedIssue -vvv --evm-version cancun

Was this helpful?