Core Contracts

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

Incorrect Reward Calculation on Repeated Claims

Summary

The claimRewards() function incorrectly resets userRewards[user] to totalDistributed, causing users to be unable to claim rewards after the first claim. This results in incorrect reward calculations and prevents users from receiving their entitled rewards on subsequent claims.

Vulnerability Details

The issue lies in the following line inside claimRewards():

userRewards[user] = totalDistributed;

This incorrectly resets the user's reward tracking, preventing them from claiming additional rewards in the future.

How It Works:

1.First Claim:

  • pendingReward = totalDistributed - userRewards[user]

  • userRewards[user] is updated to totalDistributed, making pendingReward = 0 on the next claim.

  • The user successfully claims their reward.

2.Second Claim:

  • pendingReward = totalDistributed - userRewards[user]Results in 0

  • The function reverts with InsufficientBalance().

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();
// ISSUE: Resetting userRewards incorrectly
userRewards[user] = totalDistributed;
// Transfer rewards
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

PoC

it("should allow users to claim rewards", async function () {
await feeCollector.connect(owner).distributeCollectedFees();
await time.increase(WEEK);
const initialBalance = await raacToken.balanceOf(user2.address);
const expectReward = await feeCollector.getPendingRewards(user2.address);
// First claim
await feeCollector.connect(user2).claimRewards(user2.address);
// Collect fees
const taxRate = SWAP_TAX_RATE + BURN_TAX_RATE; // 150 basis points (1.5%)
const grossMultiplier = BigInt(BASIS_POINTS * BASIS_POINTS) / BigInt(BASIS_POINTS * BASIS_POINTS - taxRate * BASIS_POINTS);
const protocolFeeGross = ethers.parseEther("50") * grossMultiplier / BigInt(10000);
const lendingFeeGross = ethers.parseEther("30") * grossMultiplier / BigInt(10000);
const swapTaxGross = ethers.parseEther("20") * grossMultiplier / BigInt(10000);
await feeCollector.connect(user1).collectFee(protocolFeeGross, 0);
await feeCollector.connect(user1).collectFee(lendingFeeGross, 1);
await feeCollector.connect(user1).collectFee(swapTaxGross, 6);
await time.increase(WEEK);
await feeCollector.connect(owner).distributeCollectedFees();
// Second Claim
await expect(feeCollector.connect(user2).claimRewards(user2.address)).to.be.revertedWithCustomError(feeCollector, "InsufficientBalance");
expect(await raacToken.balanceOf(user2.address)).to.be.gt(initialBalance);
console.log(initialBalance, expectReward, await raacToken.balanceOf(user2.address))
});

Impact

  • Users can only claim rewards once.

  • Any future rewards are lost, making staking and rewards distribution ineffective.

  • This severely impacts the integrity of the rewards mechanism.

Tools Used

Manual review Hardhat

Recommendations

Modify claimRewards() so that userRewards[user] is updated correctly without resetting the rewards.

Updates

Lead Judging Commences

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

FeeCollector::claimRewards sets `userRewards[user]` to `totalDistributed` seriously grieving users from rewards

Support

FAQs

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

Give us feedback!