58519 sc high double counting of collateral due to mytsharesdeposited not being updated during liquidations

Submitted on Nov 2nd 2025 at 23:50:45 UTC by @DeusVult for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58519

  • Report Type: Smart Contract

  • Report severity: High

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

  • Impacts:

    • Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield

    • Protocol insolvency

Description

Description:

  • The Alchemist tracks protocol TVL for MYT shares via the internal counter _mytSharesDeposited, and getTotalUnderlyingValue() computes TVL from this counter: it returns convertYieldTokensToUnderlying(_mytSharesDeposited).

  • Several pathways transfer MYT shares out of the Alchemist without decrementing _mytSharesDeposited, leaving the Alchemist’s reported TVL unchanged even though shares actually left the contract:

    • _forceRepay: Transfers MYT to the Transmuter and to the protocol fee receiver, but never decrements _mytSharesDeposited.

    • _liquidate early-exit branches (repayment-only, no liquidation): Pays the repayment fee to the liquidator from Alchemist MYT, but never decrements _mytSharesDeposited.

    • _doLiquidation (actual liquidation): Transfers MYT to the Transmuter and potentially to the liquidator as fee, but never decrements _mytSharesDeposited.

  • The Transmuter computes a “bad-debt ratio” using a denominator equal to $TVL_{alchemist} + underlying(\text{Transmuter MYT})$. Because _mytSharesDeposited is not decreased when MYT leaves the Alchemist for the Transmuter, the same shares are effectively counted twice in the denominator: once via the Alchemist’s stale TVL and once via the Transmuter’s live balance.

Impact:

  • Suppressed scaling during claimRedemption:

    • The bad-debt ratio becomes artificially small because the denominator is inflated by double counting, reducing or eliminating the intended scaling of redemptions under insolvency. Early claimants can redeem too much at par, to the detriment of later participants.

  • Under-liquidation:

    • AlchemixV3::calculateLiquidation uses the global collateralization underlying/total_debt. With an overstated _getTotalUnderlyingValue(), the system appears healthier, causing lighter liquidations or skipping high-LTV full liquidations that should occur, leaving the system riskier and less solvent than reported.

Proof of Concept

Proof of Concept:

  • The provided test demonstrates the issue end-to-end with logs and assertions:

    • Fees are zeroed to isolate share accounting.

    • A borrower deposits MYT and mints near the minimum collateralization, then a Transmuter redemption is created so earmarks accrue.

    • A price move makes the borrower liquidatable.

    • Before liquidation, the test snapshots:

      • Alchemist TVL: getTotalUnderlyingValue() (which relies on _mytSharesDeposited),

      • Transmuter’s MYT share balance, and

      • The “Transmuter denominator” denom = TVL + underlying(transmuter).

    • After triggering alchemist.liquidate(tokenIdA), MYT shares move from the Alchemist to the Transmuter:

      • The Alchemist’s actual MYT ERC20 balance drops by the amount moved.

      • However, getTotalUnderlyingValue() remains unchanged because _mytSharesDeposited was not decremented in the liquidation paths.

      • The denominator used by the Transmuter increases exactly by the underlying value of the shares that moved, even though total system underlying did not increase. This proves double counting: those shares are still counted inside the Alchemist’s TVL while also being counted again as Transmuter-held MYT.

    • The assertions confirm:

      • Alchemist ERC20 balance decreased as expected,

      • TVL (from _mytSharesDeposited) incorrectly stayed the same,

      • The combined denominator increased by the underlying value of the transferred shares, evidencing the double count.

To run this POC paste the below code in AlchemixV3.t.sol and run the POC using the command forge test --mt testPOC_MYT_DoubleCounting_After_Liquidation_Moves_Shares_To_Transmuter_WITH_LOGS

Was this helpful?