Core Contracts

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

LendingPool utilization rate is incorrectly calculated

Summary

LendingPool utilization rate is incorrectly calculated due to incorrect total supply value returned by DebtToken.

Vulnerability Details

LendingPool utilization rate is calculated based on reserve.totalLiquidity and reserve.totalUsage.

ReserveLibrary.sol::updateInterestRatesAndLiquidity()

// Calculate utilization rate
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, reserve.totalUsage);

reserve.totalLiquidity is updated when user deposits into LendingPool, and the value is in underlying asset units.

ReserveLibrary.sol::updateInterestRatesAndLiquidity()

// Update total liquidity
if (liquidityAdded > 0) {
reserve.totalLiquidity = reserve.totalLiquidity + liquidityAdded.toUint128();
}
if (liquidityTaken > 0) {
if (reserve.totalLiquidity < liquidityTaken) revert InsufficientLiquidity();
reserve.totalLiquidity = reserve.totalLiquidity - liquidityTaken.toUint128();
}

reserve.totalUsage is updated when DebtToken tokens are minted or burned (when user borrows or repays).

LendingPool.sol::borrow()

// Mint DebtTokens to the user (scaled amount)
@> (bool isFirstMint, uint256 amountMinted, uint256 newTotalSupply) = IDebtToken(reserve.reserveDebtTokenAddress).mint(msg.sender, msg.sender, amount, reserve.usageIndex);
// Transfer borrowed amount to user
IRToken(reserve.reserveRTokenAddress).transferAsset(msg.sender, amount);
user.scaledDebtBalance += scaledAmount;
// reserve.totalUsage += amount;
@> reserve.totalUsage = newTotalSupply;

Essentially, reserve.totalUsage is the value returned by DebtToken's totalSupply().

DebtToken.sol::mint()

return (scaledBalance == 0, amountToMint, totalSupply());

DebtToken overrides ERC20's _update(), and the store value is scaled and divided by reserve usage index.

DebtToken.sol::_update()

uint256 scaledAmount = amount.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
super._update(from, to, scaledAmount);

totalSupply() is also overriden, it firstly calls ERC20's totalSupply() to get the store value, then divides the value by reserve usage index.

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}

That mean, the value returned by totalSupply() is non-scaled supply value / usageIndex0 / usageIndex1, the value is scaled by twice.

calculateUtilizationRate() does not do any conversion, it adds reserve.totalLiquidity to reserve.totalUsage directly, this is obviously wrong as reserve.totalLiquidity is non-scaled.

ReserveLibrary.sol::calculateUtilizationRate()

function calculateUtilizationRate(uint256 totalLiquidity, uint256 totalDebt) internal pure returns (uint256) {
if (totalLiquidity < 1) {
return WadRayMath.RAY; // 100% utilization if no liquidity
}
uint256 utilizationRate = totalDebt.rayDiv(totalLiquidity + totalDebt).toUint128();
return utilizationRate;
}

Impact

LendingPool utilization rate is used for calculating borrow rate, incorrect utilization rate leads to incorrect borrow rate and borrower pays incorrect interests.

Tools Used

Manual Review

Recommendations

totalSuppy() should return non-scaled value.

function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
- return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
+ return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
Updates

Lead Judging Commences

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

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

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

DebtToken::totalSupply incorrectly uses rayDiv instead of rayMul, severely under-reporting total debt and causing lending protocol accounting errors

Support

FAQs

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

Give us feedback!