Core Contracts

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

Inconsistent Debt Scaling Between Borrow Function and DebtToken Mint Leads to Incorrect Debt Tracking

Description

A critical inconsistency exists in the debt tracking mechanism between the lending protocol's borrow function and the DebtToken's mint function. While DebtToken properly handles interest accrual through index scaling, the borrow function's debt tracking fails to account for accrued interest on existing debt. This mismatch creates a discrepancy between the actual user debt (tracked by DebtToken balance) and the internal accounting (scaledDebtBalance).

Affected code

// In Lending.sol
function borrow(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// ...
uint256 scaledAmount = amount.rayDiv(reserve.usageIndex);
(bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) =
IDebtToken(reserve.reserveDebtTokenAddress).mint(
msg.sender,
msg.sender,
amount,
reserve.usageIndex
);
user.scaledDebtBalance += scaledAmount; // @audit incorrect scaling
// ...
}
// In DebtToken.sol
function mint(
address user,
address onBehalfOf,
uint256 amount,
uint256 index
) external override onlyReservePool returns (bool, uint256, uint256) {
uint256 balanceIncrease = 0;
if (_userState[onBehalfOf].index != 0 && _userState[onBehalfOf].index < index) {
balanceIncrease = scaledBalance.rayMul(index) -
scaledBalance.rayMul(_userState[onBehalfOf].index);
}
uint256 amountToMint = amount + balanceIncrease;
// ...
}

Vulnerability details

The vulnerability manifests when a user with existing debt borrows additional funds. The core issue lies in how the borrow function updates scaledDebtBalance without considering previously accrued interest, while the DebtToken correctly accounts for this interest in its minting process.

Consider this scenario:

  1. User initially borrows 1000 tokens at index 1.0 RAY

    • scaledDebtBalance = 1000e18

    • DebtToken balance = 1000e18

  2. Index increases to 1.1 RAY (10% interest accrual)

  3. User borrows additional 500 tokens

    • borrow function adds: scaledAmount = 500e18/1.1e27 ≈ 454.54e18

    • New scaledDebtBalance = 1000e18 + 454.54e18 = 1454.54e18

    • DebtToken mint calculates:

      • Interest on existing debt: 1000e18 * (1.1 - 1.0) = 100e18

      • Total mint amount: 500e18 + 100e18 = 600e18

The discrepancy becomes apparent:

  • scaledDebtBalance in the protocol: 1454.54e18

  • When converting to actual debt: 1454.54e18 * 1.1e27 ≈ 1600e18

  • Actual DebtToken balance: 1600e18

This incorrect tracking of scaledDebtBalance causes:

  1. Potential errors in liquidation calculations

  2. Inaccurate interest accrual tracking

  3. Unreliable system-wide debt accounting

Tools Used

Manual Review

Recommended Mitigation Steps

Update the borrow function to use the mint return values for accurate debt scaling:

function borrow(uint256 amount) external {
// ... existing checks ...
(bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) =
IDebtToken(reserve.reserveDebtTokenAddress).mint(
msg.sender,
msg.sender,
amount,
reserve.usageIndex
);
// Use amountMinted for accurate scaling
user.scaledDebtBalance = amountMinted.rayDiv(reserve.usageIndex);
// ... rest of function ...
}

This ensures that scaledDebtBalance correctly reflects both the new borrowed amount and any accrued interest on existing debt.

Updates

Lead Judging Commences

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

LendingPool::borrow tracks debt as user.scaledDebtBalance += scaledAmount while DebtToken mints amount+interest, leading to accounting mismatch and preventing full debt repayment

Support

FAQs

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

Give us feedback!