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 10 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!