Core Contracts

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

Unchecked Borrowing Allows Users to Bypass Collateral Requirements

Summary

LendingPool's borrowing mechanism lets users accumulate debt far beyond their collateral-backed limits. When a user with just 2 units of collateral and existing high debt attempts to borrow 9 more units, the transaction succeeds despite exceeding the maximum safe borrowing limit of 3 units.

Picture a user who deposits minimal collateral worth 2 units into the LendingPool. Through a series of transactions, they've already accumulated substantial debt (e.g, 0x16a34...fc48). Despite this high leverage, they can still borrow 9 more units because the contract checks their collateral against current debt only, not including the new borrow amount.

The contract's liquidation threshold (0x45ff) should restrict borrowing to a maximum of 3 units given their collateral, but this safety check fails. Why? Because the validation occurs before adding the new borrow amount to their total debt position.

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// Collateral validation occurs before adding new borrow amount
UserData storage user = userData[msg.sender];
uint256 collateralValue = getUserCollateralValue(msg.sender);
if (collateralValue < userTotalDebt.percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
// Uncheck for collateralValue < (userTotalDebt + amount).percentMul(liquidationThreshold)
}

Vulnerability Details

A borrower with significant existing debt can exploit the LendingPool's insufficient validation to push their position far beyond safe collateral limits. The attack is remarkably simple, despite having a massive existing debt of approximately 1.3e20 wei, they can continue borrowing additional amounts. The contract's borrow() function fails to properly validate that this combined debt stays within the collateral's safety threshold.

Think of it like a credit card that keeps accepting charges even after exceeding its limit, the system's safety checks exist but aren't being enforced at the critical moment.

The core of the vulnerability lies in the relationship between debt and collateral. When a user with an existing debt of 1.3e20 wei attempts to borrow just 9 more wei, the contract calculates a maximum safe borrowing limit (maxBorrow) of only 3 wei based on their collateral value and the liquidationThreshold (0x45ff ≈ 80%).

The critical flaw emerges in the execution path

// Current state
currentDebt = 0x16a34c2d9925ea5a800f593f381f4275a98cfc48 // ~1.3e20 wei
amount = 9 wei
maxBorrow = 3 wei // Calculated from collateral * liquidationThreshold / 10000
// LendingPool.borrow() succeeds despite:
currentDebt + amount >> maxBorrow

This violates the fundamental invariant that total debt must never exceed the collateral-backed borrowing limit.

the contract state during this attack:

function borrow(uint256 amount) external {
// User has 2 units collateral
uint256 collateralValue = getUserCollateralValue(msg.sender); // Returns 2
// Existing debt is already massive
uint256 currentDebt = getUserDebt(msg.sender); // Returns 0x16a34...fc48
// Yet this borrow of 9 units succeeds
// Because maxBorrow (3) check happens against current debt only
}

This vulnerability fundamentally breaks the lending protocol's risk management because users can accumulate debt positions worth several times their collateral value, leaving the protocol severely undercollateralized. In a market downturn, these positions would become unrecoverable, potentially leading to protocol insolvency.

Impact

A user with minimal collateral (2 units) and existing high debt (0x16a34...fc48) can successfully borrow an additional 9 units, even though this exceeds their maximum borrowing capacity of 3 units based on the liquidation threshold.

Tools Used

vs

Recommendations

Add validation that includes the new borrow amount

function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
UserData storage user = userData[msg.sender];
uint256 collateralValue = getUserCollateralValue(msg.sender);
uint256 userTotalDebt = getUserDebt(msg.sender);
if (collateralValue < (userTotalDebt + amount).percentMul(liquidationThreshold)) {
revert NotEnoughCollateralToBorrow();
}
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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