Summary
A user can take a flashloan deposit rtokens and get DECRVUSD and immediately withdraw to get the raac rewards in the stability contract. These rewards serve as an incentive to liquidity providers, but an attacker can profit by using flashloan to take as many rewards as possible from the pool.
Vulnerability Details
In the code there is no time constraint to prevent a flashloan attack in the stability pool .
user takes a flashloan gets rtoken deposit and withdraws in the same block.
* @notice Allows a user to deposit rToken and receive deToken.
* @param amount Amount of rToken to deposit.
*/
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);
}
Based on the withdrawal process the user gets rewards based on what was deposited, hence allowing an attacker to deposit and withdraw rewards meant for liquidity providers
function withdraw(uint256 deCRVUSDAmount) external nonReentrant whenNotPaused validAmount(deCRVUSDAmount) {
_update();
if (deToken.balanceOf(msg.sender) < deCRVUSDAmount) revert InsufficientBalance();
uint256 rcrvUSDAmount = calculateRcrvUSDAmount(deCRVUSDAmount);
@audit>>> 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);
@audit>>> if (raacRewards > 0) {
@audit>>> raacToken.safeTransfer(msg.sender, raacRewards);
}
emit Withdraw(msg.sender, rcrvUSDAmount, deCRVUSDAmount, raacRewards);
}
* @notice Calculates the pending RAAC rewards for a user.
* @param user Address of the user.
* @return Amount of RAAC rewards.
*/
function calculateRaacRewards(address user) public view returns (uint256) {
uint256 userDeposit = userDeposits[user];
uint256 totalDeposits = deToken.totalSupply();
@audit>>> uint256 totalRewards = raacToken.balanceOf(address(this));
if (totalDeposits < 1e6) return 0;
@audit>>> return (totalRewards * userDeposit) / totalDeposits;
}
Impact
With flashloan or a lot of funds, an attacker can steal the raac rewards to be distributed to liquidity providers.
Tools Used
Manual review
Recommendations
Implement a time lock for liquidity providers or allow reward calculation only when the user has deposited over a certain period of time.