58236 sc high accounting mismatch forcerepay doliquidation fail to decrement mytsharesdeposited locking deposit capacity and overstating collateral
Submitted on Oct 31st 2025 at 16:13:03 UTC by @unineko for Audit Comp | Alchemix V3
Report ID: #58236
Report Type: Smart Contract
Report severity: High
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Protocol insolvency
Smart contract unable to operate due to lack of token funds
Description
Brief/Intro
_forceRepay and _doLiquidation transfer MYT vault shares out of the Alchemist but never decrement _mytSharesDeposited. When the deposit cap has been saturated, any forced repayment that sends shares away fails to free capacity, so subsequent deposit() calls revert even though the real share balance is lower. Because the stale _mytSharesDeposited also feeds protocol TVL and solvency metrics, the system overstates collateral and produces unsafe liquidation decisions.
Vulnerability Details
The storage counter
_mytSharesDepositedis incremented on deposit and decremented on voluntary withdrawals. Forced repayments and liquidations also move MYT shares (from the borrower to the Transmuter and fee receiver), but the counter is never reduced.deposit()checks_mytSharesDeposited + amount <= depositCap. After the cap has been fully consumed, a forced repayment/liquidation sends shares out but_mytSharesDepositedremains unchanged, so the capacity never re-opens and new depositors continue to hitIllegalState()._getTotalUnderlyingValue()and collateralization calculations rely on_mytSharesDeposited, so the protocol continues to report the pre-liquidation TVL and collateral ratios even when shares have already left the system. This causes liquidation sizing to underestimate the required repayment and masks under-collateralization.The PoC deposits up to the cap, mints debt, matures an earmark, collapses MYT share price, and triggers liquidation. The logs show the relationships
actual shares < depositCap,getTotalDeposited()matches the ERC-4626 balance, yetreported underlying by Alchemist > actual underlying backing. A new depositor is reverted despite the vault holding zero shares.
Impact Details
Protocol insolvency / solvency manipulation: Inflated
_mytSharesDepositedoverstates global collateralization, under-sizes liquidations, and can accumulate bad debt.Smart contract unable to operate due to lack of token funds: Misleading solvency metrics reduce liquidation/redemption flows, starving the system of the assets needed to keep operations healthy.
Operational disruption: Stale accounting breaks metrics that integrators and DAO tooling rely on (cap usage, health checks, risk dashboards).
References
Vulnerable accounting:
src/AlchemistV3.sol::_forceRepay(lines ~749-792) and_doLiquidation(lines ~820-900).PoC:
src/test/H1_AlchemistV3DepositCapAccounting.t.sol::testDepositCapAccountingBreaksAfterForceRepay.Supporting notes:
audit-data/audit_memo.md(High finding).
Impact (Immunefi Classification)
Protocol insolvency – inflated
_mytSharesDepositedproduces unbacked collateral values and underestimates liquidation requirements.Smart contract unable to operate due to lack of token funds – liquidation/redemption flows rely on overstated balances and may run out of funds, halting normal operations.
Steps to Reproduce
Check out the scoped commit and install dependencies (
forge install).Run the PoC (Cancun EVM, offline to avoid Foundry’s macOS proxy crash):
Inspect the logs emitted by the test:
actual MYT shares held < deposit capreported underlying by Alchemist > actual underlying backing
Observe that
alchemist.deposit(cap / 2, newDepositor, 0)reverts withIllegalStateeven though no shares remain in the contract.
Technical Details
Similar logic exists in
_doLiquidation, where MYT shares are transferred out without adjusting_mytSharesDeposited.deposit()enforces(_mytSharesDeposited + amount) <= depositCap, so with a stale counter the cap is permanently “full”._getTotalUnderlyingValue()multiplies_mytSharesDepositedby the current share price, inflating global collateral metrics.
Recommended Fix
Whenever MYT shares leave the Alchemist (forced repayment, protocol fee, liquidation path), decrement
_mytSharesDepositedby the exact amount transferred.Ensure
_getTotalUnderlyingValue()and any other metrics that depend on_mytSharesDepositedmatch the true ERC-4626 share balance (IERC20(myt).balanceOf(address(this))).Add regression tests covering both forced repayment and liquidation flows to confirm the counter stays synchronized.
As an operational workaround, raising
depositCapor triggering a withdrawal/redemption that legitimately decrements_mytSharesDepositedrestores capacity, but the contract should not rely on this manual intervention.
Supporting Evidence
PoC test demonstrates the cap DoS and TVL inflation in a single scenario (
src/test/H1_AlchemistV3DepositCapAccounting.t.sol).Logs show the discrepancy between reported and actual collateral following liquidation.
_mytSharesDepositedis only updated indeposit()andwithdraw(), confirming the oversight.
Link to Proof of Concept
https://gist.github.com/unineko5555/3f6f27274aa992bc3d60b8775b8da45b
Proof of Concept
Was this helpful?