Core Contracts

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

Sandwich attack on RAACMinter::tick() allows inflating the RAAC rewards emission

Summary

A user can sandwich the RAACMinter::tick() function to massively inflate the emission of rewards.

Vulnerability Details

For this attack to work, an attacker (Alice) must first stake a large amount of rToken into the Stability Pool contract. This ensures she is eligible for RAAC rewards.

Next, Alice can front-run an upcoming mint of RAAC rewards. Since minting is triggered by the tick() function, Alice can even manually initiate it—she just needs to time the emission interval correctly.

Schematic Proof of Concept (PoC):

  1. Alice deposits a large amount of rToken into the StabilityPool, receiving deToken and becoming eligible for rewards.

  2. Alice waits for the emission interval to complete so that tick() can be called and RAAC rewards can be distributed.

  3. Once the emission interval has passed, Alice, while maintaining healthy collateral level, can borrow a large amount of funds, inflating the RAAC rewards emission by front-running the tick() function.

  4. In the next block, Alice repays the debt, but the inflated RAAC rewards remain issued.

Why This Happens:

Let's look at the code snippets below:

function updateEmissionRate() public whenNotPaused {
if (emissionUpdateInterval > 0 && block.timestamp < lastEmissionUpdateTimestamp + emissionUpdateInterval) {
revert EmissionUpdateTooFrequent();
}
uint256 newRate = calculateNewEmissionRate();
emissionRate = newRate;
lastEmissionUpdateTimestamp = block.timestamp;
emit EmissionRateUpdated(newRate);
}

Update calls calculate ->

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;
}

The emission rate is calculated based on the utilization rate, which includes totalBorrowed from the lending pool.

function getUtilizationRate() internal view returns (uint256) {
uint256 totalBorrowed = lendingPool.getNormalizedDebt();
uint256 totalDeposits = stabilityPool.getTotalDeposits();
if (totalDeposits == 0) return 0;
return (totalBorrowed * 100) / totalDeposits;
}

Since higher borrowing leads to higher rewards, Alice can exploit this mechanism to artificially increase her RAAC rewards without real cost.

Impact

Stealing from the protocol

Tools Used

Manual review

Recommendations

Fix is not trivial

Updates

Lead Judging Commences

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

RAACMinter's utilization rate calculation uses point-in-time values that can be manipulated via flash borrowing/lending, allowing control of emission rates at minimal cost

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

RAACMinter's utilization rate calculation uses point-in-time values that can be manipulated via flash borrowing/lending, allowing control of emission rates at minimal cost

Support

FAQs

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

Give us feedback!