Obedient Lava Monkey
Medium
The calculation of healthFactor
before accounting for the new borrow amount results in an inaccurate validation of the health factor for borrowers. This allows users to borrow amounts that lower their health factor below the liquidation threshold, leading to immediate liquidation risks upon borrowing.
The issue occurs in the ValidationLogic.validateBorrow function. Specifically, the healthFactor
is validated before the new borrow amount is added to the user's debt. This is demonstrated by the following validation logic:
require(
vars.healthFactor > HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
);
This check uses outdated values of userDebtInBaseCurrency and healthFactor that do not account for the impact of the new loan. As a result, borrowers can take out loans that drop their actual health factor below the liquidation threshold, bypassing proper solvency checks.
The issue lies in these two steps of the borrow validation process:
- The current health factor is checked using the pre-borrow state:
require( vars.healthFactor > HEALTH_FACTOR_LIQUIDATION_THRESHOLD, Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD );
- The collateral coverage is separately validated based on the loan-to-value (LTV) requirement:
vars.collateralNeededInBaseCurrency = (vars.userDebtInBaseCurrency + vars.amountInBaseCurrency) .percentDiv(vars.currentLtv); require( vars.collateralNeededInBaseCurrency <= vars.userCollateralInBaseCurrency, Errors.COLLATERAL_CANNOT_COVER_NEW_BORROW );
Key Observations:
- The
healthFactor
validation occurs using the pre-borrow state. - The LTV validation considers the new borrow amount but does not recalculate the health factor after adding the new debt.
- These two checks are fundamentally different: satisfying the LTV requirement does not guarantee that the borrower's health factor remains above the liquidation threshold.
As a result, the following attack scenario is possible:
- A borrower with a health factor slightly above 1 requests a loan that satisfies the LTV requirement.
- The protocol approves the borrow based on the outdated health factor.
- After the loan is executed, the borrower’s health factor drops below 1, making them immediately liquidatable.
To address this issue, the health factor must be recalculated after incorporating the new borrow amount and validated before the borrow is finalized.
- The borrower has collateral supplied, resulting in a calculated
healthFactor
just above the liquidation threshold. - The borrower requests a loan amount that would drop their post-borrow health factor below the threshold.
- The
validateBorrow
function does not revalidate the updated health factor after including the new borrow amount.
- The oracle provides asset price information for the borrower’s collateral.
- The borrowed asset has sufficient liquidity in the pool.
- A borrower supplies collateral with a health factor near the liquidation threshold (e.g., 1.01).
- The borrower calls the
borrow
function, requesting an amount that lowers their health factor to below 1 after accounting for the new debt. - The protocol approves the borrow request since the health factor check uses pre-borrow values and does not consider the impact of the new loan.
- The borrower becomes immediately liquidatable, leading to liquidation risks and potential collateral loss.
- Borrowers are exposed to immediate liquidation risks, potentially losing a significant portion of their collateral.
- The protocol incurs unnecessary liquidation activity, which disrupts overall stability and erodes user trust.
The ValidationLogic.validateBorrow
function should recalculate the health factor after adding the new borrow amount to the user's debt. This recalculated health factor must then be validated to ensure that it remains above the liquidation threshold before finalizing the borrow request.