Core Contracts

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

Malicious users can steal rewards from FeeCollector without calling `collectFee`, preventing normal users from claiming rewards, which ultimately harms the protocol

Summary

In claimRewards, normal users can claim their rewards as long as they have enough veRAACToken.

However, this function doesn't have any access control, so anyone can call it. It's possible for malicious users can continuously monitor the contract state to see if the rewards are stealable.

FeeCollector::L199-213

function claimRewards(address user) external override nonReentrant whenNotPaused returns (uint256) {
if (user == address(0)) revert InvalidAddress();
uint256 pendingReward = _calculatePendingRewards(user);
if (pendingReward == 0) revert InsufficientBalance();
// Reset user rewards before transfer
userRewards[user] = totalDistributed;
// Transfer rewards
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

Vulnerability Details

Before diving into the attack path, let's take a look at the preconditions.

FeeCollector can gain RAACToken in two ways:

  1. A user calls collectFees and transfer their RAACToken to the FeeCollector contract.

  2. When a user transfer RAACToken, they pay a tax to the FeeCollector Contract.

In order to claim the rewards, a user must have enough veRAACToken (aka voting power).

The problem is that a malicious user can simply wait for other users to call the collectFees and for the protocol team to call distributedCollectedFees once a week, then steal the rewards.

Here's the attack path:

  1. The malicious user lock their RAACToken to mint veRAACToken.

  2. Instead calling collectFees function themselves, the malicious user waits for other users to call it.

  3. The malicious user continuously monitors the contract, once the protocol team calls calls the distributedCollectedFees, the malicious user will automatically call the claimRewards to steal the rewards.

Impact

If there are a lot malicious users keep monitoring the contract all the time, once they got a chance, they will automatically call the claimRewards, so that the normal users can't claim rewards because the contract doesn't have enough RAACToken since it's drained by malicious user.

When most of the users notice this problem, they will simply just stop calling collectFees and will never transfer RAACToken, which it's bad for the entire protocol.

Proof of Concept

Add to the test/unit/core/collectors/FeeCollector.test.js in the Fee Collection and Distribution context.

// User1 is normal user, user2 is malicious user
it("malicious user can steal rewards from FeeCollector", async function () {
var collectAmount = ethers.parseEther("50");
await feeCollector.connect(owner).distributeCollectedFees();
console.log("Contract balance before first steal: " + await raacToken.balanceOf(feeCollector.target));
var firstSteal = await feeCollector.getPendingRewards(user2.address);
await feeCollector.connect(user2).claimRewards(user2.address);
console.log("First steal: " + firstSteal);
console.log("Contract balance after first steal: " + await raacToken.balanceOf(feeCollector.target));
// here user1 calls collectFee
await feeCollector.connect(user1).collectFee(collectAmount, 0);
await time.increase(WEEK);
await feeCollector.connect(owner).distributeCollectedFees();
console.log("Contract balance before second steal: " + await raacToken.balanceOf(feeCollector.target));
// user2 keep monitoring the contract to see if is claimable
var secondSteal = await feeCollector.getPendingRewards(user2.address);
await feeCollector.connect(user2).claimRewards(user2.address);
console.log("Second steal: " + secondSteal);
console.log("Contract balance after second steal: " + await raacToken.balanceOf(feeCollector.target));
var totalSteal = firstSteal + secondSteal;
console.log("Total steal: " + totalSteal);
});

Console output:

Contract balance before first steal: 5005000000000000
First steal: 2499999920725520
Contract balance after first steal: 2505000237823440
Contract balance before second steal: 25027505000237823440
Second steal: 12257724838042241877
Contract balance after second steal: 12769780162195581563
Total steal: 12260224837962967397

Tools Used

Manual Review

Recommendations

Consider adding access control or reasonable minimal claim interval to claimRewards.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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