APE Price: $0.51 (-0.66%)

Contract Diff Checker

Contract Name:
NFTStats

Contract Source Code:

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "./interfaces/INFTStats.sol";
import "./interfaces/ICollectionRegistry.sol";
import "./libraries/StatsCalculator.sol";
import "./libraries/StatValidation.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";

/// @title NFTStats
/// @notice Manages individual NFT stats, experience, and leveling
contract NFTStats is INFTStats, Ownable, IEntropyConsumer {
    // ------------------------- Immutable state variables -------------------------
    ICollectionRegistry public immutable collectionRegistry;
    IEntropy public constant entropy =
        IEntropy(0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320);
    address public constant provider =
        0x52DeaA1c84233F7bb8C8A45baeDE41091c616506;
    bytes5 public constant STAT_VARIATION_BY_RARITY =
        bytes5(
            abi.encodePacked(
                uint8(10),
                uint8(20),
                uint8(30),
                uint8(50),
                uint8(100)
            )
        );

    // ------------------------- Constants for XP and leveling -------------------------
    uint96 private constant BASE_XP_PER_LEVEL = 100;
    uint96 private constant XP_MULTIPLIER = 150; // 150% increase per level
    uint96 private constant BASE_STAT_INCREASE_PERCENT = 5; // 5% increase per level
    uint32 private constant STARTING_LEVEL = 1;

    // ------------------------- State variables -------------------------
    mapping(address => mapping(uint256 => NFTStatsData)) private nftStats;
    mapping(uint64 => PendingRoll) private pendingRolls;
    mapping(address => bool) private authorizedContracts;

    // ------------------------- Structs -------------------------
    struct PendingRoll {
        address collection;
        uint256 tokenId;
    }

    // ------------------------- Modifiers -------------------------
    modifier onlyAuthorized() {
        require(
            msg.sender == owner() || authorizedContracts[msg.sender],
            "Not authorized"
        );
        _;
    }

    // ------------------------- Constructor -------------------------
    constructor(address _collectionRegistry) Ownable(msg.sender) {
        require(_collectionRegistry != address(0), "Invalid registry address");
        collectionRegistry = ICollectionRegistry(_collectionRegistry);
    }

    // ------------------------- External functions - Admin -------------------------
    /// @notice Set authorization for a contract to modify stats
    /// @param contract_ Address of the contract
    /// @param authorized Whether the contract should be authorized
    function setContractAuthorization(
        address contract_,
        bool authorized
    ) external onlyOwner {
        require(contract_ != address(0), "Invalid contract address");
        authorizedContracts[contract_] = authorized;
    }

    // ------------------------- External functions - Core game mechanics -------------------------
    /// @notice Initialize stats for an NFT based on its collection's base stats and a random number
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    function rollStats(address collection, uint256 tokenId) external {
        require(
            !nftStats[collection][tokenId].initialized,
            "Already initialized"
        );
        require(
            collectionRegistry.isWhitelisted(collection),
            "Collection not whitelisted"
        );

        // Verify NFT ownership
        try IERC721(collection).ownerOf(tokenId) returns (address) {
            // NFT exists, proceed with initialization
        } catch {
            revert("NFT does not exist");
        }

        // Store pending roll using sequence number
        bytes32 pseudoRandomNumber = keccak256(
            abi.encode(block.timestamp, block.number, collection, tokenId)
        );

        // Get the required fee
        uint128 requestFee = entropy.getFee(provider);

        // Request entropy and trigger callback
        uint64 sequenceNumber = entropy.requestWithCallback{value: requestFee}(
            provider,
            pseudoRandomNumber
        );

        // Store pending roll
        pendingRolls[sequenceNumber] = PendingRoll({
            collection: collection,
            tokenId: tokenId
        });
    }

    /// @notice Award XP for dungeon progress
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @param xpAmount Amount of XP to award
    /// @param roomsCleared Number of rooms cleared in this run
    function awardXP(
        address collection,
        uint256 tokenId,
        uint256 xpAmount,
        uint256 roomsCleared
    ) external onlyAuthorized {
        require(
            nftStats[collection][tokenId].initialized,
            "Stats not initialized"
        );

        NFTStatsData storage stats = nftStats[collection][tokenId];

        // Single storage update for XP
        uint256 newXP = stats.currentXP + xpAmount;
        stats.currentXP = uint96(newXP);
        stats.roomsCleared = uint32(stats.roomsCleared + roomsCleared);

        emit XPGained(collection, tokenId, xpAmount, newXP);
    }

    /// @notice Process pending level ups for a character
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    function levelUp(address collection, uint256 tokenId) external {
        require(
            nftStats[collection][tokenId].initialized,
            "Stats not initialized"
        );

        NFTStatsData storage stats = nftStats[collection][tokenId];

        uint96 currentXP = stats.currentXP;
        uint96 xpToNextLevel = stats.xpToNextLevel;
        uint32 currentLevel = stats.level;

        require(currentXP >= xpToNextLevel, "Insufficient XP");

        // Calculate all level ups at once
        uint256 statMultiplier = 0;
        while (currentXP >= xpToNextLevel) {
            currentXP -= xpToNextLevel;
            currentLevel++;
            statMultiplier++;
            xpToNextLevel = getXPForNextLevel(currentLevel);
        }

        // Get base stats and calculate increases
        (
            uint64 vitalityIncrease,
            uint64 strengthIncrease,
            uint64 agilityIncrease,
            uint64 defenseIncrease
        ) = getLevelUpStats(collection, tokenId);

        // Apply multiplier for multiple level ups
        vitalityIncrease = uint64(uint256(vitalityIncrease) * statMultiplier);
        strengthIncrease = uint64(uint256(strengthIncrease) * statMultiplier);
        agilityIncrease = uint64(uint256(agilityIncrease) * statMultiplier);
        defenseIncrease = uint64(uint256(defenseIncrease) * statMultiplier);

        // Calculate new stats
        uint64[4] memory newStats = [
            stats.vitality + vitalityIncrease,
            stats.strength + strengthIncrease,
            stats.agility + agilityIncrease,
            stats.defense + defenseIncrease
        ];

        // Validate new stats
        StatValidation.validateClassStats(
            collectionRegistry.getCollectionStats(collection).classType,
            newStats
        );

        // Apply all updates in one SSTORE each
        stats.vitality = newStats[0];
        stats.strength = newStats[1];
        stats.agility = newStats[2];
        stats.defense = newStats[3];
        stats.level = uint32(currentLevel);
        stats.xpToNextLevel = uint96(xpToNextLevel);
        stats.currentXP = uint96(currentXP);

        emit LevelUp(collection, tokenId, currentLevel);
        emit StatsBoosted(
            collection,
            tokenId,
            newStats[0],
            newStats[1],
            newStats[2],
            newStats[3]
        );
    }

    /// @notice Record a dungeon run attempt
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @param success Whether the run was successful
    function recordRun(
        address collection,
        uint256 tokenId,
        bool success
    ) external onlyAuthorized {
        require(
            nftStats[collection][tokenId].initialized,
            "Stats not initialized"
        );

        NFTStatsData storage stats = nftStats[collection][tokenId];
        stats.dungeonRuns += 1;
        if (success) {
            stats.successfulRuns += 1;
        }

        emit RunRecorded(
            collection,
            tokenId,
            success,
            stats.roomsCleared,
            stats.currentXP
        );
    }

    // ------------------------- External view functions -------------------------
    /// @notice Get current stats for an NFT
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return NFTStatsData struct containing current stats
    function getStats(
        address collection,
        uint256 tokenId
    ) external view returns (NFTStatsData memory) {
        return nftStats[collection][tokenId];
    }

    /// @notice Check if an NFT has been initialized
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return bool True if NFT has been initialized
    function isInitialized(
        address collection,
        uint256 tokenId
    ) external view returns (bool) {
        return nftStats[collection][tokenId].initialized;
    }

    /// @notice Get secondary stats derived from core stats
    /// @param vitality Character's vitality stat
    /// @param strength Character's strength stat
    /// @param agility Character's agility stat
    /// @param defense Character's defense stat
    /// @return criticalRate Chance to land critical hits (0-15)
    /// @return dodgeChance Chance to dodge attacks (0-10)
    /// @return blockRate Chance to block attacks (0-10)
    /// @return initiative Determines turn order in combat (0-100)
    function getSecondaryStats(
        uint64 vitality,
        uint64 strength,
        uint64 agility,
        uint64 defense
    )
        external
        pure
        returns (
            uint8 criticalRate,
            uint8 dodgeChance,
            uint8 blockRate,
            uint8 initiative
        )
    {
        return
            StatsCalculator.calculateSecondaryStats(
                vitality,
                strength,
                agility,
                defense
            );
    }

    /// @notice Get the stat increases for a level up
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return vitalityIncrease Amount vitality increases
    /// @return strengthIncrease Amount strength increases
    /// @return agilityIncrease Amount agility increases
    /// @return defenseIncrease Amount defense increases
    function getLevelUpStats(
        address collection,
        uint256 tokenId
    )
        public
        view
        returns (
            uint64 vitalityIncrease,
            uint64 strengthIncrease,
            uint64 agilityIncrease,
            uint64 defenseIncrease
        )
    {
        require(
            nftStats[collection][tokenId].initialized,
            "Stats not initialized"
        );

        NFTStatsData memory stats = nftStats[collection][tokenId];

        vitalityIncrease = uint64(
            (uint256(stats.vitality) * BASE_STAT_INCREASE_PERCENT) / 100
        );
        strengthIncrease = uint64(
            (uint256(stats.strength) * BASE_STAT_INCREASE_PERCENT) / 100
        );
        agilityIncrease = uint64(
            (uint256(stats.agility) * BASE_STAT_INCREASE_PERCENT) / 100
        );
        defenseIncrease = uint64(
            (uint256(stats.defense) * BASE_STAT_INCREASE_PERCENT) / 100
        );
    }

    // ------------------------- Public view functions -------------------------
    /// @notice Calculate XP required for next level
    /// @param currentLevel Current level of the NFT
    /// @return uint256 XP required for next level
    function getXPForNextLevel(
        uint32 currentLevel
    ) public pure returns (uint96) {
        return BASE_XP_PER_LEVEL * ((currentLevel * XP_MULTIPLIER) / 100);
    }

    // ------------------------- Internal functions -------------------------
    /// @notice Required by IEntropyConsumer interface
    function getEntropy() internal pure override returns (address) {
        return address(entropy);
    }

    /// @notice Callback function for entropy service
    function entropyCallback(
        uint64 sequenceNumber,
        address /* provider */,
        bytes32 randomNumber
    ) internal override {
        PendingRoll memory pendingRoll = pendingRolls[sequenceNumber];

        // Get base stats from collection registry
        ICollectionRegistry.CollectionStats
            memory baseStats = collectionRegistry.getCollectionStats(
                pendingRoll.collection
            );

        // Generate random variations using entropy
        uint256 seed = uint256(randomNumber);

        uint256 rarityRoll = uint256(seed & 0xFF) % 100;
        uint8 rarityIndex;
        if (rarityRoll < 2) {
            rarityIndex = 4; // Legendary
        } else if (rarityRoll < 10) {
            rarityIndex = 3; // Epic
        } else if (rarityRoll < 30) {
            rarityIndex = 2; // Rare
        } else if (rarityRoll < 50) {
            rarityIndex = 1; // Uncommon
        } else {
            rarityIndex = 0; // Common
        }

        uint8 statVariation = uint8(STAT_VARIATION_BY_RARITY[rarityIndex]);

        // Calculate randomized stats within variation range
        uint64 vitalityVariation = uint64(
            (uint256(baseStats.baseVitality) *
                statVariation *
                uint256(seed & 0xFF)) / (255 * 100)
        );
        uint64 strengthVariation = uint64(
            (uint256(baseStats.baseStrength) *
                statVariation *
                uint256((seed >> 8) & 0xFF)) / (255 * 100)
        );
        uint64 agilityVariation = uint64(
            (uint256(baseStats.baseAgility) *
                statVariation *
                uint256((seed >> 16) & 0xFF)) / (255 * 100)
        );
        uint64 defenseVariation = uint64(
            (uint256(baseStats.baseDefense) *
                statVariation *
                uint256((seed >> 24) & 0xFF)) / (255 * 100)
        );

        // Calculate final stats
        uint64[4] memory stats = [
            baseStats.baseVitality + vitalityVariation,
            baseStats.baseStrength + strengthVariation,
            baseStats.baseAgility + agilityVariation,
            baseStats.baseDefense + defenseVariation
        ];

        // Validate stats for class type
        StatValidation.validateClassStats(baseStats.classType, stats);

        // Initialize NFT stats with randomized values
        nftStats[pendingRoll.collection][pendingRoll.tokenId] = NFTStatsData({
            vitality: stats[0],
            strength: stats[1],
            agility: stats[2],
            defense: stats[3],
            level: uint32(STARTING_LEVEL),
            currentXP: 0,
            xpToNextLevel: getXPForNextLevel(STARTING_LEVEL),
            dungeonRuns: 0,
            successfulRuns: 0,
            roomsCleared: 0,
            initialized: true,
            rarity: Rarity(rarityIndex)
        });

        // Clean up pending roll
        delete pendingRolls[sequenceNumber];

        emit StatsInitialized(
            pendingRoll.collection,
            pendingRoll.tokenId,
            stats[0],
            stats[1],
            stats[2],
            stats[3]
        );
    }

    // ------------------------- Fallback functions -------------------------
    receive() external payable {}
}

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

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @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 {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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);
    }
}

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

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 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 ERC-721 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 ERC-721
     * 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 address zero.
     *
     * 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);
}

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

/// @title INFTStats
/// @notice Interface for managing individual NFT stats and progression
interface INFTStats {
    // ------------------------- Type definitions -------------------------
    /// @notice Enum representing different rarity levels
    /// affects the stat variation of the NFT
    enum Rarity {
        Common,
        Uncommon,
        Rare,
        Epic,
        Legendary
    }

    /// @notice Structure for NFT permanent stats
    struct NFTStatsData {
        // Core Stats (256 bits)
        uint64 vitality; // Replaces HP
        uint64 strength; // Replaces attack
        uint64 agility; // Replaces speed
        uint64 defense; // New stat
        // Progression data (256 bits)
        uint32 level;
        uint96 currentXP;
        uint96 xpToNextLevel;
        uint32 dungeonRuns;
        uint32 successfulRuns;
        uint32 roomsCleared;
        bool initialized;
        Rarity rarity;
    }

    // ------------------------- Events - Stats -------------------------
    /// @notice Event emitted when an NFT's stats are initialized
    event StatsInitialized(
        address indexed collection,
        uint256 indexed tokenId,
        uint64 vitality,
        uint64 strength,
        uint64 agility,
        uint64 defense
    );

    /// @notice Event emitted when an NFT's stats are boosted
    event StatsBoosted(
        address indexed collection,
        uint256 indexed tokenId,
        uint64 newVitality,
        uint64 newStrength,
        uint64 newAgility,
        uint64 newDefense
    );

    // ------------------------- Events - Progression -------------------------
    /// @notice Event emitted when XP is gained
    event XPGained(
        address indexed collection,
        uint256 indexed tokenId,
        uint256 xpGained,
        uint256 newTotalXP
    );

    /// @notice Event emitted when a level up occurs
    event LevelUp(
        address indexed collection,
        uint256 indexed tokenId,
        uint256 newLevel
    );

    /// @notice Event emitted when a run is recorded
    event RunRecorded(
        address indexed collection,
        uint256 indexed tokenId,
        bool success,
        uint256 roomsCleared,
        uint256 xpGained
    );

    // ------------------------- View/Pure Functions -------------------------
    /// @notice Get current stats for an NFT
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return NFTStatsData struct containing current stats
    function getStats(
        address collection,
        uint256 tokenId
    ) external view returns (NFTStatsData memory);

    /// @notice Check if an NFT has been initialized
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return bool True if NFT has been initialized
    function isInitialized(
        address collection,
        uint256 tokenId
    ) external view returns (bool);

    /// @notice Calculate XP required for next level
    /// @param currentLevel Current level of the NFT
    /// @return uint256 XP required for next level
    function getXPForNextLevel(
        uint32 currentLevel
    ) external pure returns (uint96);

    /// @notice Get secondary stats derived from core stats
    /// @param vitality Character's vitality stat
    /// @param strength Character's strength stat
    /// @param agility Character's agility stat
    /// @param defense Character's defense stat
    /// @return criticalRate Chance to land critical hits (0-15)
    /// @return dodgeChance Chance to dodge attacks (0-10)
    /// @return blockRate Chance to block attacks (0-10)
    /// @return initiative Determines turn order in combat (0-100)
    function getSecondaryStats(
        uint64 vitality,
        uint64 strength,
        uint64 agility,
        uint64 defense
    )
        external
        pure
        returns (
            uint8 criticalRate,
            uint8 dodgeChance,
            uint8 blockRate,
            uint8 initiative
        );

    /// @notice Get the stat increases for a level up
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @return vitalityIncrease Amount vitality increases
    /// @return strengthIncrease Amount strength increases
    /// @return agilityIncrease Amount agility increases
    /// @return defenseIncrease Amount defense increases
    function getLevelUpStats(
        address collection,
        uint256 tokenId
    )
        external
        view
        returns (
            uint64 vitalityIncrease,
            uint64 strengthIncrease,
            uint64 agilityIncrease,
            uint64 defenseIncrease
        );

    // ------------------------- State-Changing Functions -------------------------
    /// @notice Initialize stats for an NFT based on its collection's base stats
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    function rollStats(address collection, uint256 tokenId) external;

    /// @notice Award XP for dungeon progress
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @param xpAmount Amount of XP to award
    /// @param roomsCleared Number of rooms cleared in this run
    function awardXP(
        address collection,
        uint256 tokenId,
        uint256 xpAmount,
        uint256 roomsCleared
    ) external;

    /// @notice Record a dungeon run attempt
    /// @param collection Address of the NFT collection
    /// @param tokenId Token ID of the NFT
    /// @param success Whether the run was successful
    function recordRun(
        address collection,
        uint256 tokenId,
        bool success
    ) external;
}

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

/// @title ICollectionRegistry
/// @notice Interface for managing whitelisted NFT collections and their base stats
interface ICollectionRegistry {
    // ------------------------- Type definitions -------------------------
    /// @notice Enum for different class archetypes
    enum ClassArchetype {
        WARRIOR, // High strength/defense
        ROGUE, // High agility/critical
        PALADIN, // Balanced with healing
        BERSERKER // High damage/risk
    }

    /// @notice Stats structure for NFT collections
    struct CollectionStats {
        uint64 baseVitality;
        uint64 baseStrength;
        uint64 baseAgility;
        uint64 baseDefense;
        uint8 classType; // ClassArchetype
        uint8 complexity; // For gas limit determination
        bool isWhitelisted;
    }

    // ------------------------- Events -------------------------
    /// @notice Event emitted when a collection is whitelisted
    event CollectionWhitelisted(
        address indexed collection,
        uint64 baseVitality,
        uint64 baseStrength,
        uint64 baseAgility,
        uint64 baseDefense,
        ClassArchetype classType,
        uint8 complexity
    );

    /// @notice Event emitted when a collection's stats are updated
    event CollectionStatsUpdated(
        address indexed collection,
        uint64 baseVitality,
        uint64 baseStrength,
        uint64 baseAgility,
        uint64 baseDefense,
        ClassArchetype classType,
        uint8 complexity
    );

    /// @notice Event emitted when a collection is removed from whitelist
    event CollectionRemoved(address indexed collection);

    // ------------------------- Admin functions -------------------------
    /// @notice Whitelist a new NFT collection with base stats
    /// @param collection Address of the NFT collection
    /// @param baseVitality Initial vitality for NFTs from this collection
    /// @param baseStrength Initial strength for NFTs from this collection
    /// @param baseAgility Initial agility for NFTs from this collection
    /// @param baseDefense Initial defense for NFTs from this collection
    /// @param classType Class archetype for this collection
    /// @param complexity Gas complexity tier (1-3)
    function whitelistCollection(
        address collection,
        uint64 baseVitality,
        uint64 baseStrength,
        uint64 baseAgility,
        uint64 baseDefense,
        ClassArchetype classType,
        uint8 complexity
    ) external;

    /// @notice Update base stats for a whitelisted collection
    /// @param collection Address of the NFT collection
    /// @param baseVitality New base vitality
    /// @param baseStrength New base strength
    /// @param baseAgility New base agility
    /// @param baseDefense New base defense
    /// @param classType New class archetype
    /// @param complexity New complexity tier
    function updateCollectionStats(
        address collection,
        uint64 baseVitality,
        uint64 baseStrength,
        uint64 baseAgility,
        uint64 baseDefense,
        ClassArchetype classType,
        uint8 complexity
    ) external;

    /// @notice Remove a collection from the whitelist
    /// @param collection Address of the NFT collection to remove
    function removeCollection(address collection) external;

    // ------------------------- View functions -------------------------
    /// @notice Check if a collection is whitelisted
    /// @param collection Address of the NFT collection to check
    /// @return bool True if collection is whitelisted
    function isWhitelisted(address collection) external view returns (bool);

    /// @notice Get base stats for a collection
    /// @param collection Address of the NFT collection
    /// @return CollectionStats struct containing base stats
    function getCollectionStats(
        address collection
    ) external view returns (CollectionStats memory);
}

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

/// @title StatsCalculator
/// @notice Library for calculating derived stats and combat values
library StatsCalculator {
    // ------------------------- Constants -------------------------
    uint8 private constant BASE_CRITICAL_RATE = 5;
    uint8 private constant MAX_CRITICAL_RATE = 15;
    uint8 private constant MAX_DODGE_CHANCE = 10;
    uint8 private constant MAX_BLOCK_RATE = 10;
    uint8 private constant CRITICAL_DAMAGE_PERCENT = 150;
    uint8 private constant BLOCK_REDUCTION_PERCENT = 50;
    uint256 private constant HP_PER_VITALITY = 5;

    // ------------------------- Core stat calculations -------------------------
    /// @notice Calculate max HP from vitality
    /// @param vitality Character's vitality stat
    /// @return uint256 Maximum HP value
    function calculateHp(uint64 vitality) public pure returns (uint256) {
        return uint256(vitality) * HP_PER_VITALITY;
    }

    /// @notice Calculate damage considering strength and enemy defense
    /// @param strength Attacker's strength stat
    /// @param enemyDefense Defender's defense stat
    /// @return uint256 Base damage value
    function calculateDamage(
        uint64 strength,
        uint64 enemyDefense
    ) public pure returns (uint256) {
        return (uint256(strength) * 100) / (100 + uint256(enemyDefense));
    }

    // ------------------------- Secondary stat calculations -------------------------
    /// @notice Calculate all secondary stats
    /// @param vitality Character's vitality stat
    /// @param strength Character's strength stat
    /// @param agility Character's agility stat
    /// @param defense Character's defense stat
    /// @return criticalRate Chance to land critical hits (0-15)
    /// @return dodgeChance Chance to dodge attacks (0-10)
    /// @return blockRate Chance to block attacks (0-10)
    /// @return initiative Determines turn order in combat (0-100)
    function calculateSecondaryStats(
        uint64 vitality,
        uint64 strength,
        uint64 agility,
        uint64 defense
    )
        public
        pure
        returns (
            uint8 criticalRate,
            uint8 dodgeChance,
            uint8 blockRate,
            uint8 initiative
        )
    {
        criticalRate = calculateCriticalRate(agility);
        dodgeChance = calculateDodgeChance(agility);
        blockRate = calculateBlockRate(defense);
        initiative = calculateInitiative(agility, strength);
    }

    /// @notice Calculate critical hit rate from agility
    /// @param agility Character's agility stat
    /// @return uint8 Critical hit chance (0-15)
    function calculateCriticalRate(uint64 agility) public pure returns (uint8) {
        uint8 critRate = BASE_CRITICAL_RATE + uint8(agility / 40);
        return critRate > MAX_CRITICAL_RATE ? MAX_CRITICAL_RATE : critRate;
    }

    /// @notice Calculate dodge chance from agility
    /// @param agility Character's agility stat
    /// @return uint8 Dodge chance (0-10)
    function calculateDodgeChance(uint64 agility) public pure returns (uint8) {
        uint8 dodgeChance = uint8(agility / 50);
        return dodgeChance > MAX_DODGE_CHANCE ? MAX_DODGE_CHANCE : dodgeChance;
    }

    /// @notice Calculate block rate from defense
    /// @param defense Character's defense stat
    /// @return uint8 Block chance (0-10)
    function calculateBlockRate(uint64 defense) public pure returns (uint8) {
        uint8 blockRate = uint8(defense / 50);
        return blockRate > MAX_BLOCK_RATE ? MAX_BLOCK_RATE : blockRate;
    }

    /// @notice Calculate initiative for combat order
    /// @param agility Character's agility stat
    /// @param strength Character's strength stat
    /// @return uint8 Initiative value (0-100)
    function calculateInitiative(
        uint64 agility,
        uint64 strength
    ) public pure returns (uint8) {
        return uint8(((uint256(agility) * 2) + uint256(strength)) / 3);
    }

    // ------------------------- Combat calculations -------------------------
    /// @notice Calculate final damage including critical hits
    /// @param baseDamage Base damage amount
    /// @param criticalRate Critical hit chance
    /// @param entropy Random value for critical determination
    /// @return uint256 Final damage amount
    function calculateDamageWithCrit(
        uint256 baseDamage,
        uint8 criticalRate,
        bytes32 entropy
    ) public pure returns (uint256) {
        bool isCritical = uint8(uint256(entropy) & 0xFF) < criticalRate;
        return
            isCritical
                ? (baseDamage * CRITICAL_DAMAGE_PERCENT) / 100
                : baseDamage;
    }

    /// @notice Calculate damage reduction from blocking
    /// @param incomingDamage Original damage amount
    /// @param blockRate Block chance
    /// @param entropy Random value for block determination
    /// @return uint256 Final damage after potential block
    function calculateDamageReduction(
        uint256 incomingDamage,
        uint8 blockRate,
        bytes32 entropy
    ) public pure returns (uint256) {
        bool isBlocked = uint8(uint256(entropy >> 8) & 0xFF) < blockRate;
        return
            isBlocked
                ? (incomingDamage * BLOCK_REDUCTION_PERCENT) / 100
                : incomingDamage;
    }
}

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

import "../interfaces/ICollectionRegistry.sol";

/// @title StatValidation
/// @notice Library for validating stat ranges and class-specific requirements
library StatValidation {
    // ------------------------- Constants -------------------------
    uint64 private constant MIN_STAT_VALUE = 50;
    uint64 private constant MAX_STAT_VALUE = 200;
    uint64 private constant MAX_STAT_INCREASE = 50;
    uint8 private constant MAX_COMPLEXITY = 3;

    // Gas limits by complexity tier
    uint256 private constant TIER1_GAS_LIMIT = 30000;
    uint256 private constant TIER2_GAS_LIMIT = 50000;
    uint256 private constant TIER3_GAS_LIMIT = 80000;

    // ------------------------- Errors -------------------------
    error InvalidStatRange(uint64 value, uint64 min, uint64 max);
    error InvalidStatIncrease(
        uint64 oldValue,
        uint64 newValue,
        uint64 maxIncrease
    );
    error InvalidClassStats(uint8 classType, string reason);
    error InvalidComplexity(uint8 complexity, uint256 gasUsed);

    // ------------------------- Core validation -------------------------
    /// @notice Validate a stat value is within acceptable range
    /// @param value Stat value to check
    /// @param min Minimum allowed value
    /// @param max Maximum allowed value
    function validateStatRange(
        uint64 value,
        uint64 min,
        uint64 max
    ) public pure {
        if (value < min || value > max) {
            revert InvalidStatRange(value, min, max);
        }
    }

    /// @notice Validate a stat increase is within acceptable range
    /// @param oldValue Previous stat value
    /// @param newValue New stat value
    /// @param maxIncrease Maximum allowed increase
    function validateStatIncrease(
        uint64 oldValue,
        uint64 newValue,
        uint64 maxIncrease
    ) public pure {
        if (newValue < oldValue || newValue > oldValue + maxIncrease) {
            revert InvalidStatIncrease(oldValue, newValue, maxIncrease);
        }
    }

    // ------------------------- Class validation -------------------------
    /// @notice Validate stats are appropriate for class type
    /// @param classType The class archetype
    /// @param stats Array of stats [vitality, strength, agility, defense]
    function validateClassStats(
        uint8 classType,
        uint64[4] memory stats
    ) public pure {
        ICollectionRegistry.ClassArchetype archetype = ICollectionRegistry
            .ClassArchetype(classType);

        // Validate base requirements for each class
        if (archetype == ICollectionRegistry.ClassArchetype.WARRIOR) {
            if (stats[1] < 80 || stats[3] < 80) {
                // strength and defense
                revert InvalidClassStats(
                    classType,
                    "Warrior requires high strength and defense"
                );
            }
        } else if (archetype == ICollectionRegistry.ClassArchetype.ROGUE) {
            if (stats[2] < 80) {
                // agility
                revert InvalidClassStats(
                    classType,
                    "Rogue requires high agility"
                );
            }
        } else if (archetype == ICollectionRegistry.ClassArchetype.PALADIN) {
            if (stats[0] < 80 || stats[3] < 70) {
                // vitality and defense
                revert InvalidClassStats(
                    classType,
                    "Paladin requires high vitality and defense"
                );
            }
        } else if (archetype == ICollectionRegistry.ClassArchetype.BERSERKER) {
            if (stats[1] < 90) {
                // strength
                revert InvalidClassStats(
                    classType,
                    "Berserker requires very high strength"
                );
            }
        }

        // Validate all stats are within global range
        for (uint256 i = 0; i < 4; i++) {
            validateStatRange(stats[i], MIN_STAT_VALUE, MAX_STAT_VALUE);
        }
    }

    // ------------------------- Complexity validation -------------------------
    /// @notice Validate gas usage against complexity tier
    /// @param complexity Complexity tier (1-3)
    /// @param gasUsed Amount of gas used
    function validateComplexityRequirements(
        uint8 complexity,
        uint256 gasUsed
    ) public pure {
        if (complexity == 0 || complexity > MAX_COMPLEXITY) {
            revert InvalidComplexity(complexity, gasUsed);
        }

        uint256 gasLimit = complexity == 1
            ? TIER1_GAS_LIMIT
            : complexity == 2
                ? TIER2_GAS_LIMIT
                : TIER3_GAS_LIMIT;

        if (gasUsed > gasLimit) {
            revert InvalidComplexity(complexity, gasUsed);
        }
    }

    // ------------------------- Utility functions -------------------------
    /// @notice Get the gas limit for a complexity tier
    /// @param complexity Complexity tier (1-3)
    /// @return uint256 Gas limit for the tier
    function getGasLimitForComplexity(
        uint8 complexity
    ) public pure returns (uint256) {
        return
            complexity == 1
                ? TIER1_GAS_LIMIT
                : complexity == 2
                    ? TIER2_GAS_LIMIT
                    : complexity == 3
                        ? TIER3_GAS_LIMIT
                        : 0;
    }
}

// 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 v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * 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[ERC 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);
}

// 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: 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):