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 V3
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 > depositCapeven if real on-chain balance allows, because_mytSharesDepositedremained 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:
setDepositCapchecks 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:
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)
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 > 0outflow = 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?