When liquidation enters the “repayment-only” path (earmarks fully repay the borrower’s debt), the protocol pays the liquidator the full theoretical repayment fee even if the borrower’s collateral couldn’t fund it. The shortfall is taken from the contract’s MYT balance.
Vulnerability Details
Liquidation first earmarks and repays via _forceRepay. If this fully clears debt, it executes the following if block: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L823-L828
// If debt is fully cleared, return with only the repaid amount, no liquidation needed, caller receives repayment fee
if (account.debt == 0) {
feeInYield = _resolveRepaymentFee(accountId, repaidAmountInYield);
TokenUtils.safeTransfer(myt, msg.sender, feeInYield);
return (repaidAmountInYield, feeInYield, 0);
}
The _resolveRepaymentFee computes the theoretical fee and deducts only what’s available from the borrower’s balance, but returns the full, unclamped fee: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol?utm_source=immunefi#L900-L907
The call site then transfers that full theoretical fee to the liquidator from the contract’s balance, regardless of what was actually deducted from the borrower.
The calculation in the _doLiquidation function has proper checks,