Core Contracts

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

Wrong liquidationThreshold check allows user to borrow MORE than their liquidatable threshold AND EVEN more than their collateral value

Summary

In LendingPoola user is able to borrow reserveTokensand is minted equal debtTokens, After depositing their NFT as collateral.

The protocol has a base LiquidationThreshold-> 80%

This means that the user can borrow / have debt up to 80% of the value of their collateral. If the value of their collateral falls or any situation which the user ends up with more debt than 80% of the value of their collateral, they can be liquidated to save the protocol from having bad debt.

BUT there is a wrong check for the value that a user can borrow in the LendingPool::borrowfunction, which allows the user to receive MORE than their borrowing power and even more than the value of thier collateral.

Vulnerability Details

The liquidationThreshold percentage should be applied to the collateral that a user has to accurately check if they have less debt than their borrowing power. The function however, applies the liquidationThresholdto the users debtbalance, which, since it is a percentage (80%) -> lowers the value of the actual debt they have.

/**
* @notice Allows a user to borrow reserve assets using their NFT collateral
* @param amount The amount of reserve assets to borrow
*/
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
uint256 userTotalDebt = user.scaledDebtBalance.rayMul(reserve.usageIndex) + amount;
// Ensure the user has enough collateral to cover the new debt
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}

Lets follow this scenario:

  1. User has borrowed 0, they are making their first borrow. User deposits their NFT worth a value of 100.

  2. User calls borrowwith 120as the amount to borrow.

  3. userTotalDebtis calculated and 120is added, so it will equal 120

  4. The ifstatement checks incorrectly:

    • collateralValue= 100.

    • userTotalDebt* * 80% -> 120 * * 80% -> Which equals 96

    • if 100 < 96 , then revert.

    • But 100 IS NOT less than 96. So the function continues.

  5. The function continues, the user is minted 120 debtTokensand sent 120 reserveTokens

The user ends up with still 100 collateral value and 120 debtTokens and reserveTokens. The protocol immediately has 20 bad debt that it incurs as an imemdiate loss.

Verification of the wrong liquidationThresholdcheck can be seen here, when the liquidationThresholdis correctly applied to the collateralValueof the user -> to see if their debt amount is above the threshold:

function calculateHealthFactor(address userAddress) public view returns (uint256) {
uint256 collateralValue = getUserCollateralValue(userAddress);
uint256 userDebt = getUserDebt(userAddress);
if (userDebt < 1) return type(uint256).max;
uint256 collateralThreshold = collateralValue.percentMul(liquidationThreshold);
return (collateralThreshold * 1e18) / userDebt;
}

Impact

NOT only is the user receiving / borrowing more than their liquidation threshold amount of 80, they are also receiving more than their entire collateral value.

The user can GO COMPLETELY INSOLVENT. The protocol will have to take on that bad debt as a loss.

The example above uses small values for simplicity, but the NFT's represent real-estate which can be very large values. The user can have very large collateral, and exploit this will very large values.

For example, instead of 100 collateral and 120 borrow amount -> use 100,000 and 120,000. The protocol now has a 20,000 loss. And this can keep scaling. The protocol gets bad debt and immediate loss, can be very substancial loss.

Tools Used

manual review

Recommendations

The liquidationThresholdshould be applied to the collateralValueof the user. And then compare that value to the amount of debt they have, which will accurately ensure they can borrow that amount.

In the above example, If correctly applying liquidationThreshold:

The ifstatement will operate as such:

  • collateralValue *liquidtyThreshold -> 100 * * 80% -> collateralValue= 80

  • userTotalDebt = 120

  • 80 < 120 , then revert.

  • 80 Is definitely less than 120, which reverts and does not allow the user to borrow that amount.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month 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.