Core Contracts

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

Incorrect Utilization Rate Calculation in RAAC Minter leads to emission rate always increasing after first deposit

Summary

The RAAC Minter contract incorrectly calculates the utilization rate, leading to unintended emission rate adjustments. The function getUtilizationRate() retrieves the total borrowed value from lendingPool.getNormalizedDebt(), which returns the usage index instead of the total normalized debt supply. This results in an incorrect utilization rate calculation, causing emission rates to always increase after the first deposit in StabilityPool::deposit.

Vulnerability Details

Vulnerability Details
The vulnerability exists in the following function inside the RAAC Minter contract:

/**
* @dev Calculates the current system utilization rate
* @return The utilization rate as a percentage (0-100)
*/
function getUtilizationRate() public view returns (uint256) {
uint256 totalBorrowed = lendingPool.getNormalizedDebt();
uint256 totalDeposits = stabilityPool.getTotalDeposits();
if (totalDeposits == 0) return 0;
return (totalBorrowed * 100) / totalDeposits;
}

The function lendingPool.getNormalizedDebt() does not return the total normalized debt but rather the usageIndex, which is not useful for utilization calculations.The correct value for totalBorrowed should be the total normalized supply of the debt token, which represents the actual outstanding debt in the system.Since the incorrect totalBorrowed value is used, the utilization rate calculation is fundamentally flawed. As a result, getUtilizationRate() returns a nonzero utilization rate even when there is no actual borrowed amount. Since the usageIndex is in ray precision (1e27) and total deposits have 18 decimal precision, the calculation will be flawed. As a result, the emission rate will only ever decrease once which will be the first time a user deposits using StabilityPool::deposit. Every deposit/borrow after that will produce an increase in the emission rate which is not intended behaviour.

Proof Of Code (POC)

This test was run in protocols-test.js in the "StabilityPool" describe block

it("test minter utilization rate never less than utilization target after first deposit", async function () {
const preupdateemissionrate = await contracts.minter.getEmissionRate();
console.log(`preupdateemissionrate: ${preupdateemissionrate}`);
console.log(`benchmark: ${await contracts.minter.benchmarkRate()}`);
const utilratepredeposit = await contracts.minter.getUtilizationRate();
console.log(`utilratepredeposit: ${utilratepredeposit}`);
//c start from a position where there is no debt in the protocol but there are rtoken deposits in the stability pool. In this position, the utilization rate should be 0 but it wont be due to wrong calculation in the minter contract
//c deposit function calls raacminter::tick which updatesemissionrate when utilization rate is 0 so the emission rate will decrease on the first mint as expected
await contracts.stabilityPool.connect(user1).deposit(STABILITY_DEPOSIT);
//c to run this test, temporarily, change the getUtilizationRate() function in the minter contract to public. This will show the incorrect utilization rate. since there is no debt accrued in the lending pool, the utilization rate should be 0 but it isnt because the minter contract calculates the utilization rate incorrectly
const mintutilrate = await contracts.minter.getUtilizationRate();
console.log(`mintutilrate: ${mintutilrate}`);
assert(mintutilrate > 0);
const emissionrateafterdeposit = await contracts.minter.getEmissionRate();
console.log(`emissionrateafterdeposit: ${emissionrateafterdeposit}`);
//allow some time to pass with no more deposits or borrows. then updateemissionsrate and see that the rate has increased to match the benchmark rate when it should decrease
await time.increase(73 * 60 * 60);
await contracts.minter.updateEmissionRate();
const postupdateemissionrate = await contracts.minter.getEmissionRate();
console.log(`postupdateemissionrate: ${postupdateemissionrate}`);
assert(postupdateemissionrate > emissionrateafterdeposit);
});

Impact

Inaccurate Emission Adjustments: Since the emission rate is dynamically adjusted based on utilization, an incorrect calculation means the protocol could emit too many or too few rewards, affecting the incentive structure.
Protocol Inefficiency: A nonzero utilization rate when no debt exists means emissions could be kept higher than necessary, leading to excessive inflation of RAAC rewards.
Misleading Data: Users relying on the utilization rate for decision-making (e.g., whether to deposit or borrow) could be misled into making incorrect financial decisions.

Tools Used

Manual Review, Hardhat

Recommendations

To fix this issue, totalBorrowed should be set to the actual total supply of the debt token rather than relying on lendingPool.getNormalizedDebt(). Modify getUtilizationRate() as follows:

/**
* @dev Calculates the current system utilization rate
* @return The utilization rate as a percentage (0-100)
*/
function getUtilizationRate() public view returns (uint256) {
uint256 totalBorrowed = IDebtToken(debtTokenAddress).totalSupply();
// Fix: Get total normalized supply of the debt token instead of using getNormalizedDebt()
uint256 totalDeposits = stabilityPool.getTotalDeposits();
if (totalDeposits == 0) return 0;
return (totalBorrowed * 100) / totalDeposits;
}

This ensures:

The correct total borrowed value is used.
The utilization rate reflects actual lending activity.
Emissions are adjusted appropriately based on real system usage.
By implementing this fix, the protocol will correctly adjust emission rates based on actual utilization, preventing unintended reward distortions and ensuring better efficiency.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACMinter::getUtilizationRate incorrectly mixes stability pool deposits with lending pool debt index instead of using proper lending pool metrics

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

RAACMinter::getUtilizationRate incorrectly mixes stability pool deposits with lending pool debt index instead of using proper lending pool metrics

Support

FAQs

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