Core Contracts

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

Incorrect precision handling will result in hyper-inflated utilisation rate, affecting the emission rate of RAAC Token

Summary

The RAACMinter is responsible for managing the minting and distribution of RAAC tokens based on a dynamic emissions schedule. These tokens are minted to be distributed for rewards. To calculate the emission rate, the minting strategy adjusts based on the system utilization rate.

However, due to incorrect precision handling when calculating the utilization rate, it will result in a overly large value, hence affecting the emIssion schedule of RAAC Token.

Vulnerability Details

Assume this scenario whereby tick()function is called:

  1. Assume the first if statement executes, calling updateEmissionRate()

    function tick() external nonReentrant whenNotPaused {
    if (emissionUpdateInterval == 0 || block.timestamp >= lastEmissionUpdateTimestamp + emissionUpdateInterval) {
    updateEmissionRate();
    }
    uint256 currentBlock = block.number;
    uint256 blocksSinceLastUpdate = currentBlock - lastUpdateBlock;
    if (blocksSinceLastUpdate > 0) {
    uint256 amountToMint = emissionRate * blocksSinceLastUpdate;
    if (amountToMint > 0) {
    excessTokens += amountToMint;
    lastUpdateBlock = currentBlock;
    raacToken.mint(address(stabilityPool), amountToMint);
    emit RAACMinted(amountToMint);
    }
    }
  2. In updateEmissionRate(), assume the if statement does not execute. calculateNewEmissionRate()is executed

    if (emissionUpdateInterval > 0 && block.timestamp < lastEmissionUpdateTimestamp + emissionUpdateInterval) {
    revert EmissionUpdateTooFrequent();
    }
    uint256 newRate = calculateNewEmissionRate();
  3. Now in calculateNewEmissionRate(), getUtilizationRate()is called

    function calculateNewEmissionRate() internal view returns (uint256) {
    uint256 utilizationRate = getUtilizationRate();
  4. To get totalBorrowed, getNormalizedDebt()is called, which will return the reserve.usageIndex. This value is in 27 decimals, as seen at constructor whereby reserve.usageIndex = uint128(WadRayMath.RAY). Assume totalBorrowed = 1.1e27

    function getUtilizationRate() internal view returns (uint256) {
    uint256 totalBorrowed = lendingPool.getNormalizedDebt();
    uint256 totalDeposits = stabilityPool.getTotalDeposits();
    if (totalDeposits == 0) return 0;
    return (totalBorrowed * 100) / totalDeposits;
    }
    1. to get totalDeposits, getTotalDeposits()is called, which will return rToken.balanceOf(address(this)). This will be in 18 decimals. Assume totalDeposits = 100_000e18

  5. UtilizationRate = (1.1e27 * 100) / 100_000e18 = 1.1e6

  6. Now back to calculateEmissionRate():

    1. Assume current emissionRate = 100 * 1e18

    2. adjustment = ( 100e18 * 5) / 100

    3. utilizationTarget = 70, hence the if statement will execute as utilizationRate > utilizationTarget

      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;
      }
    4. increasedRate = 100e18 + (( 100e18 * 5) / 100) = 105e18

    5. since increasedRate > 100e18, maxRate = increasedRate

    6. since maxRate < 2000e18, maxRate = 105e18

As seen in 6(iii), the if statement checks if (UtilizationRate > UtilizationTarget) will always execute, due to the low value of it being 70. Even if updater role updates the UtilizationTarget via setUtilizationTarget(), the maximum it can be set to is 100, which is the MAX_UTILIZATION_TARGET.

Additionally, as long as rToken.balanceOf(address(this)) !=0, it is 100% certainty that the logic of getUtilizationRate() will result in utilizationRate > utilizationTarget, as usage index is in 27 decimals, and R Token is in 18 decimals.

Impact

Emission rate can never be decreased, as the else statement (utilizationRate < utilizationTarget) will never execute unless rToken.balanceOf(address(this))= 0. The emission rate will either stay the same or increase, and can never be decreased. The utilizationTarget is rendered useless, and even if system is heavily utilized, RAAC emission rate will not be reduce. This can lead to oversupply of RAAC tokens.

Tools Used

Manual

Recommendations

Ensure the utilizationTarget is handled correctly to match precision of utilizationRate.

Updates

Lead Judging Commences

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

Give us feedback!