S Price: $0.505293 (+12.02%)

Contract Diff Checker

Contract Name:
GameFactory

Contract Source Code:

// SPDX-License-Identifier: Business-Source-License-1.1
// https://pob.fun/license
// Use is restricted for business purposes only for the first 2 years.
// On 2028-02-18, this code is licensed under the MIT License.

pragma solidity ^0.8.26;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; 
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { IEntropyConsumer } from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
import { IEntropy } from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";

interface IBurnable {
    function burn(uint256 amount) external;
}

/*//////////////////////////////////////////////////////////////
                          CUSTOM ERRORS
//////////////////////////////////////////////////////////////*/

error NotManager();
error NotAdmin();
error ZeroAdminAddress();
error ZeroManagerAddress();
error ZeroEntropyAddress();
error PrizeMustBeGreaterThanZero();
error NotAuthorizedToCreateGame();
error NotAuthorizedGame();
error GameAlreadyEnded();
error PastEndTime();
error BeforeEndTime();
error ZeroAmount();
error RandomNotReady();
error PrizeAlreadyClaimed();
error AlreadyFulfilled();
error NoWinnerFound();
error FeeTooLow();
error NotFactory();

/**
 * @title GameFactory
 * @notice Deploys and manages Game contracts.
 */
contract GameFactory is ReentrancyGuard {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////
                                STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice The administrator address with elevated privileges.
    address public admin;

    /// @notice Mapping of addresses allowed to create games (if restricted).
    mapping(address => bool) public managers;                

    /// @notice The Entropy contract address used for randomness.
    address public entropyAddress; 

    /// @notice Burn address for tokens.
    address constant ZERO_ADDRESS = address(0);
    address constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;

    /// @notice Mapping to track authorized game addresses.
    mapping(address => bool) private authorizedGames;

    /// @notice Maps a game address to its index in `allGames`.
    mapping(address => uint256) public gameIndex;

    /// @notice Struct to hold game details for external viewing.
    struct GameInfo {
        address gameAddress;
        uint256 numParticipants;
        uint256 totalBurned;
        address token;
        uint256 deadline;
        uint256 prizeAmount;
        uint256 finalPrize;
        uint256 tokenDecimals;
        string tokenSymbol;
        string tag;
    }

    /// @notice Array holding information about all created games.
    GameInfo[] public allGames;

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a new game is created.
     * @param gameAddress The address of the newly created game.
     * @param creator The address that initiated the creation.
     * @param tokenAddress The address of the ERC20 token used for the game prize.
     */
    event GameCreated(
        address indexed gameAddress,
        address indexed creator,
        address tokenAddress
    );

    /**
     * @notice Emitted when game information is updated.
     * @param gameAddress The address of the game being updated.
     * @param numParticipants The current number of participants in the game.
     * @param totalBurned The total amount of tokens burned for the game.
     */
    event GameInfoUpdated(
        address indexed gameAddress,
        uint256 numParticipants,
        uint256 totalBurned
    );

    /**
     * @notice Emitted when tokens are burned via `depositIntoGame`.
     * @param sender The address that sent tokens to be burned.
     * @param amount The actual amount of tokens burned (accounting for taxes).
     */
    event TokensBurned(address indexed sender, uint256 amount);

    /**
    * @dev Emitted when tokens are burned using one of the burn methods.
    * @param token The address of the ERC20 token that was attempted to be burned.
    * @param attemptedAmount The total amount of tokens the function attempted to burn.
    * @param actualBurned The actual amount of tokens removed from the contract’s balance after the burn operation.
    * @param method A string identifier indicating the burn method used:
    *   - "ZeroAddressTransfer" if tokens were sent to the zero address,
    *   - "BurnFunction" if the token’s burn() function was successfully called,
    *   - "DeadAddressTransfer" if tokens were sent to the dead address.
    */
    event TokenBurnMethodUsed(
        address indexed token,
        uint256 attemptedAmount,
        uint256 actualBurned,
        string method
    );

    event BurnMethodSkipped(address indexed token, string reason);

    /*//////////////////////////////////////////////////////////////
                                MODIFIERS
    //////////////////////////////////////////////////////////////*/

    /**
     * @dev Restricts function access to only the admin.
     */
    modifier onlyAdmin() {
        if (msg.sender != admin) revert NotAdmin();
        _;
    }

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

    /**
     * @notice Sets the admin and initializes the default Entropy contract.
     * @param _admin The address of the admin.
     */
    constructor(address _admin) {
        if (_admin == address(0)) revert ZeroAdminAddress();
        admin = _admin;

        // Default Entropy address (can be updated via updateEntropyAddress).
        entropyAddress = 0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320; // 
    }

    /*//////////////////////////////////////////////////////////////
                     INTERNAL LOGIC
    //////////////////////////////////////////////////////////////*/

    function _supportsBurnMethod(address token) internal view returns (bool) {
        (bool success, ) = token.staticcall(abi.encodeWithSelector(IBurnable.burn.selector, 1));
        return success;
    }

    function _attemptTokenBurn(IERC20 token, uint256 amount) internal returns (uint256) {
        uint256 balanceBefore = token.balanceOf(address(this));
        uint256 burnedAmount;

        // 1. Check if the burn() method exists before calling it
        if (_supportsBurnMethod(address(token))) {
            try IBurnable(address(token)).burn(amount) {
                burnedAmount = balanceBefore - token.balanceOf(address(this));
                emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "BurnFunction");
                return burnedAmount;
            } catch {
                emit BurnMethodSkipped(address(token), "BurnFunction reverted");
            }
        } else {
            emit BurnMethodSkipped(address(token), "BurnFunction not supported");
        }

        // 2. Try sending to the zero address using a low-level call
        {
            (bool success, bytes memory data) = address(token).call(
                abi.encodeWithSelector(token.transfer.selector, ZERO_ADDRESS, amount)
            );
            bool transferred = success && (data.length == 0 || abi.decode(data, (bool)));
            if (transferred) {
                burnedAmount = balanceBefore - token.balanceOf(address(this));
                emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "ZeroAddressTransfer");
                return burnedAmount;
            }
        }

        // 3. If both fail, use SafeERC20 to transfer to DEAD_ADDRESS
        token.safeTransfer(DEAD_ADDRESS, amount);
        burnedAmount = balanceBefore - token.balanceOf(address(this));
        emit TokenBurnMethodUsed(address(token), amount, burnedAmount, "DeadAddressTransfer");
        return burnedAmount;
    }

    /*//////////////////////////////////////////////////////////////
                          EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Deposits (and burns) tokens into an existing game.
     * @dev Pulls tokens from the caller to this factory, then attempts to burn them.
     * @param gameAddress The address of the game to deposit into.
     * @param amount The amount of tokens the user wishes to deposit (burn).
     */

    function depositIntoGame(address gameAddress, uint256 amount) 
        external 
        nonReentrant 
    {
        if (!authorizedGames[gameAddress]) revert NotAuthorizedGame();
        uint256 idx = gameIndex[gameAddress];
        GameInfo storage info = allGames[idx];

        if (block.timestamp >= info.deadline) revert PastEndTime();

        Game game = Game(gameAddress);
        if (game.gameEnded()) revert GameAlreadyEnded();
        if (amount == 0) revert ZeroAmount();

        IERC20 token = IERC20(info.token);

        // 1. Measure factory's balance before pull.
        uint256 factoryBalanceBefore = token.balanceOf(address(this));

        // 2. Pull tokens from user -> factory (SafeERC20 reverts on fail).
        token.safeTransferFrom(msg.sender, address(this), amount);

        // 3. Measure factory balance after pull.
        uint256 factoryBalanceAfterUserDeposit = token.balanceOf(address(this));

        // The net tokens *received* from user (accounting for any first tax).
        uint256 netDeposit = factoryBalanceAfterUserDeposit - factoryBalanceBefore;

        // 4. Attempt to burn `netDeposit` from the factory using multiple methods.
        uint256 actualBurned = _attemptTokenBurn(token, netDeposit);

        emit TokensBurned(msg.sender, actualBurned);

        // 5. Notify the Game contract of the actual amount burned.
        game.recordDeposit(msg.sender, actualBurned);
    }


    /**
     * @notice Creates a new game and transfers the prize directly into the new contract.
     * @dev The caller must have approved this factory to spend `_prizeAmount`.
     * @param _token The ERC20 token address to be used for prizes.
     * @param _endTimestamp The game end timestamp.
     * @param _prizeAmount The amount of tokens to be transferred as the initial prize.
     * @param _tag An arbitrary tag (metadata) for the game.
     * @return gameAddress The address of the newly created game contract.
     */
    function createGame(
        address _token,
        uint64 _endTimestamp,
        uint256 _prizeAmount,
        string calldata _tag
    ) 
        external 
        nonReentrant 
        returns (address gameAddress) 
    {
        // If only managers can create games, ensure caller is admin or manager.
        if (msg.sender != admin && !managers[msg.sender]) revert NotAuthorizedToCreateGame();

        // Ensure the end time is in the future.
        if (_endTimestamp <= block.timestamp) revert PastEndTime();

        // ensure prize is non 0
        if (_prizeAmount == 0) revert PrizeMustBeGreaterThanZero();

        // 1. Deploy a fresh Game contract.
        Game newGame = new Game(
            _token,
            _endTimestamp,
            entropyAddress,
            msg.sender,
            _tag
        );

        IERC20 token = IERC20(_token);

        // 2. Measure the new Game contract's balance before the transfer.
        uint256 gameBalanceBefore = token.balanceOf(address(newGame));

        // 3. Transfer `_prizeAmount` directly from the caller to the new Game contract (SafeERC20).
        token.safeTransferFrom(msg.sender, address(newGame), _prizeAmount);

        // 4. Measure how many tokens actually arrived (accounting for any token tax/deflation).
        uint256 gameBalanceAfter = token.balanceOf(address(newGame));
        uint256 netPrize = gameBalanceAfter - gameBalanceBefore;

        // Mark this new game as authorized.
        authorizedGames[address(newGame)] = true;

        // 5. Store the new game info.
        allGames.push(
            GameInfo({
                gameAddress: address(newGame),
                numParticipants: 0,
                totalBurned: 0,
                token: _token,
                deadline: _endTimestamp,
                prizeAmount: netPrize,
                finalPrize: 0,
                tokenSymbol: IERC20Metadata(_token).symbol(),
                tokenDecimals: IERC20Metadata(_token).decimals(),
                tag: _tag
            })
        );

        // 6. Track the index for future reference.
        uint256 idx = allGames.length - 1;
        gameIndex[address(newGame)] = idx;

        // 7. Emit an event.
        emit GameCreated(address(newGame), msg.sender, _token);

        // 8. Return the new game address.
        return address(newGame);
    }

    /**
     * @notice Updates the address of the Entropy contract.
     * @param _newEntropyAddress The new Entropy contract address.
     */
    function updateEntropyAddress(address _newEntropyAddress) external onlyAdmin {
        if (_newEntropyAddress == address(0)) revert ZeroEntropyAddress();
        entropyAddress = _newEntropyAddress;
    }

    /**
     * @notice Updates the admin address.
     * @param _newAdmin The new admin address.
     */
    function updateAdmin(address _newAdmin) external onlyAdmin {
        if (_newAdmin == address(0)) revert ZeroAdminAddress();
        admin = _newAdmin;
    }

    /**
     * @notice Adds a new manager with game-creation privileges.
     * @param managerAddress The address to be granted manager privileges.
     */
    function addManager(address managerAddress) external onlyAdmin {
        if (managerAddress == address(0)) revert ZeroManagerAddress();
        managers[managerAddress] = true;
    }

    /**
     * @notice Removes manager privileges from an address.
     * @param managerAddress The address to remove from manager privileges.
     */
    function removeManager(address managerAddress) external onlyAdmin {
        if (managerAddress == address(0)) revert ZeroManagerAddress();
        managers[managerAddress] = false;
    }

    /**
     * @notice Returns the full list of all created games.
     * @return An array of `GameInfo` structs.
     */
    function getAllGames() external view returns (GameInfo[] memory) {
        return allGames;
    }

    /**
     * @notice Updates the final prize for the caller's game.
     * @dev Only callable by an authorized game contract.
     * @param _finalPrize The final prize amount (for record-keeping).
     */
    function updateFinalPrize(uint256 _finalPrize) external {
        if (!authorizedGames[msg.sender]) revert NotAuthorizedGame();
        uint256 idx = gameIndex[msg.sender];
        GameInfo storage info = allGames[idx];
        info.finalPrize = _finalPrize;
    }

    /**
     * @notice Updates game info such as participant count or total burned amount.
     * @dev Only callable by an authorized game contract.
     * @param numParticipants The updated number of participants.
     * @param totalBurned The updated total burned amount.
     */
    function updateGameInfo(
        uint256 numParticipants,
        uint256 totalBurned
    ) external 
    {
        if (!authorizedGames[msg.sender]) revert NotAuthorizedGame();
        uint256 idx = gameIndex[msg.sender];
        GameInfo storage info = allGames[idx];

        info.numParticipants = numParticipants;
        info.totalBurned = totalBurned;

        emit GameInfoUpdated(msg.sender, numParticipants, totalBurned);
    }
}

/**
 * @title Game
 * @notice A single-round lottery-style game using token burns for entries.
 */
contract Game is IEntropyConsumer, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /*//////////////////////////////////////////////////////////////
                                STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice The ERC20 token used in this game.
    IERC20 public immutable token;

    /// @notice The Entropy interface for randomness.
    IEntropy public immutable entropy;

    /// @notice The factory address that deployed this game.
    address public immutable factory;

    /// @notice The creator address of this game.
    address public immutable gameCreator;

    /// @notice The timestamp after which the game can be ended.
    uint64 public immutable endTimestamp; 

    /// @notice The number of decimals for the token (cached from the token contract).
    uint8 public immutable tokenDecimals;
    
    /// @notice The symbol for the token (cached from the token contract).
    string public tokenSymbol;

    /// @notice Indicates if the game has ended.
    bool public gameEnded; 

    /// @notice The total amount of tokens burned by all participants.
    uint256 public totalBurned;

    /// @notice The total number of unique participants in the game.
    uint256 public numParticipants;

    /// @notice True if the random number for this game was already fulfilled.
    bool public randomFulfilled;

    /// @notice The winner address selected by randomness.
    address public winner;

    /// @notice True if the winner (or creator if no participants) has claimed the prize.
    bool public winnerClaimed;

    /// @notice Arbitrary metadata tag for the game.
    string public tag;

    /// @notice The final prize snapshot, saved at claim time for historical reference.
    uint256 public finalPrize;

    /// @notice Struct for tracking user entry ranges in the burn-based index.
    struct Range {
        address user;
        uint256 end;
    }
    Range[] public ranges;

    /// @notice Readable structure for front-end usage (returned by `getGameStatus`).
    struct GameStatusView {
        bool isEnded;
        bool fulfilled;
        uint256 timeLeft;
        address[] participants;
        uint256[] shares;
        address erc20Token;
        string symbol;
        uint8 decimals;
        uint256 burns;
        uint256 totalPrize;
        address prizeWinner;
        bool prizeWasClaimed;
        uint256 finalPrize;
        string tag;
    }

    /// @notice Mapping from user address to total tokens burned by that user in this game.
    mapping(address => uint256) public userBurns;

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when a user burns tokens for the game.
     * @param user The address of the participant.
     * @param amount The amount of tokens burned.
     */
    event Burned(address indexed user, uint256 amount);

    /**
     * @notice Emitted when the game ends and a randomness request is triggered.
     * @param requestId The sequence number for the Entropy request.
     */
    event GameEnded(uint256 requestId);

    /**
     * @notice Emitted when a winner is selected by randomness.
     * @param winner The address of the selected winner.
     */
    event WinnerSelected(address indexed winner);

    /**
     * @notice Emitted after the randomness callback is processed.
     * @param sequenceNumber The sequence number of the Entropy request.
     * @param provider The address of the Entropy provider.
     */
    event EntropyCalledBack(uint64 sequenceNumber, address indexed provider);

    /**
     * @notice Emitted when the prize is claimed by the winner (or returned to creator if no participants).
     * @param winner The address claiming the prize.
     * @param amount The amount of tokens claimed.
     */
    event PrizeClaimed(address indexed winner, uint256 amount);

    /**
     * @notice Emitted if there are no participants, and the prize is returned to the game creator.
     * @param winner The address returning the prize (the game creator).
     * @param amount The amount of tokens returned.
     */
    event PrizeReturned(address indexed winner, uint256 amount);

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

    /**
     * @notice Sets up a new Game contract.
     * @param _token The ERC20 token used by this game.
     * @param _endTimestamp The timestamp after which the game can be ended.
     * @param _entropyAddress The address of the Entropy contract.
     * @param _gameCreator The address that is creating this game.
     * @param _tag Arbitrary metadata for the game.
     */
    constructor(
        address _token,
        uint64 _endTimestamp,
        address _entropyAddress,
        address _gameCreator,
        string memory _tag
    ) {
        if (_endTimestamp <= block.timestamp) revert PastEndTime();

        token = IERC20(_token);
        entropy = IEntropy(_entropyAddress);
        factory = msg.sender;
        gameCreator = _gameCreator;
        endTimestamp = _endTimestamp;
        tokenSymbol = IERC20Metadata(_token).symbol();
        tokenDecimals = IERC20Metadata(_token).decimals();
        tag = _tag;
    }

    /*//////////////////////////////////////////////////////////////
                          EXTERNAL FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Records a user deposit (burn) into this game.
     * @dev Only callable by the factory.
     * @param user The address depositing tokens.
     * @param burnAmount The amount of tokens burned.
     */
    function recordDeposit(address user, uint256 burnAmount) external nonReentrant {
        if (msg.sender != factory) revert NotFactory();

        // If this is the user's first deposit, increment participant count.
        if (userBurns[user] == 0) {
            numParticipants++;
        }

        userBurns[user] += burnAmount;
        totalBurned += burnAmount;

        // Update the ranges array for random selection.
        uint256 endSlot = totalBurned - 1;
        if (ranges.length != 0 && ranges[ranges.length - 1].user == user) {
            ranges[ranges.length - 1].end += burnAmount;
        } else {
            ranges.push(Range({ user: user, end: endSlot }));
        }

        emit Burned(user, burnAmount);

        // Notify the factory to keep track of participants and burned amount.
        GameFactory(factory).updateGameInfo(numParticipants, totalBurned);
    }

    /**
     * @notice Ends the game after `endTimestamp` and requests random from Pyth Entropy.
     * @dev After calling, it will initiate the randomness callback workflow.
     * @param userRandomNumber An optional user-supplied seed.
     */
    function endGame(bytes32 userRandomNumber) external payable nonReentrant {
        if (gameEnded) revert GameAlreadyEnded();
        if (block.timestamp < endTimestamp) revert BeforeEndTime();

        gameEnded = true;

        address entropyProvider = entropy.getDefaultProvider();
        uint256 fee = entropy.getFee(entropyProvider);
        
        if(msg.value < fee) revert FeeTooLow();
        if (msg.value > fee) {
            payable(msg.sender).transfer(msg.value - fee);
        }

        // Request randomness with callback.
        uint64 sequenceNumber = entropy.requestWithCallback{ value: fee }(
            entropyProvider,
            userRandomNumber
        );
        emit GameEnded(sequenceNumber);
    }

    /**
     * @notice Gets the fee required for requesting randomness from the default provider.
     * @return fee The required fee in wei.
     */
    function getFee() external view returns (uint256 fee) {
        address entropyProvider = entropy.getDefaultProvider();
        fee = entropy.getFee(entropyProvider);
    }

    /**
     * @notice Allows the winner (or game creator if no participants) to claim the prize.
     * @dev Must be called after the random number is fulfilled.
     */
    function claimPrize() external nonReentrant {
        uint256 prizeAmount = token.balanceOf(address(this));

        if (!gameEnded) revert GameAlreadyEnded();
        if (!randomFulfilled) revert RandomNotReady();
        if (winnerClaimed) revert PrizeAlreadyClaimed();

        finalPrize = prizeAmount;

        // Update the final prize in the factory for record-keeping.
        GameFactory(factory).updateFinalPrize(prizeAmount);

        winnerClaimed = true;
        uint256 payout = prizeAmount;
        prizeAmount = 0;

        // If there were no burns, no winner is selected => prize goes back to gameCreator.
        if (totalBurned == 0) {
            token.safeTransfer(gameCreator, payout);
            emit PrizeReturned(gameCreator, payout);
        } else {
            token.safeTransfer(winner, payout);
            emit PrizeClaimed(winner, payout);
        }
    }

    /*//////////////////////////////////////////////////////////////
                     ENTROPY CONSUMER (RNG) LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Internal callback executed once the randomness is fulfilled.
     * @param sequenceNumber The Entropy request sequence number.
     * @param provider The address of the Entropy provider.
     * @param randomNumber The random number returned from Entropy.
     */
    function entropyCallback(
        uint64 sequenceNumber,
        address provider,
        bytes32 randomNumber
    ) internal override {
        if (!gameEnded) revert GameAlreadyEnded();
        if (randomFulfilled) revert AlreadyFulfilled();

        randomFulfilled = true;

        uint256 rangeSize = totalBurned;

        // If there were no participants, winner stays address(0).
        if (rangeSize == 0) {
            winner = address(0);
        } else {
            // Randomly select a slot in [0, rangeSize - 1].
            uint256 randomNormalized = uint256(randomNumber) % rangeSize;
            winner = findWinnerByBinarySearch(randomNormalized);
        }

        emit WinnerSelected(winner);
        emit EntropyCalledBack(sequenceNumber, provider);
    }


    /**
     * @notice Returns the address of the Entropy contract to the `IEntropyConsumer` interface.
     */
    function getEntropy() internal view override returns (address) {
        return address(entropy);
    }

    /*//////////////////////////////////////////////////////////////
                       INTERNAL / VIEW FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Finds the winner by performing a binary search over the `ranges` array.
     * @param winningSlot The random slot index (0-based).
     * @return The address of the winning participant.
     */
    function findWinnerByBinarySearch(uint256 winningSlot) internal view returns (address) {
        uint256 left = 0;
        uint256 right = ranges.length - 1;

        while (left <= right) {
            uint256 mid = (left + right) >> 1; // mid = (left + right) / 2
            Range memory midRange = ranges[mid];
            if (winningSlot <= midRange.end) {
                return midRange.user;
            } else {
                left = mid + 1;
            }
        }
        revert NoWinnerFound();
    }

    /*//////////////////////////////////////////////////////////////
                          PUBLIC VIEW
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the number of ranges used in random selection.
     * @return The length of the `ranges` array.
     */
    function getRangeCount() external view returns (uint256) {
        return ranges.length;
    }

    /**
     * @notice Returns the user and end index for a specific range.
     * @param index The index of the range in the `ranges` array.
     * @return user The address of the participant.
     * @return endSlot The last burn slot index belonging to this user.
     */
    function getRange(uint256 index) external view returns (address user, uint256 endSlot) {
        Range memory r = ranges[index];
        return (r.user, r.end);
    }

    /**
     * @notice Returns a view struct containing most game data in a single call.
     * @return status A `GameStatusView` struct with relevant game information.
     */
    function getGameStatus() external view returns (GameStatusView memory status) {
        status.isEnded = gameEnded;
        status.fulfilled = randomFulfilled;
        status.timeLeft = block.timestamp < endTimestamp ? endTimestamp - uint64(block.timestamp) : 0;
        status.erc20Token = address(token);
        status.symbol = tokenSymbol;
        status.decimals = tokenDecimals;
        status.burns = totalBurned;
        status.totalPrize = token.balanceOf(address(this));
        status.prizeWinner = winner;
        status.prizeWasClaimed = winnerClaimed;
        status.finalPrize = finalPrize;
        status.tag = tag;

        uint256 rangeCount = ranges.length;
        if (rangeCount == 0) {
            // No participants => empty arrays.
            status.participants = new address[](0);
            status.shares = new uint256[](0);
            return status;
        }

        // Deduplicate participants and sum their shares in memory.
        address[] memory tempParticipants = new address[](rangeCount);
        uint256[] memory tempShares = new uint256[](rangeCount);
        uint256 uniqueCount;

        for (uint256 i = 0; i < rangeCount; i++) {
            Range memory currentRange = ranges[i];
            uint256 rangeStart = (i == 0) ? 0 : (ranges[i - 1].end + 1);
            uint256 rangeEnd = currentRange.end;
            uint256 rangeSlotCount = rangeEnd - rangeStart + 1;

            // Check if user is already in temp array.
            bool found;
            for (uint256 j = 0; j < uniqueCount; j++) {
                if (tempParticipants[j] == currentRange.user) {
                    tempShares[j] += rangeSlotCount;
                    found = true;
                    break;
                }
            }

            // If not found, add a new entry.
            if (!found) {
                tempParticipants[uniqueCount] = currentRange.user;
                tempShares[uniqueCount] = rangeSlotCount;
                uniqueCount++;
            }
        }

        // Create final arrays with exact sizes.
        address[] memory finalParticipants = new address[](uniqueCount);
        uint256[] memory finalShares = new uint256[](uniqueCount);

        for (uint256 i = 0; i < uniqueCount; i++) {
            finalParticipants[i] = tempParticipants[i];
            finalShares[i] = tempShares[i];
        }

        // Populate the return struct.
        status.participants = finalParticipants;
        status.shares = finalShares;

        return status;
    }
}

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

import "./EntropyEvents.sol";

interface IEntropy is EntropyEvents {
    // Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
    // and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates
    // the feeInWei).
    //
    // chainLength is the number of values in the hash chain *including* the commitment, that is, chainLength >= 1.
    function register(
        uint128 feeInWei,
        bytes32 commitment,
        bytes calldata commitmentMetadata,
        uint64 chainLength,
        bytes calldata uri
    ) external;

    // Withdraw a portion of the accumulated fees for the provider msg.sender.
    // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
    // balance of fees in the contract).
    function withdraw(uint128 amount) external;

    // Withdraw a portion of the accumulated fees for provider. The msg.sender must be the fee manager for this provider.
    // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient
    // balance of fees in the contract).
    function withdrawAsFeeManager(address provider, uint128 amount) external;

    // As a user, request a random number from `provider`. Prior to calling this method, the user should
    // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
    // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
    //
    // This method returns a sequence number. The user should pass this sequence number to
    // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
    // number. The user should then call fulfillRequest to construct the final random number.
    //
    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
    // Note that excess value is *not* refunded to the caller.
    function request(
        address provider,
        bytes32 userCommitment,
        bool useBlockHash
    ) external payable returns (uint64 assignedSequenceNumber);

    // Request a random number. The method expects the provider address and a secret random number
    // in the arguments. It returns a sequence number.
    //
    // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
    // The `entropyCallback` method on that interface will receive a callback with the generated random number.
    //
    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
    // Note that excess value is *not* refunded to the caller.
    function requestWithCallback(
        address provider,
        bytes32 userRandomNumber
    ) external payable returns (uint64 assignedSequenceNumber);

    // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
    // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
    // the corresponding random number.
    //
    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
    // If you need to use the returned random number more than once, you are responsible for storing it.
    function reveal(
        address provider,
        uint64 sequenceNumber,
        bytes32 userRevelation,
        bytes32 providerRevelation
    ) external returns (bytes32 randomNumber);

    // Fulfill a request for a random number. This method validates the provided userRandomness
    // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated
    // and the requestor address is a contract address, this function calls the requester's entropyCallback method with the
    // sequence number, provider address and the random number as arguments. Else if the requestor is an EOA, it won't call it.
    //
    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
    // If you need to use the returned random number more than once, you are responsible for storing it.
    //
    // Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
    function revealWithCallback(
        address provider,
        uint64 sequenceNumber,
        bytes32 userRandomNumber,
        bytes32 providerRevelation
    ) external;

    function getProviderInfo(
        address provider
    ) external view returns (EntropyStructs.ProviderInfo memory info);

    function getDefaultProvider() external view returns (address provider);

    function getRequest(
        address provider,
        uint64 sequenceNumber
    ) external view returns (EntropyStructs.Request memory req);

    function getFee(address provider) external view returns (uint128 feeAmount);

    function getAccruedPythFees()
        external
        view
        returns (uint128 accruedPythFeesInWei);

    function setProviderFee(uint128 newFeeInWei) external;

    function setProviderFeeAsFeeManager(
        address provider,
        uint128 newFeeInWei
    ) external;

    function setProviderUri(bytes calldata newUri) external;

    // Set manager as the fee manager for the provider msg.sender.
    // After calling this function, manager will be able to set the provider's fees and withdraw them.
    // Only one address can be the fee manager for a provider at a time -- calling this function again with a new value
    // will override the previous value. Call this function with the all-zero address to disable the fee manager role.
    function setFeeManager(address manager) external;

    function constructUserCommitment(
        bytes32 userRandomness
    ) external pure returns (bytes32 userCommitment);

    function combineRandomValues(
        bytes32 userRandomness,
        bytes32 providerRandomness,
        bytes32 blockHash
    ) external pure returns (bytes32 combinedRandomness);
}

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.0;

abstract contract IEntropyConsumer {
    // This method is called by Entropy to provide the random number to the consumer.
    // It asserts that the msg.sender is the Entropy contract. It is not meant to be
    // override by the consumer.
    function _entropyCallback(
        uint64 sequence,
        address provider,
        bytes32 randomNumber
    ) external {
        address entropy = getEntropy();
        require(entropy != address(0), "Entropy address not set");
        require(msg.sender == entropy, "Only Entropy can call this function");

        entropyCallback(sequence, provider, randomNumber);
    }

    // getEntropy returns Entropy contract address. The method is being used to check that the
    // callback is indeed from Entropy contract. The consumer is expected to implement this method.
    // Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
    function getEntropy() internal view virtual returns (address);

    // This method is expected to be implemented by the consumer to handle the random number.
    // It will be called by _entropyCallback after _entropyCallback ensures that the call is
    // indeed from Entropy contract.
    function entropyCallback(
        uint64 sequence,
        address provider,
        bytes32 randomNumber
    ) internal virtual;
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    function safeTransfer(
        IERC20 token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20 token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (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() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

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

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

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}

// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import "./EntropyStructs.sol";

interface EntropyEvents {
    event Registered(EntropyStructs.ProviderInfo provider);

    event Requested(EntropyStructs.Request request);
    event RequestedWithCallback(
        address indexed provider,
        address indexed requestor,
        uint64 indexed sequenceNumber,
        bytes32 userRandomNumber,
        EntropyStructs.Request request
    );

    event Revealed(
        EntropyStructs.Request request,
        bytes32 userRevelation,
        bytes32 providerRevelation,
        bytes32 blockHash,
        bytes32 randomNumber
    );
    event RevealedWithCallback(
        EntropyStructs.Request request,
        bytes32 userRandomNumber,
        bytes32 providerRevelation,
        bytes32 randomNumber
    );

    event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);

    event ProviderUriUpdated(address provider, bytes oldUri, bytes newUri);

    event ProviderFeeManagerUpdated(
        address provider,
        address oldFeeManager,
        address newFeeManager
    );

    event Withdrawal(
        address provider,
        address recipient,
        uint128 withdrawnAmount
    );
}

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

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

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

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

contract EntropyStructs {
    struct ProviderInfo {
        uint128 feeInWei;
        uint128 accruedFeesInWei;
        // The commitment that the provider posted to the blockchain, and the sequence number
        // where they committed to this. This value is not advanced after the provider commits,
        // and instead is stored to help providers track where they are in the hash chain.
        bytes32 originalCommitment;
        uint64 originalCommitmentSequenceNumber;
        // Metadata for the current commitment. Providers may optionally use this field to help
        // manage rotations (i.e., to pick the sequence number from the correct hash chain).
        bytes commitmentMetadata;
        // Optional URI where clients can retrieve revelations for the provider.
        // Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
        // TODO: specify the API that must be implemented at this URI
        bytes uri;
        // The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
        // The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
        // If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.
        uint64 endSequenceNumber;
        // The sequence number that will be assigned to the next inbound user request.
        uint64 sequenceNumber;
        // The current commitment represents an index/value in the provider's hash chain.
        // These values are used to verify requests for future sequence numbers. Note that
        // currentCommitmentSequenceNumber < sequenceNumber.
        //
        // The currentCommitment advances forward through the provider's hash chain as values
        // are revealed on-chain.
        bytes32 currentCommitment;
        uint64 currentCommitmentSequenceNumber;
        // An address that is authorized to set / withdraw fees on behalf of this provider.
        address feeManager;
    }

    struct Request {
        // Storage slot 1 //
        address provider;
        uint64 sequenceNumber;
        // The number of hashes required to verify the provider revelation.
        uint32 numHashes;
        // Storage slot 2 //
        // The commitment is keccak256(userCommitment, providerCommitment). Storing the hash instead of both saves 20k gas by
        // eliminating 1 store.
        bytes32 commitment;
        // Storage slot 3 //
        // The number of the block where this request was created.
        // Note that we're using a uint64 such that we have an additional space for an address and other fields in
        // this storage slot. Although block.number returns a uint256, 64 bits should be plenty to index all of the
        // blocks ever generated.
        uint64 blockNumber;
        // The address that requested this random number.
        address requester;
        // If true, incorporate the blockhash of blockNumber into the generated random value.
        bool useBlockhash;
        // If true, the requester will be called back with the generated random value.
        bool isRequestWithCallback;
        // There are 2 remaining bytes of free space in this slot.
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):