58347 sc high accounting drift due to missing mytsharesdeposited decrements during liquidation
Submitted on Nov 1st 2025 at 12:50:44 UTC by @InquisitorScythe for Audit Comp | Alchemix V3
Report ID: #58347
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 during liquidation operations. This causes the recorded total value locked (TVL) to diverge from the actual vault balance, making the TVL inflated. Since subsequent liquidation strategy calculations depend on accurate system-wide TVL metrics, this accounting drift can lead to incorrect liquidation decisions that fail to restore collateralization properly, ultimately causing cascading defaults and bad debt accumulation across the protocol.
Vulnerability Details
In the AlchemistV3 protocol, when borrowers are liquidated, the contract transfers MYT (Alchemix Yield Token) from the alchemist contract to either the transmuter or the liquidator. However, the code fails to decrement the _mytSharesDeposited state variable that tracks how much MYT the protocol has deposited. This creates an accounting inconsistency where _getTotalUnderlyingValue() returns a figure larger than the actual MYT balance held by the contract.
The bug manifests in three code paths within the liquidation logic: the primary liquidation flow in _liquidate(), the main liquidation calculation in _doLiquidation(), and the forced repayment path in _forceRepay(). In all three cases, when TokenUtils.safeTransfer(myt, ...) is called to move tokens out of the alchemist contract, the corresponding _mytSharesDeposited -= amount; statement is missing.
The _getTotalUnderlyingValue() function calculates TVL by converting the recorded _mytSharesDeposited to underlying tokens:
When liquidation occurs, tokens are transferred out but _mytSharesDeposited is never decremented. This means the recorded state still counts these transferred tokens as belonging to the protocol, even though they no longer exist in the contract.
The affected transfer calls are:
In
_liquidate()at line 828:TokenUtils.safeTransfer(myt, msg.sender, feeInYield);- transfers repayment fee to liquidatorIn
_doLiquidation()at lines 879-880:TokenUtils.safeTransfer(myt, transmuter, amountLiquidated - feeInYield);andTokenUtils.safeTransfer(myt, msg.sender, feeInYield);- transfers liquidated amount to transmuter and fee to liquidatorIn
_forceRepay()at lines 777-778:TokenUtils.safeTransfer(myt, protocolFeeReceiver, protocolFeeTotal);andTokenUtils.safeTransfer(myt, address(transmuter), creditToYield);- transfers protocol fees and repaid debt
The accounting drift created by this bug affects downstream protocol mechanics through the calculateLiquidation() function. This function takes the system-wide collateralization ratio as a parameter:
Because _getTotalUnderlyingValue() returns an inflated value, the system appears more collateralized than it actually is. When subsequent liquidations of other borrowers are calculated, the liquidation algorithm receives this false system health metric and may decide to liquidate less debt than necessary, leaving undercollateralized positions unfixed.
For example, if after a liquidation the true system TVL is 3.7 billion tokens but _getTotalUnderlyingValue() returns 3.78 billion tokens due to the 8.67 million token drift, the system appears 0.2% healthier than reality. A borrower on the liquidation edge who should be fully liquidated might instead be only partially liquidated based on the inflated metrics, leaving residual debt that cannot be sufficiently secured.
Unlike a one-time loss vulnerability, this bug accumulates with each liquidation. If borrower A is liquidated with a 10M token drift, and then borrower B is liquidated with another 10M token drift, the total unaccounted TVL becomes 20M. Each liquidation compounds the accounting error, creating a growing wedge between actual protocol collateral and recorded collateral. This makes the system increasingly vulnerable to subsequent market downturns and can trigger cascading liquidations that the protocol cannot properly handle.
Impact Details
The vulnerability directly impacts protocol solvency by creating systematic underestimation of risk. As the accounting drift accumulates across multiple liquidations, the protocol's ability to assess and manage collateralization ratios degrades. This has several concrete consequences:
First, liquidation strategy failures emerge. When the next undercollateralized borrower enters liquidation after the drift exists, the liquidation calculation receives inflated system collateralization data. This causes the protocol to calculate smaller liquidation amounts than necessary to restore the borrower to the minimum collateralization threshold. The borrower remains partially undercollateralized even after liquidation.
Second, bad debt accumulates when borrowers cannot be properly liquidated. If a liquidation is insufficient due to using inflated TVL metrics, the remaining debt persists. If the asset price continues to decline, the borrower may become severely undercollateralized or completely insolvent, with debt exceeding collateral value. This bad debt must be absorbed by the protocol or remaining depositors.
Third, protocol insolvency becomes possible at scale. With each liquidation introducing drift, and each drift making subsequent liquidations less effective, the protocol enters a vicious cycle. Under market stress with multiple liquidations in succession, the accumulated drift could cause the protocol to be unable to maintain sufficient collateralization of its total debt, resulting in either frozen funds or insolvency.
Quantified Impact
From the POC test execution:
Single liquidation transfer: 109M MYT
Amount recorded as deducted from TVL: 0 MYT
Actual drift created: 109M MYT (100% of transfer undecrmented)
System collateralization inflation: from 2.098x to 4.885x (2.78x increase)
False system health improvement: 132% higher collateralization than reality
This means after a single liquidation, if the system needed to liquidate another borrower, it would believe the system is 2.78 times more collateralized than it actually is, leading to significantly undersized liquidations.
References
File:
src/AlchemistV3.solFunctions affected:
_liquidate()(lines 791-846),_doLiquidation()(lines 851-916),_forceRepay()(lines 738-788)TVL calculation function:
_getTotalUnderlyingValue()(lines 1238-1242)Liquidation strategy function:
calculateLiquidation()(lines 1244+)
Proof of Concept
Proof of Concept
create src/test/LiquidationBugPOC.t.sol
run forge test --match-path ./src/test/LiquidationBugPOC.t.sol to see two failing tests.
explain:
Test 1: Direct Drift Detection
The first POC demonstrates the most direct evidence of the bug. It creates two positions: one health deposit to maintain system collateralization, and one overleveraged position to liquidate. After triggering undercollateralization via a simulated price drop and executing liquidation, it compares the actual vault balance with the recorded TVL.
Expected behavior: vault.balanceOf(alchemist) should equal getTotalUnderlyingValue()
Actual behavior: getTotalUnderlyingValue() exceeds vault balance by 8.67M MYT
Console output:
Test 2: Impact on Liquidation Strategy
The second POC demonstrates how this accounting drift directly affects liquidation strategy calculations. It records the system's collateralization ratio before liquidation (when TVL is accurate), then records it again after liquidation (when TVL is inflated by the drift), showing the system appears significantly healthier than it actually is.
Expected behavior: System collateralization should remain constant or decrease after liquidation
Actual behavior: System collateralization increases by 2.78x due to inflated TVL
Console output:
This demonstrates the concrete impact: if another borrower were to be liquidated based on the post-liquidation metrics, the system would believe collateralization is 4.88x when it's actually 2.10x, leading to under-liquidation.
Was this helpful?