57950 sc high unit mismatch in adddebt collateralization check allows unbacked debt issuance and protocol insolvency

Submitted on Oct 29th 2025 at 15:05:15 UTC by @MentemDeus28 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57950

  • 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

The _addDebt function in AlchemistV3.sol incorrectly calculates required locked collateral using convertDebtTokensToYield, a function that converts debt tokens to yield tokens while comparing it directly against collateralBalance (which is in yield token shares). This unit mismatch breaks the collateralization invariant and causes the collateralization ratio to be incorrectly evaluated, allowing users to mint debt far exceeding the actual value of their collateral. In production, this leads to unbacked synthetic issuance, protocol insolvency, and total loss of depositor funds.

Vulnerability Details

The core logic of _addDebt() performs a collateralization check before allowing additional debt to be added to a user’s account. However, the implementation mistakenly uses convertDebtTokensToYield() to convert debt values into yield tokens, creating a unit mismatch between collateral and debt.

The core issue lies in _addDebt(), called during mint():

 function _addDebt(uint256 tokenId, uint256 amount) internal {
        Account storage account = _accounts[tokenId];

        // 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 += amount;
    }

Problem Breakdown:

  1. account.collateralBalance is denominated in yield tokens (shares).

  2. convertDebtTokensToYield() converts debt tokens into yield tokens, based on the current exchange rate.

  3. convertDebtTokensToYield() assumes parity between yield token shares and underlying debt value, which only holds when the exchange rate = 1.0.

This introduces a unit mismatch when the exchange rate between yield tokens and underlying assets changes (e.g., due to yield accumulation). As a result: The protocol may think an account is over-collateralized when it’s actually under-collateralized.

Or revert valid mints when the account is safely collateralized.

Scenario

ALICE collateralBalance = 100 shares (100 yieldtokenMYT)

ALICE debt = 50 debt token alice minted 50 debt tokens

conversion rate = 1 yield = 2 debt tokens Exchange rate after yield growth

Current logic:

lockedCollateral = convertDebtTokensToYield(50) = 100 shares

toLock = assuming 50

check: 100 - 100 < 50 → revert (incorrect)

Reality:

Collateral value = 100 shares × 2 = 200 debt tokens

Debt value = 100 debt tokens (after mint)

Should pass (collateralization = 200%)

Because the check is done in mixed units, _addDebt() either falsely reverts or fails to detect insolvency.

Uses shares, not value

When rate > 1.0, toLock shrinks undercollateralized mint

When rate < 1.0, toLock inflates false revert

Even with correct rate, the logic is fundamentally flawed, it measures shares, not economic value.

Impact Details

Severity: Critical

This bug directly affects the core accounting of collateralization:

Attackers can over-mint debt beyond their collateral value when the conversion rate skews, draining protocol liquidity.

Conversely, users may experience false reverts, preventing valid mints and disrupting user experience.

If exploited over time, this can lead to system-wide insolvency, where total outstanding debt exceeds total collateral.

Potential impact:

Complete loss of funds backing the synthetic/debt tokens.

Broken mint/burn balance and liquidation logic.

Systemic collapse of protocol solvency guarantees.

Use consistent units across the collateralization check always compare values in the same domain (debt units). A correct implementation would look like this

This ensures collateral and debt are compared in the same unit system (debt value), eliminating the mismatch and preventing insolvency risks.

Reference

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

Proof of Concept

Proof of Concept

Expected Output

Collateral (shares): 1e24

Debt (tokens): 8e23

Collateral Value: 2e24

final/proper analysis

Debt = 8e23

Value = 2e24

Ratio = 8e23 / 2e24 = 0.4x

0.4x collateralization

Should require at least 1.5x (minimumCollateralization)

Mint should revert

BUT IT PASSED

The check is based on shares, not value.It ignores the vault rate.

Was this helpful?