// File: /contracts/LowGasVRF1/Lean/NFTSingleNoGatingSevPrices.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBase.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/// @title Raffles manager (single winner and NFT prize)
/// @author Luis Pando
/// @notice It consumes VRF v1 from Chainlink. It has the role
/// "operator" that is the one used by a backend app to make some calls
/// @dev It saves in an ordered array the player wallet and the current
/// entries count. So buying entries has a complexity of O(1)
/// For calculating the winner, from the huge random number generated by Chainlink
/// a normalized random is generated by using the module method, adding 1 to have
/// a random from 1 to entriesCount.
/// So next step is to perform a binary search on the ordered array to get the
/// player O(log n)
/// Example:
/// 0 -> { 1, player1} as player1 buys 1 entry
/// 1 -> {51, player2} as player2 buys 50 entries
/// 2 -> {52, player3} as player3 buys 1 entry
/// 3 -> {53, player4} as player4 buys 1 entry
/// 4 -> {153, player5} as player5 buys 100 entries
/// So the setWinner method performs a binary search on that sorted array to get the upper bound.
/// If the random number generated is 150, the winner is player5. If the random number is 20, winner is player2

contract NFTSingleNoGatingSevPrices is
    AccessControl,
    ReentrancyGuard,
    VRFConsumerBase
{
    ////////// CHAINLINK VRF v1 /////////////////
    bytes32 internal keyHash; // chainlink
    uint256 internal fee; // fee paid in LINK to chainlink. 0.1 in Rinkeby, 2 in mainnet

    struct RandomResult {
        uint256 randomNumber; // random number generated by chainlink.
        uint256 nomalizedRandomNumber; // random number % entriesLength + 1. So between 1 and entries.length
    }

    // event sent when the random number is generated by the VRF
    event RandomNumberCreated(
        uint256 indexed idFromMetawin,
        uint256 randomNumber,
        uint256 normalizedRandomNumber
    );

    struct RaffleInfo {
        uint256 id; // raffleId
        uint256 size; // length of the entries array of that raffle
    }

    mapping(uint256 => RandomResult) public requests;
    // map the requestId created by chainlink with the raffle info passed as param when calling getRandomNumber()
    mapping(bytes32 => RaffleInfo) public chainlinkRaffleInfo;

    /////////////// END CHAINKINK VRF V1 //////////////

    error EntryNotAllowed(string errorType);

    // Event sent when the raffle is created by the operator
    event RaffleCreated(
        uint256 indexed raffleId,
        address indexed nftAddress,
        uint256 indexed nftId
    );
    // Event sent when the owner of the nft stakes it for the raffle
    event RaffleStarted(uint256 indexed raffleId, address indexed seller);
    // Event sent when the raffle is finished (either early cashout or successful completion)
    event RaffleEnded(
        uint256 indexed raffleId,
        address indexed winner,
        uint256 amountRaised,
        uint256 randomNumber
    );
    // Event sent when one or more entries are sold (info from the price structure)
    event EntrySold(
        uint256 indexed raffleId,
        address indexed buyer,
        uint256 currentSize,
        uint256 priceStructureId
    );
    // Event sent when a free entry is added by the operator
    event FreeEntry(
        uint256 indexed raffleId,
        address[] buyer,
        uint256 amount,
        uint256 currentSize
    );
    // Event sent when a raffle is asked to cancel by the operator
    event RaffleCancelled(uint256 indexed raffleId, uint256 amountRaised);
    // The raffle is closed successfully and the platform receives the fee
    event FeeTransferredToPlatform(
        uint256 indexed raffleId,
        uint256 amountTransferred
    );
    // When the raffle is asked to be cancelled and 30 days have passed, the operator can call a method
    // to transfer the remaining funds and this event is emitted
    event RemainingFundsTransferred(
        uint256 indexed raffleId,
        uint256 amountInWeis
    );
    // When the raffle is asked to be cancelled and 30 days have not passed yet, the players can call a
    // method to refund the amount spent on the raffle and this event is emitted
    event Refund(
        uint256 indexed raffleId,
        uint256 amountInWeis,
        address indexed player
    );
    event EarlyCashoutTriggered(uint256 indexed raffleId, uint256 amountRaised);
    event SetWinnerTriggered(uint256 indexed raffleId, uint256 amountRaised);
    // Emitted when an entry is cancelled
    event EntryCancelled(
        uint256 indexed raffleId,
        uint256 amountOfEntriesCanceled,
        address player
    );

    /* every raffle has an array of price structure (max size = 5) with the different 
    prices for the different entries bought. The price for 1 entry is different than 
    for 5 entries where there is a discount*/
    struct PriceStructure {
        uint256 id;
        uint48 numEntries;
        uint256 price;
    }
   // mapping(uint256 => PriceStructure[5]) public prices;

    mapping(uint256 => PriceStructure) public pricesList;

    // Every raffle has a funding structure.
    struct FundingStructure {
        uint128 minimumFundsInWeis;
        uint128 desiredFundsInWeis;
    }
    mapping(uint256 => FundingStructure) public fundingList;

    // In order to calculate the winner, in this struct is saved for each bought the data
    struct EntriesBought {
        uint48 currentEntriesLength; // current amount of entries bought in the raffle
        address player; // wallet address of the player
    }
    // every raffle has a sorted array of EntriesBought. Each element is created when calling
    // either buyEntry or giveBatchEntriesForFree
    mapping(uint256 => EntriesBought[]) public entriesList;

    // Main raffle data struct
    struct RaffleStruct {
        uint48 platformPercentage; // percentage of the funds raised that goes to the platform
        uint48 cancellingDate;
        uint48 collateralId; // NFT id of the NFT
        address collateralAddress; // address of the NFT
        address winner; // address of thed winner of the raffle. Address(0) if no winner yet
        address seller; // address of the seller of the NFT
        uint256 randomNumber; // normalized (0-Entries array size) random number generated by the VRF
    }
    // The main structure is an array of raffles
    RaffleStruct[] public raffles;

    struct EntryInfoStruct {
        STATUS status; // status of the raffle. Can be created, accepted, ended, etc
        ENTRY_TYPE entryType;
        uint48 entriesLength; // to easy frontend, the length of the entries array is saved here
        uint128 amountRaised; // funds raised so far in wei
    }
    // The main structure is an array of raffles
    EntryInfoStruct[] public rafflesEntryInfo;

    // Map that contains the number of entries each user has bought, to prevent abuse, and the claiming info
    /*    struct ClaimStruct {
        uint128 numEntriesPerUser;
        uint128 amountSpentInWeis;
  //      bool claimed;
    }*/
    mapping(bytes32 => uint48) public claimsData;

    // All the different status a rafVRFCoordinatorfle can have
    enum STATUS {
        CREATED, // the operator creates the raffle
        ACCEPTED, // the seller stakes the nft for the raffle
        EARLY_CASHOUT, // the seller wants to cashout early
        CANCELLED, // the operator cancels the raffle and transfer the remaining funds after 30 days passes
        CLOSING_REQUESTED, // the operator sets a winner
        ENDED, // the raffle is finished, and NFT and funds were transferred
        CANCEL_REQUESTED // operator asks to cancel the raffle. Players has 30 days to ask for a refund
    }

    enum ENTRY_TYPE {
        ONLY_DIRECTLY,
        ONLY_EXTERNAL_CONTRACT,
        MIXED
    }

    // The operator role is operated by a backend application
    bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR");
    // requested by Hamburger. Role for the buy method of the hamburger (only that contract)
    bytes32 public constant MINTERCONTRACT_ROLE = keccak256("MINTERCONTRACT");

    // address of the wallet controlled by the platform that will receive the platform fee
    address payable public destinationWallet =
        payable(0x52a032cF59eA274f9D745f29b6D514fe95Ba192D);

    constructor(
        address _vrfCoordinator,
        address _linkToken,
        bytes32 _keyHash,
        bool _mainetFee
    )
        VRFConsumerBase(
            _vrfCoordinator, // VRF Coordinator
            _linkToken // LINK Token
        )
    {
        _setupRole(OPERATOR_ROLE, 0x13503B622abC0bD30A7e9687057DF6E8c42Fb928);
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);

        keyHash = _keyHash;
        if (_mainetFee == true)
            fee = 2 * 10 ** 18; // in mainnet, the fee must be 2 LINK
        else fee = 0.1 * 10 ** 18; // 0.1 LINK In Rinkeby and Goerli
    }

    function callVRFAndGetRequestId() internal returns (bytes32 requestId) {
        require(
            LINK.balanceOf(address(this)) >= fee,
            "Not enough LINK - fill contract with faucet"
        );
        bytes32 result = requestRandomness(keyHash, fee);
        return result;
    }

    /// @dev this is the method that will be called by the smart contract to get a random number
    /// @param _id Id of the raffle
    /// @param _entriesSize length of the entries array of that raffle
    /// @param _requestId id generated by Chainlink
    function getRandomNumber(
        uint256 _id,
        uint256 _entriesSize,
        bytes32 _requestId
    ) internal {
        chainlinkRaffleInfo[_requestId] = RaffleInfo({
            id: _id,
            size: _entriesSize
        });
    }

    /// @dev Callback function used by VRF Coordinator. Is called by chainlink
    /// the random number generated is normalized to the size of the entries array, and an event is
    /// generated, that will be listened by the platform backend to be checked if corresponds to a
    /// member of the MW community, and if true will call transferNFTAndFunds
    /// @param requestId id generated previously (on method getRandomNumber by chainlink)
    /// @param randomness random number (huge) generated by chainlink
    function fulfillRandomness(
        bytes32 requestId,
        uint256 randomness
    ) internal override {
        // randomness is the actual random number. Now extract from the aux map the original param id of the call
        RaffleInfo memory raffleInfo = chainlinkRaffleInfo[requestId];
        // save the random number on the map with the original id as key
        uint256 normalizedRandomNumber = (randomness % raffleInfo.size) + 1;

        RandomResult memory result = RandomResult({
            randomNumber: randomness,
            nomalizedRandomNumber: normalizedRandomNumber
        });

        requests[raffleInfo.id] = result;

        // send the event with the original id and the random number
        emit RandomNumberCreated(
            raffleInfo.id,
            randomness,
            normalizedRandomNumber
        );

        transferNFTAndFunds(raffleInfo.id, normalizedRandomNumber);
    }

    //////////////////////////////////////////////

    /// @param _desiredFundsInWeis the amount the seller would like to get from the raffle
    /// @param _collateralAddress The address of the NFT of the raffle
    /// @param _collateralId The id of the NFT (ERC721)
    /// @param _minimumFundsInWeis The mininum amount required for the raffle to set a winner
    /// @param _prices Array of prices and amount of entries the customer could purchase
    //   /// @param _commissionInBasicPoints commission for the platform, in basic points
    /// @notice Creates a raffle
    /// @dev creates a raffle struct and push it to the raffles array. Some data is stored in the funding data structure
    /// sends an event when finished
    /// @return raffleId
    function createRaffle(
        uint128 _desiredFundsInWeis,
        address _collateralAddress,
        uint48 _collateralId,
        uint128 _minimumFundsInWeis,
        PriceStructure[] calldata _prices,
        uint48 _commissionInBasicPoints,
        ENTRY_TYPE _entryType
    ) external onlyRole(OPERATOR_ROLE) returns (uint256) {
        require(_collateralAddress != address(0), "NFT is null");
        require(_commissionInBasicPoints <= 5000, "commission too high");

        RaffleStruct memory raffle = RaffleStruct({
            collateralAddress: _collateralAddress,
            collateralId: _collateralId,
            winner: address(0),
            randomNumber: 0,
            seller: address(0),
            platformPercentage: _commissionInBasicPoints,
            cancellingDate: 0
        });

        raffles.push(raffle);

        saveEntryInfo(_entryType);

        uint256 idRaffle = raffles.length - 1;
        
        require(_prices.length > 0, "No prices");

        for (uint256 i = 0; i < _prices.length; i++) {
            require(_prices[i].numEntries > 0, "numEntries is 0");

            PriceStructure memory p = PriceStructure({
                id: idRaffle,
                numEntries: _prices[i].numEntries,
                price: _prices[i].price
            });

            // prices[idRaffle][i] = p;
            pricesList[_prices[i].id] = p;
        }

        fundingList[idRaffle] = FundingStructure({
            minimumFundsInWeis: _minimumFundsInWeis,
            desiredFundsInWeis: _desiredFundsInWeis
        });

        emit RaffleCreated(idRaffle, _collateralAddress, _collateralId);

        // Initialize the entries list array, by adding a player and removing it
        EntriesBought memory entryBought = EntriesBought({
            player: msg.sender,
            currentEntriesLength: uint48(1)
        });
        entriesList[idRaffle].push(entryBought);
        delete entriesList[idRaffle][0];

        return idRaffle;
    }

    function saveEntryInfo(
        ENTRY_TYPE _entryType
    ) internal {
        EntryInfoStruct memory entryInfo = EntryInfoStruct({
            status: STATUS.CREATED,
            amountRaised: 0,
            entriesLength: 0,
            entryType: _entryType
        });
        rafflesEntryInfo.push(entryInfo);
    }

    /* * Example of a price structure:
1 ticket 0.02
5 tickets 0.018 (10% discount)
10 tickets 0.16  (20% discount)
25 tickets 0.35  (30% discount) 
50 tickets 0.6 (40% discount)
*/
 /*   /// @param _idRaffle raffleId
    /// @param _id Id of the price structure
    /// @return the price structure of that particular Id + raffle
    /// @dev Returns the price structure, used in the frontend
    function getPriceStructForId(
        uint256 _idRaffle,
        uint256 _id
    ) internal view returns (PriceStructure memory) {
        for (uint256 i = 0; i < 5; i++) {
            if (prices[_idRaffle][i].id == _id) {
                return prices[_idRaffle][i];
            }
        }
        return PriceStructure({id: 0, numEntries: 0, price: 0});
    }
*/
    /*
    Callable only by the owner of the NFT
    Once the operator has created the raffle, he can stake the NFT
    At this moment, the NFT is locked and the players can buy entries
    */
    /// @param _raffleId Id of the raffle
    /// @notice The owner of the NFT can stake it on the raffle. At this moment the raffle starts and can sell entries to players
    /// @dev the owner must have approved this contract before. Otherwise will revert when transferring from the owner
    function stakeNFT(uint256 _raffleId) external {
        RaffleStruct storage raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        // Check if the raffle is already created
        require(entryInfo.status == STATUS.CREATED, "Raffle not CREATED");
        // the owner of the NFT must be the current caller
        IERC721 token = IERC721(raffle.collateralAddress);
        require(
            token.ownerOf(raffle.collateralId) == msg.sender,
            "NFT is not owned by caller"
        );

        entryInfo.status = STATUS.ACCEPTED;
        raffle.seller = msg.sender;

        // transfer the asset to the contract
        token.transferFrom(msg.sender, address(this), raffle.collateralId); // transfer the token to the contract

        emit RaffleStarted(_raffleId, msg.sender);
    }

    /// @dev callable by players. Depending on the number of entries assigned to the price structure the player buys (_id parameter)
    /// one or more entries will be assigned to the player.
    /// Also it is checked the maximum number of entries per user is not reached
    /// As the method is payable, in msg.value there will be the amount paid by the user
    /// @notice If the operator set requiredNFTs when creating the raffle, only the owners of nft on that collection can make a call to this method. This will be
    /// used for special raffles
    /// @param _raffleId: id of the raffle
    /// @param _id: id of the price structure
    function buyEntry(
        uint256 _raffleId,
        uint256 _id
    ) external payable /*nonReentrant*/ {
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        // 06.05.2023 To avoid late players to buy entries after the draw
        if (entryInfo.status != STATUS.ACCEPTED)
            revert EntryNotAllowed("Not in ACCEPTED");
        // Not callable if the contract is only using hamburgers (150 gas)
        if (entryInfo.entryType == ENTRY_TYPE.ONLY_EXTERNAL_CONTRACT)
            revert EntryNotAllowed("Entry type not allowed");
        // Price checks
        if (_id == 0) revert EntryNotAllowed("howMany is 0");
        PriceStructure memory priceStruct = pricesList[_id];
        if (priceStruct.id != _raffleId) revert EntryNotAllowed("Id not in raffleId");
        uint48 numEntries = priceStruct.numEntries;
        if (numEntries == 0) revert EntryNotAllowed("Id not supported");
        if (msg.value != priceStruct.price)
            revert EntryNotAllowed("msg.value must be equal to the price");
        // save the entries onchain
        uint48 entriesLength = entryInfo.entriesLength;
        EntriesBought memory entryBought = EntriesBought({
            player: msg.sender,
            currentEntriesLength: uint48(entriesLength + numEntries)
        });
        entriesList[_raffleId].push(entryBought);
        // update raffle variables
        entryInfo.amountRaised += uint128(msg.value);
        entryInfo.entriesLength = entriesLength + numEntries;

        emit EntrySold(_raffleId, msg.sender, entryInfo.entriesLength, _id);

        /*    bytes32 hash = keccak256(abi.encode(msg.sender, _raffleId));
        // check there are enough entries left for this particular user
        if (claimsData[hash] + numEntries > entryInfo.maxEntries)
            revert EntryNotAllowed("Bought too many entries");
        //update claim data
        claimsData[hash] += numEntries;
*/
    }

    // The operator can add free entries to the raffle
    /// @param _raffleId Id of the raffle
    /// @param _freePlayers array of addresses corresponding to the wallet of the users that won a free entrie
    /// @dev only operator can make this call. Assigns a single entry per user, except if that user already reached the max limit of entries per user
    function giveBatchEntriesForFree(
        uint256 _raffleId,
        address[] memory _freePlayers
    ) external nonReentrant onlyRole(OPERATOR_ROLE) {
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        require(
            entryInfo.status == STATUS.ACCEPTED,
            "Raffle is not in accepted"
        );

        uint256 freePlayersLength = _freePlayers.length;
        uint48 validPlayersCount = 0;
        for (uint256 i = 0; i < freePlayersLength; i++) {
            address entry = _freePlayers[i];
            /*      if (
                claimsData[keccak256(abi.encode(entry, _raffleId))]
                    .numEntriesPerUser +
                    1 <=
                entryInfo.maxEntries
            ) */ {
                // add a new element to the entriesBought array.
                // as this method only adds 1 entry per call, the amountbought is always 1
                EntriesBought memory entryBought = EntriesBought({
                    player: entry,
                    currentEntriesLength: uint48(
                        entryInfo.entriesLength + i + 1
                    )
                });
                entriesList[_raffleId].push(entryBought);

                /*         claimsData[keccak256(abi.encode(entry, _raffleId))]
                    .numEntriesPerUser++;
*/
                ++validPlayersCount;
            }
        }

        entryInfo.entriesLength = entryInfo.entriesLength + validPlayersCount;

        emit FreeEntry(
            _raffleId,
            _freePlayers,
            freePlayersLength,
            entryInfo.entriesLength
        );
    }

    // helper method to get the winner address of a raffle
    /// @param _raffleId Id of the raffle
    /// @param _normalizedRandomNumber Generated by chainlink
    /// @return the wallet that won the raffle
    /// @dev Uses a binary search on the sorted array to retreive the winner
    /// but if the winner candidate is blacklisted, loop through the left looking for
    /// a candidate not blacklisted
    function getWinnerAddressFromRandom(
        uint256 _raffleId,
        uint256 _normalizedRandomNumber
    ) public view returns (address) {
        uint256 position = findUpperBound(
            entriesList[_raffleId],
            _normalizedRandomNumber
        );

        address candidate = entriesList[_raffleId][position].player;
        // general case
        if (candidate != address(0)) return candidate;
        // special case. The user is blacklisted, so try next on the left until find a non-blacklisted
        else {
            bool ended = false;
            uint256 i = position;
            while (
                ended == false && entriesList[_raffleId][i].player == address(0)
            ) {
                if (i == 0) i = entriesList[_raffleId].length - 1;
                else i = i - 1;
                // we came to the beginning without finding a non blacklisted player
                if (i == position) ended == true;
            }
            require(!ended, "All users blacklisted");
            return entriesList[_raffleId][i].player;
        }
    }

    /// @param array sorted array of EntriesBought. CurrentEntriesLength is the numeric field used to sort
    /// @param element uint256 to find. Goes from 1 to entriesLength
    /// @dev based on openzeppelin code (v4.0), modified to use an array of EntriesBought
    /// Searches a sorted array and returns the first index that contains a value greater or equal to element.
    /// If no such index exists (i.e. all values in the array are strictly less than element), the array length is returned. Time complexity O(log n).
    /// array is expected to be sorted in ascending order, and to contain no repeated elements.
    /// https://docs.openzeppelin.com/contracts/3.x/api/utils#Arrays-findUpperBound-uint256---uint256-
    function findUpperBound(
        EntriesBought[] storage array,
        uint256 element
    ) internal view returns (uint256) {
        if (array.length == 0) {
            return 0;
        }

        uint256 low = 0;
        uint256 high = array.length;

        while (low < high) {
            uint256 mid = Math.average(low, high);

            // Note that mid will always be strictly less than high (i.e. it will be a valid array index)
            // because Math.average rounds down (it does integer division with truncation).
            if (array[mid].currentEntriesLength > element) {
                high = mid;
            } else {
                low = mid + 1;
            }
        }

        // At this point `low` is the exclusive upper bound. We will return the inclusive upper bound.
        if (low > 0 && array[low - 1].currentEntriesLength == element) {
            return low - 1;
        } else {
            return low;
        }
    }

    // The operator can call this method once they receive the event "RandomNumberCreated"
    // triggered by the VRF v1 consumer contract (RandomNumber.sol)
    /// @param _raffleId Id of the raffle
    /// @param _normalizedRandomNumber index of the array that contains the winner of the raffle. Generated by chainlink
    /// @notice it is the method that sets the winner and transfers funds and nft
    /// @dev called by Chainlink callback
    function transferNFTAndFunds(
        uint256 _raffleId,
        uint256 _normalizedRandomNumber
    ) internal nonReentrant {
        RaffleStruct storage raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        // Only when the raffle has been asked to be closed and the platform
        require(
            entryInfo.status == STATUS.EARLY_CASHOUT ||
                entryInfo.status == STATUS.CLOSING_REQUESTED,
            "Raffle in wrong status"
        );

        raffle.randomNumber = _normalizedRandomNumber;
        raffle.winner = getWinnerAddressFromRandom(
            _raffleId,
            _normalizedRandomNumber
        );
        entryInfo.status = STATUS.ENDED;

        IERC721 _asset = IERC721(raffle.collateralAddress);
        _asset.transferFrom(address(this), raffle.winner, raffle.collateralId); // transfer the tokens to the contract

        uint256 amountForPlatform = (entryInfo.amountRaised *
            raffle.platformPercentage) / 10000;
        uint256 amountForSeller = entryInfo.amountRaised - amountForPlatform;
        // transfer amount (75%) to the seller.
        (bool sent, ) = raffle.seller.call{value: amountForSeller}("");
        require(sent, "Failed to send Ether");
        // transfer the amount to the platform
        (bool sent2, ) = destinationWallet.call{value: amountForPlatform}("");
        require(sent2, "Failed send Eth to MW");
        emit FeeTransferredToPlatform(_raffleId, amountForPlatform);

        emit RaffleEnded(
            _raffleId,
            raffle.winner,
            entryInfo.amountRaised,
            _normalizedRandomNumber
        );
    }

    function setCashOutActions(
        uint256 _raffleId
    ) internal returns (EntryInfoStruct memory) {
        RaffleStruct memory raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        FundingStructure memory funding = fundingList[_raffleId];

        require(raffle.seller == msg.sender, "Not the seller");
        // Check if the raffle is already accepted
        require(
            entryInfo.status == STATUS.ACCEPTED,
            "Raffle not in accepted status"
        );
        require(
            entryInfo.amountRaised >= funding.minimumFundsInWeis,
            "Not enough funds raised"
        );

        entryInfo.status = STATUS.EARLY_CASHOUT;

        emit EarlyCashoutTriggered(_raffleId, entryInfo.amountRaised);

        return entryInfo;
    }

    // can be called by the seller at every moment once enough funds has been raised
    /// @param _raffleId Id of the raffle
    /// @notice the seller of the nft, if the minimum amount has been reached, can call an early cashout, finishing the raffle
    /// @dev it triggers Chainlink VRF1 consumer, and generates a random number that is normalized and checked that corresponds to a MW player
    function earlyCashOut(uint256 _raffleId) external {
    /*    EntryInfoStruct memory raffle = setWinnerActions(_raffleId);
        bytes32 requestId = callVRFAndGetRequestId();
        getRandomNumber(_raffleId, raffle.entriesLength, requestId);*/
    }

    function setWinnerActions(
        uint256 _raffleId
    ) internal returns (EntryInfoStruct memory) {
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        // RaffleStruct storage raffle = raffles[_raffleId];
        FundingStructure storage funding = fundingList[_raffleId];
        // Check if the raffle is already accepted or is called again because early cashout failed
        require(entryInfo.status == STATUS.ACCEPTED, "Raffle in wrong status");
        require(
            entryInfo.amountRaised >= funding.minimumFundsInWeis,
            "Not enough funds raised"
        );

        require(
            funding.desiredFundsInWeis <= entryInfo.amountRaised,
            "Desired funds not raised"
        );
        entryInfo.status = STATUS.CLOSING_REQUESTED;

        emit SetWinnerTriggered(_raffleId, entryInfo.amountRaised);
        return entryInfo;
    }

    /// @param _raffleId Id of the raffle
    /// @notice the operator finish the raffle, if the desired funds has been reached
    /// @dev it triggers Chainlink VRF1 consumer, and generates a random number that is normalized and checked that corresponds to a MW player
    function setWinner(
        uint256 _raffleId
    ) external nonReentrant onlyRole(OPERATOR_ROLE) {
        EntryInfoStruct memory raffle = setWinnerActions(_raffleId);
        // this call trigers the VRF v1 process from Chainlink

        bytes32 requestId = callVRFAndGetRequestId();
        getRandomNumber(_raffleId, raffle.entriesLength, requestId);
    }

    /// @param _newAddress new address of the platform
    /// @dev Change the wallet of the platform. The one that will receive the platform fee when the raffle is closed.
    /// Only the admin can change this
    function setDestinationAddress(
        address payable _newAddress
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        destinationWallet = _newAddress;
    }

    /// @param _raffleId Id of the raffle
    /// @dev The operator can cancel the raffle. The NFT is sent back to the seller
    /// The raised funds are send to the destination wallet. The buyers will
    /// be refunded offchain in the metawin wallet
    function cancelRaffle(
        uint256 _raffleId
    ) external nonReentrant onlyRole(OPERATOR_ROLE) {
        RaffleStruct storage raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        // Dont cancel twice, or cancel an already ended raffle
        require(
            entryInfo.status != STATUS.ENDED &&
                entryInfo.status != STATUS.CANCELLED &&
                entryInfo.status != STATUS.EARLY_CASHOUT &&
                entryInfo.status != STATUS.CLOSING_REQUESTED &&
                entryInfo.status != STATUS.CANCEL_REQUESTED,
            "Wrong status"
        );

        // only if the raffle is in accepted status the NFT is staked and could have entries sold
        if (entryInfo.status == STATUS.ACCEPTED) {
            // transfer nft to the owner
            IERC721 _asset = IERC721(raffle.collateralAddress);
            _asset.transferFrom(
                address(this),
                raffle.seller,
                raffle.collateralId
            );
        }
        entryInfo.status = STATUS.CANCEL_REQUESTED;
        raffle.cancellingDate = uint48(block.timestamp);

        emit RaffleCancelled(_raffleId, entryInfo.amountRaised);
   }
  
    /// @param _raffleId Id of the raffle
    /// @dev after 30 days after cancelling passes, the operator can transfer to
    /// destinationWallet the remaining funds
    function transferRemainingFunds(
        uint256 _raffleId
    ) external nonReentrant onlyRole(OPERATOR_ROLE) {
   //     RaffleStruct memory raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];
        require(entryInfo.status == STATUS.CANCELLED || entryInfo.status == STATUS.CANCEL_REQUESTED, "Wrong status");
       
        entryInfo.status = STATUS.CANCELLED;

        (bool sent, ) = destinationWallet.call{value: entryInfo.amountRaised}(
            ""
        );
        require(sent, "Fail send Eth to MW");

        emit RemainingFundsTransferred(_raffleId, entryInfo.amountRaised);

        entryInfo.amountRaised = 0;
    }

    /// @param _raffleId Id of the raffle
    /// @return array of entries bougth of that particular raffle
    function getEntriesBought(
        uint256 _raffleId
    ) external view returns (EntriesBought[] memory) {
        return entriesList[_raffleId];
    }

    /*   /// @param _raffleId Id of the raffle
    /// @param _player wallet of the player
    /// @return Claims data of the player on that raffle
    function getClaimData(
        uint256 _raffleId,
        address _player
    ) external view returns (ClaimStruct memory) {
        return claimsData[keccak256(abi.encode(_player, _raffleId))];
    }
*/
    /// @dev for different reasons player entries should be void
    /// this has a cost in gas, but this makes cheaper in gas the callback from chainlink
    /// This method has to be called for every raffle of the blacklisted player
    /// @param _raffleId Id of the raffle
    /// @param entriesToCancel array that contains the index of the entries to cancel. 0 based
    /// @param _player player who owns the entry to be voided
    function cancelEntry(
        uint256 _raffleId,
        uint256[] calldata entriesToCancel,
        address _player
    ) external onlyRole(OPERATOR_ROLE) {
        uint256 totalEntriesBoughtCancelled = 0;
        for (uint256 i = 0; i < entriesToCancel.length; i++) {
            EntriesBought storage entry = entriesList[_raffleId][
                entriesToCancel[i]+1
            ];

            require(entry.player == _player, "Entry did not belong to player");

            entry.player = address(0);

            uint256 previousTotalEntriesLength;
            if (entriesToCancel[i] == 0) previousTotalEntriesLength = 0;
            else
                previousTotalEntriesLength = entriesList[_raffleId][
                    entriesToCancel[i] /* - 1*/
                ].currentEntriesLength;
            totalEntriesBoughtCancelled +=
                entry.currentEntriesLength -
                previousTotalEntriesLength;
        }
        emit EntryCancelled(_raffleId, totalEntriesBoughtCancelled, _player);
    }

    // Requested by Valerio Di Napoli.
    /// @param _raffleId id of the raffle
    /// @param _amountOfEntries amount of entries. Must be lower than maxEntriesPerUser
    /// @param _player The address of the player
    /// @notice Called by other contract created by Valerio, that minted an NFT and after that call this method
    /// @dev add entries but they are free so no payable
    function createFreeEntriesFromExternalContract(
        uint256 _raffleId,
        uint48 _amountOfEntries,
        address _player
    ) external onlyRole(MINTERCONTRACT_ROLE) nonReentrant {
        // if can buy run this code.
        bytes32 hash = keccak256(abi.encode(_player, _raffleId));

        //    RaffleStruct storage raffle = raffles[_raffleId];
        EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];

        require(
            entryInfo.entryType == ENTRY_TYPE.ONLY_EXTERNAL_CONTRACT ||
                entryInfo.entryType == ENTRY_TYPE.MIXED,
            "Entry type not allowed"
        );

        EntriesBought memory entryBought = EntriesBought({
            player: _player,
            currentEntriesLength: entryInfo.entriesLength + _amountOfEntries
        });
        entriesList[_raffleId].push(entryBought);

        // update the field entriesLength.
        entryInfo.entriesLength += _amountOfEntries;
        //update claim data
        //     claimsData[hash].numEntriesPerUser += _amountOfEntries;

        address[] memory players = new address[](1);
        players[0] = _player;
        emit FreeEntry(
            _raffleId,
            players,
            _amountOfEntries,
            entryInfo.entriesLength
        );
    }

    function getRafflesEntryInfo(
        uint256 _raffleId
    ) public view returns (EntryInfoStruct memory) {
        return rafflesEntryInfo[_raffleId];
    }

    function playerIsBlacklisted(address _player) external pure returns (bool) {
        //  return blackListManager.isBlackListed(_player);
        return false;
    }

    function raffleNotInAcceptedState(
        uint256 _raffleId
    ) external view returns (bool) {
        EntryInfoStruct memory entryInfo = rafflesEntryInfo[_raffleId];
        return (entryInfo.status != STATUS.ACCEPTED);
    }

    function playerIsSeller(
        address _player,
        uint256 _raffleId
    ) external view returns (bool) {
        RaffleStruct memory raffle = raffles[_raffleId];
        return (raffle.seller == _player);
    }

    function playerReachedMaxEntries(
        address _player,
        uint256 _raffleId,
        uint256 _amountOfEntries
    ) external pure returns (bool) {
        /*     EntryInfoStruct storage entryInfo = rafflesEntryInfo[_raffleId];

        bytes32 hash = keccak256(abi.encode(_player, _raffleId));
        // check there are enough entries left for this particular user
        return (claimsData[hash].numEntriesPerUser + _amountOfEntries >
            entryInfo.maxEntries);*/
        return false;
    }

    /* Requested by Valerio Di Napoli.
    @param _player The address of the player    
    @param _raffleId id of the raffle
    @param _colection Address of the required collection, if any
    @param _tokenIdUsed Id of the token of the required collection the player says he has and want to use in the raffle
    @returns bool if the player can buy or not, and string with the cause of the rejection. "" if can buy
    @notice Method that returns if the user can or cannot buy entries because the player owns an nft in the case or required collections.
    */
    function playerHasRequiredNFTs(
        address _player,
        uint256 _raffleId,
        address _collection,
        uint256 _tokenIdUsed
    ) external pure returns (bool canBuy, string memory cause) {
        return (true, "");
    }
}

// File: @openzeppelin/contracts/utils/math/Math.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a >= b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1);

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(
        uint256 x,
        uint256 y,
        uint256 denominator,
        Rounding rounding
    ) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. It the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`.
        // We also know that `k`, the position of the most significant bit, is such that `msb(a) = 2**k`.
        // This gives `2**k < a <= 2**(k+1)` → `2**(k/2) <= sqrt(a) < 2 ** (k/2+1)`.
        // Using an algorithm similar to the msb conmputation, we are able to compute `result = 2**(k/2)` which is a
        // good first aproximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1;
        uint256 x = a;
        if (x >> 128 > 0) {
            x >>= 128;
            result <<= 64;
        }
        if (x >> 64 > 0) {
            x >>= 64;
            result <<= 32;
        }
        if (x >> 32 > 0) {
            x >>= 32;
            result <<= 16;
        }
        if (x >> 16 > 0) {
            x >>= 16;
            result <<= 8;
        }
        if (x >> 8 > 0) {
            x >>= 8;
            result <<= 4;
        }
        if (x >> 4 > 0) {
            x >>= 4;
            result <<= 2;
        }
        if (x >> 2 > 0) {
            result <<= 1;
        }

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        uint256 result = sqrt(a);
        if (rounding == Rounding.Up && result * result < a) {
            result += 1;
        }
        return result;
    }
}

// File: @openzeppelin/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// File: @openzeppelin/contracts/utils/introspection/ERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

// File: @openzeppelin/contracts/utils/Strings.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _HEX_SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

// File: @openzeppelin/contracts/token/ERC721/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

// File: @openzeppelin/contracts/security/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        // On the first call to nonReentrant, _notEntered will be true
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }
}

// File: @openzeppelin/contracts/access/IAccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControl {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

// File: @openzeppelin/contracts/access/AccessControl.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControl is Context, IAccessControl, ERC165 {
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
    }

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        Strings.toHexString(uint160(account), 20),
                        " is missing role ",
                        Strings.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleGranted} event.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     *
     * May emit a {RoleRevoked} event.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     *
     * May emit a {RoleRevoked} event.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * May emit a {RoleGranted} event.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
}

// File: @chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol



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

interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(address owner) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(
    address to,
    uint256 value,
    bytes calldata data
  ) external returns (bool success);

  function transferFrom(
    address from,
    address to,
    uint256 value
  ) external returns (bool success);
}

// File: @chainlink/contracts/src/v0.8/VRFRequestIDBase.sol



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

contract VRFRequestIDBase {
  /**
   * @notice returns the seed which is actually input to the VRF coordinator
   *
   * @dev To prevent repetition of VRF output due to repetition of the
   * @dev user-supplied seed, that seed is combined in a hash with the
   * @dev user-specific nonce, and the address of the consuming contract. The
   * @dev risk of repetition is mostly mitigated by inclusion of a blockhash in
   * @dev the final seed, but the nonce does protect against repetition in
   * @dev requests which are included in a single block.
   *
   * @param _userSeed VRF seed input provided by user
   * @param _requester Address of the requesting contract
   * @param _nonce User-specific nonce at the time of the request
   */
  function makeVRFInputSeed(
    bytes32 _keyHash,
    uint256 _userSeed,
    address _requester,
    uint256 _nonce
  ) internal pure returns (uint256) {
    return uint256(keccak256(abi.encode(_keyHash, _userSeed, _requester, _nonce)));
  }

  /**
   * @notice Returns the id for this request
   * @param _keyHash The serviceAgreement ID to be used for this request
   * @param _vRFInputSeed The seed to be passed directly to the VRF
   * @return The id for this request
   *
   * @dev Note that _vRFInputSeed is not the seed passed by the consuming
   * @dev contract, but the one generated by makeVRFInputSeed
   */
  function makeRequestId(bytes32 _keyHash, uint256 _vRFInputSeed) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(_keyHash, _vRFInputSeed));
  }
}

// File: @chainlink/contracts/src/v0.8/VRFConsumerBase.sol



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

import "./interfaces/LinkTokenInterface.sol";

import "./VRFRequestIDBase.sol";

/** ****************************************************************************
 * @notice Interface for contracts using VRF randomness
 * *****************************************************************************
 * @dev PURPOSE
 *
 * @dev Reggie the Random Oracle (not his real job) wants to provide randomness
 * @dev to Vera the verifier in such a way that Vera can be sure he's not
 * @dev making his output up to suit himself. Reggie provides Vera a public key
 * @dev to which he knows the secret key. Each time Vera provides a seed to
 * @dev Reggie, he gives back a value which is computed completely
 * @dev deterministically from the seed and the secret key.
 *
 * @dev Reggie provides a proof by which Vera can verify that the output was
 * @dev correctly computed once Reggie tells it to her, but without that proof,
 * @dev the output is indistinguishable to her from a uniform random sample
 * @dev from the output space.
 *
 * @dev The purpose of this contract is to make it easy for unrelated contracts
 * @dev to talk to Vera the verifier about the work Reggie is doing, to provide
 * @dev simple access to a verifiable source of randomness.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFConsumerBase, and can
 * @dev initialize VRFConsumerBase's attributes in their constructor as
 * @dev shown:
 *
 * @dev   contract VRFConsumer {
 * @dev     constructor(<other arguments>, address _vrfCoordinator, address _link)
 * @dev       VRFConsumerBase(_vrfCoordinator, _link) public {
 * @dev         <initialization with other arguments goes here>
 * @dev       }
 * @dev   }
 *
 * @dev The oracle will have given you an ID for the VRF keypair they have
 * @dev committed to (let's call it keyHash), and have told you the minimum LINK
 * @dev price for VRF service. Make sure your contract has sufficient LINK, and
 * @dev call requestRandomness(keyHash, fee, seed), where seed is the input you
 * @dev want to generate randomness from.
 *
 * @dev Once the VRFCoordinator has received and validated the oracle's response
 * @dev to your request, it will call your contract's fulfillRandomness method.
 *
 * @dev The randomness argument to fulfillRandomness is the actual random value
 * @dev generated from your seed.
 *
 * @dev The requestId argument is generated from the keyHash and the seed by
 * @dev makeRequestId(keyHash, seed). If your contract could have concurrent
 * @dev requests open, you can use the requestId to track which seed is
 * @dev associated with which randomness. See VRFRequestIDBase.sol for more
 * @dev details. (See "SECURITY CONSIDERATIONS" for principles to keep in mind,
 * @dev if your contract could have multiple requests in flight simultaneously.)
 *
 * @dev Colliding `requestId`s are cryptographically impossible as long as seeds
 * @dev differ. (Which is critical to making unpredictable randomness! See the
 * @dev next section.)
 *
 * *****************************************************************************
 * @dev SECURITY CONSIDERATIONS
 *
 * @dev A method with the ability to call your fulfillRandomness method directly
 * @dev could spoof a VRF response with any random value, so it's critical that
 * @dev it cannot be directly called by anything other than this base contract
 * @dev (specifically, by the VRFConsumerBase.rawFulfillRandomness method).
 *
 * @dev For your users to trust that your contract's random behavior is free
 * @dev from malicious interference, it's best if you can write it so that all
 * @dev behaviors implied by a VRF response are executed *during* your
 * @dev fulfillRandomness method. If your contract must store the response (or
 * @dev anything derived from it) and use it later, you must ensure that any
 * @dev user-significant behavior which depends on that stored value cannot be
 * @dev manipulated by a subsequent VRF request.
 *
 * @dev Similarly, both miners and the VRF oracle itself have some influence
 * @dev over the order in which VRF responses appear on the blockchain, so if
 * @dev your contract could have multiple VRF requests in flight simultaneously,
 * @dev you must ensure that the order in which the VRF responses arrive cannot
 * @dev be used to manipulate your contract's user-significant behavior.
 *
 * @dev Since the ultimate input to the VRF is mixed with the block hash of the
 * @dev block in which the request is made, user-provided seeds have no impact
 * @dev on its economic security properties. They are only included for API
 * @dev compatability with previous versions of this contract.
 *
 * @dev Since the block hash of the block which contains the requestRandomness
 * @dev call is mixed into the input to the VRF *last*, a sufficiently powerful
 * @dev miner could, in principle, fork the blockchain to evict the block
 * @dev containing the request, forcing the request to be included in a
 * @dev different block with a different hash, and therefore a different input
 * @dev to the VRF. However, such an attack would incur a substantial economic
 * @dev cost. This cost scales with the number of blocks the VRF oracle waits
 * @dev until it calls responds to a request.
 */
abstract contract VRFConsumerBase is VRFRequestIDBase {
  /**
   * @notice fulfillRandomness handles the VRF response. Your contract must
   * @notice implement it. See "SECURITY CONSIDERATIONS" above for important
   * @notice principles to keep in mind when implementing your fulfillRandomness
   * @notice method.
   *
   * @dev VRFConsumerBase expects its subcontracts to have a method with this
   * @dev signature, and will call it once it has verified the proof
   * @dev associated with the randomness. (It is triggered via a call to
   * @dev rawFulfillRandomness, below.)
   *
   * @param requestId The Id initially returned by requestRandomness
   * @param randomness the VRF output
   */
  function fulfillRandomness(bytes32 requestId, uint256 randomness) internal virtual;

  /**
   * @dev In order to keep backwards compatibility we have kept the user
   * seed field around. We remove the use of it because given that the blockhash
   * enters later, it overrides whatever randomness the used seed provides.
   * Given that it adds no security, and can easily lead to misunderstandings,
   * we have removed it from usage and can now provide a simpler API.
   */
  uint256 private constant USER_SEED_PLACEHOLDER = 0;

  /**
   * @notice requestRandomness initiates a request for VRF output given _seed
   *
   * @dev The fulfillRandomness method receives the output, once it's provided
   * @dev by the Oracle, and verified by the vrfCoordinator.
   *
   * @dev The _keyHash must already be registered with the VRFCoordinator, and
   * @dev the _fee must exceed the fee specified during registration of the
   * @dev _keyHash.
   *
   * @dev The _seed parameter is vestigial, and is kept only for API
   * @dev compatibility with older versions. It can't *hurt* to mix in some of
   * @dev your own randomness, here, but it's not necessary because the VRF
   * @dev oracle will mix the hash of the block containing your request into the
   * @dev VRF seed it ultimately uses.
   *
   * @param _keyHash ID of public key against which randomness is generated
   * @param _fee The amount of LINK to send with the request
   *
   * @return requestId unique ID for this request
   *
   * @dev The returned requestId can be used to distinguish responses to
   * @dev concurrent requests. It is passed as the first argument to
   * @dev fulfillRandomness.
   */
  function requestRandomness(bytes32 _keyHash, uint256 _fee) internal returns (bytes32 requestId) {
    LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, USER_SEED_PLACEHOLDER));
    // This is the seed passed to VRFCoordinator. The oracle will mix this with
    // the hash of the block containing this request to obtain the seed/input
    // which is finally passed to the VRF cryptographic machinery.
    uint256 vRFSeed = makeVRFInputSeed(_keyHash, USER_SEED_PLACEHOLDER, address(this), nonces[_keyHash]);
    // nonces[_keyHash] must stay in sync with
    // VRFCoordinator.nonces[_keyHash][this], which was incremented by the above
    // successful LINK.transferAndCall (in VRFCoordinator.randomnessRequest).
    // This provides protection against the user repeating their input seed,
    // which would result in a predictable/duplicate output, if multiple such
    // requests appeared in the same block.
    nonces[_keyHash] = nonces[_keyHash] + 1;
    return makeRequestId(_keyHash, vRFSeed);
  }

  LinkTokenInterface internal immutable LINK;
  address private immutable vrfCoordinator;

  // Nonces for each VRF key from which randomness has been requested.
  //
  // Must stay in sync with VRFCoordinator[_keyHash][this]
  mapping(bytes32 => uint256) /* keyHash */ /* nonce */
    private nonces;

  /**
   * @param _vrfCoordinator address of VRFCoordinator contract
   * @param _link address of LINK token contract
   *
   * @dev https://docs.chain.link/docs/link-token-contracts
   */
  constructor(address _vrfCoordinator, address _link) {
    vrfCoordinator = _vrfCoordinator;
    LINK = LinkTokenInterface(_link);
  }

  // rawFulfillRandomness is called by VRFCoordinator when it receives a valid VRF
  // proof. rawFulfillRandomness then calls fulfillRandomness, after validating
  // the origin of the call
  function rawFulfillRandomness(bytes32 requestId, uint256 randomness) external {
    require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
    fulfillRandomness(requestId, randomness);
  }
}