57553 sc high mytsharesdeposited is not updated in liquidations which breaks bad debt ratio alchemistcr calculations and causes failures in bad debt handling and liquidation handling

Submitted on Oct 27th 2025 at 08:07:52 UTC by @niroh for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #57553

  • 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

AlchemistV3 maintains a _mytSharesDeposited state that tracks the amount of collateral deposited in the system. The state is updated in deposits/withdrawals/redeems and when collateral is paid out as fees. However it is not reduced when collateral is transferred to Transmuter during liquidations.

The value of _mytSharesDeposited is used in these two places (among other)

  1. In transmuter::claimRedemption when calculating the system bad debt ratio so that redemption amounts can be scaled down based on bad debt. (alchemist.getTotalUnderlyingValue() is based on _mytSharesDeposited):

// Ratio of total synthetics issued by the alchemist / underlingying value of collateral stored in the alchemist
// If the system experiences bad debt we use this ratio to scale back the value of yield tokens that are transmuted
uint256 yieldTokenBalance = TokenUtils.safeBalanceOf(alchemist.myt(), address(this));
// Avoid divide by 0
uint256 denominator = alchemist.getTotalUnderlyingValue() + alchemist.convertYieldTokensToUnderlying(yieldTokenBalance) > 0 ? alchemist.getTotalUnderlyingValue() + alchemist.convertYieldTokensToUnderlying(yieldTokenBalance) : 1;
uint256 badDebtRatio = alchemist.totalSyntheticsIssued() * 10**TokenUtils.expectDecimals(alchemist.underlyingToken()) / denominator;

uint256 scaledTransmuted = amountTransmuted;

if (badDebtRatio > 1e18) {
    scaledTransmuted = amountTransmuted * FIXED_POINT_SCALAR / badDebtRatio;
}
  1. In DoLiqquidation/CalculateLiquidation where system CR is checked and if bellow the minimum CR - a full liquidation is run instead of a partial one:

Vulnerability Details

Failing to update mytSharesDeposited during liquidations breaks both calculations that rely on it (mentioned above):

  1. In transmuter - the bad debt ratio is calculated largely as totalDebtTokenIssuance / (Alchemist.GetTotalUnderlyingValue() + TransmuterCurrentCollateralAsUnderlying). However, AlchemistTotalUnderlyingValue is based on the alchemist _mytSharesDeposited state. Since this state is not reduced during liquidations, any collateral transfered from alchemist to transmuter during liquidations will be counted twice. This will "overestimate" system health and fail to detect bad debt when it exists. The result is that redemptions will not be scaled down to bad debt, which will cause a "last redeemer loses" situation leading to bank runs and accelerate system involvancy during bad debt periods.

  2. Similarly, In DoLiquidation/calculateLiquidation, the CR calc is based on the ratio of _mytSharesDeposited (in debt token terms) to the Alchemist debt. Here too the CR calculation should not include collateral that was transferred to Transmuter (because only collateral in Alchemist covers alchemist debt). Liquidations will cause this calculation to "overestimate" system health and fail to detect high LTV situations, enabling partial liquidations that increase insolvancy risk during system-high-ltv periods (in addition to under-paying liquidator fees which are supposed to be based on full liquidations in high-ltv scenarios) .

Impact Details

  1. Failure to assign bad debt on redeemers fairly which can lead to bank runs or assign the full bad debt loss to the last redeemers

  2. Failure to perform full liquidations in times of systemwide-high-ltv, increasing system risk of insolvancy.

References

https://github.com/alchemix-finance/v3-poc/blob/a192ab313c81ba3ab621d9ca1ee000110fbdd1e9/src/AlchemistV3.sol#L541C9-L541C29

Proof of Concept

Proof of Concept

How to run:

  1. Copy the code below into the IntegrationTest contract is test/IntergrationTest.t.sol

  2. Comment out the line addDepositsToMYT(); in the setup() function (this makes myt price drop simulation easier)

  3. add the following import: import {AlchemistTokenVault} from "../AlchemistTokenVault.sol";

  4. Run with FOUNDRY_PROFILE=default forge test --fork-url https://mainnet.gateway.tenderly.co --match-test testMytSharesDepositedErrorPOC -vvv --evm-version cancun

Was this helpful?