57559 sc high missing mytsharesdeposited decrement in liquidation paths enables theft of unclaimed yield and protocol insolvency
Submitted on Oct 27th 2025 at 09:19:23 UTC by @rshackin for Audit Comp | Alchemix V3
Report ID: #57559
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Theft of unclaimed yield
Description
Summary :
In AlchemistV3's liquidation mechanism when MYT (Meta Yield Token) is transferred out during liquidation to pay the liquidator fee and send assets to the transmuter, the internal _mytSharesDeposited counter, which tracks total deposited MYT collateral and is used to compute protocol TVL, is not decremented. This creates two simultaneous impacts: (1) immediate theft of MYT from victim positions by arbitrary callers who trigger liquidation, and (2) persistent overstatement of protocol TVL that weakens subsequent liquidation enforcement, enabling bad debt accumulation toward insolvency.
Vulnerability Details :
Affected Contract: AlchemistV3.sol
Affected Functions:
_doLiquidation(),_liquidate()(repayment-only branch)Root Cause: Inconsistent accounting between liquidation paths and other MYT outflow paths (repay/burn) (https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L852-L880)
When a liquidation occurs, AlchemistV3 executes the following MYT transfers:
Sends
amountLiquidated - feeInYieldto the transmuter.Sends
feeInYielddirectly tomsg.sender(the liquidator). However, unlikerepay()andburn()functions which correctly decrement_mytSharesDepositedwhen MYT leaves the contract, the liquidation paths omit this accounting step entirely.
Evidence: Correct implementation in repay() (lines ~455-490):
While in _doLiquidation:
Impact Details :
Impact 1: Theft of Unclaimed Yield:
Any external user can call
liquidate()on an under-collateralized position.The caller receives
feeInYieldin MYT directly from the victim's collateral to their wallet.This is a permissionless extraction of yield-bearing assets from the victim's position.
The victim loses MYT collateral value equal to the liquidation fee without any compensation.
Impact 2: Protocol Insolvency:
_mytSharesDepositedis used in_getTotalUnderlyingValue()to calculate protocol-wide TVL.TVL calculations feed into collateralization checks via
calculateLiquidation().After each liquidation,
_mytSharesDepositedoverstates actual MYT holdings by the amount that left.This makes collateralization ratios appear healthier than reality.
Over repeated exploitations, bad debt accumulates as the TVL/debt ratio diverges from reality.
System-wide insolvency risk increases as actual collateral < recorded collateral.
The liquidation mechanism being permissionless is intentional. What is unintentional is the accounting inconsistency:
repay()decrements_mytSharesDepositedfor MYT outflowsburn()decrements_mytSharesDepositedfor MYT outflowswithdraw()decrements_mytSharesDepositedfor MYT outflowsliquidate()does NOT decrement_mytSharesDepositedfor MYT outflows
Attack Path:
Attacker monitors for positions approaching liquidation threshold.
When a position becomes eligible, attacker calls
liquidate(victimId).Attacker receives
feeInYieldMYT to their address (immediate profit).Protocol's
_mytSharesDepositedremains unchanged despite MYT leaving.Future liquidation checks use inflated TVL, allowing risky positions to persist.
Attacker repeats on multiple positions, compounding both theft and accounting drift.
Protocol accumulates bad debt as actual collateral falls below liabilities.
Proof of Concept
Proof of Concept:
Step 1: Add helper to read private storage slot: In AlchemistV3.t.sol, add this constant and helper function near the top of the test contract:
Step 2: Add the PoC test:
Recommended Fix: Add in
_doLiquidation()after MYT transfers:
Was this helpful?