Closed sherlock-admin4 closed 3 months ago
Duplicate with #2. Not an issue in practice as the reward will always be many orders of magnitudes larger than the rewardsDuration (the reward will be (much) larger than 10^18) so the rewardRate will always be greater than 0. But even if that was not the case, the owner could always recover any rewards dust using recoverERC20(). In any case, note that this relates to the pre-existing Synthetix contract, which is out of scope as per the rules: "Any issue that exists in the original non-Maker Syntetix staking rewards contract is out of scope."
EFCCWEB3
Medium
Loss of rewards when distribution occurs caused by # notifyRewardAmount
Summary
The distribute function is responsible for distributing rewards that have accrued since the last distribution. It interacts with a vesting contract (dssVest) to fetch the amount of rewards that are due, then transfers these rewards to a staking rewards contract (stakingRewards). Finally, it notifies the staking rewards contract about the new reward amount.
However the some part of the rewards in the contract will locked due to precision loss and trauncations https://github.com/sherlock-audit/2024-06-makerdao-endgame/blob/main/endgame-toolkit/src/VestedRewardsDistribution.sol#L152
Vulnerability Detail
When
distirbution
occurs to the function calls stakingRewards.notifyRewardAmount(amount). This function in the StakingRewards contract adjusts the internal accounting to account for the new rewards.In the notifyRewardAMount there is sought of precision loss, meaning users get to get less of their notified reward rather than expected, This can be seen here:
How can this occur, if you look at the function logic you can see that there can be a precision loss because of the way the integer is been handled, Solidity only supports integer division, which can result in precision loss. This precision loss can become significant when dealing with reward rates and durations, causing the calculations to deviate from the expected values.
The staking rewards contract is initialized with a
rewardsDuration
of 7 days (604800 seconds). TheperiodFinish
is the timestamp when the current reward period ends. Initially, rewardRate is 0, and the contract has a balance of 1000rewardsToken
.Assume the current
periodFinish
isJuly 1st, 2024
, and Alice adds a reward onJuly 2nd, 2024
(after the current period has finished). Alice callsnotifyRewardAmount
with reward = 700.Since
block.timestamp
is afterperiodFinish
, the conditionblock.timestamp
>=periodFinish
is true. TherewardRate
is calculated asreward
/rewardsDuration
, which is 700 / 604800 = 0 (due to integer division).The balance in the contract is 1000 tokens.
balance / rewardsDuration
is 1000 / 604800 = 0 (due to integer division). The checkrequire(rewardRate <= 0)
is true since rewardRate is 0. The function proceeds without reverting.lastUpdateTime
is updated to the current timestamp(July 2nd, 2024).
periodFinish
is set to July 9th, 2024. RewardAdded event is emitted with reward = 700.The rewardRate is set to 0 due to integer division (700 / 604800). This means no rewards will be distributed over the new period despite adding 700 tokens.
Impact
This indicates a significant issue because the actual reward distribution does not match the intended reward
Code Snippet
Tool used
Manual Review
Recommendation
Find a way to prevent precision loss, either by using safeMath because the notifyRewardAmount shouldn't be in anyway decreasing.