Summary
The calculated utilization rate is incorrect in term of decimals, which can cause emission rate update incorrect
Vulnerability Details
The function RAACMinter::getUtilizationRate()
computes system utilization rate by using normalized debt from Lending Pool and total deposits in Stability Pool.
Here, the function lendingPool.getNormalizedDebt()
returns value in term of RAY
(1e27), when stabilityPool.getTotalDeposits()
returns value in term of 1e18 (RToken's decimals). This can cause the returned value of getUtilizationRate()
to be in term of 1e11
. So, the function calculateNewEmissionRate()
always goes into the if branch if (utilizationRate > utilizationTarget)
because utilizationTarget
's value range is (1, 100].
As a result, the emission rate will always increase until reached maximum emission rate.
function getUtilizationRate() internal view returns (uint256) {
@> uint256 totalBorrowed = lendingPool.getNormalizedDebt();
@> uint256 totalDeposits = stabilityPool.getTotalDeposits();
if (totalDeposits == 0) return 0;
@> return (totalBorrowed * 100) / totalDeposits;
}
function calculateNewEmissionRate() internal view returns (uint256) {
uint256 utilizationRate = getUtilizationRate();
uint256 adjustment = (emissionRate * adjustmentFactor) / 100;
@> if (utilizationRate > utilizationTarget) {
uint256 increasedRate = emissionRate + adjustment;
uint256 maxRate = increasedRate > benchmarkRate ? increasedRate : benchmarkRate;
return maxRate < maxEmissionRate ? maxRate : maxEmissionRate;
} else if (utilizationRate < utilizationTarget) {
uint256 decreasedRate = emissionRate > adjustment ? emissionRate - adjustment : 0;
uint256 minRate = decreasedRate < benchmarkRate ? decreasedRate : benchmarkRate;
return minRate > minEmissionRate ? minRate : minEmissionRate;
}
return emissionRate;
}
PoC
Add the test to file test/unit/core/pools/StabilityPool/StabilityPool.test.js
describe("Deposits", function () {
it.only("incorrect utilization rate", async function(){
async function utilizationRate() {
let borrowed = await lendingPool.getNormalizedDebt();
let deposits = await stabilityPool.getTotalDeposits();
return borrowed * 100n / deposits
}
const depositAmount = ethers.parseEther("50");
await stabilityPool.connect(user2).deposit(depositAmount);
await ethers.provider.send("evm_increaseTime", [86400 + 1]);
await ethers.provider.send("evm_mine");
const tokenId = 1;
const price = ethers.parseEther("100");
await raacHousePrices.setOracle(owner.address);
await raacHousePrices.setHousePrice(tokenId, price);
await crvusd.mint(user1.address, price)
await crvusd.connect(user1).approve(raacNFT.target, price)
await raacNFT.connect(user1).mint(tokenId, price);
await raacNFT.connect(user1).approve(lendingPool.target, tokenId);
await lendingPool.connect(user1).depositNFT(tokenId);
await lendingPool.connect(user1).borrow(price / 2n)
await lendingPool.connect(user1).updateState();
console.log(`UR ${await utilizationRate()}`)
})
Run the test and console shows:
StabilityPool
Core Functionality
Deposits
UR 2000136992
✔ incorrect utilization rate
1 passing (2s)
Impact
Tools Used
Manual
Recommendations
Scale the utilization rate accordingly