Core Contracts

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

Dynamic emissions schedule based on system utilization doesn’t work

Summary

Utilisation rate in the RaacMinter is calculated in a such way

function getUtilizationRate() public view returns (uint256) {
uint256 totalBorrowed = lendingPool.getNormalizedDebt();// get the normalised debt, and take into account the compounding effect.
uint256 totalDeposits = stabilityPool.getTotalDeposits(); //rToken(balanceOf(address(stabilityPool)))
if (totalDeposits == 0) return 0;
return (totalBorrowed * 100) / totalDeposits;
}

However the utilization target equals 70

uint256 public utilizationTarget = 70;

Most of the time, is the total deposits into the stabilityPool > 1, the utilisationRate will be higher than target.

It means that very soon it will reach the max emission rate, and max emission rate will be used during the minting

function calculateNewEmissionRate() internal view returns (uint256) {
uint256 utilizationRate = getUtilizationRate(); //get the rate based on total borrowed, total deposited
uint256 adjustment = (emissionRate * adjustmentFactor) / 100; //take the emmission rate *const.updated and mult. it by 3 and divide by 100 , which will endup in the adjustmetn
if (utilizationRate > utilizationTarget) {//if is higher than the target *30 calc the increasement
uint256 increasedRate = emissionRate + adjustment; //@audit what if manipulate, increase the rate ? How we can profit?
uint256 maxRate = increasedRate > benchmarkRate ? increasedRate : benchmarkRate; //cap to the max benchmark
return maxRate < maxEmissionRate ? maxRate : maxEmissionRate; //cap to maxEmissionRate?

Vulnerability Details

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "lib/forge-std/src/Test.sol";
//Import which is neccessery for the LendingPool.sol
import "contracts/core/pools/LendingPool/LendingPool.sol";
import "contracts/core/tokens/RToken.sol";
import "contracts/core/tokens/DebtToken.sol";
import "contracts/core/tokens/RAACNFT.sol";
import "contracts/core/tokens/RAACToken.sol";
import "contracts/core/minters/RAACMinter/RAACMinter.sol";
import "contracts/core/pools/StabilityPool/StabilityPool.sol";
import "contracts/core/tokens/DEToken.sol";
interface ITokenERC20{
function approve(address spender, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract ArsenTest is Test{
//Actual, non-modifier contracts
RToken public rToken;
DebtToken public dToken;
RAACNFT public raacNFT;
RAACToken public raacToken;
DEToken public deToken;
//deploy minters
RAACMinter public raacMinter;
//Mock contracts
HousePrices public housePrices;
PriceOracle public priceOracle;
//deploy the Lending Pool
LendingPool public lendingPool;
//deploy Stability Pool
StabilityPool public stabilityPool;
address public attacker = address(8888);
address public user_1 = address(1111);
address public user_2 = address(2222);
address CRV_USD = address(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E);
string ETH_RPC_URL = "https://mainnet.infura.io/v3/2cef90f9f3f44a1fb5201d8547183d42";
function setUp() public{
uint256 ethFork = vm.createFork(ETH_RPC_URL);
vm.selectFork(ethFork);
housePrices = new HousePrices();
priceOracle = new PriceOracle();
rToken = new RToken(
"rToken",
"RTOKEN",
address(this), //init owner
CRV_USD //crvUsd addr
);
dToken = new DebtToken(
"Debt Token",
"DBTTOKEN",
address(this) //init owner
);
raacNFT = new RAACNFT(
CRV_USD, //token
address(housePrices), //house prices
address(this) //init owner
);
raacToken = new RAACToken(
address(this),
500, //initialSwapTaxRate
500 //initialBurnTaxRate
);
deToken = new DEToken(
"DeToken",
"deToken",
address(this),
address(rToken)
);
lendingPool = new LendingPool(
CRV_USD, //crvUsd addr
address(rToken), //rToken
address(dToken), //debtToken
address(raacNFT), //raacNFT
address(priceOracle),//priceOracle addr
100000000000000000000000000 //init prime rate
);
stabilityPool = new StabilityPool(address(this));
raacMinter = new RAACMinter(
address(raacToken),
address(stabilityPool),
address(lendingPool),
address(this)
);
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter), //raacMinter
CRV_USD,
address(lendingPool)
);
//@audit TO DO - set the setParameter , like threshold, e.t.c for the Lending Pool
//set the reserve pool for RToken to be able to mint stuff
rToken.setReservePool(address(lendingPool));
//set the reserve pool for the DToken to be able to mint stuff
dToken.setReservePool(address(lendingPool));
}
//forge test --match-test test_deposit -vvvv
function test_deposit() public{
//PREPARATION STUFF
deToken.setStabilityPool(address(stabilityPool)); //set the pool
raacToken.setMinter(address(raacMinter));
// raacToken.mint(address(stabilityPool), 1000e18); //mint the raac rewards into the pool in advance.
rToken.testMint(user_1);
rToken.testMint(user_2);
//------------------------
vm.startPrank(user_1); //user deposit the 100e18 rToken in the pool
rToken.approve(address(stabilityPool), 100e18);
stabilityPool.deposit(1e18);
vm.stopPrank();
//------------------------
console.log("The current utilisationRate after the first deposit is", raacMinter.getUtilizationRate());
console.log("The current utilisation target is,", raacMinter.utilizationTarget());
while(raacMinter.emissionRate() != raacMinter.maxEmissionRate()){
vm.prank(user_1);
stabilityPool.deposit(1e18);
vm.warp(raacMinter.lastEmissionUpdateTimestamp() + raacMinter.emissionUpdateInterval());
raacMinter.updateEmissionRate();
}
console.log("Current emission rate is", raacMinter.getEmissionRate());
while(deToken.totalSupply() != 1e18){
vm.prank(user_1);
stabilityPool.withdraw(1e18);
}
console.log("Current emission rate is", raacMinter.getEmissionRate());
}
//@note MOCK CONTRACTS FOR THE POC PURPOSES ONLY
contract HousePrices{
function tokenToHousePrice(uint256 _tokenId) external view returns (uint256){
return 10_000e18;
}
}
contract PriceOracle{
function getLatestPrice(uint256 _tokenId) external view returns (uint256, uint256){
return(10_000e18, block.timestamp);
}
}

Impact

Incorrect utilisation rate

Tools Used

Foundry

Recommendations

Ensure the logic functions correctly

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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

RAACMinter::getUtilizationRate allows values over 100% despite function comment specifying 0-100 range, causing emission rate to permanently reach maximum when borrows exceed deposits

inallhonesty Lead Judge 6 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

RAACMinter::getUtilizationRate allows values over 100% despite function comment specifying 0-100 range, causing emission rate to permanently reach maximum when borrows exceed deposits

Support

FAQs

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