Core Contracts

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

`StabilityPool::calculatercrvUSDAmount()` calculates wrong amount in `StabilityPool::withdraw()`

Summary

Users of the RAAC protocol can deposit in the stability pool to get RAAC rewards minted to them.

As per the code

// Allow to make rToken / deToken decimals flexible
uint8 public rTokenDecimals;
uint8 public deTokenDecimals;

rTokenDecimals and deTokenDecimals could be different in the future. This creates inconsistencies in the calculation of the tokens for deposit -> withdraw sequence.

Vulnerability Details

Let's look at this scenario:

rTokenDecimals = 6;

deTokenDecimals = 18;

Alice deposits 100e6 rcrvUSD amount:

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}
/**
* @notice Calculates the amount of deToken to mint for a given rToken deposit.
* @param rcrvUSDAmount Amount of rToken deposited.
* @return Amount of deToken to mint.
*/
function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();
}

Alice gets minted (100e6 * 1e30) / 1e18 = 100e18 dCRVUSD

Alice then withdraws immediatly her 100e18 dCRVUSD:

function calculateRcrvUSDAmount(uint256 deCRVUSDAmount) public view returns (uint256) {
uint256 scalingFactor = 10**(18 + rTokenDecimals - deTokenDecimals);
return (deCRVUSDAmount * getExchangeRate()) / scalingFactor; // e18 * e18 / e6
}
/**
* @notice Gets the current exchange rate between rToken and deToken.
* @return Current exchange rate.
*/
function getExchangeRate() public view returns (uint256) {
// uint256 totalDeCRVUSD = deToken.totalSupply();
// uint256 totalRcrvUSD = rToken.balanceOf(address(this));
// if (totalDeCRVUSD == 0 || totalRcrvUSD == 0) return 10**18;
// uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
// return (totalRcrvUSD * scalingFactor) / totalDeCRVUSD;
return 1e18;
}
/**
* @notice Allows a user to withdraw their rToken and RAAC rewards.
* @param deCRVUSDAmount Amount of deToken to redeem.
*/
function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
_update();
if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
uint256 raacRewards = calculateRaacRewards(msg.sender);
if (userDeposits[msg.sender] < rcrvUSDAmount) revert InsufficientBalance();
userDeposits[msg.sender] -= rcrvUSDAmount;
if (userDeposits[msg.sender] == 0) {
delete userDeposits[msg.sender];
}
deToken.burn(msg.sender, deCRVUSDAmount);
rToken.safeTransfer(msg.sender, rcrvUSDAmount);
if (raacRewards > 0) {
raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}

calculateRcrvUSDAmount will return = (100e18 * 1e18)/ 1e6 = 100e30

100e30 rcrvUSD which is more than enough to drain the protocol from its rTokens.

Impact

Draining protocol

Tools Used

Manual review

Recommendations

When calculating rcrvUSDAmount switch exchange rate and scalingFactor in the return statement.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Incorrect scaling factor formula in StabilityPool::calculateRcrvUSDAmount function

Both tokens have 18 decimals. Info

Support

FAQs

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

Give us feedback!