Core Contracts

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

`RAACToken` rewards can be front run resulting in loss of rewards for other participants

Summary

The StabilityPool mints RAACToken rewards for users that have deposited their RToken. Attackers can observe the mempool and deposit a large amount of RTokens into the StabilityPool right before RAACToken rewards get minted, resulting in the attacker getting most of the rewards. The attacker then leaves the system right after that by withdrawing their RToken from the StabilityPool.

Vulnerability Details

The StabilityPool mints RAACToken whenever users deposit() into or withdraw() from the pool. By depositing RToken into the pool, users get DEToken in return at a 1:1 ratio, representing their share of the rewards.

Here's what that looks like:

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update(); // <-- `RAACToken` rewards being minted
rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount); // <-- `DEToken` minted for deposited `RToken`
userDeposits[msg.sender] += amount;
_mintRAACRewards(); // <-- this is a noop and can be ignored
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

To get access to the rewards, users simply have to withdraw() their RToken by sending their DEToken back to the stability pool:

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); // <-- rewards are calculated here
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); // <-- and sent to the account here
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}

This looks okay, but there's a problem in the calculateRAACRewards() function. It doesn't take the time factor into account and simply calculates the rewards based on the DEToken supply and deposit, regardless how long the user has participated in the system:

function calculateRaacRewards(address user) public view returns (uint256) {
uint256 userDeposit = userDeposits[user];
uint256 totalDeposits = deToken.totalSupply();
uint256 totalRewards = raacToken.balanceOf(address(this));
if (totalDeposits < 1e6) return 0;
return (totalRewards * userDeposit) / totalDeposits; // <-- proportional reward calculation without time factor
}

What this means is, it allows an attacker to deposit a large amount of RToken when a lot of RAACToken have been minted, withdraw again, and receive a large portion of the rewards, while other users have been locking their RToken in the StabilityPool for much longer.

Impact

An attacker with significant funds is able to take most of the rewards out of the system, leaving other participants with a loss of potential rewards.

Tools Used

Manual review.

Recommendations

To account for this, it's better to include some sort of time factor in the rewards calculation.
This can be done by issuing shares inside of the StabilityPool using an "index", similar to how it's done in the LendingPool.

Users that have deposited their RToken longer will naturally have more shares, resulting in a fair reward distribution.

Relevant links

Updates

Lead Judging Commences

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

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

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