Core Contracts

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

Anybody can call `FeeCollector::claimRewards` which will be loss for the actual `user`

Summary

The FeeCollector::claimRewards can be called by anyone and will transfer the rewards to the user.
But the distribution is actually Time-Weighted. This approach incentivizes long-term participation and discourages short-term speculation.

Now let's say the user is willing to claim rewards after a long time so that he gets a good amount of rewards.
But a malicious actor can call FeeCollector::claimRewards with his address which will claim the amount and transfer it to the user

Here the actual user lost funds,
As in the claiming calculation, it calculates uint256 timeDelta = timestamp - point.timestamp; .

It calculates timeDelta between the current time and the creation time (point.timestamp).

So the more will be current time the more he gets.

(this is just an example) Even let's say he can claim the amount again after claiming this time, in this scenario, he also loses funds as the calculation starting time will be from the last claimed time. His time gets reset.

Vulnerability Details

FeeCollector::claimRewards

/**
* @notice Claims accumulated rewards for a user
* @param user Address of the user claiming rewards
* @return amount Amount of rewards claimed
*/
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;
}

The claimRewards call _calculatePendingRewards

_calculatePendingRewards call veRAACToken.getVotingPower(user) which call _votingState.getCurrentPower(account, block.timestamp) of VotingPowerLib::getCurrentPower.

On VotingPowerLib::getCurrentPower it then calculates timeDelta and multiply it.

/**
* @notice Gets the current voting power for an account
* @param state The voting power state
* @param account The account to check
* @param timestamp The timestamp to check power at
* @return The current voting power
*/
function getCurrentPower(
VotingPowerState storage state,
address account,
uint256 timestamp
) internal view returns (uint256) {
RAACVoting.Point memory point = state.points[account];
if (point.timestamp == 0) return 0;
if (timestamp < point.timestamp) {
return uint256(uint128(point.bias));
}
@> uint256 timeDelta = timestamp - point.timestamp;
// Calculate decay
int128 adjustedBias = point.bias;
if (timeDelta > 0) {
// Calculate decay per second and multiply by time delta
int128 decay = (point.slope * int128(int256(timeDelta))) / int128(int256(1));
adjustedBias = point.bias - decay;
}
// Return 0 if power has fully decayed
return adjustedBias > 0 ? uint256(uint128(adjustedBias)) : 0;
}

Impact

The user aims to call claimRewards after a longer time but anybody can call any user's address and sent a claim for them. As a result the user loss funds as he was aiming to claim rewards at a later time for a bigger amount.

Tools Used

Manual review

Recommendations

Add check that the msg.sender is the actual owner of the address user

Updates

Lead Judging Commences

inallhonesty Lead Judge
6 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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