// File: contracts/MutariuumGiveaways.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import "../interfaces/IERC721.sol";
import "../libraries/ECDSA.sol";
import "./Roles.sol";

contract MutariuumGiveaways is Roles {
    error ClaimTimeout();

    constructor() {
        _setRole(msg.sender, 0, true);
    }

    function claim(
        address nft,
        address sender,
        uint256[] calldata tokenIds,
        uint256 blockNumber,
        bytes calldata signature
    ) external {
        _verifySignature(nft, sender, tokenIds, blockNumber, signature);
        IERC721 collection = IERC721(nft);
        uint256 tokensLength = tokenIds.length;
        for (uint256 i = 0; i < tokensLength;) {
            collection.safeTransferFrom(sender, msg.sender, tokenIds[i]);
            unchecked { ++i; }
        }
    }

    function _verifySignature(
        address nft,
        address sender,
        uint256[] calldata tokenIds,
        uint256 blockNumber,
        bytes calldata signature
    ) internal view {
        unchecked {
            if (block.number > blockNumber + 10) {
                revert ClaimTimeout();
            }
        }
        address signer = _getSigner(
            keccak256(
                abi.encode(
                    msg.sender, nft, sender, tokenIds, blockNumber
                )
            ), signature
        );
        if (!_hasRole(signer, 1)) {
            revert ECDSA.InvalidSignature();
        }
    }

    function _getSigner(bytes32 message, bytes calldata signature) internal pure returns(address) {
        bytes32 hash = keccak256(
            abi.encodePacked(
                "\x19Ethereum Signed Message:\n32",
                message
            )
        );
        return ECDSA.recover(hash, signature);
    }
}

// File: contracts/Roles.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

import "../libraries/Bits.sol";

contract Roles {
    using Bits for bytes32;

    error MissingRole(address user, uint256 role);

    event RoleUpdated(address indexed user, uint256 indexed role, bool indexed status);

    /**
     * @dev There is a maximum of 256 roles: each bit says if the role is on or off
     */
    mapping(address => bytes32) private _addressRoles;

    function _hasRole(address user, uint8 role) internal view returns(bool) {
        bytes32 roles = _addressRoles[user];
        return roles.getBool(role);
    }

    function _checkRole(address user, uint8 role) internal virtual view {
        if (user == address(this)) return;
        bytes32 roles = _addressRoles[user];
        bool allowed = roles.getBool(role);
        if (!allowed) {
            revert MissingRole(user, role);
        }
    }

    function _setRole(address user, uint8 role, bool status) internal virtual {
        _addressRoles[user] = _addressRoles[user].setBit(role, status);
        emit RoleUpdated(user, role, status);
    }

    function setRole(address user, uint8 role, bool status) external virtual {
        _checkRole(msg.sender, 0);
        _setRole(user, role, status);
    }

    function getRoles(address user) external view returns(bytes32) {
        return _addressRoles[user];
    }
}

// File: interfaces/IERC721.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

interface IERC721 {
  function safeTransferFrom(address from, address to, uint256 tokenId) external;
}

// File: libraries/Bits.sol



// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

library Bits {
    /**
     * @dev unpack bit [offset] (bool)
     */
    function getBool(bytes32 p, uint8 offset) internal pure returns (bool r) {
        assembly {
            r := and(shr(offset, p), 1)
        }
    }

    /**
     * @dev set bit [{offset}] to {value}
     */
    function setBit(
        bytes32 p,
        uint8 offset,
        bool value
    ) internal pure returns (bytes32 np) {
        assembly {
            np := or(
                and(
                    p,
                    xor(
                        0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
                        shl(offset, 1)
                    )
                ),
                shl(offset, value)
            )
        }
    }
}

// File: libraries/ECDSA.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.19;

library ECDSA {
  error InvalidSignature();

  function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
    if (signature.length != 65) {
      revert InvalidSignature();
    }
    bytes32 r;
    bytes32 s;
    uint8 v;
    assembly {
      r := mload(add(signature, 0x20))
      s := mload(add(signature, 0x40))
      v := byte(0, mload(add(signature, 0x60)))
    }
    return tryRecover(hash, v, r, s);
  }

  function tryRecover(
    bytes32 hash,
    uint8 v,
    bytes32 r,
    bytes32 s
  ) internal pure returns (address) {
    if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
      revert InvalidSignature();
    }
    return ecrecover(hash, v, r, s);
  }
}