sherlock-audit / 2024-03-zivoe-judging

8 stars 6 forks source link

heedfxn - Reward rate can be diluted in rewards contracts #702

Closed sherlock-admin3 closed 6 months ago

sherlock-admin3 commented 6 months ago

heedfxn

medium

Reward rate can be diluted in rewards contracts

heedfxn

medium

Summary

Reward rate can be diluted in rewards contracts

Vulnerability Detail

ZivoeRewards as well as ZivoeRewardsVesting use a function depositReward to deposit a reward to be vested over time. Claimable rewards are emitted every second according to rewardRate. When depositReward is called and a reward period is still ongoing, the newly deposited reward is added with remaining period reward and a new period is created, so a new rewardRate is calculated. depositReward however, can be called by anyone and it does not check if a 0 amount is passed as the reward amount. If someone calls does that, then the current rewardRate is diluted based on how far into the period in time the current block is.

function depositReward(address _rewardsToken, uint256 reward) external updateReward(address(0)) nonReentrant { //qq no 0 check reward
        IERC20(_rewardsToken).safeTransferFrom(_msgSender(), address(this), reward);

        // Update vesting accounting for reward (if existing rewards being distributed, increase proportionally).
        if (block.timestamp >= rewardData[_rewardsToken].periodFinish) { //= vesting over at this point in time
            rewardData[_rewardsToken].rewardRate = reward.div(rewardData[_rewardsToken].rewardsDuration); //= rewards per second
        } else { //=before old vesting finished
            uint256 remaining = rewardData[_rewardsToken].periodFinish.sub(block.timestamp);//= remaining seconds 
            uint256 leftover = remaining.mul(rewardData[_rewardsToken].rewardRate); //= remaining seconds * current reward rate
            rewardData[_rewardsToken].rewardRate = reward.add(leftover).div(rewardData[_rewardsToken].rewardsDuration); // rewards rate = (total reward) / dura
        }

        rewardData[_rewardsToken].lastUpdateTime = block.timestamp;
        rewardData[_rewardsToken].periodFinish = block.timestamp.add(rewardData[_rewardsToken].rewardsDuration);
        emit RewardDeposited(_rewardsToken, reward, _msgSender());
    }

The final line uses rewardsDuration, which is the duration of a full period. So essentially a new period is started without adding rewards in this case.

Impact

Rewards emitted per second will be lower for stakers.

Code Snippet

https://github.com/sherlock-audit/2024-03-zivoe/blob/d4111645b19a1ad3ccc899bea073b6f19be04ccd/zivoe-core-foundry/src/ZivoeRewards.sol#L228 https://github.com/sherlock-audit/2024-03-zivoe/blob/d4111645b19a1ad3ccc899bea073b6f19be04ccd/zivoe-core-foundry/src/ZivoeRewardsVesting.sol#L352

Tool used

Manual Review

Recommendation

Include a minimum amount for reward to be used in depositReward in both contracts, or add access modifiers to ensure it is not called with low values.

Duplicate of #11

sherlock-admin2 commented 6 months ago

1 comment(s) were left on this issue during the judging contest.

panprog commented:

high, dup of #11, allows anyone to extend reward finish time indefinitely and decrease reward rate.