57088 sc high unscaled collateral accounting in redeem lets users withdraw more than intended

Submitted on Oct 23rd 2025 at 11:19:36 UTC by @hashbug for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57088

  • 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

Intro

The AlchemistV3 contract uses weights to keep track of earmarked debt, redeemed earmarked debt, and collateral reductions following redemption claims.

Additionally, it uses a locking mechanism to ensure proper collateralization of debt.

Due to incorrect handling of _totalLocked and _collateralWeight, the collateral left in user accounts after _sync will generally be higher than it should be. Users will be able to withdraw this additional collateral credited to their account that would otherwise belong to other users.

Vulnerability Details

The collateral sent to the Transmuter is accounted for in two functions: redeem and _sync.

The redeem function updates the global _totalLocked and _collateralWeight. The _sync function utilizes _collateralWeight to compute the collateral to remove from individual CDPs.

The calculation in redeem is as follows:

Note, that _totalLocked is adjusted in the following manner (using a saturated subtraction):

This is inconsistent with the way _totalLocked is incremented in _addDebt called from mint:

Here, the yield units are converted to locked yield units, i.e., scaled to match the overcollateralization requirement.

If a user takes on debt and transmutes that debt fully in the Transmuter, _totalLocked will not be sufficiently decreased during redeem.

Note, that in this scenario, account.rawLocked will still be calculated correctly in _sync using account.debt.

A mirrored issue is present in _sync, where account.rawLocked scaled using _collateralWeight is used to compute the collateralToRemove. At which point, the yield units and locked yield units are mixed up again.

The solution is likely to change

to

in redeem.

And to also change

to

in _sync.

Impact Details

The error in _totalLocked will accumulate and will influence the _collateralWeight more and more significantly over time. In redeem, the weight multiplicand computation (in regular arithmetic)

will tend to 1 since _totalLocked will be disproportionately large. Therefore, the delta between two _collateralWeight snapshots will tend to 0. Consequently, collateralToRemove in _sync will tend to 0.

This creates an accounting bug, where less collateral is deduced from each CDP than appropriate. Thus, users will be able to withdraw more than they should. These withdrawals eventually cannot be supported by the contract's MYT balance. Some users will never get their tokens back because other users overwithdrew. They may do it both on purpose or by accident.

References

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

This issue also plays a role in my other report "Vulnerable redemption survival ratio in _sync allows theft of alTokens", where the accounting error can be seen as well. The effect is, however, not explainable solely by this issue, they have to be combined to produce the effect shown in the PoC of the above-mentioned report.

Proof of Concept

Proof of Concept

We will demonstrate the reported behavior by running two full redemption cycles from a single account (0xbeef). After the first redemption claim, _totalLockedis decreased incorrectly. This value is then used in the second cycle to create the leftover collateral effect.

The new test is derived from AlchemistV3Test and the main code is in test_twoTransmutations.

The new file PoC.t.sol:

Was this helpful?