In the LendingPool
contract, the comparison used to check the Loan-to-Value (LTV) threshold is inverted, causing the protocol to allow borrowing amounts that exceed the intended liquidation threshold. Instead of enforcing a strict limit (for example, an 80% LTV), the code permits effectively borrowing up to 125% when liquidationThreshold
is set to 80%. This discrepancy arises from a condition that compares collateralValue < userTotalDebt.percentMul(liquidationThreshold)
rather than the expected userTotalDebt > collateralValue.percentMul(liquidationThreshold)
. Consequently, users can surpass the supposed LTV cap and face immediate liquidation risk, while the protocol absorbs additional potential defaults beyond the intended safety margin.
This issue stems from an inverted check in the borrow()
function of the LendingPool contract. Rather than confirming that the user’s total debt does not exceed the liquidation threshold (userTotalDebt <= collateralValue × liquidationThreshold
), the code performs the reverse check (collateralValue < userTotalDebt.percentMul(liquidationThreshold)
). As a result, when liquidationThreshold
is configured at 80%, the user can borrow an amount equivalent to 125% of their collateral, instead of being limited to 80%. This discrepancy directly undermines the intended LTV cap and exposes both the user and the protocol to immediate liquidation scenarios, with the potential for excessive defaults beyond the originally designed safety margin.
Allowing borrowers to exceed the intended liquidation threshold significantly weakens the protocol’s security. Users can immediately open positions above the safe LTV range (e.g., 125% instead of 80%), placing them at high liquidation risk. In turn, this increases the likelihood of defaults and poses a significant financial risk to the system as a whole. The discrepancy between the stated threshold and the actual enforced limit can undermine user trust, as positions become disproportionately overleveraged, and liquidation may occur in scenarios the protocol intended to prevent.
The logical vulnerability arises from an inverted condition used to ensure the Loan-to-Value (LTV) ratio does not exceed a specified threshold. This allows users to borrow beyond the intended “liquidationThreshold” (e.g., 80%), effectively reaching a 125% LTV if liquidationThreshold = 80%
. As a result, users can exceed the “safe” borrowing limit and become immediately eligible for liquidation.
Below is a concise extract from the borrow()
function, highlighting the key portion of the bug:
The line:
is logically equivalent to:
collateralValue >= userTotalDebt * liquidationThreshold
In other words:
userTotalDebt <= collateralValue / liquidationThreshold
If liquidationThreshold
is 80% (0.80)
, this effectively allows borrowing up to 125% of collateral (1 / 0.80 = 1.25), instead of 80%. Ideally, the correct check should be:
A user deposits an NFT valued at 1000.
The liquidationThreshold
is set to 80%.
Due to the inverted check, the borrow()
function allows the user to take on 1250 of debt instead of the intended maximum of 800.
The position ends up with an LTV of 125%.
Other participants can immediately trigger liquidation since the actual LTV exceeds the safety threshold.
The test sets an NFT’s value to 100 crvUSD and applies an 80% liquidation threshold, meaning the user should only be allowed to borrow up to 80 crvUSD. Instead, the user successfully borrows 125 crvUSD (125% LTV) without reverting. This outcome confirms that the check responsible for enforcing the threshold is inverted, thereby allowing borrow amounts above the intended limit. The balance and debt verifications further prove that the logic is flawed, as it fails to trigger the expected "NotEnoughCollateralToBorrow"
error.
Add the following test inside the describe("Borrow and Repay", function () {})
block in test/unit/core/pools/LendingPool/LendingPool.test.js
.
The code comments indicate an 80% liquidation threshold.
Because of the inverted comparison, the user can exceed that threshold.
In practice, the user accrues more debt than permitted, exposing them to instant liquidation and contradicting the original validation intent.
This confirms that the issue is real and constitutes a significant logical vulnerability in how LTV is enforced within the borrow()
function.
Manual Code Review
A thorough examination of the contract’s logical checks identified the inverted condition in the borrow()
function, revealing that users could exceed the intended liquidation threshold. This manual inspection highlighted the discrepancy between the protocol’s intended LTV enforcement and its actual implementation.
To resolve the inverted check, correct the condition in the borrow()
function to ensure the user cannot exceed the intended LTV threshold:
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.