Core Contracts

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

Inflated emission rate in the RAACMinter contract

Summary

In RAACMinter::getUtilizationRate, the utilization calculation fails to normalize the RAY scale (1e27) of totalBorrowed against the regular decimal scale (1e18) of totalDeposits. This causes the utilization rate to be inflated by 1e9, leading to maximum emission rates and excessive token minting.

Vulnerability Details

In the RAACMinter::getUtilizationRate. totalBorrowed gets the value of the lendingPool.getNormalizedDebt() which represent the ReserveData - usageIndex. That's a RAY scaled number (1e27). It divides it by the totalDeposits whuch is 1e18 scaled number and finally passes the inflated result to the utilizationRate variable in the calculateNewEmissionRate function. The check utilizationRate > utilizationTarget is always true since the utilizationTarget is set in the beginning to 70, so on every call the emission rate is being increased.

POC

Create a foundry setup using the commands in this document:
https://book.getfoundry.sh/config/hardhat?highlight=hardhat#adding-foundry-to-a-hardhat-project

Create a raacFoundrySetup.t.sol file under the test directory and add this code:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {LendingPool} from "contracts/core/pools/LendingPool/LendingPool.sol";
import {StabilityPool} from "contracts/core/pools/StabilityPool/StabilityPool.sol";
import {crvUSDToken} from "contracts/mocks/core/tokens/crvUSDToken.sol";
import {RToken} from "contracts/core/tokens/RToken.sol";
import {DebtToken} from "contracts/core/tokens/DebtToken.sol";
import {RAACNFT} from "contracts/core/tokens/RAACNFT.sol";
import {RAACHousePricesMock} from "contracts/mocks/core/primitives/RAACHousePricesMock.sol";
import {RAACHousePriceOracle} from "contracts/core/oracles/RAACHousePriceOracle.sol";
import {MockFunctionsRouter} from "contracts/mocks/core/oracles/MockFunctionsRouter.sol";
import {FeeCollector} from "contracts/core/collectors/FeeCollector.sol";
import {MockVeToken} from "contracts/mocks/core/tokens/MockVeToken.sol";
import {RAACMinter} from "contracts/core/minters/RAACMinter/RAACMinter.sol";
import {DEToken} from "contracts/core/tokens/DEToken.sol";
import {RAACToken} from "contracts/core/tokens/RAACToken.sol";
contract SetupContract is Test {
address public user1;
address public user2;
address public user3;
uint256 public currentBlockTimestamp = 1000 days;
address public treasury;
address public repairFund;
LendingPool public lendingPool;
crvUSDToken public _crvUSDToken;
RToken public rToken;
MockVeToken public veRToken;
DebtToken public debtToken;
RAACHousePricesMock public raacHousePrices;
RAACNFT public raacNFT;
RAACHousePriceOracle public raacHousePriceOracle;
FeeCollector public feeCollector;
StabilityPool public stabilityPool;
RAACMinter public raacMinter;
DEToken public deToken;
RAACToken public raacToken;
uint256 public constant INITIAL_PRIME_RATE = 1e26;
function setUp() external {
vm.warp(currentBlockTimestamp);
user1 = makeAddr("user1");
user2 = makeAddr("user2");
user3 = makeAddr("user3");
treasury = makeAddr("treasury");
repairFund = makeAddr("repairFund");
stabilityPool = new StabilityPool(address(this));
veRToken = new MockVeToken();
_crvUSDToken = new crvUSDToken(address(this));
rToken = new RToken("rtoken", "rtk", address(this), address(_crvUSDToken)); // reserve pool
raacToken = new RAACToken(address(this), 0, 0);
deToken = new DEToken("deToken", "detk", address(this), address(rToken)); // setStabilityPool;
debtToken = new DebtToken("debtToken", "dtk", address(this)); //reservePool
raacHousePrices = new RAACHousePricesMock();
raacNFT = new RAACNFT(address(_crvUSDToken), address(raacHousePrices), address(this));
raacHousePriceOracle = new RAACHousePriceOracle(
address(new MockFunctionsRouter()), bytes32(bytes("fun-ethereum-mainnet-1")), address(this)
);
feeCollector = new FeeCollector(address(rToken), address(veRToken), treasury, repairFund, address(this));
lendingPool = new LendingPool(
address(_crvUSDToken),
address(rToken),
address(debtToken),
address(raacNFT),
address(raacHousePrices),
INITIAL_PRIME_RATE
);
raacMinter = new RAACMinter(address(raacToken), address(stabilityPool), address(lendingPool), address(this));
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter),
address(_crvUSDToken),
address(lendingPool)
);
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
deToken.setStabilityPool(address(stabilityPool));
_crvUSDToken.mint(user1, 10000e18);
_crvUSDToken.mint(user2, 100e18);
_crvUSDToken.mint(user3, 1000e18);
}
function testDepositNFTandBorrow() public {
vm.startPrank(user1);
_crvUSDToken.approve(address(lendingPool), 1000e18);
lendingPool.deposit(1000e18);
vm.stopPrank();
raacHousePrices.setTokenPrice(1, 100e18);
vm.startPrank(user3);
_crvUSDToken.approve(address(raacNFT), 100e18);
raacNFT.mint(1, 100e18);
raacNFT.approve(address(lendingPool), 1);
lendingPool.depositNFT(1);
lendingPool.borrow(101e18);
vm.stopPrank();
}
function testInflateUtilizationRate() public {
testDepositNFTandBorrow();
vm.startPrank(user1);
rToken.approve(address(stabilityPool), 100e18);
stabilityPool.deposit(100e18);
vm.stopPrank();
console.log("Emission Rate: ", raacMinter.emissionRate());
vm.warp(currentBlockTimestamp + 1 days);
raacMinter.updateEmissionRate();
console.log("Emission Rate: ", raacMinter.emissionRate());
vm.warp(currentBlockTimestamp + 2 days);
raacMinter.updateEmissionRate();
console.log("Emission Rate: ", raacMinter.emissionRate());
vm.warp(block.timestamp + 2 days);
raacMinter.updateEmissionRate();
console.log("Emission Rate: ", raacMinter.emissionRate());
}
}

Getting this resutls:

Ran 1 test for test/foundry/setup.t.sol:SetupContract
[PASS] testInflateUtilizationRate() (gas: 1061897)
Logs:
Emission Rate: 131944444444444444
Emission Rate: 138888888888888888
Emission Rate: 145833333333333332
Emission Rate: 153124999999999998

Impact

The issue creates critical economic risk for the protocol:

  • Maximum emission rate is always triggered due to artificially high utilization

  • Excessive tokens are minted, causing hyperinflation

  • Protocol cannot properly adjust emissions based on true utilization

Recommendations

Normalize the utilization rate calculation to account for RAY scaling.

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!