Core Contracts

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

Real Estate Valuation Overflow Creates Phantom Debt Positions

Summary

DebtToken's mint function fails to properly scale balances when the usage index is very high, leading to incorrect debt accounting. This could cause users to have more debt than intended, potentially destabilizing the lending protocol's economics.

The issue occurs in the mint function when calculating scaled amounts using ray division. When a user borrows tokens, the contract attempts to scale their debt using the current usage index. However, if the index becomes significantly high, the ray division calculation produces incorrect results, leading to improper debt tracking.

We expects scaled balances to match the ray division calculation of amount/index. However, with a high index value, the actual scaled balance differs from the expected value.

Looking at DebtToken.sol:

function mint(address user, address onBehalfOf, uint256 amount, uint256 index) {
uint256 amountScaled = amount.rayDiv(index); // Scaling calculation where precision loss occurs
// Scaling calculation becomes imprecise with very high index values
}

At its core, the DebtToken contract tracks user borrowing positions against real estate NFT collateral.

Think of the DebtToken like a mortgage ledger, it needs to perfectly track every dollar borrowed. The issue emerges in the mint function's scaling calculations, particularly when dealing with large house valuations and borrowing amounts.

The key interaction happens between the LendingPool and DebtToken contracts. When a user borrows against their RAAC NFT collateral, the protocol:

  1. Values the property using RAACHousePrices oracle

  2. Calculates maximum borrowing power

  3. Mints debt tokens to track the loan

We found that the debt scaling calculation can produce incorrect results with certain combinations of:

  • Large property valuations (tracked in RAACHousePrices)

  • High utilization rates (affecting the interest rate index)

  • Significant borrowing amounts

Vulnerability Details

When a property owner takes a loan through the LendingPool, their debt gets tracked by the DebtToken contract. The contract uses ray math (27 decimal precision) to scale debt amounts based on the current interest rate index. However, the scaling calculation breaks down when handling luxury properties worth millions of dollars.

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
// πŸ”’ Basic input validation
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
​
// 🚨 Critical Point: Scaling calculation where precision loss occurs
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
​
// πŸ“Š Track user's current position
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
​
// πŸ“ˆ Calculate interest accrual
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
​
// πŸ’Ύ Update user's index
_userState[onBehalfOf].index = index.toUint128();
​
// πŸ’° Calculate final mint amount
uint256 amountToMint = amount + balanceIncrease;
​
// 🏭 Mint the tokens
_mint(onBehalfOf, amountToMint.toUint128());
​
// πŸ“’ Emit events
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
​
return (scaledBalance == 0, amountToMint, totalSupply());
}

The vulnerability centers around the ray division operation where amount.rayDiv(index) can produce incorrect results with large real estate values. This scaling operation affects all subsequent calculations in the function.

Here's what happens: A user deposits a high-value property NFT (tracked in RAACHousePrices) and requests a loan. The LendingPool calculates their borrowing power and calls DebtToken.mint(). At this point, the ray division between the large loan amount and current index produces incorrect results, leading to mismatched debt accounting.

This precision loss has real consequences. For a $5 million property with 80% LTV ($4M borrowing power), the debt token balance could be off by hundreds of thousands of dollars. This directly impacts:

  • The borrower's actual debt obligation

  • The protocol's collateralization calculations

  • The accuracy of total debt tracking

Impact

In real estate lending, even small accounting errors compound significantly over time. A borrower could end up with a debt position that's materially different from their actual borrowed amount.

For example, on a $500,000 property with 80% LTV, the debt token balance could be off by several thousand dollars due to scaling imprecision. This directly impacts:

  • Borrower's collateralization ratio

  • Protocol's risk calculations

  • Overall system solvency

The assumption is standard DeFi token scaling would work for real estate values. However, RAAC's unique position as a real-world asset protocol means dealing with significantly larger numbers than typical DeFi applications.

Recommendations

when dealing with real-world assets. We should:

  1. Implement strict bounds on the interest rate index

  2. Add safety checks in the scaling calculations

  3. Consider using a more precise numerical representation for real estate values

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
// πŸ”’ Basic input validation
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
​
// ⚑ Add maximum bounds check for index
if (index > MAX_INDEX) revert IndexTooHigh(); //< New protection
​
// πŸ›‘οΈ Add maximum amount check
if (amount > MAX_AMOUNT) revert AmountTooHigh(); //< New protection
​
// 🚨 Enhanced scaling calculation with safety checks
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0 || amountScaled > MAX_SCALED_AMOUNT) revert InvalidScaling(); //< Enhanced check
​
// πŸ“Š Track user's current position
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
​
// πŸ“ˆ Calculate interest accrual with overflow protection
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
require(balanceIncrease <= MAX_BALANCE_INCREASE, "Excessive interest accrual"); //< New protection
}
​
// πŸ’Ύ Update user's index with type safety
_userState[onBehalfOf].index = index.toUint128();
​
// πŸ’° Calculate final mint amount with overflow check
uint256 amountToMint = amount + balanceIncrease;
require(amountToMint >= amount, "Overflow on mint amount"); //< New protection
​
// 🏭 Mint tokens with additional safety
_mint(onBehalfOf, amountToMint.toUint128());
​
// πŸ“’ Events
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
​
return (scaledBalance == 0, amountToMint, totalSupply());
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
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!