57918 sc high incorrect totallocked collateral accounting in alchemistv3

Submitted on Oct 29th 2025 at 13:03:39 UTC by @Razkky for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57918

  • 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

Summary

A critical accounting mismatch exists in the AlchemistV3 protocol’s handling of the totalLocked collateral variable. When the system’s minimumCollateralization ratio is updated after users have already minted debt, the protocol’s global totalLocked value becomes inconsistent with the sum of all users’ actual locked collateral (rawLocked). This discrepancy can lead to incorrect calculations of collateral weight during redemptions, resulting in unfair or inaccurate outcomes for users.


Technical Details

Debt Minting and Collateral Locking

When a user mints debt, the mint is called which calls _mint, which in turn calls _addDebt. This function:

  • Updates the user’s debt.

  • Locks the required amount of collateral for the new debt, based on the current minimumCollateralization value.

  • Recalculates the user’s previous locked collateral using the current minimumCollateralization value, ensuring the user’s rawLocked is always correct and up-to-date.

The Mismatch

However, the protocol’s global totalLocked variable is only incremented by the new collateral locked for each mint, calculated using the current minimumCollateralization. It does not retroactively update the previously calculated locked collateral for the users with the current minimumCollateralization.

Example Scenario

  1. Initial State:

    • minimumCollateralization = 1.5e18

    • User mints 1000 debt tokens.

    • User’s rawLocked = 1000 * 1.5e18 / 1e18 = 1500e18

    • totalLocked = 1500e18

  2. Collateralization Ratio Update:

    • minimumCollateralization is increased to 1.6e18.

  3. Second Mint:

    • User mints another 1000 debt tokens.

    • The user’s rawLocked is recalculated to include both the old and new debt at the new ratio:

      • Old locked collateral is updated: 1000 * 1.6e18 / 1.0e18 = 1600e18

      • New locked collateral: 1000 * 1.6e18 / 1e18 = 1600e18

      • Total rawLocked = 3200e18

    • But totalLocked is only incremented by the new collateral locked for the second mint:

      • totalLocked = 1500e18 (old) + 1600e18 (new) = 3100e18

    • The correct total locked collateral should be the sum of all users’ rawLocked, which is now 3200e18, not 3100e18 since we only have one user with debt and locked collateral.

Impact on Collateral Weight and Redemptions

The protocol uses totalLocked to calculate the collateral weight during redemptions:

If totalLocked is incorrect, the old value used in this calculation is wrong, leading to an incorrect update of _collateralWeight. This, in turn, causes the protocol to redeem the wrong amount of collateral for users, resulting in unfair or inaccurate redemptions.

Security and Fairness Implications

  • Unfair Redemptions: Users may receive less or more collateral than they are entitled to during redemptions, depending on the direction of the mismatch.

  • Protocol Risk: If the mismatch is exploited or grows over time, it could lead to systemic insolvency or unfair liquidations.

  • User Trust: Inaccurate accounting undermines user trust in the protocol’s safety and correctness.

Recommendations

  • Synchronize totalLocked: Ensure that totalLocked is always updated to reflect the sum of all users’ rawLocked whenever minimumCollateralization changes.

    • Concrete approach: When a user mints new debt, first subtract their previous rawLocked value from totalLocked, then recalculate their new rawLocked using the current minimumCollateralization, and finally add this new value back to totalLocked. This ensures that totalLocked always matches the sum of all users’ locked collateral, even as the collateralization ratio changes.

Proof of Concept

Proof of Concept

Add the below test case to AlchemistV3.t.sol and run the following command to run the test forge test --mt test_AccountingMissmatchTotalRawlockedAndtotalLocked -vvv

Add console.log to the _addDebt function to log the raw locked and totalLocked to match the logs outpout

Test Logs

Was this helpful?