Lost rewards when the supply is 0, which always happens if the rewards are queued before anyone has StakeTracker tokens
Vulnerability Detail
The rewards are destributed in a MasterChef style, which takes snapshots of the total accrued rewards over time and whenever someone wants to get the rewards, it subtracts the snapshot of the user from the most updated, global snapshot.
The rewardsPerToken() calculation factors the blocks passed times the reward rate by the totalSupply(), to get the reward per token in a specific interval (and then accrues to the previous intervals, as stated in the last paragraph). When the totalSupply() is 0, there is 0 rewardPerToken() increment as there is no supply to factor the rewards by.
The current solution is to maintain the same rewardsPerToken() if the totalSupply() is 0, but the lastUpdateBlock is still updated. This means that, during the interval in which the totalSupply() is 0, no rewards are destributed but the block numbers still move forward, leaving the tokens stuck in the MainRewarder and ExtraRewarder smart contracts.
This will always happen if the rewards are quewed before the totalSupply() is bigger than 0 (before an initial deposit to either DestinationVault or LMPVault). It might also happen if users withdraw all their tokens from the vaults, leading to a totalSupply() of 0, but this is very unlikely.
Impact
Lost reward tokens. The amount depends on the time during which the totalSupply() is 0, but could be significant.
Code Snippet
The rewardPerToken() calculation:
function rewardPerToken() public view returns (uint256) {
uint256 total = totalSupply();
if (total == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + ((lastBlockRewardApplicable() - lastUpdateBlock) * rewardRate * 1e18 / total);
}The rewardPerTokenStored does not increment when the totalSupply() is 0.
Tool used
Vscode Foundry Manual Review
Recommendation
The totalSupply() should not realistically be 0 after the initial setup period (unless for some reason everyone decides to withdraw from the vaults, but this should be handled separately). It should be enough to only allow queueing rewards if the totalSupply() is bigger than 0. For this, only a new check needs to be added:
function queueNewRewards(uint256 newRewards) external onlyWhitelisted {
if (totalSupply() == 0) revert ZeroTotalSupply();
...
}