56365 sc critical liquidation fee overdraft drains pooled collateral
Submitted on Oct 15th 2025 at 06:04:30 UTC by @failsafe_intern for Audit Comp | Alchemix V3
Report ID: #56365
Report Type: Smart Contract
Report severity: Critical
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Description
The _liquidate function pays liquidators a repayment fee that exceeds what was deducted from the liquidated account, stealing the shortfall from the protocol's pooled MYT collateral shared by all users.
Vulnerability Mechanism
When an account with earmarked debt is liquidated and the debt fully clears, _resolveRepaymentFee calculates the fee but only deducts min(fee, collateralBalance) from the account:
File: v3-poc-b0505da/src/AlchemistV3.sol
Lines: 515-533
function _resolveRepaymentFee(uint256 accountId, uint256 repaidAmountInYield) internal returns (uint256 fee) {
Account storage account = _accounts[accountId];
fee = repaidAmountInYield * repaymentFee / BPS;
account.collateralBalance -= fee > account.collateralBalance ? account.collateralBalance : fee;
return fee; // Returns FULL fee
}However, _liquidate unconditionally transfers the full returned fee to the liquidator from the contract's pooled MYT balance:
File: v3-poc-b0505da/src/AlchemistV3.sol
Lines: 495-499
Root Cause
The mismatch occurs because:
_forceRepayconsumes most/all account collateral during earmarked debt repayment_resolveRepaymentFeecan only deduct remaining collateral but returns the full calculated fee_liquidatetransfers the full fee amount from_mytSharesDeposited(aggregate pool)
File: v3-poc-b0505da/src/AlchemistV3.sol
Lines: 377-415 (excerpt)
Attack Scenario
Setup:
Account has 1000 MYT earmarked debt
Account has only 10 MYT remaining collateral
repaymentFee= 5% (500 BPS)
Execution:
Attacker calls
liquidate(accountId)_forceRepayclears the 1000 MYT earmarked debt, consuming the 10 MYT collateralAccount debt reaches 0, triggering
_resolveRepaymentFeeCalculated fee:
1000 * 5% = 50 MYTAccount deduction:
min(50, 0) = 0 MYT(collateral already depleted)Liquidator receives:
50 MYTfrom contract's pooled balance
Result: Protocol loses 50 MYT from pooled collateral, account contributed 0 MYT
Impact
Direct theft of pooled user funds (
_mytSharesDeposited)Accounting desynchronization between tracked collateral and actual MYT balance
Cascading insolvency as repeated liquidations drain the pool
Scalable attack via
batchLiquidateacross multiple accountsNo special privileges required - any address can call
liquidate()
Link to Proof of Concept
https://gist.github.com/Joshua-Medvinsky/9e997ff1c82ae9267b8442a9696b064b
Proof of Concept
Proof of Concept
Step 1: Identify Vulnerable Account
Query accounts via getCDP(accountId) to find:
account.earmarked > 0(has earmarked debt)account.collateralBalance < (account.earmarked * repaymentFee / BPS)(insufficient collateral for fee)Account is liquidatable (collateralization <=
collateralizationLowerBound)
Step 2: Execute Liquidation
Call liquidate(accountId) from any address
Step 3: Observe Overdraft
If earmarked debt clears the position (account.debt == 0):
Account Side:
Collateral deducted:
min(calculatedFee, account.collateralBalance)(capped)
Liquidator Side:
Fee received:
calculatedFee(uncapped, from pooled MYT)
Shortfall:
This amount is extracted from _mytSharesDeposited (all users' collateral)
Step 4: Repeat Attack
Target additional vulnerable accounts via batchLiquidate() to systematically drain pooled collateral
Expected Results
Single liquidation example:
Earmarked debt cleared: 1000 MYT
Repayment fee (5%): 50 MYT
Account collateral available: 10 MYT
Account deduction: 10 MYT
Liquidator receives: 50 MYT
Stolen from pool: 40 MYT
Scaled attack (100 similar accounts):
Total stolen: 4,000 MYT from pooled collateral
Protocol becomes insolvent
Legitimate withdrawals fail
References
Affected Functions:
liquidate(uint256)- Entry point atAlchemistV3.sol:417_liquidate(uint256)- Transfers full fee at lines 495-499_resolveRepaymentFee(uint256,uint256)- Overdraft calculation at lines 515-533_forceRepay(uint256,uint256)- Depletes collateral at lines 377-415batchLiquidate(uint256[])- Enables scaled exploitation
Was this helpful?