Rich Smoke Cheetah
High
If there is a distribution and all the stakers withdraw, then for the rest of the reward duration, if no one stakes, the reward will be stuck forever.
We can see here that the reward is distributed only if the totalEarningPower is higher than 0.
function rewardPerTokenAccumulated() public view virtual returns (uint256) {
if (totalEarningPower == 0) return rewardPerTokenAccumulatedCheckpoint;
return rewardPerTokenAccumulatedCheckpoint
+ (scaledRewardRate * (lastTimeRewardDistributed() - lastCheckpointTime)) / totalEarningPower;
}
The problem is that if during a reward distribution all the stakers withdraw, then the funds will be stuck forever in the smart contract.
You can copy-paste this test in a contract of the GovernanceStaker.t.sol file and run forge test --mt test_stuckBalancePOC
.
function test_stuckBalancePOC() public {
address BOB = makeAddr("BOB");
// Bob stake 1 token
(, GovernanceStaker.DepositIdentifier _depositId) = _boundMintAndStake(BOB, 1e18,BOB);
uint256 _rewardAmount = 1000e18;
//The protocol notify some rewards
_mintTransferAndNotifyReward( _rewardAmount);
//We jump the half of the reward duration
_jumpAheadByPercentOfRewardDuration(50);
//Bob claim his rewards
vm.prank(BOB);
govStaker.claimReward(_depositId);
//Bob the withdraw his staking balance
vm.prank(BOB);
govStaker.withdraw(_depositId,1e18);
//We jump the rest of the reward duration
_jumpAheadByPercentOfRewardDuration(50);
uint256 unclaimedReward= govStaker.unclaimedReward(_depositId);
uint256 rewardGovBalance = rewardToken.balanceOf(address(govStaker));
// we can see that Bob have no unclaimed reward
assertEq(unclaimedReward, 0);
//There is some funds that are stuck in the contract
assertEq(rewardGovBalance, 500e18 +1);
}
The rewards will be stuck forever.
https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L303-L308
Foundry
The protocol should implement a function to redistribute the undistributed rewards.