Core Contracts

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

Emission rate can be manipulated

Summary

The function RAACMinter::updateEmissionRate() can update the emission rate if the emission update interval passed. Here exists an flaw that can allow an attacker to inflate the emission rate by increasing utilization each time time emission rate is updated.

Vulnerability Details

The function RAACMinter::updateEmissionRate() allows anyone to update the emission rate if an emission update interval passed. The new rate is calculated based on the current system utilization. The utilization rate depends on current LendingPool's total borrows and StabilityPool's total deposits.

This can open up an attack vector that right after the update interval passes, an attacker borrows a large amount of funds from LendingPool to increase the utilization rate (to be higher than the target utilization) in order to increase the emission rate and then finally repay the debt without any interest. By this, the attacker can repeat the same attack vector after each update interval passes. As a result, after many intervals pass, the emission rate can be inflated to be maximum value. The higher the value adjustmentFactor is it, the less intervals needed to inflate the emission rate to maximum

Note that the utilization rate can also be manipulated to be lower to decrease emission rate

function updateEmissionRate() public whenNotPaused {
if (emissionUpdateInterval > 0 && block.timestamp < lastEmissionUpdateTimestamp + emissionUpdateInterval) {
revert EmissionUpdateTooFrequent();
}
@> uint256 newRate = calculateNewEmissionRate();
@> emissionRate = newRate;
lastEmissionUpdateTimestamp = block.timestamp;
emit EmissionRateUpdated(newRate);
}
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;
}
function getUtilizationRate() internal view returns (uint256) {
@> uint256 totalBorrowed = lendingPool.getNormalizedDebt();
@> uint256 totalDeposits = stabilityPool.getTotalDeposits();
if (totalDeposits == 0) return 0;
@> return (totalBorrowed * 100) / totalDeposits;
}

PoC

Add the test to test/unit/core/minters/RAACMinter.test.js

describe("RAACMinter", function () {
...
it.only("inflate emission rate", async function(){
// @audit POC inflate emission rate
// emission interval passed
await ethers.provider.send("evm_increaseTime", [86400 + 1]);
await ethers.provider.send("evm_mine");
// 1. normal emission rate
// LendingPool normalized debt is in RAY
await lendingPool.mockGetNormalizedDebt(ethers.parseUnits("20", 27));
// assume that total deposit is 100 RAY
await stabilityPool.mockGetTotalDeposits(ethers.parseUnits("100", 27));
await raacMinter.updateEmissionRate();
const initialEmissionRate = await raacMinter.emissionRate();
for(let i = 0 ; i < 16; ++i){
// emission interval passed
await ethers.provider.send("evm_increaseTime", [86400 + 1]);
await ethers.provider.send("evm_mine");
// 2. Attacker deposits NFT and borrow as much as possible
// assume that each time the emission rate is about to be updated,
// the attacker borrows a large amount of funds and then call updateEmissionRate()
// and then repay debt
await lendingPool.mockGetNormalizedDebt(ethers.parseUnits("1000", 27));
await raacMinter.updateEmissionRate();
}
let currentEmissionRate = await raacMinter.emissionRate();
console.log(initialEmissionRate, currentEmissionRate)
expect(currentEmissionRate).to.eq(await raacMinter.maxEmissionRate())
})

Run the test and console shows:

RAACMinter
131944444444444444n 277777777777777777n
✔ inflate emission rate (74ms)
1 passing (2s)

It means that the emission rate reaches maximum after 16 intervals

Impact

  • Emission rate is manipulated

  • Rewards minting process is manipulated

Tools Used

Manual

Recommendations

Consider capping the rate change based on the difference between utilization rates

Updates

Lead Judging Commences

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

RAACMinter vulnerable to manipulation via flash borrowing to artificially inflate emission rates by temporarily spiking utilization

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

RAACMinter vulnerable to manipulation via flash borrowing to artificially inflate emission rates by temporarily spiking utilization

Support

FAQs

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