Skip to content

Latest commit

 

History

History
106 lines (60 loc) · 5.28 KB

062.md

File metadata and controls

106 lines (60 loc) · 5.28 KB

Calm Khaki Sealion

High

Unbounded Reward Start Time in L2Staking Contract

Summary

https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/staking/L2Staking.sol#L237

https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/staking/L2Staking.sol#L251

https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/staking/L2Staking.sol#L252

https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/staking/L2Staking.sol#L255

https://github.com/morph-l2/morph/blob/main/contracts/contracts/l2/staking/L2Staking.sol#L363

The L2Staking contract allows the owner to set and update the rewardStartTime at any point in the future using the updateRewardStartTime() function. There is no restriction on how far into the future the rewardStartTime can be set. This creates a potential vulnerability where the owner can delay the start of rewards indefinitely, negatively impacting the staking incentives and trust in the contract. The absence of proper limits on updating the reward start time can lead to exploitation by the owner or accidental mismanagement, causing stakers to lose out on rewards or undermining the staking system entirely.

Vulnerability Detail

The updateRewardStartTime() function allows the contract owner to set or update the reward start time to any future time, without any upper limit. As long as rewardStarted remains false, the owner can keep updating the start time to push it further into the future.

The startReward() function requires block.timestamp >= rewardStartTime to set rewardStarted = true. If rewardStartTime is set too far in the future, rewards will never start, which means stakers delegating before the rewards start will not be linked to a proper reward epoch (effectiveEpoch remains 0).

Stakers may stake and unstake without proper lock-up periods or receiving rewards, defeating the purpose of the staking system. Additionally, the owner could repeatedly postpone the rewards, creating an environment of distrust and exploitation.

The startReward() function was missing an event to indicate when rewards are officially started. Without this event, it becomes difficult to track the start of the reward period off-chain, leading to a lack of transparency.

Code Snippet

bool public rewardStarted;

    /// @notice reward start time
    uint256 public rewardStartTime;

function updateRewardStartTime(uint256 _rewardStartTime) external onlyOwner {
        require(!rewardStarted && rewardStartTime > block.timestamp, "reward already started");
        require(
            _rewardStartTime > block.timestamp &&
                _rewardStartTime % REWARD_EPOCH == 0 &&
                _rewardStartTime != rewardStartTime,
            "invalid reward start time"
        );
        uint256 _oldTime = rewardStartTime;
        rewardStartTime = _rewardStartTime;
        emit RewardStartTimeUpdated(_oldTime, _rewardStartTime);
    }

Impact

Delayed or Never-Started Rewards: If rewardStartTime is set too far in the future, the staking rewards are delayed indefinitely. This reduces the effectiveness of the staking system, as stakers who delegate before the rewards start are not tied to a reward epoch.

Loss of Staker Incentives: Stakers may not receive rewards for extended periods, and without proper epochs, their delegations may become meaningless. This can result in stakers leaving the system or refraining from participating altogether.

Exploitation by the Owner: The owner has complete control over the reward start time and can push it into the future indefinitely. This undermines trust in the system and creates a centralization risk, as the owner can manipulate when and if rewards are distributed.

Incorrect Epoch Calculations: Any delegations made before rewards start have an effectiveEpoch of 0, leading to incorrect or non-existent reward distribution for stakers who participate before the rewards begin.

Premature Undelegation: Stakers can undelegate and claim their stake before the rewards start, effectively bypassing any intended lock-up periods, leading to further exploitation.

Tool used

Manual Review

Recommendation

Limit the Maximum Future rewardStartTime

To prevent the owner from setting the rewardStartTime too far into the future, we can implement a maximum allowable future time, such as 30 days from the current block.timestamp.

uint256 public constant MAX_FUTURE_START_TIME = 30 days; // Maximum 30 days in the future

function updateRewardStartTime(uint256 _rewardStartTime) external onlyOwner {
    require(!rewardStarted && rewardStartTime > block.timestamp, "Reward already started");

    // Ensure that rewardStartTime is not more than MAX_FUTURE_START_TIME in the future
    require(
        _rewardStartTime <= block.timestamp + MAX_FUTURE_START_TIME,
        "Reward start time too far in the future"
    );
    
    require(_rewardStartTime > block.timestamp && _rewardStartTime % REWARD_EPOCH == 0, "Invalid reward start time");

    uint256 oldTime = rewardStartTime;
    rewardStartTime = _rewardStartTime;
    emit RewardStartTimeUpdated(oldTime, _rewardStartTime);
}

This limits the rewardStartTime to a maximum of 30 days into the future and ensures that the rewards cannot be delayed indefinitely.