in this contract // SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../interfaces/IVirtualStakingRewards.sol";

contract VirtualStakingRewards is IVirtualStakingRewards, Ownable {
    using SafeERC20 for IERC20;

    error ZeroAddress();
    error ZeroAmount();
    error Forbidden(address sender);
    error RewardPeriodNotFinished();
    error InsufficientRewards();

    /* ========== STATE VARIABLES ========== */

    address public rewardsDistribution;
    address public operator;

    address public immutable rewardsToken;
    uint256 public periodFinish;
    uint256 public rewardRate;
    uint256 public rewardsDuration = 7 days;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;

    /* ========== MODIFIERS ========== */

    modifier onlyRewardsDistribution() {
        if (msg.sender != rewardsDistribution) {
            revert Forbidden(msg.sender);
        }
        _;
    }

    modifier onlyOperator() {
        if (msg.sender != operator) {
            revert Forbidden(msg.sender);
        }
        _;
    }

    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }

    /* ========== CONSTRUCTOR ========== */

    constructor(address _rewardsDistribution, address _rewardsToken) {
        if (_rewardsToken == address(0) || _rewardsDistribution == address(0)) {
            revert ZeroAddress();
        }
        rewardsToken = _rewardsToken;
        rewardsDistribution = _rewardsDistribution;
    }

    /* ========== VIEWS ========== */

    function totalSupply() external view returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) external view returns (uint256) {
        return _balances[account];
    }

    function lastTimeRewardApplicable() public view returns (uint256) {
        return block.timestamp < periodFinish ? block.timestamp : periodFinish;
    }

    function rewardPerToken() public view returns (uint256) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18) / _totalSupply);
    }

    function earned(address account) public view returns (uint256) {
        return (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 + rewards[account];
    }

    function getRewardForDuration() external view returns (uint256) {
        return rewardRate * rewardsDuration;
    }

    /* ========== MUTATIVE FUNCTIONS ========== */

    function stake(address user, uint256 amount) external updateReward(user) onlyOperator {
        if (amount == 0) {
            revert ZeroAmount();
        }
        if (user == address(0)) {
            revert ZeroAddress();
        }
        _totalSupply += amount;
        _balances[user] += amount;
        emit Staked(user, amount);
    }

    function withdraw(address user, uint256 amount) public updateReward(user) onlyOperator {
        if (amount == 0) {
            revert ZeroAmount();
        }
        _totalSupply -= amount;
        _balances[user] -= amount;
        emit Withdrawn(user, amount);
    }

    function getReward(address user) public updateReward(user) returns (uint256 reward) {
        reward = rewards[user];
        if (reward != 0) {
            rewards[user] = 0;
            IERC20(rewardsToken).safeTransfer(user, reward);
            emit RewardPaid(user, reward);
        }
    }

    function exit(address user) external {
        if (_balances[user] != 0) {
            withdraw(user, _balances[user]);
        }
        getReward(user);
    }

    /* ========== RESTRICTED FUNCTIONS ========== */

    function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
        if (block.timestamp >= periodFinish) {
            rewardRate = reward / rewardsDuration;
        } else {
            uint256 remaining = periodFinish - block.timestamp;
            uint256 leftover = remaining * rewardRate;
            rewardRate = (reward + leftover) / rewardsDuration;
        }

        // Ensure the provided reward amount is not more than the balance in the contract.
        // This keeps the reward rate in the right range, preventing overflows due to
        // very high values of rewardRate in the earned and rewardsPerToken functions;
        // Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
        uint256 balance = IERC20(rewardsToken).balanceOf(address(this));
        if (rewardRate > balance / rewardsDuration) {
            revert InsufficientRewards();
        }

        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp + rewardsDuration;
        emit RewardAdded(reward);
    }

    function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
        if (block.timestamp <= periodFinish) {
            revert RewardPeriodNotFinished();
        }
        if (_rewardsDuration == 0) {
            revert ZeroAmount();
        }

        rewardsDuration = _rewardsDuration;
        emit RewardsDurationUpdated(_rewardsDuration);
    }

    function setRewardsDistribution(address _rewardsDistribution) external onlyOwner {
        if (_rewardsDistribution == address(0)) {
            revert ZeroAddress();
        }
        rewardsDistribution = _rewardsDistribution;

        emit RewardsDistributionUpdated(_rewardsDistribution);
    }

    function setOperator(address _operator) external onlyOwner {
        if (_operator == address(0)) {
            revert ZeroAddress();
        }
        operator = _operator;

        emit OperatorUpdated(_operator);
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);
    event RewardsDurationUpdated(uint256 newDuration);
    event RewardsDistributionUpdated(address indexed rewardsDistribution);
    event OperatorUpdated(address indexed operator);
}  i found this vulnerabilites review and prove if are valid or invalid with evidence and the cause and the vulnerable part with code - issue 1 Centralized Control and Single Point of Failurehigh

The smart contract design places significant control in the hands of the owner, who has the authority to set the rewards distribution address and the operator. If the owner's account is compromised, an attacker could potentially manipulate the reward rates, pause the reward distribution, or redirect funds by changing the operator or rewards distribution address to a malicious one. This centralization creates a single point of failure, which could lead to a loss of funds or trust in the system.

What security measures are in place to protect the owner's account from being compromised?- issue 2 Reentrancy Attackhigh

The contract functions such as 'stake', 'withdraw', and 'getReward' are potentially vulnerable to reentrancy attacks. If the contract interacts with malicious external contracts or addresses during these functions, the external contract could call back into the staking contract before the initial execution is complete. This could lead to unexpected behavior, such as withdrawing more tokens than intended or manipulating reward distributions. Although the contract uses the OpenZeppelin's SafeERC20 library, which is designed to prevent such attacks, it's important to ensure that all external calls are made in a way that does not allow for reentrancy.

Does the contract follow the 'checks-effects-interactions' pattern to prevent reentrancy, and are there any external calls made after state changes? - issue 3 Arithmetic Overflow and Underflowmedium

The contract's reward calculation relies on arithmetic operations that could be susceptible to overflow and underflow issues. For example, the rewardPerToken function performs multiplication and division that could overflow if the values are too large. Solidity 0.8 and above includes built-in overflow and underflow checks, but it's still important to ensure that the calculations are designed to prevent these issues, especially since the contract deals with financial incentives and token distributions. An overflow or underflow could result in incorrect reward calculations, leading to loss of funds or unfair distribution of rewards.

Has the contract undergone a security audit to check for potential arithmetic overflow and underflow vulnerabilities? -ISSUE 4 Time Manipulationlow

The contract uses block timestamps to determine the last time rewards are applicable and to calculate reward rates. Miners have some control over the block timestamp, which could potentially be manipulated to influence these calculations. Although the impact of time manipulation is limited in this context, as the contract deals with longer-term timeframes for staking rewards, it's still a potential vector for attack. An attacker with sufficient mining power could manipulate the timestamp to alter the reward distribution schedule or to gain an unfair advantage in earning rewards.

How does the contract mitigate the risks associated with block timestamp manipulation, and what is the potential impact on reward calculations?