Core Contracts

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

DebtToken.totalSupply() is incorrect

Summary

The DebtToken.totalSupply() function is using division (rayDiv) instead of multiplication, causing incorrect results which is used by different critical functions like mint, burn.

Vulnerability Details

  1. Consider the state of system: index = 10 and no tokens minted yet (totalSupply = 0).

  2. A user borrows 100 tokens by calling borrow() with amount=100

    function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
    if (isUnderLiquidation[msg.sender]) revert CannotBorrowUnderLiquidation();
    --snip--
    // 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 = newTotalSupply;
    --snip--
    }
  3. This triggers DebtToken.mint() with amount=100

  4. Inside mint(), the system updates user balance and _totalSupply by amount / index = 100 / 10 = 10.

    1. Note: DebtToken.mint() > _mint() > _update > super._update(amount/index)

    2. So now, super.totalSupply = 10.

  5. DebtToken.mint returns totalSupply() which is 1. (This is where wrong calculation happens)

    • totalSupply() = super.totalSupply / index = 10 / 10 = 1.

    function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
    uint256 scaledSupply = super.totalSupply();
    return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
    }
  6. Borrow function updates reserve’s totalUsage to newSupply = 1

  7. The real amount borrowed was 100, but reserve.totalUsage is incorrectly set to 1 instead of 100.

Since totalUsage affects utilization rate and interest rates, this miscalculation breaks the borrowing and lending logic.

Impact

  • Incorrect reserve state: reserve.totalUsage is updated with a scaled value, leading to wrong reserve’s actual debt, utilization rates and interest rates

Tools Used

Manual code review

Recommendations

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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 6 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.