56672 sc high inconsistent myt share accounting leads to under liquidation and solvency risk

Submitted on Oct 19th 2025 at 08:15:09 UTC by @yesofcourse for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #56672

  • 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

    • Protocol insolvency

Description

Brief/Intro

AlchemistV3._forceRepay transfers MYT (vault shares) out of the Alchemist during repay-only liquidations but does not decrement the internal _mytSharesDeposited counter used to compute TVL.

As a result, getTotalUnderlyingValue() overstates collateral, the global collateralization ratio is inflated, and the system can skip emergency full-liquidation when it should trigger - creating persistent under-liquidation and elevated insolvency risk.

Vulnerability Details

  • The protocol tracks TVL via _mytSharesDeposited:

    function _getTotalUnderlyingValue() internal view returns (uint256) {
        return convertYieldTokensToUnderlying(_mytSharesDeposited);
    }
  • In _forceRepay, when earmarked debt is repaid using a user’s MYT collateral, the Alchemist sends MYT out (to the Transmuter and optionally the fee receiver) but does not decrease _mytSharesDeposited.

Elsewhere in the codebase, when MYT leaves the Alchemist, _mytSharesDeposited is lowered to keep TVL honest; this function breaks that invariant.

Affected snippet (inside _forceRepay):

  • Because TVL is read from _mytSharesDeposited, not from the live MYT balance, every _forceRepay outflow leaves reported TVL stale/too high.

Why this matters

The liquidation controller uses TVL to compute the Alchemist’s global collateralization and decide whether to take the emergency full-liquidation branch:

If _getTotalUnderlyingValue() is overstated, alchemistCurrentCollateralization is inflated and the emergency branch can fail to trigger when the true (economic) ratio is already below the minimum. Positions that should be fully liquidated are only partially delevered or not delevered at all.

Scenario PoC

  • Set fees to zero for clarity.

  • Deposit MYT, mint debt, create and mature a redemption (to push the repay-only path).

  • Force a price drop so _forceRepay sends MYT out to the Transmuter.

  • Observe:

    • vault.balanceOf(Alchemist) drops (real MYT left).

    • getTotalUnderlyingValue() (derived from _mytSharesDeposited) does not drop accordingly (remains stale).

    • The computed global collateralization is too high vs. reality.

The coded testForceRepay_MissingMYTOutflow_DeSync() demonstrates exactly this: real MYT outflow occurs, but reported TVL is unchanged within tight tolerance.

Impact Details

Primary impact: Protocol insolvency (systemic). By overstating TVL, the protocol underestimates risk and skips emergency full-liquidations that should occur. Under-liquidation lets unhealthy positions linger, allowing bad debt to accumulate, and reduces liquidator compensation (“outsourced fee”), weakening incentives.

Secondary impacts:

  • Temporary freezing of funds. As the true asset base shrinks while TVL appears healthy, later withdrawals/redemptions can revert once the real shortfall surfaces.

  • Contract fails to deliver promised safety behavior. The system’s liquidation guarantees rely on accurate TVL; misreporting breaks those guarantees.

Concrete loss mechanics (example):

  • A repay-only liquidation transfers 100 MYT shares out (to Transmuter + fees).

  • Real MYT holdings drop by 100 shares; reported TVL is unchanged.

  • The global collateralization ratio derived from TVL stays artificially high and emergency liquidation is not triggered.

  • Repeating this across multiple undercollateralized accounts can compound the TVL gap, increasing bad debt and leading to withdrawal failures; in the worst case, overall insolvency.

References

  • Affected file: src/AlchemistV3.sol_forceRepay outflows without _mytSharesDeposited decrement: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol#L764-L775

  • TVL source: _getTotalUnderlyingValue() uses _mytSharesDeposited: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol#L1238-L1241

Proof of Concept

Proof of Concept

Paste the following test in AlchemistV3.t.sol and run with forge test --match-test testForceRepay_MissingMYTOutflow_DeSync:

Was this helpful?