56442 sc high inflated totallocked because vault yield accrual would skew collateralweight calculation

Submitted on Oct 16th 2025 at 02:15:36 UTC by @farismaulana for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56442

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Protocol insolvency

Description

Brief/Intro

MYT is VaultV2 share which overtime would accrue yield. this yield more or less growing and can be used as part of the user debt payment.

but there are issue in AlchemistV3::_addDebt that chain into redeem function, which makes the _collateralWeight inaccurate.

Vulnerability Details

        // Update collateral variables
@>      uint256 toLock = convertDebtTokensToYield(amount) * minimumCollateralization / FIXED_POINT_SCALAR;
@>      uint256 lockedCollateral = convertDebtTokensToYield(account.debt) * minimumCollateralization / FIXED_POINT_SCALAR;

        if (account.collateralBalance - lockedCollateral < toLock) revert Undercollateralized();

        account.rawLocked = lockedCollateral + toLock;
@>      _totalLocked += toLock;
        account.debt += amount;
        totalDebt += a

toLock which is the amount of shares (MYT) needed for debt amount is added into _totalLocked .

notice that this value toLock is recalculated using convertDebtTokensToYield which if the VaultV2 accruing yield, the same amount at timestamp X would result different share if done at timestamp X + Y where the yield accrued.

this ultimately create discrepancy in the real _totalLocked, because how it is updated only with toLock which after many subsequent deposit it would became inflated/innacurate.

example: at timestamp X, the asset per share is 1:1, user mint 100 debt token which need 111 MYT as collateral. 111 MYT is added to _totalLocked. account.rawLocked is 111 MYT.

at timestamp X+Y, the asset per share is 1:0.9, user mint 100 debt token which need only 99.9 MYT collateral. 99.9 MYT is added to _totalLocked . now _totalLocked is 111 MYT + 99.9 MYT = 210.9 MYT.

notice the account.rawLocked is each time recalculate the lockedCollateral + toLock using latest value per share which is now respectively: 99.9 MYT and the new debt requires 99.9 MYT to lock, for a new total of 199.8 MYT.

this discrepancy itself already sufficient for a bug. but we can further check where the additional impact is:

when redeem happening, the _collateralWeight would be calculated using the _totalLocked and totalOut . now we know that _totalLocked is inflated, this means the _collateralWeight would be inaccurate and affect all users.

Impact Details

_totalLocked would be inflated over time which later would severely affect how the _collateralWeight is calculated. potentially to an artificially low or "stunted" _collateralWeight over time.

References

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

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

Proof of Concept

Proof of Concept

there are multiple files that need to modified:

AlchemistV3.sol so we can see the _totalLocked value

VaultV2.sol, MockMYTVault.sol and so we can quickly simulate/mock that Vault accrue yield

and lastly, the test itself:

we then call call forge test --mt test_poc_amountLockedIssue -vvvv to see our Debug event from second deposit:

the first debug is _totalLocked after first deposit where the asset per share is still 1:1

the second debug is _totalLocked after the second deposit when 1 share is about 1.001 asset worth

the third debug is account.rawLocked which shown that it is not the same as latest _totalLocked at second debug event.

this clearly prove that the _totalLocked over time would inflates.

Was this helpful?