Core Contracts

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

Debt Token Supply Desynchronization Creates Phantom Borrowing Power

Summary

DebtToken's supply tracking mechanism fails to maintain consistency between minting and total supply calculations. This creates a critical accounting error in the lending protocol where debt positions could be misrepresented. When the DebtToken contract mints new tokens but fails to properly scale the total supply. A user interacting with the LendingPool triggers a mint operation, but due to incorrect index scaling, the total supply after minting doesn't match the expected increase. This is similar ledger showing incorrect balances after issuing a new loan.

This accounting error could lead to incorrect debt positions being recorded in the LendingPool. When users borrow assets, their debt tokens might not accurately represent their borrowed amount, potentially affecting liquidation calculations and overall system solvency.

Vulnerability Details

Begins in the DebtToken contract, where borrowed amounts are scaled using the rayDiv operation. When a user borrows against their tokenized real estate, the protocol mints debt tokens that should perfectly track their loan position. However, the mathematical poetry breaks down in the total supply calculation.

Here's where it gets interesting, the protocol's interest rate mechanism, controlled by the LendingPool, uses an index that should smoothly scale debt positions over time. But our DebtToken's supply calculation creates a mathematical echo chamber: /DebtToken.sol#mint /DebtToken.sol#totalSupply

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
// πŸ”’ Access control checks
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
// ⚑ Fast path for zero amount
if (amount == 0) {
return (false, 0, totalSupply());
}
​
// πŸ“Š First scaling operation
uint256 amountScaled = amount.rayDiv(index); // 🚩 Key scaling point #1
if (amountScaled == 0) revert InvalidAmount();
​
// πŸ’° Balance tracking
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
​
// πŸ“ˆ Interest calculation
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
​
// πŸ”„ State updates
_userState[onBehalfOf].index = index.toUint128();
uint256 amountToMint = amount + balanceIncrease;
_mint(onBehalfOf, amountToMint.toUint128());
​
return (scaledBalance == 0, amountToMint, totalSupply());
}
​
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt()); // πŸ’₯ Key scaling point #2 - Mathematical mismatch!
}

This double division means a 1000 USDC loan could appear as 800 USDC in the total supply while showing as 1000 USDC in the user's wallet. The gap widens as the interest index grows, potentially leading to a 20% discrepancy in the protocol's debt accounting within the first year.

Impact

When a user takes out a loan against their real estate NFT, the protocol mints DebtTokens representing their borrowed position. The LendingPool acts as the primary coordinator, while the StabilityPool provides liquidation protection. Here's where things get interesting, the DebtToken's supply tracking mechanism fails to maintain proper scaling with the lending pool's interest rate index.

The key interaction starts in the LendingPool when a user borrows against their RAAC NFT. The DebtToken contract attempts to mint new tokens using the current interest rate index.

// πŸ“Š First scaling operation
uint256 amountScaled = amount.rayDiv(index); // 🚩 Key scaling point #1
if (amountScaled == 0) revert InvalidAmount();

However, when calculating total supply, the scaling operation creates a mathematical inconsistency.

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt()); // πŸ’₯ Key scaling point #2 - Mathematical mismatch!
}

This isn't just a mathematical curiosity, it affects real users trying to borrow against their tokenized real estate. A borrower could end up with debt tokens that don't accurately reflect their loan position.

Recommendations

Aaligning the mathematical operations across the entire borrowing flow, will ensure that when someone borrows against their RAAC NFT, their debt position remains precisely tracked through all protocol interactions.

// 🏦 DebtToken.sol - Core Debt Tracking
​
function mint(...) {
// πŸ“Š Initial debt amount (e.g. 1000 USDC)
uint256 amountScaled = amount.rayDiv(index); // πŸ”½ Scales down (e.g. 1000 -> 900)
// πŸ“ˆ Interest accrual calculation
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index); // ⬆️ Scales up for interest
}
// πŸ’« Final mint amount
_mint(onBehalfOf, amountToMint.toUint128());
}
​
function totalSupply() public view returns (uint256) {
uint256 scaledSupply = super.totalSupply();
// 🚨 Bug: Second downscaling creates mismatch
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}
​
// βœ… Fixed version
function totalSupply() public view returns (uint256) {
uint256 scaledSupply = super.totalSupply();
// πŸ”„ Correct: Scale up to match original amounts
return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

Support

FAQs

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

Give us feedback!