Core Contracts

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

Insufficient Balance Validation in BaseGauge Can Lead to Reward Insolvency

Summary

In BaseGauge.sol, the contract only validates that it has enough tokens for the current reward rate period but fails to account for previously earned but unclaimed rewards. This could lead to a situation where the contract's total reward obligations exceed its token balance.

Vulnerability Details

The issue exists in the notifyRewardAmount function:

function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
rewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}

The vulnerability stems from three key factors:

  • The balance check only validates: rewardRate * getPeriodDuration() > balance

  • It doesn't account for already earned but unclaimed rewards stored in userStates[account].rewards

  • Rewards accumulate over time through getRewardPerToken() and are stored in rewardPerTokenStored

Users' rewards are calculated in the earned function:

function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

Impact

  • Contract insolvency where total reward obligations exceed available tokens

  • Users being unable to claim their earned rewards

  • First-come-first-served situation for reward claims

  • Potential loss of user funds if they cannot claim their rightfully earned rewards

Attack Path:

  • Period 1: Contract receives 1000 tokens

  • Users earn but don't claim 800 tokens

  • Period 2: Contract receives another 1000 tokens

  • Balance check passes (1000 >= new_rate * duration)

  • Total obligations become 1800 tokens

  • This pattern can continue until obligations significantly exceed balance

Tools Used

  • Manual code review

Recommendations

  • Implement tracking of total unclaimed rewards:

uint256 private totalUnclaimedRewards;
function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
// ... existing checks ...
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() + totalUnclaimedRewards > balance) {
revert InsufficientRewardBalance();
}
// ... rest of the function
}
  • Update unclaimed rewards tracking:

function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
uint256 newRewards = earned(account) - state.rewards;
totalUnclaimedRewards += newRewards;
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
emit RewardUpdated(account, state.rewards);
}
}
function getReward() external updateReward(msg.sender) {
uint256 reward = userStates[msg.sender].rewards;
if (reward > 0) {
userStates[msg.sender].rewards = 0;
totalUnclaimedRewards -= reward;
rewardToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
Updates

Lead Judging Commences

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

BaseGauge::notifyRewardAmount checks token balance without accounting for unclaimed rewards, allowing allocation of more rewards than available tokens

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

BaseGauge::notifyRewardAmount checks token balance without accounting for unclaimed rewards, allowing allocation of more rewards than available tokens

Support

FAQs

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