A token owner can call Voter.poke to update the voting power, during the Voter.poke call, the Bribe.totalVoting isn't updated correctly, which results that the Bribe.earned will not calculate the rewards correctly.
Vulnerability Details
While a token owner calls Voter.poke, Voter._reset is called at the beginning of the Voter._vote. In Voter._reset, Bribe.withdraw is called in Voter.sol#L396 And Bribe.withdraw is defined as
221functionearned(address token,uint256 tokenId) publicviewreturns (uint256) {...242if (_endIndex >=0) {243for (uint256 i = _startIndex; i <= _endIndex; i++) {...254 prevRewards.timestamp = _nextEpochStart;255 _prevSupply = votingCheckpoints[getPriorVotingIndex(_nextEpochStart + DURATION)].votes; <<<--- totalVoting is used here
256257// Prevent divide by zero258if (_prevSupply ==0) {259 _prevSupply =1;260 }261 prevRewards.balanceOf = (cp0.balanceOf * tokenRewardsPerEpoch[token][_nextEpochStart]) / _prevSupply;
262 }263 }...268 uint256 _priorSupply = votingCheckpoints[getPriorVotingIndex(_lastEpochEnd)].votes; <<<--- totalVoting is used here
...279return reward;280 }
So to sum up, during Voter.poke call:
Bribe.withdraw will be called, but within the function Bribe.totalVoting isn't deducting amount
Bribe.deposit will be called in Voter._vote, but this time, Bribe.totalVoting is added amount So Voter.poke function will cause Bribe.totalVoting to increase. Then when calculating the rewards amount in Bribe.earned, Bribe.totalVoting is used, which will result wrong amount of rewards.
Impact Details
User might receive less reward token after Voter.poke is called, and the unclaimed reward token will stuck in the contract.
References
Add any relevant links to documentation or code
Proof of Concept
Add the following code to src/test/Voting.t.sol, and run