Core Contracts

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

incorrect amount burned while repaying a debt

Summary

The DebtToken contract burns the nominal amount instead of the scaled amount, creating a mismatch between the LendingPool's debt tracking and the actual DebtToken balances.

Vulnerability Details

The RAAC protocol's DebtToken contract contains a critical bug in the burn function, which leads to a discrepancy in debt balance tracking. This bug affects the protocol's accounting system, causing inconsistencies between the LendingPool and DebtToken contracts. The issue arises due to incorrect scaling of the repayment amount, leading to mismatched debt records

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L210

if(amount > userBalance){
amount = userBalance;
}
uint256 amountScaled = amount.rayDiv(index);
if (amountScaled == 0) revert InvalidAmount();
_burn(from, amount.toUint128()); ///@audit Burns amount instead of amountScaled
emit Burn(from, amountScaled, index);
return (amount, totalSupply(), amountScaled, balanceIncrease);
}

The LendingPool reduces the user's debt by the scaled amount (amountScaled), while the DebtToken burns the full repayment amount (amount). This creates a discrepancy between the two systems

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/pools/LendingPool/LendingPool.sol#L425

// Transfer reserve assets from the caller (msg.sender) to the reserve
IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amountScaled);
reserve.totalUsage = newTotalSupply;
user.scaledDebtBalance -= amountBurned; //@audit here
// Update liquidity and interest rates
ReserveLibrary.updateInterestRatesAndLiquidity(reserve, rateData, amountScaled, 0);
emit Repay(msg.sender, onBehalfOf, actualRepayAmount);
}

Repayment Fails Due to Insufficient Debt Balance
When a user attempts to repay their debt, the LendingPool checks the user's debt balance to ensure the repayment amount does not exceed the outstanding debt. However, because the DebtToken contract has already burned more than it should have (due to the bug), the user's debt balance in the DebtToken contract will be lower than expected

PROOF OF CONCEPT

  1. Initial Borrow:
    User borrows 1000 USDC
    Initial index = 1.0 RAY (1e27)
    Initial scaled debt = 1000

  2. After Interest Accrual:
    Index rises to 1.2 RAY
    Actual debt = 1000 * 1.2 = 1200 USDC

  3. Partial Repayment Attempt:
    User attempts to repay 600 USDC

  4. LendingPool calculation
    scaledRepay = 600 / 1.2 = 500
    remainingScaledDebt = 1000 - 500 = 500

  5. debttokenbalance tracking
    burnAmount = 600 // Burns nominal amount
    remainingScaledDebt = 1000 - 600 = 400

due to two different balance tracking when users will try to repay their borrow fully this check will block repayment

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/DebtToken.sol#L202-L203

function burn(
address from,
uint256 amount,
uint256 index
) external override onlyReservePool returns (uint256, uint256, uint256, uint256) {
if (from == address(0)) revert InvalidAddress();
if (amount == 0) {
return (0, totalSupply(), 0, 0);
}
uint256 userBalance = balanceOf(from);
uint256 balanceIncrease = 0;
if (_userState[from].index != 0 && _userState[from].index < index) {
uint256 borrowIndex = ILendingPool(_reservePool).getNormalizedDebt();
balanceIncrease = userBalance.rayMul(borrowIndex) - userBalance.rayMul(_userState[from].index);
amount = amount;
}
_userState[from].index = index.toUint128();
if(amount > userBalance){ ////@audit this will block repayment of loans fully
amount = userBalance;
}

this check will block reayment because the userbalance which is debttokens balance tracking will have a lower balance due to the incorrect burn logic

Impact

incorrect balance tracking
Users may be unable to fully repay their loans due to the accounting mismatch,

Tools Used

Recommendations

correct the balance tracking logic

Updates

Lead Judging Commences

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

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

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

DebtToken::burn calculates balanceIncrease (interest) but never applies it, allowing borrowers to repay loans without paying accrued interest

Interest IS applied through the balanceOf() mechanism. The separate balanceIncrease calculation is redundant/wrong. Users pay full debt including interest via userBalance capping.

RToken::burn incorrectly burns amount (asset units) instead of amountScaled (token units), breaking token economics and interest-accrual mechanism

Support

FAQs

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

Give us feedback!