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



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

interface VRFV2WrapperInterface {
  /**
   * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
  function lastRequestId() external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   */
  function calculateRequestPrice(uint32 _callbackGasLimit) external view returns (uint256);

  /**
   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
   *
   * @dev This is a convenience function that can be called in simulation to better understand
   * @dev pricing.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
   */
  function estimateRequestPrice(uint32 _callbackGasLimit, uint256 _requestGasPriceWei) external view returns (uint256);
}

// File: @chainlink/contracts/src/v0.8/shared/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/vrf/VRFV2WrapperConsumerBase.sol



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

import "../shared/interfaces/LinkTokenInterface.sol";
import "../interfaces/VRFV2WrapperInterface.sol";

/** *******************************************************************************
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2 requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2 subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2WrapperConsumerBase. The consumer must be funded
 * @dev with enough LINK to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomness' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2WrapperConsumerBase {
  LinkTokenInterface internal immutable LINK;
  VRFV2WrapperInterface internal immutable VRF_V2_WRAPPER;

  /**
   * @param _link is the address of LinkToken
   * @param _vrfV2Wrapper is the address of the VRFV2Wrapper contract
   */
  constructor(address _link, address _vrfV2Wrapper) {
    LINK = LinkTokenInterface(_link);
    VRF_V2_WRAPPER = VRFV2WrapperInterface(_vrfV2Wrapper);
  }

  /**
   * @dev Requests randomness from the VRF V2 wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2 request ID of the newly created randomness request.
   */
  function requestRandomness(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords
  ) internal returns (uint256 requestId) {
    LINK.transferAndCall(
      address(VRF_V2_WRAPPER),
      VRF_V2_WRAPPER.calculateRequestPrice(_callbackGasLimit),
      abi.encode(_callbackGasLimit, _requestConfirmations, _numWords)
    );
    return VRF_V2_WRAPPER.lastRequestId();
  }

  /**
   * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
  function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

  function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
    require(msg.sender == address(VRF_V2_WRAPPER), "only VRF V2 wrapper can fulfill");
    fulfillRandomWords(_requestId, _randomWords);
  }
}

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



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

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/interfaces/IERC721.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (interfaces/IERC721.sol)

pragma solidity ^0.8.0;

import "../token/ERC721/IERC721.sol";

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



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * 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/token/ERC721/IERC721Receiver.sol



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

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

// 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/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: contracts/interfaces/IPudgRaffle.sol



// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";

interface IPudgRaffle is IERC721Receiver {
    struct Raffle {
        bytes32 ipfsHash;
        address receiver;
        uint256 nftId;
        address nftContract;
        uint256 minSnapshotTimestamp;
        uint256 maxSnapshotTimestamp;
        uint256 snapshotBlock;
        uint256 claimDeadline;
        uint256 claimedBlock;
        uint256 vrfRequestId;
        uint256 vrfNumber;
        uint256 randomNumber;
    }

    function getRaffle(uint256 roundId) external view returns (Raffle memory);

    function getLatestRounds(uint256 numRounds) external view returns (Raffle[] memory);

    function claim(uint256 roundId) external;

    function initRaffle(
        address nftContract,
        uint256 pudgyId,
        uint256 minSnapshotTimestamp,
        uint256 maxSnapshotTimestamp
    ) external returns (uint256 roundId);

    function closeRaffle(uint256 roundId) external returns (uint256 vrfRequestId);

    function publishResults(
        uint256 roundId,
        bytes32 ipfsHash,
        address receiver,
        uint256 snapshotBlock,
        uint256 randomNumber
    ) external;

    event Claimed(uint256 indexed roundId, address indexed winner);
    event RaffleInitiated(uint256 indexed roundId, address indexed nftContract, uint256 nftId);
    event RaffleClosed(uint256 indexed roundId, uint256 vrfRequestId);
    event RaffleFailed(uint256 indexed roundId);
    event ResultsPublished(uint256 indexed roundId, address indexed receiver);

    error InvalidRound(uint256 roundId);
    error ReceiverNotPublished();
    error InvalidClaimer(address claimer);
    error AlreadyClaimed();
    error ClaimPeriodEnded(uint256 claimDeadline);
    error InsufficientLINK();
    error ReceiverAlreadyPublished();
    error InvalidMinConfirmations();
    error InvalidCallbackGasLimit();
    error InvalidClaimPeriod();
    error InvalidVRFResponseTime();
    error InvalidNftId(uint256 nftId);
    error RaffleAlreadyClosed(uint256 roundId);
    error VrfRequestNotCompleted();
    error InvalidSnapshotMinTimestamp(uint256 minSnapshotTimestamp);
    error InvalidSnapshotMaxTimestamp(uint256 maxSnapshotTimestamp);
    error PreviousRaffleNotConcluded();
}

// File: contracts/PudgRaffle.sol



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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol";

import "./interfaces/IPudgRaffle.sol";

contract PudgRaffle is VRFV2WrapperConsumerBase, Ownable, IPudgRaffle {
    mapping(uint256 roundId => Raffle) public raffles;
    mapping(uint256 vrfRequestId => uint256) private _roundIds;
    uint256 public lastRound;

    uint256 public claimPeriod = 7 days;

    // Chainlink VRF variables
    uint16 private _vrfMinConfirmations;
    uint32 private _vrfCallbackGasLimit;
    uint256 private _vrfResponseTime;
    uint256 private _vrfResponseDeadline;

    constructor(
        address _link,
        address _vrfV2Wrapper,
        uint32 vrfCallbackGasLimit,
        uint16 vrfMinConfirmations
    ) VRFV2WrapperConsumerBase(_link, _vrfV2Wrapper) {
        _vrfMinConfirmations = vrfMinConfirmations;
        _vrfCallbackGasLimit = vrfCallbackGasLimit;
        _vrfResponseTime = 1 hours;
    }

    function getRaffle(uint256 roundId) external view override returns (Raffle memory) {
        return raffles[roundId];
    }

    function getLatestRounds(uint256 numRounds) external view returns (Raffle[] memory) {
        if (lastRound < numRounds) {
            numRounds = lastRound;
        }

        Raffle[] memory latestRaffles = new Raffle[](numRounds);

        for (uint256 i = lastRound; i > lastRound - numRounds; i--) {
            latestRaffles[i] = raffles[i];
        }

        return latestRaffles;
    }

    function setVRFMinConfirmations(uint16 vrfMinConfirmations) external onlyOwner {
        if (vrfMinConfirmations == 0) revert InvalidMinConfirmations();
        _vrfMinConfirmations = vrfMinConfirmations;
    }

    function setVRFCallbackGasLimit(uint32 vrfCallbackGasLimit) external onlyOwner {
        if (vrfCallbackGasLimit == 0) revert InvalidCallbackGasLimit();
        _vrfCallbackGasLimit = vrfCallbackGasLimit;
    }

    function setClaimPeriod(uint256 _claimPeriod) external onlyOwner {
        if (_claimPeriod == 0) revert InvalidClaimPeriod();
        claimPeriod = _claimPeriod;
    }

    function setVRFResponseTime(uint256 vrfResponseTime) external onlyOwner {
        if (vrfResponseTime == 0) revert InvalidVRFResponseTime();
        _vrfResponseTime = vrfResponseTime;
    }

    function claim(uint256 roundId) external override {
        Raffle memory raffle = raffles[roundId];
        if (raffle.receiver == address(0)) revert ReceiverNotPublished();
        if (raffle.receiver != msg.sender) revert InvalidClaimer(msg.sender);
        if (raffle.claimedBlock != 0) revert AlreadyClaimed();
        if (block.timestamp > raffle.claimDeadline) revert ClaimPeriodEnded(raffle.claimDeadline);

        raffle.claimedBlock = block.number;
        raffles[roundId] = raffle;

        IERC721(raffle.nftContract).safeTransferFrom(address(this), msg.sender, raffle.nftId);

        emit Claimed(roundId, msg.sender);
    }

    function initRaffle(
        address nftContract,
        uint256 nftId,
        uint256 minSnapshotTimestamp,
        uint256 maxSnapshotTimestamp
    ) external override onlyOwner returns (uint256 roundId) {
        if (minSnapshotTimestamp < block.timestamp) revert InvalidSnapshotMinTimestamp(minSnapshotTimestamp);
        if (maxSnapshotTimestamp < minSnapshotTimestamp) revert InvalidSnapshotMaxTimestamp(maxSnapshotTimestamp);
        if (IERC721(nftContract).ownerOf(nftId) != address(this)) revert InvalidNftId(nftId);
        if (lastRound != 0) {
            Raffle memory lastRaffle = raffles[lastRound];
            bool isVrfFailure = lastRaffle.vrfRequestId != 0 &&
                lastRaffle.vrfNumber == 0 &&
                block.timestamp > _vrfResponseDeadline;

            if (isVrfFailure) {
                emit RaffleFailed(lastRound);
            } else if (lastRaffle.receiver == address(0)) {
                revert PreviousRaffleNotConcluded();
            }
        }

        lastRound++;

        raffles[lastRound] = Raffle({
            ipfsHash: "",
            receiver: address(0),
            nftId: nftId,
            nftContract: nftContract,
            minSnapshotTimestamp: minSnapshotTimestamp,
            maxSnapshotTimestamp: maxSnapshotTimestamp,
            snapshotBlock: 0,
            claimDeadline: 0,
            claimedBlock: 0,
            vrfRequestId: 0,
            vrfNumber: 0,
            randomNumber: 0
        });

        emit RaffleInitiated(lastRound, nftContract, nftId);

        return lastRound;
    }

    function closeRaffle(uint256 roundId) external override onlyOwner returns (uint256 vrfRequestId) {
        Raffle memory raffle = raffles[roundId];
        if (raffle.minSnapshotTimestamp == 0) revert InvalidRound(roundId);
        if (raffle.vrfRequestId != 0) revert RaffleAlreadyClosed(roundId);

        uint256 fee = VRF_V2_WRAPPER.calculateRequestPrice(_vrfCallbackGasLimit);
        if (LINK.balanceOf(address(this)) < fee) revert InsufficientLINK();

        // Request randomness from Chainlink VRF
        vrfRequestId = requestRandomness(_vrfCallbackGasLimit, _vrfMinConfirmations, 1);

        raffle.vrfRequestId = vrfRequestId;
        _vrfResponseDeadline = block.timestamp + _vrfResponseTime;
        raffles[roundId] = raffle;
        _roundIds[vrfRequestId] = roundId;

        emit RaffleClosed(roundId, vrfRequestId);

        return vrfRequestId;
    }

    function publishResults(
        uint256 roundId,
        bytes32 ipfsHash,
        address receiver,
        uint256 snapshotBlock,
        uint256 randomNumber
    ) external override onlyOwner {
        Raffle memory raffle = raffles[roundId];
        if (raffle.vrfNumber == 0) revert VrfRequestNotCompleted();
        if (raffle.receiver != address(0)) revert ReceiverAlreadyPublished();

        raffle.ipfsHash = ipfsHash;
        raffle.receiver = receiver;
        raffle.snapshotBlock = snapshotBlock;
        raffle.claimDeadline = block.timestamp + claimPeriod;
        raffle.randomNumber = randomNumber;
        raffles[roundId] = raffle;

        emit ResultsPublished(roundId, receiver);
    }

    // solhint-disable-next-line private-vars-leading-underscore
    function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
        uint256 roundId = _roundIds[requestId];
        raffles[roundId].vrfNumber = randomWords[0];
    }

    function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }
}