Core Contracts

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

`DebtToken::mint` double counts interest and mints the wrong amount leads to user debt positions inflated

Summary

The LendingPool::borrow function allows a user to borrow reserve assets using their NFT collateral. This function calls DebtToken::mint that mints the equivalent debtToken that represents the user debt. The DebtToken::mint implementation contains two issues:

  • it incorrectly adds accrued interest to new borrowings when minting debtToken, effectively converting interest into principal and creating more debt tokens than the actual borrowed amount. This creates a compound effect where interest is double-counted: once through the index mechanism and again through direct token minting.

  • the function mints amountToMint (which includes both the new amount and the accrued interest) instead of using amountScaled. This is wrong because the protocol uses scaled balances to track debt positions. By minting the unscaled amount, it creates an inconsistency in the debt accounting system where the user's debt position becomes severely inflated.

This leads to wrong users' debt positions accounting.

Vulnerability Details

function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
_userState[onBehalfOf].index = index.toUint128();
@> uint256 amountToMint = amount + balanceIncrease;
@> _mint(onBehalfOf, amountToMint.toUint128());
emit Transfer(address(0), onBehalfOf, amountToMint);
emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
return (scaledBalance == 0, amountToMint, totalSupply());
}

Impact

  • The system calculates balanceIncrease to represent accrued interest on existing debt

  • This balanceIncrease is added to the new borrowed amount (amount) before minting

  • The resulting amountToMint includes both the new borrowed amount and the accrued interest

  • The system mint amountToMint instead of amountSscaled

The vulnerability leads to artificially inflated users debt positions and manipulated protocol's accounting by having their interest double-counted and wrongly scaled, leading to incorrect debt calculations, unreliable protocol metrics, and wrong liquidations.

Tools Used

Manual review

Recommendations

The debtToken::mint function should be modified to only mint tokens for the new borrowed amount, leaving interest accounting to the index mechanism.

function mint(address user, address onBehalfOf, uint256 amount, uint256 index) external override onlyReservePool returns (bool, uint256, uint256) {
if (user == address(0) || onBehalfOf == address(0)) revert InvalidAddress();
if (amount == 0) {
return (false, 0, totalSupply());
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
uint256 scaledBalance = balanceOf(onBehalfOf);
bool isFirstMint = scaledBalance == 0;
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) - scaledBalance.rayMul(_userState[onBehalfOf].index);
}
_userState[onBehalfOf].index = index.toUint128();
- uint256 amountToMint = amount + balanceIncrease;
- _mint(onBehalfOf, amountToMint.toUint128());
+ _mint(onBehalfOf, amountScaled.toUint128());
- emit Transfer(address(0), onBehalfOf, amountToMint);
+ emit Transfer(address(0), onBehalfOf, amountScaled;
- emit Mint(user, onBehalfOf, amountToMint, balanceIncrease, index);
+ emit Mint(user, onBehalfOf, amountScaled, balanceIncrease, index);
- return (scaledBalance == 0, amountToMint, totalSupply());
+ return (scaledBalance == 0, amountScaled, totalSupply());
}
Updates

Lead Judging Commences

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

DebtToken::mint miscalculates debt by applying interest twice, inflating borrow amounts and risking premature liquidations

Support

FAQs

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

Give us feedback!