Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Incorrect application of LTV in LendingPool allows undercollateralized loans

Summary

Incorrect LTV logic in the following functions of LendingPool

1. borrow - allows borrowing more than collateral value.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L344-L346 2.

2.withdrawNFT - allows collateral value to fall below debt

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L302-L304

Vulnerability Details

The standard practice is lending protocols is to allow borrowing within the safe range of 50-90% of the deposited collateral value (based on the volatility of the colateral supplied) for sustainablity of the protocol.

This value is applied on the collateral
to get the maximum amount an user can safely borrow

In the case of RAAC LendingPool,
liquidationThreshold = 8000 (basis points _ 80 %)

which means debt cannot exceed 80% of collateral value
In other words collateral should be at least 125% of debt.

i.e, maxBorrow = collateral * LTV / 100

by this logic the correct condition to maintain safe collateral ratio should be

//correct logic
if(userTotalDebt > collateralValue.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

This is where the issue lies.
notice the inversion of the logic in the following checks
inside borrow and withdrawNFT respectively

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L344-L346

//borrow condition
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L302-L304

//withdrawNFT Condition
if (collateralValue - nftValue < userDebt.percentMul(liquidationThreshold)) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}

The above conditions effectively allows an user
to obtain significantly more debt than their collateral value.

An example test Scenario using borrow function

contract BorrowLTVExploitTest is Test {
LendingPool lending;
RaacNFT nft;
function setUp() public {
lending = new LendingPool();
nft = new RaacNFT();
// Set mock price oracle (1 NFT = $1000)
lending.setNFTPrice(1, 1000 ether);
// step 1: Bob deposits NFT worth 1000 USD as collateral
vm.prank(bob);
lending.depositNFT(1);
}
// collateralValue (1000) < maxUserDebt * 8000 / 10000
// maxUserDebt = 1000*100/80 = 1250
// step 2 : Bob exploits the incorrect LTV check
function testBobCanBorrowMoreThanCollateral() public {
uint256 collateralValue = lending.getUserCollateralValue(bob);
assertEq(collateralValue, 1000 ether, "Collateral should be $1000");
// Bob tries to borrow more than 1000 USD
uint256 borrowAmount = 1200 ether; // 120% of collateral value
vm.prank(bob);
lending.borrow(borrowAmount);
// Check Bob's debt
uint256 bobDebt = lending.getUserDebt(bob);
assertTrue(bobDebt > collateralValue, "Bob borrowed more than collateral value!");
console.log("Collateral value:", collateralValue / 1e18, "USD"); //1000
console.log("Bob borrowed:", bobDebt / 1e18, "USD"); //1200
}
}

Due to the incorrect application of LTV logic in borrow function,
Bob borrows 1200 USD,
effectively borrowing more than his collateral value.

Bob is now heavily undercollateralized,
and the protocol ends up with a significant bad debt,
which can accumulate and collapse the entire protocol.

A similarly calculated attack can be executed using withdrawNFT as well.

Impact

Users could borrow and withdrawNFTs beyond the safe collateral ratio
resulting in collapse of the protocol due to significant bad debt.

Impact : High
Likelihood : High

Tools Used

Foundry, Manual Analysis

Recommendations

Modify the conditions in borrow and withdrawNFT functions as follows

//borrow
if (collateralValue.percentMul(liquidationThreshold) < userTotalDebt) {
revert NotEnoughCollateralToBorrow();
}
//withdrawNFT
if ((collateralValue - nftValue).percentMul(liquidationThreshold) < userDebt) {
revert WithdrawalWouldLeaveUserUnderCollateralized();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::borrow as well as withdrawNFT() reverses collateralization check, comparing collateral < debt*0.8 instead of collateral*0.8 > debt, allowing 125% borrowing vs intended 80%

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!