The calculateHealthFactor()
function uses an outdated reserve.usageIndex
to compute users’ health factors, potentially leading to inaccurate assessments of their financial positions. This staleness arises because the function does not update the reserve state or account for accrued interest since the last state update. As a result, users may face incorrect liquidations or avoid necessary liquidations
The calculateHealthFactor()
function in LendingPool.sol
is defined as:
This function relies on getUserDebt()
, which calculates user debt as:
The reserve.usageIndex
represents the compounded interest rate applied to variable debt balances. But, reserve.usageIndex
is updated only when updateReserveState()
(via ReserveLibrary
) is called during state-modifying operations like (e.g., borrow()
, repay()
, initiateLiquidation()
). As a view function, calculateHealthFactor()
cannot modify state and thereby uses the usageIndex
from the last state update, stored in reserve.lastUpdateTimestamp
.
If significant time passes between state updates, usageIndex
becomes stale, underestimating the user’s debt (userDebt
) because it does not reflect accrued interest. This staleness can lead to:
An overestimated health factor (if debt is underestimated), delaying liquidation when needed
An underestimated health factor (if collateral is stale—see related on my report on getNFTPrice()
), triggering unnecessary liquidations.
Users with risky positions might avoid liquidation if their debt is underestimated due to a stale usageIndex
Users with healthy positions might be flagged for liquidation if their health factor is underestimated due to stale collateral values (from getUserCollateralValue()
), combined with an accurate or underestimated debt. This could result in unfair liquidations and loss of user funds.
Manual Review
Modify getUserDebt()
to compute the current usageIndex
dynamically based on the time elapsed since reserve.lastUpdateTimestamp
, without modifying state. This leverages existing logic in ReserveLibrary.getNormalizedDebt()
Add a warning or fallback mechanism in calculateHealthFactor()
to indicate potential staleness if reserve.lastUpdateTimestamp
is too old, while keeping it view
Add a constant MAX_STATE_AGE
(e.g., 1 hours) to define acceptable staleness. This allowscallers to assess freshness and trigger state updates if needed.
Everywhere where the protocol consumes the return of this function the ReserveLibrary state is updated before the actual call.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.