58086 sc high mis accounting of myt outflows inflates tvl distorts collateralization and can dos deposits liquidations

Submitted on Oct 30th 2025 at 14:51:58 UTC by @Freescore for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58086

  • 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 AlchemistV3 contract tracks MYT collateral via an internal counter _mytSharesDeposited which is used to compute TVL and enforce the deposit cap. Several MYT outflow paths omit decrementing this counter (e.g., forced repayment and liquidation), leaving TVL overstated and causing system-wide effects: distorted global collateralization for liquidation math, incorrect redemption scaling, and premature deposit-cap rejections (DoS) for new deposits.

Vulnerability Details

AlchemistV3 reports TVL as the underlying value of _mytSharesDeposited:

  • TVL: getTotalUnderlyingValue() -> convertYieldTokensToUnderlying(_mytSharesDeposited) (src/AlchemistV3.sol:1239)

  • Deposit cap enforcement: _mytSharesDeposited + amount <= depositCap (src/AlchemistV3.sol:369)

However, _mytSharesDeposited is not reduced when the contract transfers MYT out in several flows:

  • Forced repayment (MYT to Transmuter, protocol fee to fee receiver):

    • MYT to Transmuter: src/AlchemistV3.sol:777–780

    • Protocol fee transfer: src/AlchemistV3.sol:771–775

    • No decrement of _mytSharesDeposited.

  • Liquidation (MYT to Transmuter and to liquidator as base fee):

    • Transfers: src/AlchemistV3.sol:874–881

    • No decrement of _mytSharesDeposited.

  • Repayment fee payout during liquidation (liquidator fee in MYT):

    • Fee transfer: src/AlchemistV3.sol:825–841 (post _resolveRepaymentFee)

    • No decrement of _mytSharesDeposited.

By contrast, other paths adjust _mytSharesDeposited correctly:

  • deposit: +amount (src/AlchemistV3.sol:383)

  • withdraw: −amount (src/AlchemistV3.sol:410)

  • redeem (Transmuter): −(redeemed + feeCollateral) (src/AlchemistV3.sol:638)

  • burn/repay protocol fee in MYT: (src/AlchemistV3.sol:485, 541)

Because TVL derives from _mytSharesDeposited, real outflows go unreflected in TVL, overstating system collateral and breaking downstream logic.

Impact Details

  • Overstated TVL distorts global collateralization used in liquidation math:

    • Liquidation inputs use normalizeUnderlyingTokensToDebt(_getTotalUnderlyingValue()) * FIXED_POINT_SCALAR / totalDebt (src/AlchemistV3.sol:862).

    • Inflated TVL can misclassify liquidation environment (e.g., avoid outsourced fee branch, miscompute debtToBurn), degrading risk controls.

  • Deposit-cap DoS:

    • New deposits are blocked when _mytSharesDeposited + amount > depositCap even if real on-chain balance allows, because _mytSharesDeposited remained incorrectly high while ERC20 balance decreased on outflows.

  • Inaccurate redemption haircuts:

    • Transmuter bad-debt scaling relies on Alchemist’s total underlying value. Inflated TVL can understate bad debt, paying out more to redeemers than appropriate, risking solvency.

  • Governance cap mismatch:

    • setDepositCap checks live ERC20 balance (src/AlchemistV3.sol:236–241), while enforcement uses _mytSharesDeposited. If outflows aren’t accounted, admin can set a cap lower than _mytSharesDeposited (because balance dropped), creating a contradictory state that blocks deposits.

Overall severity: Medium-High. The issue is deterministic and reachable during normal operations (forced repay, liquidation) and can produce persistent DoS on deposits, mis-liquidations, and mispriced redemptions.

References

  • TVL calculation: src/AlchemistV3.sol:1239

  • Deposit cap enforcement: src/AlchemistV3.sol:369

  • Forced repay MYT outflow: src/AlchemistV3.sol:771–780

  • Liquidation MYT outflows: src/AlchemistV3.sol:874–881

  • Correct decrement examples: deposit/withdraw/redeem/burn-fee (src/AlchemistV3.sol:383, 410, 638, 485, 541)

  • Liquidation collateralization input: src/AlchemistV3.sol:862

Proof of Concept

Proof of Concept

Two Foundry tests demonstrate the bug:

  1. Forced repayment outflow (no fees):

    • Name: testMisAccountingOnOutflows_ForceRepay()

    • Steps:

      • Set protocol and repayment fees to 0; set collateralizationLowerBound = minimumCollateralization.

      • User deposits and mints to minimum collateralization.

      • Create and mature a Transmuter redemption equal to the debt (fully earmarked).

      • Record:

        • tvlBefore = alchemist.getTotalUnderlyingValue()

        • mytBalBefore = IERC20(vault).balanceOf(alchemist)

      • Call alchemist.liquidate(tokenId); only a forced repayment occurs.

      • Assert:

        • outflow = mytBalBefore - mytBalAfter ≈ yieldAmount (MYT left to Transmuter)

        • tvlAfter ≈ tvlBefore (TVL unchanged despite ERC20 outflow)

  1. Liquidation with base fee (no protocol/repayment fees):

    • Name: testMisAccountingOnOutflows_Liquidation()

    • Steps:

      • Set liquidator fee > 0; set protocol and repayment fees to 0.

      • User deposits and mints; drop MYT price to breach lower bound.

      • Record TVL and ERC20 MYT balance.

      • Call alchemist.liquidate(tokenId).

      • Assert:

        • yieldAmount > 0

        • outflow = mytBalBefore - mytBalAfter ≈ yieldAmount (MYT sent to Transmuter + fee to liquidator)

        • tvlAfter ≈ tvlBefore (TVL unchanged despite MYT outflow)

These tests are included in src/test/AlchemistV3.t.sol and pass, proving that MYT leaves the Alchemist but TVL (computed from _mytSharesDeposited) does not reflect the outflow.

Was this helpful?