Core Contracts

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

No minimum position size in `borrow()` function in the `LendingPool.sol` contract

Summary

A vulnerability exists in the lending pool protocol where there is no enforced minimum position size for borrowing. The protocol allows creating arbitrarily small positions with minimal collateral, potentially leading to dust positions.

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L325-L369
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (isUnderLiquidation[msg.sender]) revert CannotBorrowUnderLiquidation();
UserData storage user = userData[msg.sender];
uint256 collateralValue = getUserCollateralValue(msg.sender);
if (collateralValue == 0) revert NoCollateral();
// Update reserve state before borrowing
ReserveLibrary.updateReserveState(reserve, rateData);
// Ensure sufficient liquidity is available
_ensureLiquidity(amount);
// Fetch user's total debt after borrowing
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();
}
// Update user's scaled debt balance
uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
// Mint DebtTokens to the user (scaled amount)
(bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
user.scaledDebtBalance += scaledAmount;
// reserve.totalUsage += amount;
reserve.totalUsage = newTotalSupply;
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, 0, amount);
// Rebalance liquidity after borrowing
_rebalanceLiquidity();
emit Borrow(msg.sender, amount);
}

The borrow function allows users to create positions with any amount greater than 0 , calculated using the fomula:

  • Maximum borrow = collateralValue / 0.8 (with liquidationThreshold = 80_00)

  • No minimumThreshold is enforced for either collateral or borrow amount.

  • Example a user can borrow 1 wei worth of collateral and borrow 1.25 wei.

code snippet displaying the issue

modifier onlyValidAmount(uint256 amount) {
if (amount == 0) revert InvalidAmount();
_;
}

Impact

Small underwater positions may never be liquidated because the gas cost of liquidation exceeds the potential profit from the liqidation. This creates permanent bad debt for the protocol, as liquidators have no economic incentive to clear these positions. For example, if a position has:

  • Collateral value: 0.0001 ETH

  • Debt: 0.00008 ETH

  • Gas cost to liquidate: 0.0002 ETH
    The liquidator would lose money by attempting to liquidate this position.

Tools Used

Manual review

Recommendations

uint256 public constant MIN_COLLATERAL_VALUE = 1e18; //1USD
uint256 public constant MIN_BORROW_AMOUNT = 1e17; //0.1 USD
function borrow(uint256 amount) external {
require(amount >= MIN_COLLATERAL_VALUE, " Borrow amount too small");
require (getCollateralValue(msg.sender) >= MIN_COLLATERAL_VALUE, "Insufficient collateral");
//...rest of the code..............
}
2. Implement dust collection mechanism for all existing small positions.
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!