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 V3
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
minimumCollateralizationvalue.Recalculates the user’s previous locked collateral using the current
minimumCollateralizationvalue, ensuring the user’srawLockedis 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
Initial State:
minimumCollateralization = 1.5e18User mints 1000 debt tokens.
User’s
rawLocked = 1000 * 1.5e18 / 1e18 = 1500e18totalLocked = 1500e18
Collateralization Ratio Update:
minimumCollateralizationis increased to1.6e18.
Second Mint:
User mints another 1000 debt tokens.
The user’s
rawLockedis recalculated to include both the old and new debt at the new ratio:Old locked collateral is updated:
1000 * 1.6e18 / 1.0e18 = 1600e18New locked collateral:
1000 * 1.6e18 / 1e18 = 1600e18Total
rawLocked = 3200e18
But
totalLockedis 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 now3200e18, not3100e18since 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 thattotalLockedis always updated to reflect the sum of all users’rawLockedwheneverminimumCollateralizationchanges.Concrete approach: When a user mints new debt, first subtract their previous
rawLockedvalue fromtotalLocked, then recalculate their newrawLockedusing the currentminimumCollateralization, and finally add this new value back tototalLocked. This ensures thattotalLockedalways 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?