58718 sc medium in forcerepay protocol fee collection leads to theft of unclaimed yield
Submitted on Nov 4th 2025 at 08:44:15 UTC by @legion for Audit Comp | Alchemix V3
Report ID: #58718
Report Type: Smart Contract
Report severity: Medium
Target: https://github.com/alchemix-finance/v3-poc/blob/immunefi_audit/src/AlchemistV3.sol
Impacts:
Theft of unclaimed yield
Description
Brief/Intro
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(uint256 accountId, uint256 amount) internal returns (uint256) {
// ... repayment logic ...
uint256 protocolFeeTotal = creditToYield * protocolFee / BPS;
emit ForceRepay(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_forceRepayto repay the earmarked debtAfter 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 toprotocolFeeReceiverThe 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
ForceRepayevent emitsprotocolFeeTotalas if it were collected, but no actual transfer occursPermanent 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
References
Vulnerable Code
Primary Issue:
src/AlchemistV3.sol#L771-775- Flawed boundary condition in_forceRepayEntry Point:
src/AlchemistV3.sol#L583-632-liquidate()function that calls_forceRepay
Proof of Concept
Proof of Concept
Was this helpful?