Core Contracts

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

Depositors Earn No Interest When totalDebt is Zero

Bug Report

Summary

A vulnerability exists in the calculateLiquidityRate function of the ReserveLibrary.sol contract, where the function returns 0 when totalDebt is zero. This results in the currentLiquidityRate being zero, which prevents the liquidityIndex from growing. As a consequence, depositors do not earn any interest on their deposits when there are no borrowings in the protocol.


Vulnerability Details

The calculateLiquidityRate function is responsible for calculating the interest rate earned by depositors (currentLiquidityRate). However, it returns 0 when totalDebt is zero, as shown below:

function calculateLiquidityRate(
uint256 utilizationRate,
uint256 usageRate,
uint256 protocolFeeRate,
uint256 totalDebt
) internal pure returns (uint256) {
if (totalDebt < 1) {
return 0;
}
uint256 grossLiquidityRate = utilizationRate.rayMul(usageRate);
uint256 protocolFeeAmount = grossLiquidityRate.rayMul(protocolFeeRate);
uint256 netLiquidityRate = grossLiquidityRate - protocolFeeAmount;
return netLiquidityRate;
}

This behavior causes the liquidityIndex to remain stagnant, preventing depositors from earning interest when there are no borrowings.

ImpactDepositors Earn No Interest When `totalDebt` is Zero

  1. Depositors Earn No Interest: When totalDebt is zero, depositors do not earn any interest on their funds.

  2. Protocol Reputation Damage: Users may lose trust in the protocol if they realize they are not earning interest.

  3. Economic Imbalance: The protocol's economic model relies on a balance between lenders and borrowers. If lenders are not incentivized, the protocol may struggle to attract liquidity.


Tools Used

  • Foundry: Used to write and execute the test case that demonstrates the issue.

  • Manual Code Review: Identified the vulnerability by analyzing the calculateLiquidityRate function and its usage in the protocol.


Proof of Concept (PoC)

Below is a Foundry test that demonstrates the issue:

Test Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/LendingPool.sol";
import "../src/ReserveLibrary.sol";
import "../src/WadRayMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract LendingPoolTest is Test {
LendingPool public lendingPool;
ERC20 public reserveAsset;
address public user = address(0x123);
function setUp() public {
// Deploy a mock ERC20 token for the reserve asset
reserveAsset = new ERC20("Reserve Asset", "RA");
// Deploy the LendingPool contract
lendingPool = new LendingPool(
address(reserveAsset), // reserveAssetAddress
address(0), // rTokenAddress (mock)
address(0), // debtTokenAddress (mock)
address(0), // raacNFTAddress (mock)
address(0), // priceOracleAddress (mock)
1e27 // initialPrimeRate (1e27 = 100% in RAY)
);
// Mint reserve assets to the user
reserveAsset.mint(user, 1000e18);
}
function testDepositEarnsNoInterestWhenTotalDebtIsZero() public {
// User approves the LendingPool to spend their reserve assets
vm.prank(user);
reserveAsset.approve(address(lendingPool), 100e18);
// User deposits 100 units of the reserve asset
vm.prank(user);
lendingPool.deposit(100e18);
// Simulate time passing (1 year)
vm.warp(block.timestamp + 365 days);
// User withdraws their funds
vm.prank(user);
lendingPool.withdraw(100e18);
// Check the user's balance after withdrawal
uint256 userBalance = reserveAsset.balanceOf(user);
assertEq(userBalance, 1000e18, "User did not earn any interest");
}
}

Recommendations

Introduce a minimum liquidity rate in the calculateLiquidityRate function to ensure that depositors earn interest even when totalDebt is zero. Here’s the modified function:

function calculateLiquidityRate(
uint256 utilizationRate,
uint256 usageRate,
uint256 protocolFeeRate,
uint256 totalDebt
) internal pure returns (uint256) {
uint256 minLiquidityRate = 0.01 * 1e27; // Example: 1% minimum rate in RAY
if (totalDebt < 1) {
return minLiquidityRate;
}
uint256 grossLiquidityRate = utilizationRate.rayMul(usageRate);
uint256 protocolFeeAmount = grossLiquidityRate.rayMul(protocolFeeRate);
uint256 netLiquidityRate = grossLiquidityRate - protocolFeeAmount;
return netLiquidityRate > minLiquidityRate ? netLiquidityRate : minLiquidityRate;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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