Core Contracts

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

Reward Manipulation via Outdated Timestamps in Staking Mechanism

Summary

The vulnerability occurs when the contract relies on Last_Updated timestamps to determine the reward accrual but does not update them immediately upon user interaction. If a user stakes or withdraws, the rewards should be recalculated to maintain accuracy. However, if the update_rewards function is not triggered, discrepancies arise in reward distribution.

/**
* @notice Updates a gauge's weight based on vote changes
* @dev Recalculates gauge weight using voting power
* @param gauge Address of the gauge
* @param oldWeight Previous vote weight
* @param newWeight New vote weight
* @param votingPower Voter's voting power
*/
function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}

Vulnerability Details

The RAAC protocol fails to update rewards (update_rewards) when users stake or withdraw, relying on an outdated Last_Updated timestamp. This leads to:

  1. Over-Rewarding – Users can exploit reward delays by staking and unstaking rapidly to claim excess rewards.

  2. Under-Rewarding – Users may lose out on earned rewards if they withdraw before an update occurs.

  3. Unfair Distribution – Pool rewards become misaligned due to inaccurate weight calculations.

Exploit Scenario (Proof of Concept)

Step 1: User Exploits Staking Delay

  • Initial Pool State: The reward pool has 1000 RAAC tokens allocated.

  • User1 Stakes 100 Tokens: No update_rewards is called, so the reward calculation remains unchanged.

Step 2: User Rapidly Unstakes & Restakes

  • User1 Withdraws 100 Tokens (without update_rewards)

  • User1 Stakes Again Before Update Trigger

    • The reward weight is calculated without the first withdrawal being accounted for.

    • Result: User1 claims rewards as if they were staking the whole time, leading to an over-reward.

Step 3: Exploit Conclusion

  • The user exploits this by repeating rapid stake/unstake cycles.

  • The reward system distributes excess rewards, causing reward drain from the pool.

  • Other stakers may receive less-than-earned rewards.

Impact

Excess Rewards: Attackers can repeatedly stake/unstake to claim more rewards than earned.

Lost Rewards: Users may miss out on rewards if they withdraw before updates.

Unfair Distribution: Reward pool misallocations reduce trust and efficiency.

Financial Risk: Potential draining of the staking pool, harming protocol sustainability.

Tools Used

Manual Review

Recommendations

Ensure update_rewards is always triggered before modifying stakes.

  • Call updateReward whenever stake() or withdraw() is executed to prevent outdated reward calculations.

  • Modify _updateGaugeWeight to ensure reward updates before weight changes.

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
// ✅ Ensure rewards are updated before modifying weight
_updateReward(gauge);
uint256 oldGaugeWeight = g.weight;
uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}

This fix ensures rewards are distributed fairly and prevents malicious actors from exploiting staking weight discrepancies.

Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!