// File: src/Rewards.sol



// SPDX-License-Identifier: UNLICENSED
/*

███╗   ███╗███████╗███╗   ███╗███████╗██████╗ ██╗   ██╗██████╗ ██████╗ ██╗   ██╗
████╗ ████║██╔════╝████╗ ████║██╔════╝██╔══██╗██║   ██║██╔══██╗██╔══██╗╚██╗ ██╔╝
██╔████╔██║█████╗  ██╔████╔██║█████╗  ██████╔╝██║   ██║██║  ██║██║  ██║ ╚████╔╝ 
██║╚██╔╝██║██╔══╝  ██║╚██╔╝██║██╔══╝  ██╔══██╗██║   ██║██║  ██║██║  ██║  ╚██╔╝  
██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║███████╗██████╔╝╚██████╔╝██████╔╝██████╔╝   ██║   
╚═╝     ╚═╝╚══════╝╚═╝     ╚═╝╚══════╝╚═════╝  ╚═════╝ ╚═════╝ ╚═════╝    ╚═╝   

*/

pragma solidity ^0.8.13;

import "solmate/utils/MerkleProofLib.sol";
import "solmate/auth/Owned.sol";

contract Rewards is Owned {
    uint256 public lockStartTime;

    bytes32 public root;
    mapping(bytes32 => mapping(uint256 => bool)) private _isClaimed;

    error AlreadyClaimed();
    error InvalidProof();
    error ClaimsLocked();

    event Claimed(address indexed recipient, bytes32 indexed root, uint256 amount);
    event RootAdded(bytes32 newRoot);
    event LockStartTimeSet(uint256 _lockStartTime);

    /**
     * @dev contract constructor
     */
    constructor() Owned(msg.sender) {}

    /**
     * @dev Checks if the claims are locked - 1 hour every 7 days for updates.
     * @return A boolean that indicates if the claims are locked
     */
    function isLocked() public view returns (bool) {
        return (block.timestamp - lockStartTime) % 7 days < 1 hours;
    }

    /**
     * @dev Allows a user to claim their rewards
     * @param proof The Merkle proof for the claim
     * @param index The index of the claim
     * @param amount The amount of the claim
     */
    function claim(bytes32[] calldata proof, uint256 index, uint256 amount) external {
        if (isLocked()) {
            revert ClaimsLocked();
        }

        if (_isClaimed[root][index]) revert AlreadyClaimed();

        bytes32 leaf = keccak256(abi.encodePacked(index, msg.sender, amount));

        if (!MerkleProofLib.verify(proof, root, leaf)) revert InvalidProof();

        _isClaimed[root][index] = true;
        payable(msg.sender).transfer(amount);

        emit Claimed(msg.sender, root, amount);
    }

    /**
     * @dev Allows the owner to update the root of the Merkle tree and transfer the total allocation
     * @param _root The new root of the Merkle tree
     */
    function updateRoot(bytes32 _root) external onlyOwner {
        root = _root;
        emit RootAdded(_root);
    }

    /**
     * @dev Allows the owner to set the lock start time
     * @param _lockStartTime The new lock start time
     */
    function setLockStartTime(uint256 _lockStartTime) external onlyOwner {
        lockStartTime = _lockStartTime;
        emit LockStartTimeSet(_lockStartTime);
    }

    receive() external payable {}
}

// File: lib/solmate/src/utils/MerkleProofLib.sol



// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Gas optimized merkle proof verification library.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
library MerkleProofLib {
    function verify(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shifting by 5 is like multiplying by 32.
                let end := add(proof.offset, shl(5, proof.length))

                // Initialize offset to the offset of the proof in calldata.
                let offset := proof.offset

                // Iterate over proof elements to compute root hash.
                // prettier-ignore
                for {} 1 {} {
                    // Slot where the leaf should be put in scratch space. If
                    // leaf > calldataload(offset): slot 32, otherwise: slot 0.
                    let leafSlot := shl(5, gt(leaf, calldataload(offset)))

                    // Store elements to hash contiguously in scratch space.
                    // The xor puts calldataload(offset) in whichever slot leaf
                    // is not occupying, so 0 if leafSlot is 32, and 32 otherwise.
                    mstore(leafSlot, leaf)
                    mstore(xor(leafSlot, 32), calldataload(offset))

                    // Reuse leaf to store the hash to reduce stack operations.
                    leaf := keccak256(0, 64) // Hash both slots of scratch space.

                    offset := add(offset, 32) // Shift 1 word per cycle.

                    // prettier-ignore
                    if iszero(lt(offset, end)) { break }
                }
            }

            isValid := eq(leaf, root) // The proof is valid if the roots match.
        }
    }
}

// File: lib/solmate/src/auth/Owned.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event OwnershipTransferred(address indexed user, address indexed newOwner);

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}