The _forceRepay function in AlchemistV3.sol contains an off-by-one boundary condition error that causes protocol fees to be permanently lost when a borrower's remaining collateral exactly equals the protocol fee amount. The function uses a strict greater-than comparison (>) instead of greater-than-or-equal (>=) when deciding whether to collect the protocol fee, causing the fee transfer to be silently skipped in edge cases. This results in systematic theft of protocol-owned yield that should be collected as revenue during forced liquidation repayments.
Vulnerability Details
Root Cause
In AlchemistV3.sol at lines 771-775, the protocol fee collection logic contains a flawed boundary check:
function_forceRepay(uint256accountId,uint256amount)internalreturns(uint256){// ... repayment logic ...uint256 protocolFeeTotal = creditToYield * protocolFee / BPS;emitForceRepay(accountId, amount, creditToYield, protocolFeeTotal);if(account.collateralBalance > protocolFeeTotal){// BUG: Should be >= account.collateralBalance -= protocolFeeTotal;// Transfer the protocol fee to the protocol fee receiver TokenUtils.safeTransfer(myt, protocolFeeReceiver, protocolFeeTotal);}if(creditToYield >0){// Transfer the repaid tokens from the account to the transmuter. TokenUtils.safeTransfer(myt,address(transmuter), creditToYield);}// ...}
The Problem: The condition account.collateralBalance > protocolFeeTotal requires the remaining collateral to be strictly greater than the fee amount. When they are exactly equal, the condition evaluates to false, and the entire fee collection block is skipped.
Attack Scenario
The vulnerability manifests during liquidations where:
A borrower's position has debt that is fully earmarked (e.g., after transmuter redemptions)
A liquidator calls liquidate(), which triggers _forceRepay to repay the earmarked debt
After the forced repayment transfer to the transmuter, the borrower's remaining collateral exactly equals the calculated protocol fee
Due to the strict > check, the protocol fee is never transferred to protocolFeeReceiver
The uncollected fee remains in the borrower's collateral balance indefinitely
This scenario occurs naturally without manipulation when:
The borrower's initial collateral amount
The amount force-repaid to the transmuter
The protocol fee percentage
Why This Is a Vulnerability
Silent Failure: The ForceRepay event emits protocolFeeTotal as if it were collected, but no actual transfer occurs
Permanent Loss: Once the liquidation completes, there's no mechanism to retroactively collect the missed fee
Impact Details
Direct Financial Loss
Per-Incident Loss:
Each affected liquidation loses the entire protocol fee for that forced repayment
With a 20% protocol fee (default configuration), this represents 20% of the repaid yield value
Example: A 100 MYT forced repayment with exact boundary conditions results in 20 MYT lost revenue