58526 sc high missing accounting update in liquidation functions leads to permanent dos on deposits

Submitted on Nov 3rd 2025 at 02:18:36 UTC by @ibrahimatix0x01 for Audit Comp | Alchemix V3arrow-up-right

  • Report ID: #58526

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol

  • Impacts:

    • Smart contract unable to operate due to lack of token funds

Description

Brief/Intro

The _doLiquidation() and _forceRepay() functions fail to decrement the _mytSharesDeposited state variable when transferring MYT tokens out of the contract. This variable tracks total deposits and is checked against depositCap to limit protocol exposure. After liquidations occur, _mytSharesDeposited becomes permanently inflated relative to the actual token balance, causing all subsequent deposit attempts to revert with IllegalState even when well below the configured cap. This renders the core deposit functionality permanently inoperable without a contract upgrade, effectively preventing any new capital from entering the protocol.

Vulnerability Details

The contract maintains _mytSharesDeposited as a private state variable to track the total amount of MYT tokens deposited into the protocol. This variable serves as the basis for enforcing the depositCap limit:

// Line 428 in deposit()
function deposit(uint256 amount, address recipient, uint256 tokenId) external returns (uint256) {
    _checkArgument(recipient != address(0));
    _checkArgument(amount > 0);
    _checkState(!depositsPaused);
    _checkState(_mytSharesDeposited + amount <= depositCap);  //  Fails when accounting is broken
    // ...
    _mytSharesDeposited += amount;  //  Correctly incremented on deposits
}

Every function that transfers MYT tokens out of the contract must decrement _mytSharesDeposited to maintain accurate accounting. Several functions correctly implement this pattern:

Correctly Implemented Functions:

However, the liquidation flow contains two functions that fail to maintain this invariant.

Bug Location #1: _doLiquidation() (Lines 852-893)

The function transfers amountLiquidated tokens out of the contract (split between transmuter and liquidator), but never decrements _mytSharesDeposited. This creates an accounting gap equal to the liquidation amount.

Bug Location #2: _forceRepay() (Lines 738-782)

This function is called during liquidations when earmarked debt exists. It transfers tokens to both the protocol fee receiver and transmuter, but fails to update _mytSharesDeposited.

How the Bug Manifests:

  1. Protocol starts with accurate accounting: actual balance = _mytSharesDeposited

  2. Liquidation occurs via liquidate() → calls _liquidate() → calls _doLiquidation() and/or _forceRepay()

  3. MYT tokens are transferred out, reducing actual balance

  4. _mytSharesDeposited remains unchanged, creating divergence

  5. Subsequent deposit attempts check _mytSharesDeposited + newAmount <= depositCap

  6. Check fails because _mytSharesDeposited is inflated, even though actual balance has room

  7. All deposits revert with IllegalState error

Example Scenario:

The bug is deterministic and occurs on every liquidation. There is no admin function to manually correct _mytSharesDeposited, making the issue permanent without a contract upgrade.

Impact Details

Primary Impact: Permanent Protocol Dysfunction

The vulnerability causes complete and irreversible failure of the deposit() function, which is a core protocol operation. Once any liquidation occurs, no new deposits can be processed regardless of actual token availability or deposit cap headroom.

Immediate Consequences:

  1. All New Deposits Blocked: Any user attempting to deposit will encounter IllegalState revert

  2. Zero Recovery Path: No admin function exists to correct _mytSharesDeposited manually

  3. Cumulative Degradation: Each subsequent liquidation worsens the accounting gap

  4. Requires Emergency Upgrade: Only solution is contract upgrade with full user migration

Financial Impact:

Protocol Level:

  • Revenue Loss: Protocol cannot earn fees from new deposits after first liquidation

  • TVL Stagnation: Total Value Locked permanently capped at pre-liquidation level

  • Growth Paralysis: Unable to onboard new users or accept additional capital

  • Competitive Disadvantage: Functional competing protocols will capture market share

  • Reputation Damage: Users perceive protocol as broken, leading to loss of confidence

Quantified Losses:

Assuming a protocol with:

  • 10M TVL

  • 15% APY fee generation

  • 2M liquidated in normal market conditions

User Impact:

  • Prospective Users: Cannot deposit despite advertised capacity and functional protocol

  • Existing Users: No direct fund loss; can still withdraw, repay, and perform other operations

  • Liquidators: Continue operating normally; bug does not affect liquidation mechanism itself

Why This Qualifies as "Smart contract unable to operate due to lack of token funds":

The deposit check (_mytSharesDeposited + amount <= depositCap) fails as if the contract lacks capacity to accept more funds, even though actual token balance and deposit cap allow it. The contract's deposit functionality becomes inoperable due to incorrect accounting that makes it appear "full" when it is not.

Severity Justification (High):

Permanent Dysfunction: Core protocol function permanently disabled No Recovery Mechanism: Cannot be fixed without upgrade Inevitable Trigger: Liquidations are normal protocol operations Business Critical: Prevents protocol growth and new user acquisition No Admin Override: No emergency function to correct accounting

Not Critical Because: No direct theft, no fund loss for existing users, protocol remains solvent

Attack Complexity: NONE - This is not an exploitable vulnerability; it's an inherent bug that triggers automatically during normal liquidation operations. No malicious actor is required.

Likelihood: CERTAIN - Liquidations occur regularly during market volatility. The bug will manifest in production with 100% certainty once the first liquidation executes.

References

Primary Vulnerable Code:

  • AlchemistV3.sol Line 852-893: _doLiquidation() function

  • AlchemistV3.sol Line 738-782: _forceRepay() function

Related State Variables:

  • AlchemistV3.sol Line 121: _mytSharesDeposited (private variable)

  • AlchemistV3.sol Line 58: depositCap (public variable)

Deposit Enforcement Location:

  • AlchemistV3.sol Line 428: Deposit cap check in deposit() function

Correctly Implemented Reference Functions:

  • AlchemistV3.sol Line 467: withdraw() - Correctly decrements _mytSharesDeposited

  • AlchemistV3.sol Line 633: burn() - Correctly decrements _mytSharesDeposited

  • AlchemistV3.sol Line 700: repay() - Correctly decrements _mytSharesDeposited

  • AlchemistV3.sol Line 733: redeem() - Correctly decrements _mytSharesDeposited

Proof of Concept

Proof of Concept

The following test demonstrates the vulnerability by showing how liquidations break the deposit accounting, permanently blocking all new deposits even when well below the deposit cap.

Add the below test code to AlchemistV3.t.sol

Test Code:

The Foundry test can be ran with this command forge test --mt testPoC_LiquidationBreaksDepositAccounting -vvvv.

Was this helpful?