Core Contracts

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

A user can be frontrunned, while claiming reward from gauge.

Summary

Contract - BaseGauge.sol

function_flow
when user claims his reward.

getReward() -> updateReward() -> _updateReward() -> earned() -> getRewardPerToken()

function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
if (block.timestamp - lastClaimTime[msg.sender] < MIN_CLAIM_INTERVAL) {
revert ClaimTooFrequent();
}
lastClaimTime[msg.sender] = block.timestamp;
UserState storage state = userStates[msg.sender];
uint256 reward = state.rewards; // $mynotes - the reward decided at the moment user deposited his staking token.
if (reward > 0) {
state.rewards = 0;
uint256 balance = rewardToken.balanceOf(address(this));
if (reward > balance) {
revert InsufficientBalance();
}
rewardToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
modifier updateReward(address account) {
_updateReward(account);
_;
}
function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
// here - start again
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}
function getRewardPerToken() public view returns (uint256) {
if (totalSupply() == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
@-> (lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
}

Vulnerability Details

  1. the return value in getRewardPerToken() is -

return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
  1. This can be problematic.

  2. Suppose a malicious user leveraging flashloan, deposits very high amount of staking tokens via stake() function, just before a claimer is claiming his reward. (basically frontrunning)

function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
@-> _totalSupply += amount;
_balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
  1. This will increase _totalSupply to very large value, means totalSupply() will return very large value as :-

function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
  1. subsequently, (lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply() will become very less.

  2. Leading to loss of reward claim value to claimer.

  3. One above operations done, attacker can withdraw staking tokens by calling withdraw function, replaying the flashloan amount.

Impact

  • Reward claimer is getting very less reward.

  • Hence loss of funds to claimer.

Tools Used

Manual

Recommendations

Implement a time delay mechanism between stake and withdraw function. if tha attacker wants to perform this attack it will fail, bacause of time delay between stake and withdraw function.

Updates

Lead Judging Commences

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

BaseGauge reward system can be gamed through repeated stake/withdraw cycles without minimum staking periods, allowing users to earn disproportionate rewards vs long-term stakers

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

BaseGauge reward system can be gamed through repeated stake/withdraw cycles without minimum staking periods, allowing users to earn disproportionate rewards vs long-term stakers

Support

FAQs

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