The _forceRepay() function collects and transfers protocol fees and creditToYield but fails to update _mytSharesDeposited, unlike repay() and burn() which properly decrement this accounting variable. This creates a growing discrepancy between actual token balances and recorded deposits, breaking a critical system invariant.
Vulnerability Details
The Issue
_mytSharesDeposited tracks the total yield tokens deposited in the Alchemist and is used for critical calculations like deposit caps and total value locked (TVL). When protocol fees are collected, tokens leave the contract and must be deducted from this counter.
Correct implementation in repay():
Buggy implementation in _forceRepay():
Attack Path
This happens normally from protocol usage.
User's position becomes undercollateralized (collateralizationRatio <= collateralizationLowerBound)
User's account has earmarked debt (account.earmarked > 0)
Anyone calls liquidate(accountId) on the user's position
_liquidate() calls _forceRepay() to repay the earmarked debt
Protocol fee and creditToYield transferred out, but _mytSharesDeposited is NOT decremented
Over time, _mytSharesDeposited becomes increasingly inflated
Impact
1. Broken Deposit Cap Enforcement
The inflated _mytSharesDeposited will cause deposits to hit the cap prematurely, blocking legitimate deposits even when the actual balance is lower.
2. Incorrect TVL Calculations
This inflated TVL affects liquidation calculations, potentially preventing liquidations when the system is actually undercollateralized globally.
3. Compounding Issue
Each liquidation with earmarked debt increases the discrepancy. Over time with many liquidations, the accounting error grows unbounded.