Core Contracts

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

Incorrect Scaling of DebtToken Total Supply Leading to Underestimated Utilization Rate

Summary

The DebtToken contract incorrectly scales down the total supply by dividing it with the normalized debt index instead of multiplying. This results in a lower-than-expected total supply, and make totalUsage(totalDept) less than it should be which affects the calculation of the utilization rate and interest rates in the LendingPool.

Vulnerability Details

The totalSupply function in the DebtToken contract scales down the total supply using rayDiv with ILendingPool(_reservePool).getNormalizedDebt(). This operation should instead multiply the total supply by the normalized debt index to reflect the correct scaled total supply, similar to how the balance is scaled.

The LendingPool uses the DebtToken total supply to compute totalUsage, which is then used to calculate the utilization rate. Since totalLiquidity is the asset amount in deposited andreserve.totalUsage should also give total dept amount without scaling. An underestimated total supply leads to an underestimated utilization rate. Since the utilization rate is a key factor in determining interest rates, an underestimated utilization rate results in lower interest rates than intended.

function balanceOf(address account) public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledBalance = super.balanceOf(account);
return scaledBalance.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
}
/**
* @notice Returns the scaled total supply
* @return The total supply (scaled by the usage index)
*/
function totalSupply() public view override(ERC20, IERC20) returns (uint256) {
uint256 scaledSupply = super.totalSupply();
//@audit div instead of multiply
return scaledSupply.rayDiv(ILendingPool(_reservePool).getNormalizedDebt());
}
//Lending POOl borrow
function borrow(){
(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
//
}
uint256 utilizationRate = calculateUtilizationRate(reserve.totalLiquidity, reserve.totalUsage);
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;
}
//add to 2025-02-raac/test/unit/core/tokens/DebtToken.test.js
it("should show total supply is less than balance", async function () {
const mintAmount = ethers.parseEther("100");
const index = ethers.getBigInt("1100000000000000000000000000"); // 1e27
await mockLendingPool.setNormalizedDebt(index); // Set initial normalized debt to 1
console.log("Attempting to mint", mintAmount.toString(), "tokens with index", index.toString());
const tx = await debtToken.connect(mockLendingPoolSigner).mint(user1.address, user1.address, mintAmount, index);
const receipt = await tx.wait();
console.log("Mint transaction successful, gas used:", receipt.gasUsed.toString());
const balance = await debtToken.balanceOf(user1.address);
console.log("User balance after mint:", balance.toString());
expect(balance).to.equal(mintAmount);
const scaledBalance = await debtToken.scaledBalanceOf(user1.address);
console.log("Scaled balance after mint:", scaledBalance.toString());
const totalSupply = await debtToken.totalSupply();
const totalSupplyScaled = await debtToken.scaledTotalSupply();
console.log("Total supply after mint:", totalSupply.toString());
console.log("Scaled total supply after mint:", totalSupplyScaled.toString());
//@audit totalSupply (total dept) is les than amount borrowed
expect(balance).to.be.greaterThan(totalSupply, "User balance is unexpectedly higher than total supply");
expect(mintAmount).to.be.greaterThan(totalSupply, "User balance is unexpectedly higher than total supply");
});

Impact

Lenders and the protocol may experience financial loss due to lower interest rates, as the utilization rate is underestimated.

User balances and total supply doesnt match

Tools Used

Manual

Recommendations

Modify the totalSupply function in the DebtToken contract to multiply the total supply by the normalized debt index instead of dividing

return scaledSupply.rayMul(ILendingPool(_reservePool).getNormalizedDebt());
Updates

Lead Judging Commences

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