Skip to content

Latest commit

 

History

History
74 lines (63 loc) · 3.01 KB

050.md

File metadata and controls

74 lines (63 loc) · 3.01 KB

Rich Smoke Cheetah

Medium

Rewards are underestimated because of a rounding error

Summary

There is a rounding issue in the way rewards are computed that underestimates the rewards and can lead to a loss of funds.

Vulnerability Detail

Whenever the state of a deposit is modified, two functions are called. The first one is _checkpointGlobalReward:

  function _checkpointGlobalReward() internal virtual {
    rewardPerTokenAccumulatedCheckpoint = rewardPerTokenAccumulated();
    lastCheckpointTime = lastTimeRewardDistributed();
  }

This function updates the rewardPerTokenAccumulatedCheckpoint variable:

function rewardPerTokenAccumulated() public view virtual returns (uint256) {
    if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
  }

The problem is that rewardPerTokenAccumulatedCheckpoint represents the value of one earning power in reward tokens. After that, the function _checkpointReward updates the reward of the deposit:

 function _checkpointReward(Deposit storage deposit) internal virtual {
    deposit.scaledUnclaimedRewardCheckpoint = _scaledUnclaimedReward(deposit);
    deposit.rewardPerTokenCheckpoint = rewardPerTokenAccumulatedCheckpoint;
  }

The function _scaledUnclaimedReward updates the scaledUnclaimedRewardCheckpoint by calling _scaledUnclaimedReward, as seen here:

function _scaledUnclaimedReward(Deposit storage deposit) internal view virtual returns (uint256) {
    return deposit.scaledUnclaimedRewardCheckpoint
      + (deposit.earningPower * (rewardPerTokenAccumulated() - deposit.rewardPerTokenCheckpoint));
  }

The function _scaledUnclaimedReward updates the scaledUnclaimedRewardCheckpoint by calling _scaledUnclaimedReward, as seen here:

Impact

The rewards will be underestimated.

Code Snippet

https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L748-L751

https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L759-L762

https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L522-L525

Tool used

Foundry

Recommendation

The function rewardPerTokenAccumulated should be updated as follows:

function rewardPerTokenAccumulated() public view virtual returns (uint256) {
    if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;

    return rewardPerTokenAccumulatedCheckpoint
      + (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime));
  }

And the function _scaledUnclaimedReward should be updated as follows:

function _scaledUnclaimedReward(Deposit storage deposit) internal view virtual returns (uint256) {
    return deposit.scaledUnclaimedRewardCheckpoint
      + (deposit.earningPower * (rewardPerTokenAccumulated() - deposit.rewardPerTokenCheckpoint))/totalEarningPower;
  }