57129 sc high missing mytsharesdeposited decrement in liquidation functions causes permanent tvl inflation

Submitted on Oct 23rd 2025 at 17:51:48 UTC by @Max36935 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57129

  • 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 fails to decrement the _mytSharesDeposited state variable when transferring MYT tokens out during liquidation operations, unlike all other token transfer functions (withdraw, burn, repay, claimRedemption) which properly maintain this accounting. This causes getTotalUnderlyingValue() to return permanently inflated values after each liquidation, affecting the alchemistCurrentCollateralization calculation used in critical liquidation decisions. The cumulative accounting error masks the protocol's true collateralization state, preventing the system from recognizing undercollateralization and triggering appropriate crisis responses, potentially enabling users to mint debt against phantom collateral and leading to protocol insolvency.

Vulnerability Details

Root Cause

The contract maintains _mytSharesDeposited (line 134 in AlchemistV3.sol) to track total deposited MYT (yield token) shares:

/// @dev Total yield tokens deposited
/// This is used to differentiate between tokens deposited into a CDP and balance of the contract
uint256 private _mytSharesDeposited;

This variable is consistently updated across all token transfer operations: Token deposits increase the counter:

All standard token withdrawals decrease the counter:

However, liquidation functions break this pattern:

_doLiquidation() (lines 875-879) - Missing updates:

_forceRepay() (lines 774-779) - Missing updates:

_liquidate() repayment fee paths (lines 826, 840) - Missing updates:

How the Accounting Error Propagates The inflated _mytSharesDeposited directly affects TVL calculation:

This inflated TVL then feeds into the critical liquidation calculation at line 862:

The fourth parameter is alchemistCurrentCollateralization, calculated as (Total Protocol TVL / Total Protocol Debt) * 1e18. This ratio determines whether the protocol should enter emergency liquidation mode:

Impact Details

Primary Impact: Masked Undercollateralization The accounting error causes alchemistCurrentCollateralization to appear higher than reality, with the following consequences:

  1. Failed Crisis Detection When the protocol becomes genuinely undercollateralized (TVL < Debt × globalMinimumCollateralization), the inflated TVL prevents the system from recognizing this state. The critical check at line 1257 in calculateLiquidation():

This emergency mode should trigger full liquidations when the protocol is in crisis, but the inflated alchemistCurrentCollateralization prevents this, allowing normal operations to continue during actual insolvency.

  1. Cumulative Degradation Each liquidation compounds the error:

  • First liquidation: TVL inflated by X tokens

  • Second liquidation: TVL inflated by X + Y tokens

  • After N liquidations: TVL inflated by sum of all liquidated amounts

  • No recovery mechanism exists to correct the discrepancy

  1. Incorrect Liquidation Incentives The calculateLiquidation() function uses the inflated collateralization ratio to determine liquidation amounts and fees. This can result in:

  • Undersized liquidations (liquidating less debt than needed)

  • Incorrect fee calculations

  • Delayed recognition of position health

  1. Potential for Excessive Debt Minting Users can mint debt up to minimumCollateralization ratio. If the protocol's actual TVL is lower than reported due to accumulated inflation, new debt minting could push the protocol into actual insolvency while appearing healthy.

References

AlchemistV3.sol: Main contract file

  • Line 134: _mytSharesDeposited variable declaration

  • Lines 852-890: _doLiquidation() function (missing updates at lines 875, 879)

  • Lines 735-780: _forceRepay() function (missing updates at lines 774, 779)

  • Lines 791-850: _liquidate() function (missing updates at lines 826, 840)

  • Line 1239: _getTotalUnderlyingValue() function (uses inflated _mytSharesDeposited)

  • Line 862: calculateLiquidation() call using inflated TVL

Comparison functions that correctly update _mytSharesDeposited:

  • Line 410: withdraw() - decrements properly

  • Line 485: burn() - decrements properly

  • Line 541: repay() - decrements properly

  • Line 638: claimRedemption() - decrements properly

Recommendations

Fix 1: Add _mytSharesDeposited Decrements in _doLiquidation() Update the _doLiquidation() function to decrement _mytSharesDeposited when transferring MYT tokens out, matching the pattern used in all other withdrawal functions.

Fix 2: Add _mytSharesDeposited Decrements in _forceRepay() and _liquidate() Update both _forceRepay() (called during liquidation) and the repayment fee paths in _liquidate() to maintain consistent accounting. In _forceRepay():

These changes ensure that _mytSharesDeposited is consistently maintained across all functions that transfer MYT tokens out of the contract, aligning liquidation functions with the existing pattern used by withdraw(), burn(), repay(), and claimRedemption().

Proof of Concept

Proof of Concept

Create File

src/test/Audit_MytSharesNotDecrementedOnLiquidation.t.sol

Run Test

forge test --match-contract Audit_MytSharesNotDecrementedOnLiquidation -vvv

Was this helpful?