Summary
Users can deposit rTokens in the stability pool to get deTokens, and subsequently burn the deTokens to get back their rTokens. In the process, they are eligible for some RAAC tokens rewards as well.
A user can repeat this process infinitely to farm RAAC token rewards. In deposit(), users deposit rTokens and get back deTokens. userDeposits[msg.sender] is increased by the amount of rTokens transferred in.
Vulnerability Details
In StabilityPool.sol,
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);
}
When the user withdraws, his deTokens is burned and rToken is transferred back. calculateRaacRewards() is also called and if returns >0, transfer the RAAC rewards to the user.
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);
}
calculateRaacRewards() takes the userDeposit, deToken.totalSupply, and totalRewards, and gets the percentage of the rewards based on the userDeposit.
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;
}
Users can repeatedly call deposit and withdraw simultaneously to farm RAACtoken rewards from the contract.
Impact
RAAC rewards is drained from the contracts.
Tools Used
Manual Review
Recommendations
Recommend having a timelock between the deposit and withdraw process