false
false

Contract Address Details

0x53420f8d8f0661b6e19b0868729b8eda3fac9e92

Contract Name
CutShadowForgeDiamond
Creator
0x4cbbe9–5bb1f1 at 0xd84647–765363
Balance
0 Xai ( )
Tokens
Fetching tokens...
Transactions
1 Transactions
Transfers
0 Transfers
Gas Used
21,631
Last Balance Update
44882498
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
Contract name:
CutShadowForgeDiamond




Optimization enabled
true
Compiler version
v0.8.19+commit.7dd6d404




Optimization runs
200
EVM Version
paris




Verified at
2024-06-15T18:05:17.279805Z

src/implementation/CutShadowForgeDiamond.sol

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

import {FarmingFragment} from './FarmingFragment.sol';
import {GlueFactoryFragment} from './GlueFactoryFragment.sol';
import {HatcheryRitualsFragment} from './HatcheryRitualsFragment.sol';
import {LevelFragment} from './LevelFragment.sol';
import {RewardsFragment} from './RewardsFragment.sol';
import {StakingFragment} from './StakingFragment.sol';
import {VRFCallbackCreateRitualFragment} from './VRFCallbackCreateRitualFragment.sol';
import {MigrationFragment} from './MigrationFragment.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract CutShadowForgeDiamond is
    FarmingFragment,
    GlueFactoryFragment,
    HatcheryRitualsFragment,
    LevelFragment,
    RewardsFragment,
    StakingFragment,
    VRFCallbackCreateRitualFragment,
    MigrationFragment
{
    event GasReturnedToUser(
        uint256 amountReturned,
        uint256 txPrice,
        uint256 gasSpent,
        address indexed user,
        bool indexed success,
        string indexed transactionType
    );
    event GasReturnerMaxGasReturnedPerTransactionChanged(
        uint256 oldMaxGasReturnedPerTransaction,
        uint256 newMaxGasReturnedPerTransaction,
        address indexed admin
    );
    event GasReturnerInsufficientBalance(
        uint256 txPrice,
        uint256 gasSpent,
        address indexed user,
        string indexed transactionType
    );
}
        

lib/web3/contracts/diamond/libraries/LibDiamondMoonstream.sol

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

// Adapted from the Diamond 3 reference implementation by Nick Mudge:
// https://github.com/mudgen/diamond-3-hardhat

import {IDiamondCut} from "../interfaces/IDiamondCut.sol";

library LibDiamondMoonstream {
    bytes32 constant DIAMOND_STORAGE_POSITION =
        keccak256("diamond.standard.diamond.storage");

    struct FacetAddressAndPosition {
        address facetAddress;
        uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
    }

    struct FacetFunctionSelectors {
        bytes4[] functionSelectors;
        uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
    }

    struct DiamondStorage {
        // maps function selector to the facet address and
        // the position of the selector in the facetFunctionSelectors.selectors array
        mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
        // maps facet addresses to function selectors
        mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
        // facet addresses
        address[] facetAddresses;
        // Used to query if a contract implements an interface.
        // Used to implement ERC-165.
        mapping(bytes4 => bool) supportedInterfaces;
        // owner of the contract
        address contractOwner;
    }

    function diamondStorage()
        internal
        pure
        returns (DiamondStorage storage ds)
    {
        bytes32 position = DIAMOND_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }

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

    function setContractOwner(address _newOwner) internal {
        DiamondStorage storage ds = diamondStorage();
        address previousOwner = ds.contractOwner;
        ds.contractOwner = _newOwner;
        emit OwnershipTransferred(previousOwner, _newOwner);
    }

    function contractOwner() internal view returns (address contractOwner_) {
        contractOwner_ = diamondStorage().contractOwner;
    }

    function enforceIsContractOwner() internal view {
        require(
            msg.sender == diamondStorage().contractOwner,
            "LibDiamond: Must be contract owner"
        );
    }

    event DiamondCut(
        IDiamondCut.FacetCut[] _diamondCut,
        address _init,
        bytes _calldata
    );

    // Internal function version of diamondCut
    function diamondCut(
        IDiamondCut.FacetCut[] memory _diamondCut,
        address _init,
        bytes memory _calldata
    ) internal {
        for (
            uint256 facetIndex;
            facetIndex < _diamondCut.length;
            facetIndex++
        ) {
            IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;
            if (action == IDiamondCut.FacetCutAction.Add) {
                addFunctions(
                    _diamondCut[facetIndex].facetAddress,
                    _diamondCut[facetIndex].functionSelectors
                );
            } else if (action == IDiamondCut.FacetCutAction.Replace) {
                replaceFunctions(
                    _diamondCut[facetIndex].facetAddress,
                    _diamondCut[facetIndex].functionSelectors
                );
            } else if (action == IDiamondCut.FacetCutAction.Remove) {
                removeFunctions(
                    _diamondCut[facetIndex].facetAddress,
                    _diamondCut[facetIndex].functionSelectors
                );
            } else {
                revert("LibDiamondCut: Incorrect FacetCutAction");
            }
        }
        emit DiamondCut(_diamondCut, _init, _calldata);
        initializeDiamondCut(_init, _calldata);
    }

    function addFunctions(
        address _facetAddress,
        bytes4[] memory _functionSelectors
    ) internal {
        require(
            _functionSelectors.length > 0,
            "LibDiamondCut: No selectors in facet to cut"
        );
        DiamondStorage storage ds = diamondStorage();
        require(
            _facetAddress != address(0),
            "LibDiamondCut: Add facet can't be address(0)"
        );
        uint96 selectorPosition = uint96(
            ds.facetFunctionSelectors[_facetAddress].functionSelectors.length
        );
        // add new facet address if it does not exist
        if (selectorPosition == 0) {
            addFacet(ds, _facetAddress);
        }
        for (
            uint256 selectorIndex;
            selectorIndex < _functionSelectors.length;
            selectorIndex++
        ) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds
                .selectorToFacetAndPosition[selector]
                .facetAddress;
            require(
                oldFacetAddress == address(0),
                "LibDiamondCut: Can't add function that already exists"
            );
            addFunction(ds, selector, selectorPosition, _facetAddress);
            selectorPosition++;
        }
    }

    function replaceFunctions(
        address _facetAddress,
        bytes4[] memory _functionSelectors
    ) internal {
        require(
            _functionSelectors.length > 0,
            "LibDiamondCut: No selectors in facet to cut"
        );
        DiamondStorage storage ds = diamondStorage();
        require(
            _facetAddress != address(0),
            "LibDiamondCut: Add facet can't be address(0)"
        );
        uint96 selectorPosition = uint96(
            ds.facetFunctionSelectors[_facetAddress].functionSelectors.length
        );
        // add new facet address if it does not exist
        if (selectorPosition == 0) {
            addFacet(ds, _facetAddress);
        }
        for (
            uint256 selectorIndex;
            selectorIndex < _functionSelectors.length;
            selectorIndex++
        ) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds
                .selectorToFacetAndPosition[selector]
                .facetAddress;
            require(
                oldFacetAddress != _facetAddress,
                "LibDiamondCut: Can't replace function with same function"
            );
            removeFunction(ds, oldFacetAddress, selector);
            addFunction(ds, selector, selectorPosition, _facetAddress);
            selectorPosition++;
        }
    }

    function removeFunctions(
        address _facetAddress,
        bytes4[] memory _functionSelectors
    ) internal {
        require(
            _functionSelectors.length > 0,
            "LibDiamondCut: No selectors in facet to cut"
        );
        DiamondStorage storage ds = diamondStorage();
        // if function does not exist then do nothing and return
        require(
            _facetAddress == address(0),
            "LibDiamondCut: Remove facet address must be address(0)"
        );
        for (
            uint256 selectorIndex;
            selectorIndex < _functionSelectors.length;
            selectorIndex++
        ) {
            bytes4 selector = _functionSelectors[selectorIndex];
            address oldFacetAddress = ds
                .selectorToFacetAndPosition[selector]
                .facetAddress;
            removeFunction(ds, oldFacetAddress, selector);
        }
    }

    function addFacet(
        DiamondStorage storage ds,
        address _facetAddress
    ) internal {
        enforceHasContractCode(
            _facetAddress,
            "LibDiamondCut: New facet has no code"
        );
        ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds
            .facetAddresses
            .length;
        ds.facetAddresses.push(_facetAddress);
    }

    function addFunction(
        DiamondStorage storage ds,
        bytes4 _selector,
        uint96 _selectorPosition,
        address _facetAddress
    ) internal {
        ds
            .selectorToFacetAndPosition[_selector]
            .functionSelectorPosition = _selectorPosition;
        ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(
            _selector
        );
        ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
    }

    function removeFunction(
        DiamondStorage storage ds,
        address _facetAddress,
        bytes4 _selector
    ) internal {
        require(
            _facetAddress != address(0),
            "LibDiamondCut: Can't remove function that doesn't exist"
        );
        // an immutable function is a function defined directly in a diamond
        require(
            _facetAddress != address(this),
            "LibDiamondCut: Can't remove immutable function"
        );
        // replace selector with last selector, then delete last selector
        uint256 selectorPosition = ds
            .selectorToFacetAndPosition[_selector]
            .functionSelectorPosition;
        uint256 lastSelectorPosition = ds
            .facetFunctionSelectors[_facetAddress]
            .functionSelectors
            .length - 1;
        // if not the same then replace _selector with lastSelector
        if (selectorPosition != lastSelectorPosition) {
            bytes4 lastSelector = ds
                .facetFunctionSelectors[_facetAddress]
                .functionSelectors[lastSelectorPosition];
            ds.facetFunctionSelectors[_facetAddress].functionSelectors[
                selectorPosition
            ] = lastSelector;
            ds
                .selectorToFacetAndPosition[lastSelector]
                .functionSelectorPosition = uint96(selectorPosition);
        }
        // delete the last selector
        ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
        delete ds.selectorToFacetAndPosition[_selector];

        // if no more selectors for facet address then delete the facet address
        if (lastSelectorPosition == 0) {
            // replace facet address with last facet address and delete last facet address
            uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
            uint256 facetAddressPosition = ds
                .facetFunctionSelectors[_facetAddress]
                .facetAddressPosition;
            if (facetAddressPosition != lastFacetAddressPosition) {
                address lastFacetAddress = ds.facetAddresses[
                    lastFacetAddressPosition
                ];
                ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
                ds
                    .facetFunctionSelectors[lastFacetAddress]
                    .facetAddressPosition = facetAddressPosition;
            }
            ds.facetAddresses.pop();
            delete ds
                .facetFunctionSelectors[_facetAddress]
                .facetAddressPosition;
        }
    }

    function initializeDiamondCut(
        address _init,
        bytes memory _calldata
    ) internal {
        if (_init == address(0)) {
            require(
                _calldata.length == 0,
                "LibDiamondCut: _init is address(0) but_calldata is not empty"
            );
        } else {
            require(
                _calldata.length > 0,
                "LibDiamondCut: _calldata is empty but _init is not address(0)"
            );
            if (_init != address(this)) {
                enforceHasContractCode(
                    _init,
                    "LibDiamondCut: _init address has no code"
                );
            }
            (bool success, bytes memory error) = _init.delegatecall(_calldata);
            if (!success) {
                if (error.length > 0) {
                    // bubble up the error
                    revert(string(error));
                } else {
                    revert("LibDiamondCut: _init function reverted");
                }
            }
        }
    }

    function enforceHasContractCode(
        address _contract,
        string memory _errorMessage
    ) internal view {
        uint256 contractSize;
        assembly {
            contractSize := extcodesize(_contract)
        }
        require(contractSize > 0, _errorMessage);
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol

// 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);
}
          

lib/@lagunagames/cu-common/src/libraries/LibEnvironment.sol

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

import {IArbSys} from '../interfaces/IArbSys.sol';
import {IArbInfo} from '../interfaces/IArbInfo.sol';

/// @custom:see https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/solidity-support#:~:text=Returns%20an%20%22estimate%22%20of%20the%20L1%20block%20number%20at%20which%20the%20sequencer%20received%20the%20transaction
/// @custom:see https://docs.arbitrum.io/build-decentralized-apps/precompiles/reference
/// @custom:see https://github.com/OffchainLabs/arb-os/blob/develop/contracts/arbos/builtin/ArbSys.sol
library LibEnvironment {
    address private constant ARB_SYS_ADDRESS = 0x0000000000000000000000000000000000000064;
    address private constant ARB_INFO_ADDRESS = 0x0000000000000000000000000000000000000065;

    /// @notice Get the chain ID for the local chain
    /// @dev On Arbitrum based chains, this should replace `block.chainid` in code!
    /// @return returnChainId ID of the current chain
    function chainId() internal view returns (uint256 returnChainId) {
        try IArbSys(ARB_SYS_ADDRESS).arbChainID() returns (uint256 _chainId) {
            returnChainId = _chainId;
        } catch {
            revert('LibEnvironment: address 64 is not an ArbSys contract');
        }
    }

    /// @notice Retrieves an account's balance
    function getBalance(address account) internal view returns (uint256 returnBalance) {
        try IArbInfo(ARB_INFO_ADDRESS).getBalance(account) returns (uint256 _balance) {
            returnBalance = _balance;
        } catch {
            revert('LibEnvironment: address 65 is not an ArbInfo contract');
        }
    }

    /// @notice Retrieves a contract's deployed code
    function getCode(address account) internal view returns (bytes memory returnCode) {
        try IArbInfo(ARB_INFO_ADDRESS).getCode(account) returns (bytes memory _code) {
            returnCode = _code;
        } catch {
            revert('LibEnvironment: address 65 is not an ArbInfo contract');
        }
    }

    /// @notice Get the block number for the local chain
    /// @dev On Arbitrum based chains, this should replace `block.number` in code!
    /// @return returnBlockNumber number of the current chain
    function getBlockNumber() internal view returns (uint256 returnBlockNumber) {
        try IArbSys(ARB_SYS_ADDRESS).arbBlockNumber() returns (uint256 _blockNumber) {
            returnBlockNumber = _blockNumber;
        } catch {
            revert('LibEnvironment: address 64 is not an ArbSys contract');
        }
    }

    function blockNumber() internal view returns (uint256) {
        return getBlockNumber();
    }

    function chainIsArbitrum(uint256 _chainId) internal pure returns (bool) {
        return chainIsArbitrumMainnet(_chainId) || chainIsArbitrumSepolia(_chainId);
    }

    function chainIsArbitrumMainnet(uint256 _chainId) internal pure returns (bool) {
        return _chainId == 42161;
    }

    function chainIsArbitrumSepolia(uint256 _chainId) internal pure returns (bool) {
        return _chainId == 421614;
    }

    function chainIsXai(uint256 _chainId) internal pure returns (bool) {
        return chainIsXaiMainnet(_chainId) || chainIsXaiSepolia(_chainId);
    }

    function chainIsXaiMainnet(uint256 _chainId) internal pure returns (bool) {
        return _chainId == 660279;
    }

    function chainIsXaiSepolia(uint256 _chainId) internal pure returns (bool) {
        return _chainId == 37714555429;
    }
}
          

src/libraries/LibStructs.sol

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

library LibStructs {
    struct StakeData {
        address staker; //  Address of the staker
        bool staked; //  TRUE if the stake is active
        uint256 farmableItemId; //  Id of the FarmableItem being farmed
        uint256 stakeTimestamp; //  Timestamp of the stake
    }

    /// Definition of a Terminus pool that can be yield farmed by Shadowcorns
    struct FarmableItem {
        bool active; /// TRUE if the item can be farmed
        uint256 poolId; /// Id of the pool on Terminus contract
        uint256 hourlyRate; /// how many of the items are made per hour (3 decimals)
        uint256 cap; /// max number that can be farmed per session
        string uri; /// passthrough from Terminus
        uint256 class; /// class of the shadowcorn
        uint256 stat; /// stat of the shadowcorn to use
        bool receivesHatcheryLevelBonus;
        bool receivesRarityBonus;
    }
}
          

lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol

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

pragma solidity ^0.8.0;

import "./IERC165.sol";

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

lib/@lagunagames/cu-common/src/interfaces/IArbInfo.sol

// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.4.21 <0.9.0;

/// @title Lookup for basic info about accounts and contracts.
/// @notice Precompiled contract that exists in every Arbitrum chain at 0x0000000000000000000000000000000000000065.
/// @custom:see https://docs.arbitrum.io/build-decentralized-apps/precompiles/reference
/// @custom:see https://github.com/OffchainLabs/nitro-contracts/blob/1cab72ff3dfcfe06ceed371a9db7a54a527e3bfb/src/precompiles/ArbInfo.sol
interface IArbInfo {
    /// @notice Retrieves an account's balance
    function getBalance(address account) external view returns (uint256);

    /// @notice Retrieves a contract's deployed code
    function getCode(address account) external view returns (bytes memory);
}
          

lib/openzeppelin-contracts/contracts/utils/Strings.sol

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

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

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

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

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

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

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
          

lib/@lagunagames/cu-common/src/interfaces/ISupraRouter.sol

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

/// @custom:see https://gb-docs.supraoracles.com/docs/vrf/v2-guide
interface ISupraRouter {
    function generateRequest(
        string memory _functionSig,
        uint8 _rngCount,
        uint256 _numConfirmations,
        uint256 _clientSeed,
        address _clientWalletAddress
    ) external returns (uint256);

    function generateRequest(
        string memory _functionSig,
        uint8 _rngCount,
        uint256 _numConfirmations,
        address _clientWalletAddress
    ) external returns (uint256);
}
          

src/implementation/FarmingFragment.sol

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

import {LibStructs} from '../libraries/LibStructs.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract FarmingFragment {
    event FarmableItemRegistered(
        address indexed admin,
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        string indexed uri
    );
    event FarmableItemModified(
        address indexed admin,
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat
    );

    event FarmableItemActivated(address indexed admin, uint256 farmableItemId);
    event FarmableItemDeactivated(address indexed admin, uint256 farmableItemId);
    event FarmableItemDeleted(address indexed admin, uint256 farmableItemId);

    /// Setup a new item for yield farming by the Shadowcorns.
    /// @param poolId - Terminus pool (Shadowcorns item collection only)
    /// @param hourlyRate - Number of items created in one batch per hour (3 decimals)
    /// @param cap - Max number of items that can be farmed per session
    /// @param class - Class of the Shadowcorn
    /// @param stat - Stat of the Shadowcorn to check when farming
    /// @param receivesHatcheryLevelBonus - Whether the item receives a bonus based on the hatchery level
    /// @param receivesRarityBonus - Whether the item receives a bonus based on the rarity of the minion
    function registerNewFarmableItem(
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat,
        bool receivesHatcheryLevelBonus,
        bool receivesRarityBonus
    ) external {}

    /// Change the details of a farmableItem
    /// @param farmableItemId - Item to modify
    /// @param hourlyRate - Number of items created in one batch per hour (3 decimals)
    /// @param cap - Max number of items that can be farmed per session
    /// @param receivesHatcheryLevelBonus - Whether the item receives a bonus based on the hatchery level
    /// @param receivesRarityBonus - Whether the item receives a bonus based on the rarity of the minion
    function modifyFarmableItem(
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat,
        bool receivesHatcheryLevelBonus,
        bool receivesRarityBonus
    ) external {}

    /// Turn on a Farmable item for use
    function activateFarmableItem(uint256 farmableItemId) external {}

    /// Turn off a Farmable item for use
    function deactivateFarmableItem(uint256 farmableItemId) external {}

    /// Returns a list of Farmable items that are currently active.
    function getActiveFarmables() external view returns (LibStructs.FarmableItem[] memory, uint256[] memory) {}

    /// Returns the number of FarmableItem objects registered
    function getFarmableItemCount() external view returns (uint256) {}

    function getActiveFarmableCount() external view returns (uint256) {}

    function getFarmableItemData(uint256 farmableItemId) external view returns (LibStructs.FarmableItem memory) {}

    /// @notice Retrieves the total husks from the LibFarming contract.
    /// @return totalHusks The total number of husks.
    function getTotalHusks() external view returns (uint256 totalHusks) {}

    /// @notice Retrieves the total minions from the LibFarming contract.
    /// @return totalMinions The total number of minions.
    function getTotalMinions() external view returns (uint256 totalMinions) {}

    /// @notice Sets the husk pool IDs with the provided array of IDs.
    /// @dev Only the contract owner can call this function.
    /// @param _huskPoolIds An array of husk pool IDs to be added.
    function setHuskPoolIds(uint256[] memory _huskPoolIds) external {}

    /// @notice Retrieves the husk pool IDs.
    /// @return huskPoolIds An array of husk pool IDs.
    function getHuskPoolIds() external view returns (uint256[] memory huskPoolIds) {}

    /// @notice Sets the minion pool IDs with the provided array of IDs.
    /// @dev Only the contract owner can call this function.
    /// @param _minionPoolIds An array of minion pool IDs to be added.
    function setMinionPoolIds(uint256[] memory _minionPoolIds) external {}

    /// @notice Removes a specific minion pool ID.
    /// @dev Only the contract owner can call this function.
    /// @param _minionPoolId The ID of the minion pool to be removed.
    function removeMinionPoolId(uint256 _minionPoolId) external {}

    /// @notice Removes a specific husk pool ID.
    /// @dev Only the contract owner can call this function.
    /// @param _huskPoolId The ID of the husk pool to be removed.
    function removeHuskPoolId(uint256 _huskPoolId) external {}

    /// @notice Retrieves the minion pool IDs.
    /// @return minionPoolIds An array of minion pool IDs.
    function getMinionPoolIds() external view returns (uint256[] memory minionPoolIds) {}
}
          

src/implementation/RewardsFragment.sol

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

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract RewardsFragment {
    event ClaimedRewards(address indexed player, uint256 rewardUNIM, uint256 rewardDarkMarks);
    event AddedToQueue(address indexed player, uint256 waveId, uint256 quantity);
    event BegunNewWave(uint256 newWaveId, uint256 prevWaveTime, uint256 newWaveTime);

    /// @dev Initializes the global queue. Only the contract owner can call this function.
    function initializeGlobalQueue() external {}

    /// @dev Claim the rewards for the caller.
    /// @return rewardUNIM The total amount of UNIM tokens that were claimed.
    /// @return rewardDarkMarks The total amount of DarkMarks tokens that were claimed.
    function claimRewards() external returns (uint256 rewardUNIM, uint256 rewardDarkMarks) {}

    /// @dev Returns the owed UNIM and Dark Marks rewards for the specified user.
    /// @param user The address of the user to get the rewards for.
    /// @return rewardUNIM The owed UNIM rewards for the user.
    /// @return rewardDarkMarks The owed Dark Marks rewards for the user.
    function getPlayerRewards(address user) external view returns (uint256 rewardUNIM, uint256 rewardDarkMarks) {}

    /// @dev Returns the total claimable UNIM rewards.
    /// @return claimableUNIM The total claimable UNIM rewards.
    function getClaimableUNIM(uint256 waveId) external view returns (uint256 claimableUNIM) {}

    /// @dev Returns the total claimable Dark Marks rewards.
    /// @return claimableDarkMarks The total claimable Dark Marks rewards.
    function getClaimableDarkMarks(uint256 waveId) external view returns (uint256 claimableDarkMarks) {}

    /// @dev Sets the wave UNIM rewards amount. Only the contract owner can call this function.
    /// @param _waveUNIM The new wave UNIM rewards amount to set.
    function setWaveUNIM(uint256 _waveUNIM) external {}

    /// @dev Returns the current wave UNIM rewards amount.
    /// @return waveUNIM The current wave UNIM rewards amount.
    function getWaveUNIM() external view returns (uint256 waveUNIM) {}

    /// @dev Sets the wave Dark Marks rewards amount. Only the contract owner can call this function.
    /// @param _waveDarkMarks The new wave Dark Marks rewards amount to set.
    function setWaveDarkMarks(uint256 _waveDarkMarks) external {}

    /// @dev Returns the current wave Dark Marks rewards amount.
    /// @return waveDarkMarks The current wave Dark Marks rewards amount.
    function getWaveDarkMarks() external view returns (uint256 waveDarkMarks) {}

    /// @dev Checks if the queue for the specified user has been initialized.
    /// @param user The address of the user to check the queue for.
    /// @return isInitialized True if the queue for the specified user has been initialized, false otherwise.
    function isPlayerQueueInitialized(address user) external view returns (bool isInitialized) {}

    /// @dev Checks if the global queue has been initialized.
    /// @return isInitialized True if the global queue has been initialized, false otherwise.
    function isGlobalQueueInitialized() external view returns (bool isInitialized) {}

    /// @dev Returns the length of the queue for the specified user.
    /// @param user The address of the user to get the queue length for.
    /// @return length The length of the queue for the specified user.
    function getPlayerQueueLength(address user) external view returns (uint256 length) {}

    /// @dev Returns the length of the global queue.
    /// @return length The length of the global queue.
    function getGlobalQueueLength() external view returns (uint256 length) {}

    /// @dev Returns the front item in the queue for the specified user.
    /// @param user The address of the user to get the front item for.
    /// @return timestamp The timestamp of the front item in the queue for the specified user.
    /// @return quantity The quantity associated with the front item in the queue for the specified user.
    function getPlayerQueueFront(address user) external view returns (uint256 timestamp, uint256 quantity) {}

    /// @dev Returns the front item in the global queue.
    /// @return waveId The waveId of the front item in the global queue.
    /// @return quantity The quantity associated with the front item in the global queue.
    function getGlobalQueueFront()
        external
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {}

    /// @dev Returns the tail item in the queue for the specified user.
    /// @param user The address of the user to get the tail item for.
    /// @return waveId The waveId of the tail item in the queue for the specified user.
    /// @return quantity The quantity associated with the tail item in the queue for the specified user.
    function getPlayerQueueTail(address user) external view returns (uint256 waveId, uint256 quantity) {}

    /// @dev Returns the tail item in the global queue.
    /// @return waveId The waveId of the tail item in the global queue.
    /// @return quantity The quantity associated with the tail item in the global queue.
    function getGlobalQueueTail()
        external
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {}

    /// @dev Returns the item at the given index in the queue for the specified user.
    /// @param user The address of the user to get the item from the queue for.
    /// @param index The index of the item to retrieve.
    /// @return waveId The waveId of the item at the given index in the queue for the specified user.
    /// @return quantity The quantity associated with the item at the given index in the queue for the specified user.
    function getPlayerQueueAtIndex(
        address user,
        uint256 index
    ) external view returns (uint256 waveId, uint256 quantity) {}

    /// @dev Returns the item at the given index in the global queue.
    /// @param index The index of the item to retrieve.
    /// @return waveId The waveId of the item at the given index in the global queue.
    /// @return quantity The quantity associated with the item at the given index in the global queue.
    function getGlobalQueueAtIndex(
        uint256 index
    )
        external
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {}

    /// @dev Returns the entire queue for the specified user.
    /// @param user The address of the user to get the queue for.
    /// @return waveIds The array of waveIds of items in the queue for the specified user.
    /// @return quantities The array of quantities associated with items in the queue for the specified user.
    function getPlayerQueue(
        address user
    ) external view returns (uint256[] memory waveIds, uint256[] memory quantities) {}

    /// @dev Returns the entire global queue.
    /// @return waveIds The array of waveIds of items in the global queue.
    /// @return quantities The array of quantities associated with items in the global queue.
    function getGlobalQueue() external view returns (uint256[] memory waveIds, uint256[] memory quantities) {}

    /// @notice Retrieves the wave rewards (UNIM and Dark Marks) from the global queue.
    /// @return waveUNIM An array containing the UNIM rewards for each wave in the global queue.
    /// @return waveDarkMarks An array containing the Dark Marks rewards for each wave in the global queue.
    function getGlobalQueueWaveRewards()
        external
        view
        returns (uint256[] memory waveUNIM, uint256[] memory waveDarkMarks)
    {}

    /// @notice Retrieves the unclaimed rewards (Dark Marks and UNIM) from the global queue.
    /// @return unclaimedUnim An array containing the unclaimed UNIM for each wave in the global queue.
    /// @return unclaimedDarkmarks An array containing the unclaimed Dark Marks for each wave in the global queue.
    function getGlobalQueueUnclaimedRewards()
        external
        view
        returns (uint256[] memory unclaimedUnim, uint256[] memory unclaimedDarkmarks)
    {}

    /// @notice Retrieves the current wave count from the rewards storage.
    /// @return waveCount The current wave count value.
    function getWaveCount() external view returns (uint256 waveCount) {}

    /// @notice Retrieves the current wave time from the rewards storage.
    /// @return waveTime The current wave time value.
    function getWaveTime() external view returns (uint256 waveTime) {}

    /// @notice Retrieves the UNIM tokens for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the UNIM tokens.
    /// @return waveUNIM The amount of UNIM tokens for the specified wave ID.
    function getWaveUNIMByWaveId(uint256 waveId) external view returns (uint256 waveUNIM) {}

    /// @notice Retrieves the Dark Marks for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the Dark Marks.
    /// @return waveDarkMarks The amount of Dark Marks for the specified wave ID.
    function getWaveDarkMarksByWaveId(uint256 waveId) external view returns (uint256 waveDarkMarks) {}

    /// @notice Calculates the current wave count
    /// @return waveCount The current wave count, derived from the system's reward logic.
    function calculateCurrentWaveCount() external view returns (uint256 waveCount) {}

    /// @dev Retrieves the total unclaimed rewards in the global queue.
    /// Iterates through the queue, summing the unclaimed UNIM and Darkmarks rewards.
    /// @return unclaimedUnim The total amount of unclaimed UNIM tokens.
    /// @return unclaimedDarkmarks The total amount of unclaimed Darkmarks.
    function getUnclaimedRewards() external view returns (uint256 unclaimedUnim, uint256 unclaimedDarkmarks) {}

    /// @dev Retrieves the total distributed rewards.
    /// @return distributedUNIM The total amount of distributed UNIM tokens.
    /// @return distributedDarkMarks The total amount of distributed Darkmarks.
    function getDistributedRewards() external view returns (uint256 distributedUNIM, uint256 distributedDarkMarks) {}

    /// @notice Returns both the daily UNIM and DarkMarks rewards.
    /// @return dailyUNIM The quantity of UNIM allocated for the current wave.
    /// @return dailyDarkMarks The quantity of DarkMarks allocated for the current wave.
    function getDailyRewards() external view returns (uint256 dailyUNIM, uint256 dailyDarkMarks) {}

    /// @dev Calculates the contribution percentage of a specific user in the last 7 days except the last 24 hours.
    /// It calculates the total contribution of a user in comparison to the global contributions.
    /// @param user The address of the user whose contribution percentage is being calculated.
    /// @return contributionPercentage The percentage of the user's contribution in the last 7 days except the last 24 hours.
    function getContributionPercentage(address user) external view returns (uint256 contributionPercentage) {}
}
          

src/entities/level/LevelUpgradeBonusFrequency.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

enum LevelUpgradeBonusFrequency {
    NONE, //0
    PERMANENT, //1
    HOURLY, //2
    DAILY //3
}
          

lib/@lagunagames/cu-common/src/interfaces/IUNIMControllerFacet.sol

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

interface IUNIMControllerFacet {
    /*
     * @notice Whitelist a user or contract to create new UNIM tokens.
     * @dev Can only be called by the Diamond owner of the UNIM contract.
     * @param minter - Public address of the user or contract allow
     */
    function allowAddressToMintUNIM(address minter) external;

    /*
     * @notice Revoke a user's or contract's permission to create new UNIM tokens.
     * @dev Can only be called by the Diamond owner of the UNIM contract.
     * @param minter - Public address of the user or contract to revoke
     */
    function denyAddressToMintUNIM(address minter) external;

    /*
     * @notice Print the list of wallets and contracts who can create UNIM tokens.
     * @return The full list of permitted addresses
     */
    function getAddressesPermittedToMintUNIM() external view returns (address[] memory);

    /*
     * @notice Reports the lifetime number of UNIM that an address has minted and burned.
     * @param minter - Public address of the minter
     * @return minted - The grand total number of UNIM this address has minted
     * @return burned - The grand total number of UNIM this address has burned
     */
    function getUNIMOperationsForAddress(address controller) external view returns (uint256 minted, uint256 burned);

    /*
     * @notice Create new UNIM tokens for a target wallet.
     * @dev Can only be called by an address allowed via allowAddressToMintUNIM
     * @param account - The address receiving the funds
     * @param amount - The number of UNIM tokens to create
     */
    function mintUNIM(address account, uint256 amount) external;

    /*
     * @notice Destroy UNIM tokens from a target wallet.
     * @dev Can only be called by an address allowed via allowAddressToMintUNIM
     * @dev This method uses the player's spend/burn allowance granted to GameBank,
     *     rather than allowance for msgSender, so this may have better permission.
     * @param account - The wallet to remove UNIM from
     * @param amount - The number of UNIM tokens to destroy
     */
    function burnUNIMFrom(address account, uint256 amount) external;
}
          

lib/@lagunagames/cu-common/lib/@lagunagames/lg-diamond-template/src/interfaces/IERC165.sol

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

/// @title ERC-165 Standard Interface Detection
/// @dev https://eips.ethereum.org/EIPS/eip-165
interface IERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
          

src/libraries/LibRitualNames.sol

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

import {LibRNG} from '../../lib/@lagunagames/cu-common/src/libraries/LibRNG.sol';

library LibRitualNames {
    uint256 private constant SALT_1 = 239;
    uint256 private constant SALT_2 = 240;
    uint256 private constant SALT_3 = 241;
    uint256 private constant SALT_4 = 242;

    bytes32 private constant RITUAL_NAMES_STORAGE_POSITION = keccak256('CryptoUnicorns.HatcheryRitual.Names.Storage');

    struct RitualNameStorage {
        string[] validFirstNames; //  source
        string[] validMiddleNames; //  descriptor
        string[] validLastNames; //  noun/verb
    }

    function nameStorage() internal pure returns (RitualNameStorage storage ns) {
        bytes32 position = RITUAL_NAMES_STORAGE_POSITION;
        assembly {
            ns.slot := position
        }
    }

    function getRandomName(uint256 randomness) internal view returns (string memory) {
        RitualNameStorage storage ns = nameStorage();
        if (LibRNG.expand(100, randomness, SALT_4) < 10) {
            //  10% chance that we will skip the middle descriptor
            return
                string.concat(
                    ns.validFirstNames[LibRNG.expand(ns.validFirstNames.length, randomness, SALT_1)],
                    ' ',
                    ns.validLastNames[LibRNG.expand(ns.validLastNames.length, randomness, SALT_3)]
                );
        } else {
            return
                string.concat(
                    ns.validFirstNames[LibRNG.expand(ns.validFirstNames.length, randomness, SALT_1)],
                    ' ',
                    ns.validMiddleNames[LibRNG.expand(ns.validMiddleNames.length, randomness, SALT_2)],
                    ' ',
                    ns.validLastNames[LibRNG.expand(ns.validLastNames.length, randomness, SALT_3)]
                );
        }
    }

    function registerFirstNames(string[] memory names) internal {
        RitualNameStorage storage ns = nameStorage();
        for (uint256 i = 0; i < names.length; ++i) {
            ns.validFirstNames.push(names[i]);
        }
    }

    function registerMiddleNames(uint256[] memory ids, string[] memory names, bool addToRNG) internal {
        RitualNameStorage storage ns = nameStorage();
        for (uint256 i = 0; i < names.length; ++i) {
            ns.validMiddleNames.push(names[i]);
        }
    }

    function registerLastNames(uint256[] memory ids, string[] memory names, bool addToRNG) internal {
        RitualNameStorage storage ns = nameStorage();
        for (uint256 i = 0; i < names.length; ++i) {
            ns.validLastNames.push(names[i]);
        }
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibToken.sol

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

library LibToken {
    uint256 internal constant TYPE_ERC20 = 20;
    uint256 internal constant TYPE_ERC721 = 721;
    uint256 internal constant TYPE_ERC1155 = 1155;
}
          

lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol

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

pragma solidity ^0.8.0;

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

lib/openzeppelin-contracts/contracts/token/ERC721/IERC721Receiver.sol

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

pragma solidity ^0.8.0;

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

lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol

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

import {IERC165} from '../../lib/@lagunagames/lg-diamond-template/src/interfaces/IERC165.sol';
import {IResourceLocator} from '../interfaces/IResourceLocator.sol';

/// @title LG Resource Locator Library
/// @author [email protected]
/// @notice Library for common LG Resource Locations deployed on a chain
/// @custom:storage-location erc7201:games.laguna.LibResourceLocator
library LibResourceLocator {
    //  @dev Storage slot for LG Resource addresses
    bytes32 internal constant RESOURCE_LOCATOR_SLOT_POSITION =
        keccak256(abi.encode(uint256(keccak256('games.laguna.LibResourceLocator')) - 1)) & ~bytes32(uint256(0xff));

    struct ResourceLocatorStorageStruct {
        address unicornNFT; //  ERC-721
        address landNFT; //  ERC-721
        address shadowcornNFT; //  ERC-721
        address gemNFT; //  ERC-721
        address ritualNFT; //  ERC-721
        address RBWToken; //  ERC-20
        address CUToken; //  ERC-20
        address UNIMToken; //  ERC-20
        address WETHToken; //  ERC-20 (third party)
        address darkMarkToken; //  pseudo-ERC-20
        address unicornItems; //  ERC-1155 Terminus
        address shadowcornItems; //  ERC-1155 Terminus
        address accessControlBadge; //  ERC-1155 Terminus
        address gameBank;
        address satelliteBank;
        address playerProfile; //  PermissionProvider
        address shadowForge;
        address darkForest;
        address gameServerSSS; //  ERC-712 Signing Wallet
        address gameServerOracle; //  CU-Watcher
        address testnetDebugRegistry; // PermissionProvider
        address vrfOracle; //  SupraOracles VRF
        address vrfClientWallet; //  SupraOracles VRF payer
    }

    /// @notice Storage slot for ResourceLocator state data
    function resourceLocatorStorage() internal pure returns (ResourceLocatorStorageStruct storage storageSlot) {
        bytes32 position = RESOURCE_LOCATOR_SLOT_POSITION;

        // solhint-disable-next-line no-inline-assembly
        assembly {
            storageSlot.slot := position
        }
    }

    function unicornNFT() internal view returns (address) {
        return resourceLocatorStorage().unicornNFT;
    }

    function setUnicornNFT(address a) internal {
        resourceLocatorStorage().unicornNFT = a;
    }

    function landNFT() internal view returns (address) {
        return resourceLocatorStorage().landNFT;
    }

    function setLandNFT(address a) internal {
        resourceLocatorStorage().landNFT = a;
    }

    function shadowcornNFT() internal view returns (address) {
        return resourceLocatorStorage().shadowcornNFT;
    }

    function setShadowcornNFT(address a) internal {
        resourceLocatorStorage().shadowcornNFT = a;
    }

    function gemNFT() internal view returns (address) {
        return resourceLocatorStorage().gemNFT;
    }

    function setGemNFT(address a) internal {
        resourceLocatorStorage().gemNFT = a;
    }

    function ritualNFT() internal view returns (address) {
        return resourceLocatorStorage().ritualNFT;
    }

    function setRitualNFT(address a) internal {
        resourceLocatorStorage().ritualNFT = a;
    }

    function rbwToken() internal view returns (address) {
        return resourceLocatorStorage().RBWToken;
    }

    function setRBWToken(address a) internal {
        resourceLocatorStorage().RBWToken = a;
    }

    function cuToken() internal view returns (address) {
        return resourceLocatorStorage().CUToken;
    }

    function setCUToken(address a) internal {
        resourceLocatorStorage().CUToken = a;
    }

    function unimToken() internal view returns (address) {
        return resourceLocatorStorage().UNIMToken;
    }

    function setUNIMToken(address a) internal {
        resourceLocatorStorage().UNIMToken = a;
    }

    function wethToken() internal view returns (address) {
        return resourceLocatorStorage().WETHToken;
    }

    function setWETHToken(address a) internal {
        resourceLocatorStorage().WETHToken = a;
    }

    function darkMarkToken() internal view returns (address) {
        return resourceLocatorStorage().darkMarkToken;
    }

    function setDarkMarkToken(address a) internal {
        resourceLocatorStorage().darkMarkToken = a;
    }

    function unicornItems() internal view returns (address) {
        return resourceLocatorStorage().unicornItems;
    }

    function setUnicornItems(address a) internal {
        resourceLocatorStorage().unicornItems = a;
    }

    function shadowcornItems() internal view returns (address) {
        return resourceLocatorStorage().shadowcornItems;
    }

    function setShadowcornItems(address a) internal {
        resourceLocatorStorage().shadowcornItems = a;
    }

    function accessControlBadge() internal view returns (address) {
        return resourceLocatorStorage().accessControlBadge;
    }

    function setAccessControlBadge(address a) internal {
        resourceLocatorStorage().accessControlBadge = a;
    }

    function gameBank() internal view returns (address) {
        return resourceLocatorStorage().gameBank;
    }

    function setGameBank(address a) internal {
        resourceLocatorStorage().gameBank = a;
    }

    function satelliteBank() internal view returns (address) {
        return resourceLocatorStorage().satelliteBank;
    }

    function setSatelliteBank(address a) internal {
        resourceLocatorStorage().satelliteBank = a;
    }

    function playerProfile() internal view returns (address) {
        return resourceLocatorStorage().playerProfile;
    }

    function setPlayerProfile(address a) internal {
        resourceLocatorStorage().playerProfile = a;
    }

    function shadowForge() internal view returns (address) {
        return resourceLocatorStorage().shadowForge;
    }

    function setShadowForge(address a) internal {
        resourceLocatorStorage().shadowForge = a;
    }

    function darkForest() internal view returns (address) {
        return resourceLocatorStorage().darkForest;
    }

    function setDarkForest(address a) internal {
        resourceLocatorStorage().darkForest = a;
    }

    function gameServerSSS() internal view returns (address) {
        return resourceLocatorStorage().gameServerSSS;
    }

    function setGameServerSSS(address a) internal {
        resourceLocatorStorage().gameServerSSS = a;
    }

    function gameServerOracle() internal view returns (address) {
        return resourceLocatorStorage().gameServerOracle;
    }

    function setGameServerOracle(address a) internal {
        resourceLocatorStorage().gameServerOracle = a;
    }

    function testnetDebugRegistry() internal view returns (address) {
        return resourceLocatorStorage().testnetDebugRegistry;
    }

    function setTestnetDebugRegistry(address a) internal {
        resourceLocatorStorage().testnetDebugRegistry = a;
    }

    function vrfOracle() internal view returns (address) {
        return resourceLocatorStorage().vrfOracle;
    }

    function setVRFOracle(address a) internal {
        resourceLocatorStorage().vrfOracle = a;
    }

    function vrfClientWallet() internal view returns (address) {
        return resourceLocatorStorage().vrfClientWallet;
    }

    function setVRFClientWallet(address a) internal {
        resourceLocatorStorage().vrfClientWallet = a;
    }

    /// @notice Clones the addresses from an existing diamond onto this one
    function importResourcesFromDiamond(address diamond) internal {
        require(
            IERC165(diamond).supportsInterface(type(IResourceLocator).interfaceId),
            'LibResourceLocator: target does not implement IResourceLocator'
        );
        IResourceLocator target = IResourceLocator(diamond);
        if (target.unicornNFTAddress() != address(0)) setUnicornNFT(target.unicornNFTAddress());
        if (target.landNFTAddress() != address(0)) setLandNFT(target.landNFTAddress());
        if (target.shadowcornNFTAddress() != address(0)) setShadowcornNFT(target.shadowcornNFTAddress());
        if (target.gemNFTAddress() != address(0)) setGemNFT(target.gemNFTAddress());
        if (target.ritualNFTAddress() != address(0)) setRitualNFT(target.ritualNFTAddress());
        if (target.rbwTokenAddress() != address(0)) setRBWToken(target.rbwTokenAddress());
        if (target.cuTokenAddress() != address(0)) setCUToken(target.cuTokenAddress());
        if (target.unimTokenAddress() != address(0)) setUNIMToken(target.unimTokenAddress());
        if (target.wethTokenAddress() != address(0)) setWETHToken(target.wethTokenAddress());
        if (target.darkMarkTokenAddress() != address(0)) setDarkMarkToken(target.darkMarkTokenAddress());
        if (target.unicornItemsAddress() != address(0)) setUnicornItems(target.unicornItemsAddress());
        if (target.shadowcornItemsAddress() != address(0)) setShadowcornItems(target.shadowcornItemsAddress());
        if (target.accessControlBadgeAddress() != address(0)) setAccessControlBadge(target.accessControlBadgeAddress());
        if (target.gameBankAddress() != address(0)) setGameBank(target.gameBankAddress());
        if (target.satelliteBankAddress() != address(0)) setSatelliteBank(target.satelliteBankAddress());
        if (target.playerProfileAddress() != address(0)) setPlayerProfile(target.playerProfileAddress());
        if (target.shadowForgeAddress() != address(0)) setShadowForge(target.shadowForgeAddress());
        if (target.darkForestAddress() != address(0)) setDarkForest(target.darkForestAddress());
        if (target.gameServerSSSAddress() != address(0)) setGameServerSSS(target.gameServerSSSAddress());
        if (target.gameServerOracleAddress() != address(0)) setGameServerOracle(target.gameServerOracleAddress());
        if (target.testnetDebugRegistryAddress() != address(0))
            setTestnetDebugRegistry(target.testnetDebugRegistryAddress());
        if (target.vrfOracleAddress() != address(0)) setVRFOracle(target.vrfOracleAddress());
        if (target.vrfClientWalletAddress() != address(0)) setVRFClientWallet(target.vrfClientWalletAddress());
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)

pragma solidity ^0.8.0;

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

/**
 * @title ERC721 Burnable Token
 * @dev ERC721 Token that can be burned (destroyed).
 */
abstract contract ERC721Burnable is Context, ERC721 {
    /**
     * @dev Burns `tokenId`. See {ERC721-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) public virtual {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _burn(tokenId);
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibRitualComponents.sol

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

library LibRitualComponents {

    /// @notice Components are the costs and products of the rituals
    //  both share the same format, a component can be either a cost or a product.
    //  @param amount The amount of a certain asset (applies for erc20 and 1155)
    //  @param assetType The ERC type 20 = ERC20, 721 = ERC721, 1155 = ERC1155
    //  @param asset The address of the asset
    //  @param poolId The poolId of the ERC1155 asset (only applies in that case)
    struct RitualComponent {
        uint256 amount;
        uint128 assetType;         
        uint128 poolId;  
        address asset;              
    }

    // @notice This enum contains the different transfer types for costs.
    enum RitualCostTransferType {
        NONE,
        BURN,
        TRANSFER
    }

    // @notice This enum contains the different transfer types for products.
    enum RitualProductTransferType {
        NONE,
        MINT,
        TRANSFER
    }

    // @notice RitualCosts contain components and transfer types for the costs themselves.
    // @param component The component of the cost.
    // @param transferType The transfer type of the cost.
    struct RitualCost {
        RitualComponent component;
        RitualCostTransferType transferType;
    }

    // @notice RitualProducts contain components and transfer types for the products themselves.
    // @param component The component of the product.
    // @param transferType The transfer type of the product.
    struct RitualProduct {
        RitualComponent component;
        RitualProductTransferType transferType;
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibRitualValidate.sol

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

/*
    @title LibRitualValidate
    @dev This library has methods to validate rituals
 */
library LibRitualValidate {
    /*
        @title Enforces ritual is valid
        @author Facundo Vidal
        @dev This function is called to ensure that the ritual is valid
        Reverts if the ritual is invalid
        @param _name The name of the ritual
        @param _rarity The rarity of the ritual
        @param _charges The charges of the ritual
     */
    function enforceValidRitual(string memory _name,
        uint8 _rarity,
        uint256 _charges) internal pure {
        // Check if name is valid
        require(bytes(_name).length > 0, "LibRitualValidate: invalid name");
        // Check if rarity is valid
        require(_rarity >= 1 && _rarity <= 3, "LibRitualValidate: invalid rarity"); // 1=MYTHIC, 2=RARE, 3=COMMON
        // Check if charges is valid
        require(_charges > 0, "LibRitualValidate: invalid charges");
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibRNG.sol

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

import {ISupraRouter} from '../interfaces/ISupraRouter.sol';
import {LibResourceLocator} from '../libraries/LibResourceLocator.sol';
import {LibEnvironment} from '../libraries/LibEnvironment.sol';

/// @custom:see https://gb-docs.supraoracles.com/docs/vrf/v2-guide
/// @custom:storage-location erc7201:CryptoUnicorns.RNG.storage
library LibRNG {
    bytes32 constant RNG_STORAGE_POSITION =
        keccak256(abi.encode(uint256(keccak256('CryptoUnicorns.RNG.storage')) - 1)) & ~bytes32(uint256(0xff));

    uint256 private constant CONFIRMATION_BLOCKS = 1;

    struct RNGStorage {
        // Nonce used for on-chain RNG
        uint256 rngNonce;
        //  Duration in blocks for VRF to respond with randomness
        uint16 blocksToRespond; //  Leave this `0` for unlimited TTL
        //  The wallet used to pay for Supra RNG requests (docs call this "client wallet")
        address supraClientWallet;
        //  The block number when a request was made
        mapping(uint256 vrfRequestId => uint256 blockNumber) blockRequested;
    }

    function rngStorage() internal pure returns (RNGStorage storage rs) {
        bytes32 position = RNG_STORAGE_POSITION;
        assembly {
            rs.slot := position
        }
    }

    function requestRandomness(string memory callbackSignature) internal returns (uint256 vrfRequestId) {
        RNGStorage storage rs = rngStorage();
        vrfRequestId = ISupraRouter(LibResourceLocator.vrfOracle()).generateRequest(
            callbackSignature,
            1, // random number count
            CONFIRMATION_BLOCKS,
            rs.supraClientWallet
        );
        rs.blockRequested[vrfRequestId] = LibEnvironment.getBlockNumber();
    }

    /// @notice Validate and store the randomness received from a VRF callback
    /// @dev All VRF callbacks MUST call this function
    function rngReceived(uint256 vrfRequestId) internal {
        RNGStorage storage rs = rngStorage();
        enforceCallerIsVRFRouter();

        require(rs.blockRequested[vrfRequestId] > 0, 'RNG: Request invalid or already fulfilled');

        if (rs.blocksToRespond > 0) {
            require(
                LibEnvironment.getBlockNumber() <= rs.blockRequested[vrfRequestId] + rs.blocksToRespond,
                'RNG: Request TTL expired'
            );
        }

        delete rngStorage().blockRequested[vrfRequestId];
    }

    /// @notice Ensures that the caller is the VRF Router, or reverts.
    function enforceCallerIsVRFRouter() internal view {
        require(msg.sender == LibResourceLocator.vrfOracle(), 'RNG: VRFRouter only');
    }

    function expand(uint256 _modulus, uint256 _seed, uint256 _salt) internal pure returns (uint256) {
        return uint256(keccak256(abi.encode(_seed, _salt))) % _modulus;
    }

    //  Generates a pseudo-random integer. This is the cheapestbut least secure.
    //  @return Random integer in the range of [0-_modulus)
    function getCheapRNG(uint _modulus) internal returns (uint256) {
        return uint256(keccak256(abi.encode(++rngStorage().rngNonce))) % _modulus;
    }

    function getRuntimeRNG() internal returns (uint256) {
        return getRuntimeRNG(type(uint256).max);
    }

    /// @notice Generates a pseudo-random integer. This is cheaper than VRF but less secure.
    /// The rngNonce seed should be rotated by VRF before using this pRNG.
    /// @custom:see https://www.geeksforgeeks.org/random-number-generator-in-solidity-using-keccak256/
    /// @custom:see https://docs.chain.link/docs/chainlink-vrf-best-practices/
    /// @param _modulus The range of the response (exclusive)
    /// @return Random integer in the range of [0-_modulus)
    function getRuntimeRNG(uint _modulus) internal returns (uint256) {
        return
            uint256(
                keccak256(
                    abi.encodePacked(
                        block.coinbase,
                        gasleft(),
                        LibEnvironment.getBlockNumber(),
                        ++rngStorage().rngNonce
                    )
                )
            ) % _modulus;
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol

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

pragma solidity ^0.8.0;

import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 *
 * _Available since v3.1._
 */
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    using Address for address;

    // Mapping from token ID to account balances
    mapping(uint256 => mapping(address => uint256)) private _balances;

    // Mapping from account to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    /**
     * @dev See {_setURI}.
     */
    constructor(string memory uri_) {
        _setURI(uri_);
    }

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

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256) public view virtual override returns (string memory) {
        return _uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
        require(account != address(0), "ERC1155: address zero is not a valid owner");
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    ) public view virtual override returns (uint256[] memory) {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: transfer to the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        _balances[id][to] += amount;

        emit TransferSingle(operator, from, to, id, amount);

        _afterTokenTransfer(operator, from, to, ids, amounts, data);

        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
            _balances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _afterTokenTransfer(operator, from, to, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the amounts in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        _balances[id][to] += amount;
        emit TransferSingle(operator, address(0), to, id, amount);

        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

        _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `from`
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `amount` tokens of token type `id`.
     */
    function _burn(address from, uint256 id, uint256 amount) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }

        emit TransferSingle(operator, from, address(0), id, amount);

        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
        }

        emit TransferBatch(operator, from, address(0), ids, amounts);

        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        require(owner != operator, "ERC1155: setting approval status for self");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `ids` and `amounts` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    /**
     * @dev Hook that is called after any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}
          

lib/web3/contracts/terminus/ERC1155WithTerminusStorage.sol

// SPDX-License-Identifier: Apache-2.0

/**
 * Authors: Moonstream Engineering ([email protected])
 * GitHub: https://github.com/bugout-dev/dao
 *
 * An ERC1155 implementation which uses the Moonstream DAO common storage structure for proxies.
 * EIP1155: https://eips.ethereum.org/EIPS/eip-1155
 *
 * The Moonstream contract is used to delegate calls from an EIP2535 Diamond proxy.
 *
 * This implementation is adapted from the OpenZeppelin ERC1155 implementation:
 * https://github.com/OpenZeppelin/openzeppelin-contracts/tree/6bd6b76d1156e20e45d1016f355d154141c7e5b9/contracts/token/ERC1155
 */

pragma solidity ^0.8.9;

import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol";
import "@openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol";
import "@openzeppelin-contracts/contracts/utils/Address.sol";
import "@openzeppelin-contracts/contracts/utils/Context.sol";
import "@openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import "./LibTerminus.sol";

contract ERC1155WithTerminusStorage is
    Context,
    ERC165,
    IERC1155,
    IERC1155MetadataURI
{
    using Address for address;

    constructor() {}

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

    function uri(uint256 poolID)
        public
        view
        virtual
        override
        returns (string memory)
    {
        return LibTerminus.terminusStorage().poolURI[poolID];
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id)
        public
        view
        virtual
        override
        returns (uint256)
    {
        require(
            account != address(0),
            "ERC1155WithTerminusStorage: balance query for the zero address"
        );
        return LibTerminus.terminusStorage().poolBalances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(address[] memory accounts, uint256[] memory ids)
        public
        view
        virtual
        override
        returns (uint256[] memory)
    {
        require(
            accounts.length == ids.length,
            "ERC1155WithTerminusStorage: accounts and ids length mismatch"
        );

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved)
        public
        virtual
        override
    {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator)
        public
        view
        virtual
        override
        returns (bool)
    {
        return
            LibTerminus.terminusStorage().globalOperatorApprovals[account][
                operator
            ];
    }

    function isApprovedForPool(uint256 poolID, address operator)
        public
        view
        returns (bool)
    {
        return LibTerminus._isApprovedForPool(poolID, operator);
    }

    function approveForPool(uint256 poolID, address operator) external {
        LibTerminus.enforcePoolIsController(poolID, _msgSender());
        LibTerminus._approveForPool(poolID, operator);
    }

    function unapproveForPool(uint256 poolID, address operator) external {
        LibTerminus.enforcePoolIsController(poolID, _msgSender());
        LibTerminus._unapproveForPool(poolID, operator);
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() ||
                isApprovedForAll(from, _msgSender()) ||
                isApprovedForPool(id, _msgSender()),
            "ERC1155WithTerminusStorage: caller is not owner nor approved"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155WithTerminusStorage: transfer caller is not owner nor approved"
        );
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(
            to != address(0),
            "ERC1155WithTerminusStorage: transfer to the zero address"
        );
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        require(
            !ts.poolNotTransferable[id],
            "ERC1155WithTerminusStorage: _safeTransferFrom -- pool is not transferable"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(
            operator,
            from,
            to,
            _asSingletonArray(id),
            _asSingletonArray(amount),
            data
        );

        uint256 fromBalance = ts.poolBalances[id][from];
        require(
            fromBalance >= amount,
            "ERC1155WithTerminusStorage: insufficient balance for transfer"
        );
        unchecked {
            ts.poolBalances[id][from] = fromBalance - amount;
        }
        ts.poolBalances[id][to] += amount;

        emit TransferSingle(operator, from, to, id, amount);

        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(
            ids.length == amounts.length,
            "ERC1155WithTerminusStorage: ids and amounts length mismatch"
        );
        require(
            to != address(0),
            "ERC1155WithTerminusStorage: transfer to the zero address"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            require(
                !ts.poolNotTransferable[id],
                "ERC1155WithTerminusStorage: _safeBatchTransferFrom -- pool is not transferable"
            );

            uint256 fromBalance = ts.poolBalances[id][from];
            require(
                fromBalance >= amount,
                "ERC1155WithTerminusStorage: insufficient balance for transfer"
            );
            unchecked {
                ts.poolBalances[id][from] = fromBalance - amount;
            }
            ts.poolBalances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(
            operator,
            from,
            to,
            ids,
            amounts,
            data
        );
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(
            to != address(0),
            "ERC1155WithTerminusStorage: mint to the zero address"
        );
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        require(
            ts.poolSupply[id] + amount <= ts.poolCapacity[id],
            "ERC1155WithTerminusStorage: _mint -- Minted tokens would exceed pool capacity"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(
            operator,
            address(0),
            to,
            _asSingletonArray(id),
            _asSingletonArray(amount),
            data
        );

        ts.poolSupply[id] += amount;
        ts.poolBalances[id][to] += amount;
        emit TransferSingle(operator, address(0), to, id, amount);

        _doSafeTransferAcceptanceCheck(
            operator,
            address(0),
            to,
            id,
            amount,
            data
        );
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(
            to != address(0),
            "ERC1155WithTerminusStorage: mint to the zero address"
        );
        require(
            ids.length == amounts.length,
            "ERC1155WithTerminusStorage: ids and amounts length mismatch"
        );

        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; i++) {
            require(
                ts.poolSupply[ids[i]] + amounts[i] <= ts.poolCapacity[ids[i]],
                "ERC1155WithTerminusStorage: _mintBatch -- Minted tokens would exceed pool capacity"
            );
            ts.poolSupply[ids[i]] += amounts[i];
            ts.poolBalances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _doSafeBatchTransferAcceptanceCheck(
            operator,
            address(0),
            to,
            ids,
            amounts,
            data
        );
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `from`
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `amount` tokens of token type `id`.
     */
    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal virtual {
        require(
            from != address(0),
            "ERC1155WithTerminusStorage: burn from the zero address"
        );
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        require(
            ts.poolBurnable[id],
            "ERC1155WithTerminusStorage: _burn -- pool is not burnable"
        );

        address operator = _msgSender();

        _beforeTokenTransfer(
            operator,
            from,
            address(0),
            _asSingletonArray(id),
            _asSingletonArray(amount),
            ""
        );

        uint256 fromBalance = ts.poolBalances[id][from];
        require(
            fromBalance >= amount,
            "ERC1155WithTerminusStorage: burn amount exceeds balance"
        );
        unchecked {
            ts.poolBalances[id][from] = fromBalance - amount;
            ts.poolSupply[id] -= amount;
        }

        emit TransferSingle(operator, from, address(0), id, amount);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        require(
            from != address(0),
            "ERC1155WithTerminusStorage: burn from the zero address"
        );
        require(
            ids.length == amounts.length,
            "ERC1155WithTerminusStorage: ids and amounts length mismatch"
        );

        address operator = _msgSender();

        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        for (uint256 i = 0; i < ids.length; i++) {
            require(
                ts.poolBurnable[ids[i]],
                "ERC1155WithTerminusStorage: _burnBatch -- pool is not burnable"
            );
        }

        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = ts.poolBalances[id][from];
            require(
                fromBalance >= amount,
                "ERC1155WithTerminusStorage: burn amount exceeds balance"
            );
            unchecked {
                ts.poolBalances[id][from] = fromBalance - amount;
                ts.poolSupply[id] -= amount;
            }
        }

        emit TransferBatch(operator, from, address(0), ids, amounts);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits a {ApprovalForAll} event.
     */
    function _setApprovalForAll(
        address owner,
        address operator,
        bool approved
    ) internal virtual {
        require(
            owner != operator,
            "ERC1155WithTerminusStorage: setting approval status for self"
        );
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.globalOperatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try
                IERC1155Receiver(to).onERC1155Received(
                    operator,
                    from,
                    id,
                    amount,
                    data
                )
            returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert(
                        "ERC1155WithTerminusStorage: ERC1155Receiver rejected tokens"
                    );
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert(
                    "ERC1155WithTerminusStorage: transfer to non ERC1155Receiver implementer"
                );
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try
                IERC1155Receiver(to).onERC1155BatchReceived(
                    operator,
                    from,
                    ids,
                    amounts,
                    data
                )
            returns (bytes4 response) {
                if (
                    response != IERC1155Receiver.onERC1155BatchReceived.selector
                ) {
                    revert(
                        "ERC1155WithTerminusStorage: ERC1155Receiver rejected tokens"
                    );
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert(
                    "ERC1155WithTerminusStorage: transfer to non ERC1155Receiver implementer"
                );
            }
        }
    }

    function _asSingletonArray(uint256 element)
        private
        pure
        returns (uint256[] memory)
    {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155Receiver.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
          

lib/@lagunagames/cu-common/src/interfaces/IConstraintFacet.sol

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

import {LibConstraints} from '../libraries/LibConstraints.sol';

interface IConstraintFacet {
    /// @notice Check if constraint for an owner is met
    /// @dev This uses @lg-commons/LibConstraintOperator to check.
    /// @param owner The owner to check
    /// @param constraint The constraint to check, must be one of the constraint types and operators defined in @lg-commons/LibConstraintOperator
    /// @return isMet True if the constraint is met, false otherwise
    function checkConstraint(address owner, LibConstraints.Constraint memory constraint) external view returns (bool);

    /// @notice Check if constraint for an owner or for the extraTokens is met
    /// @dev This uses @lg-commons/LibConstraintOperator to check.
    /// @param owner Will check if the constraint is met any tokens owned by this address
    /// @param constraint The constraint to check, must be one of the constraint types and operators defined in @lg-commons/LibConstraintOperator
    /// @param extraTokenIdsToCheck Will check if the constraint is met for these extra token ids
    /// @return isMet True if the constraint is met, false otherwise
    function checkConstraintForUserAndExtraTokens(
        address owner,
        LibConstraints.Constraint memory constraint,
        uint256[] memory extraTokenIdsToCheck
    ) external view returns (bool);
}
          

src/libraries/LibHatcheryRitualComponents.sol

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

import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {IERC20} from '../../lib/@lagunagames/lg-diamond-template/src/interfaces/IERC20.sol';
import {ERC20Burnable} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol';
import {LibToken} from '../../lib/@lagunagames/cu-common/src/libraries/LibToken.sol';
import {LibRitualData} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualData.sol';
import {TerminusFacet} from '../../lib/web3/contracts/terminus//TerminusFacet.sol';
import {LibRitualComponents} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualComponents.sol';
import {LibFarming} from './LibFarming.sol';
import {LibRewards} from './LibRewards.sol';
import {IUNIMControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IUNIMControllerFacet.sol';
import {IDarkMarksControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IDarkMarksControllerFacet.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';
import {LibMinion} from './LibMinion.sol';

library LibHatcheryRitualComponents {
    // @notice This function makes the player pay a RitualCost.
    // @param cost The cost to be paid.
    function payCost(LibRitualComponents.RitualCost memory cost) internal {
        LibRitualComponents.RitualCostTransferType transferType = LibRitualComponents.RitualCostTransferType(
            cost.transferType
        );
        if (cost.component.assetType == LibToken.TYPE_ERC20) {
            if (transferType == LibRitualComponents.RitualCostTransferType.BURN) {
                if (cost.component.asset == LibResourceLocator.unimToken()) {
                    IUNIMControllerFacet(LibResourceLocator.gameBank()).burnUNIMFrom(msg.sender, cost.component.amount);
                } else if (cost.component.asset == LibResourceLocator.darkMarkToken()) {
                    IDarkMarksControllerFacet(LibResourceLocator.gameBank()).burnDarkMarksFrom(
                        msg.sender,
                        cost.component.amount
                    );
                } else {
                    ERC20Burnable(cost.component.asset).burnFrom(msg.sender, cost.component.amount);
                }
            } else if (transferType == LibRitualComponents.RitualCostTransferType.TRANSFER) {
                if (cost.component.asset == LibResourceLocator.unimToken()) {
                    //HOTFIX for broken data in prod.
                    IUNIMControllerFacet(LibResourceLocator.gameBank()).burnUNIMFrom(msg.sender, cost.component.amount);
                } else {
                    IERC20(cost.component.asset).transferFrom(
                        msg.sender,
                        LibResourceLocator.gameBank(),
                        cost.component.amount
                    );
                }
            }
        } else if (cost.component.assetType == LibToken.TYPE_ERC1155) {
            if (transferType == LibRitualComponents.RitualCostTransferType.BURN) {
                TerminusFacet(cost.component.asset).burn(msg.sender, cost.component.poolId, cost.component.amount);
            } else if (transferType == LibRitualComponents.RitualCostTransferType.TRANSFER) {
                revert('LibHatcheryRitualComponents: ERC1155s should not be transferred as costs.');
            }
        } else {
            revert('LibHatcheryRitualComponents: Invalid cost asset type.');
        }
    }

    // @notice This function makes the player receive a RitualProduct.
    // @param product The product to be received.
    function mintProduct(LibRitualComponents.RitualProduct memory product) internal {
        LibRitualComponents.RitualProductTransferType transferType = LibRitualComponents.RitualProductTransferType(
            product.transferType
        );
        if (product.component.assetType == LibToken.TYPE_ERC20) {
            if (transferType == LibRitualComponents.RitualProductTransferType.TRANSFER) {
                IERC20(product.component.asset).transferFrom(
                    LibResourceLocator.gameBank(),
                    msg.sender,
                    product.component.amount
                );
            } else if (transferType == LibRitualComponents.RitualProductTransferType.MINT) {
                revert('LibHatcheryRitualComponents: ERC20 should not be minted as products.');
            }
        } else if (product.component.assetType == LibToken.TYPE_ERC1155) {
            if (transferType == LibRitualComponents.RitualProductTransferType.MINT) {
                TerminusFacet(product.component.asset).mint(
                    msg.sender,
                    product.component.poolId,
                    product.component.amount,
                    ''
                );
                addTokenToQueueIfMinion(product.component.poolId, product.component.amount, msg.sender);
            } else if (transferType == LibRitualComponents.RitualProductTransferType.TRANSFER) {
                revert('LibHatcheryRitualComponents: ERC1155s should not be transferred as products.');
            }
        } else {
            revert('LibHatcheryRitualComponents: Invalid product asset type.');
        }
    }

    function addTokenToQueueIfMinion(uint256 poolId, uint256 quantity, address user) internal {
        if (LibFarming.minionPoolExists(poolId)) {
            quantity = quantity * LibMinion.getMinionMultiplierForContribution(poolId);
            LibRewards.addToQueue(quantity, user);
        }
    }
}
          

lib/@lagunagames/cu-common/lib/openzeppelin-contracts/contracts/utils/math/Math.sol

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

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

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

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

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

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

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

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

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

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

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

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

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

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

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

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

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

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Extension of {ERC20} that allows token holders to destroy both their own
 * tokens and those that they have an allowance for, in a way that can be
 * recognized off-chain (via event analysis).
 */
abstract contract ERC20Burnable is Context, ERC20 {
    /**
     * @dev Destroys `amount` tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 amount) public virtual {
        _burn(_msgSender(), amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, deducting from the caller's
     * allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `amount`.
     */
    function burnFrom(address account, uint256 amount) public virtual {
        _spendAllowance(account, _msgSender(), amount);
        _burn(account, amount);
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC721/extensions/IERC721Metadata.sol

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

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

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

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}
          

lib/openzeppelin-contracts/contracts/token/ERC1155/IERC1155.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

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

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}
          

src/libraries/LibHatcheryRitualConstraints.sol

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

import {IConstraintFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IConstraintFacet.sol';
import {LibConstraintOperator} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraintOperator.sol';
import {LibConstraints} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol';
import {LibLevel} from './LibLevel.sol';
import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {LibStaking} from '../libraries/LibStaking.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';

library LibHatcheryRitualConstraints {
    // @notice This function is used to check a constraint.
    // @param constraint The constraint to check.
    function checkConstraint(LibConstraints.Constraint memory constraint, address user) internal view {
        LibConstraints.enforceValidConstraintType(constraint.constraintType);
        LibConstraints.ConstraintType constraintType = LibConstraints.ConstraintType(constraint.constraintType);
        if (constraintType == LibConstraints.ConstraintType.HATCHERY_LEVEL) {
            require(
                checkHatcheryLevelConstraint(constraint.operator, constraint.value, user),
                'LibHatcheryRitualConstraints: hatchery level constraint not met.'
            );
        } else if (
            (constraintType >= LibConstraints.ConstraintType.SHADOWCORN_RARITY &&
                constraintType <= LibConstraints.ConstraintType.SHADOWCORN_ARCANA) ||
            constraintType == LibConstraints.ConstraintType.BALANCE_SHADOWCORN
        ) {
            require(
                checkShadowcornConstraint(constraint, user),
                'LibHatcheryRitualConstraints: shadowcorn constraint not met.'
            );
        } else if (constraintType == LibConstraints.ConstraintType.BALANCE_UNICORN) {
            require(
                checkUnicornBalanceConstraint(constraint, user),
                'LibHatcheryRitualConstraints: unicorn balance constraint not met.'
            );
        }
    }

    // @notice This function is used to check if the user meets the hatchery level constraint.
    // @param operator The conditional operator that will be checked against the constraintType
    // @param value The value that will be checked with the operator against the constraintType
    function checkHatcheryLevelConstraint(uint256 operator, uint256 value, address user) internal view returns (bool) {
        return LibConstraintOperator.checkOperator(LibLevel.getHatcheryLevelForAddress(user), operator, value);
    }

    // @notice This function is used to check if the user meets a shadowcorn constraint.
    // @param constraint The constraint to check.
    function checkShadowcornConstraint(
        LibConstraints.Constraint memory constraint,
        address user
    ) internal view returns (bool) {
        return
            IConstraintFacet(LibResourceLocator.shadowcornNFT()).checkConstraintForUserAndExtraTokens(
                user,
                constraint,
                LibStaking.stakingStorage().userToStakedShadowcorns[user]
            );
    }

    // @notice This function is used to check if the user meets the unicorn balance constraint.
    // @param operator The conditional operator that will be checked against the constraintType
    // @param value The value that will be checked with the operator against the constraintType
    function checkUnicornBalanceConstraint(
        LibConstraints.Constraint memory constraint,
        address user
    ) internal view returns (bool) {
        return IConstraintFacet(LibResourceLocator.unicornNFT()).checkConstraint(user, constraint);
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibRitualData.sol

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

import {LibRitualComponents} from "../libraries/LibRitualComponents.sol";
import {LibConstraints} from "../libraries/LibConstraints.sol";

library LibRitualData {
    ///   @dev Basic rituals just hold basic data in order to easily handle the storage.
    ///   @param name The ritual's name
    ///   @param rarity The ritual's rarity expressed in integer value.
    ///   @param charges The amount of times the ritual can consume a charge to exchange costs for products.
    ///   @param soulbound Flag to indicate if the ritual is soulbound or not.
    struct BasicRitual {
        string name;
        uint8 rarity;
        uint256 charges;
        bool soulbound;
    }

    ///   @dev Rituals are the return data type, they hold the entire storage in a single struct.
    ///   @param name The ritual's name
    ///   @param rarity The ritual's rarity expressed in integer value.
    ///   @param costs The costs to pay to consume a ritual charge expressed in components.
    ///   @param products The outcome of consuming a ritual charge expressed in components.
    ///   @param constraints The restrictions to consume a ritual charge expressed in constraints.
    ///   @param charges The amount of times the ritual can consume a charge to exchange costs for products.
    ///   @param soulbound Flag to indicate if the ritual is soulbound or not.
    struct Ritual {
        string name;
        uint8 rarity;
        LibRitualComponents.RitualCost[] costs; 
        LibRitualComponents.RitualProduct[] products; 
        LibConstraints.Constraint[] constraints;
        uint256 charges;
        bool soulbound;
    }
}
 
          

lib/web3/contracts/diamond/interfaces/IDiamondCut.sol

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

// Adapted from the Diamond 3 reference implementation by Nick Mudge:
// https://github.com/mudgen/diamond-3-hardhat

interface IDiamondCut {
    enum FacetCutAction {
        Add,
        Replace,
        Remove
    }
    // Add=0, Replace=1, Remove=2

    struct FacetCut {
        address facetAddress;
        FacetCutAction action;
        bytes4[] functionSelectors;
    }

    /// @notice Add/replace/remove any number of functions and optionally execute
    ///         a function with delegatecall
    /// @param _diamondCut Contains the facet addresses and function selectors
    /// @param _init The address of the contract or facet to execute _calldata
    /// @param _calldata A function call, including function selector and arguments
    ///                  _calldata is executed with delegatecall on _init
    function diamondCut(
        FacetCut[] calldata _diamondCut,
        address _init,
        bytes calldata _calldata
    ) external;

    event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
          

lib/openzeppelin-contracts/contracts/utils/Context.sol

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

pragma solidity ^0.8.0;

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

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

src/entities/level/LevelUpgradeBonus.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {LevelUpgradeBonusType} from './LevelUpgradeBonusType.sol';
import {LevelUpgradeBonusFrequency} from './LevelUpgradeBonusFrequency.sol';

/*
    @title Level Upgrade Bonus
    @notice This struct is used to define the upgrade bonus for each level
    @dev Examples of bonuses:
    +1 Dark Mark per hour
        bonusType = HUSK_GENERATION
        bonusValue = 1
        bonusFrequency = HOURLY
    +10 Husk storage cap
        bonusType = HUSK_STORAGE
        bonusValue = 10
        bonusFrequency = PERMANENT
*/
struct LevelUpgradeBonus {
    LevelUpgradeBonusType bonusType;
    uint256 bonusValue;
    LevelUpgradeBonusFrequency bonusFrequency;
}
          

src/libraries/LibStaking.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {LibStructs} from './LibStructs.sol';
import {LibFarming} from './LibFarming.sol';
import {IERC721} from '../../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol';
import {TerminusFacet} from '../../lib/web3/contracts/terminus//TerminusFacet.sol';
import {LibValidate} from '../../lib/@lagunagames/cu-common/src/libraries/LibValidate.sol';
import {IShadowcornStatsFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IShadowcornStatsFacet.sol';
import {LibLevel} from './LibLevel.sol';
import {LevelFullInfo} from '../entities/level/LevelFullInfo.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';

/// @title LibStaking
/// @author Shiva Shanmuganathan
/// @dev Library implementation of the staking in minion hatchery.
library LibStaking {
    event ShadowcornStaked(
        uint256 indexed tokenId,
        address indexed staker,
        bool staked,
        uint256 farmableItemId,
        uint256 stakeTimestamp
    );

    event SetShadowcornFarmingData(
        address indexed player,
        uint256 indexed tokenId,
        LibStructs.FarmableItem farmingData
    );

    event ShadowcornUnstaked(
        uint256 indexed tokenId,
        address indexed staker,
        bool staked,
        uint256 farmableItemId,
        uint256 stakedTime
    );

    event ForcedUnstakeExecuted(address indexed player, uint256 indexed tokenId);

    event ShadowcornHarvest(
        address indexed player,
        uint256 indexed tokenId,
        uint256 indexed poolId,
        uint256 stakingRewards
    );

    uint8 private constant SHADOWCORN_CLASS_FIRE = 1;
    uint8 private constant SHADOWCORN_CLASS_SLIME = 2;
    uint8 private constant SHADOWCORN_CLASS_VOLT = 3;
    uint8 private constant SHADOWCORN_CLASS_SOUL = 4;
    uint8 private constant SHADOWCORN_CLASS_NEBULA = 5;

    uint8 private constant SHADOWCORN_STAT_MIGHT = 1;
    uint8 private constant SHADOWCORN_STAT_WICKEDNESS = 2;
    uint8 private constant SHADOWCORN_STAT_TENACITY = 3;
    uint8 private constant SHADOWCORN_STAT_CUNNING = 4;
    uint8 private constant SHADOWCORN_STAT_ARCANA = 5;

    // Position to store the staking storage
    bytes32 private constant STAKING_STORAGE_POSITION = keccak256('CryptoUnicorns.Staking.Storage');

    // Staking storage struct that holds all relevant stake data
    struct LibStakingStorage {
        mapping(uint256 tokenId => LibStructs.StakeData stakeData) shadowcornStakeData;
        mapping(uint256 tokenId => LibStructs.FarmableItem farmableItemData) shadowcornFarmingData;
        mapping(address user => uint256[] stakedTokenIds) userToStakedShadowcorns;
        mapping(uint256 levelId => uint256 cumulativeBonus) hatcheryLevelStakingCumulativeBonus;
        mapping(uint256 levelId => uint256 cumulativeHuskLimit) hatcheryLevelHuskLimitCumulative;
    }

    /// @notice Sets the cumulative husk limit for a given hatchery level
    /// @param hatcheryLevel - The hatchery level
    /// @param cumulativeHuskLimit - The cumulative husk limit
    /// @dev the limit should be set times * 100: example: if the limit is meant to be 0.2, the value should be set to 20.
    function setHatcheryLevelHuskLimitCumulative(uint256 hatcheryLevel, uint256 cumulativeHuskLimit) internal {
        stakingStorage().hatcheryLevelHuskLimitCumulative[hatcheryLevel] = cumulativeHuskLimit;
    }

    /// @notice Gets the cumulative husk limit for a given hatchery level
    /// @return cumulativeHuskLimit - The cumulative husk limit
    function getHatcheryLevelHuskLimitCumulative(
        uint256 hatcheryLevel
    ) internal view returns (uint256 cumulativeHuskLimit) {
        cumulativeHuskLimit = stakingStorage().hatcheryLevelHuskLimitCumulative[hatcheryLevel];
    }

    /// @notice Sets the cumulative staking bonus for a given hatchery level
    /// @param hatcheryLevel - The hatchery level
    /// @param cumulativeBonus - The cumulative bonus
    /// @dev the bonuses should be set times * 100: example: if the bonus is meant to be 0.2, the bonus should be set to 20.
    function setHatcheryLevelCumulativeBonus(uint256 hatcheryLevel, uint256 cumulativeBonus) internal {
        stakingStorage().hatcheryLevelStakingCumulativeBonus[hatcheryLevel] = cumulativeBonus;
    }

    /// @notice Gets the cumulative staking bonus for a given hatchery level
    /// @param hatcheryLevel - The hatchery level
    function getHatcheryLevelCumulativeBonus(uint256 hatcheryLevel) internal view returns (uint256) {
        return stakingStorage().hatcheryLevelStakingCumulativeBonus[hatcheryLevel];
    }

    function resetStakedArray(address user) internal {
        stakingStorage().userToStakedShadowcorns[user] = new uint256[](0);
    }

    /// @notice Get staked shadowcorns of sender
    /// @dev Returns paginated data of player's staked shadowcorns. Max page size is 12,
    /// The `moreEntriesExist` flag is TRUE when additional pages are available past the current call.
    /// The first page is 0
    /// @return stakedShadowcorns - Array of staked shadowcorns
    /// @return moreEntriesExist - Flag to indicate if more entries exist
    function getStakedShadowcorns(
        address staker,
        uint32 _pageNumber
    ) internal view returns (uint256[] memory stakedShadowcorns, bool moreEntriesExist) {
        uint256 balance = stakingStorage().userToStakedShadowcorns[staker].length;
        uint start = _pageNumber * 12;
        uint count = balance - start;

        if (count > 12) {
            count = 12;
            moreEntriesExist = true;
        }

        stakedShadowcorns = new uint256[](count);

        for (uint i = 0; i < count; ++i) {
            uint256 indx = start + i;

            stakedShadowcorns[i] = (stakingStorage().userToStakedShadowcorns[staker][indx]);
        }
    }

    /// Transfer a Shadowcorn into the Hatchery contract to begin yield farming.
    /// @param tokenId - The NFT to transfer
    /// @param farmableItemId - Id for the product to farm
    /// @custom:emits Transfer, ShadowcornStaked
    function stakeShadowcorn(uint256 tokenId, uint256 farmableItemId) internal {
        // check valid farmable item ID
        LibFarming.enforceValidFarmableItemId(farmableItemId);

        // check if farmable item is active
        require(
            LibFarming.farmingStorage().farmableItemData[farmableItemId].active,
            'LibStaking: Farmable item not active'
        );

        IERC721 shadowcornContract = IERC721(LibResourceLocator.shadowcornNFT());
        // check that the Shadowcorn is owned by the sender
        address shadowcornOwner = shadowcornContract.ownerOf(tokenId);
        require(shadowcornContract.ownerOf(tokenId) == msg.sender, 'LibStaking: Not owner of Shadowcorn');

        // transfer the Shadowcorn to the Hatchery contract
        // replace this with the special transfer function from the Shadowcorn contract
        shadowcornContract.transferFrom(msg.sender, address(this), tokenId);

        // set the staking data for the Shadowcorn
        stakingStorage().shadowcornStakeData[tokenId] = LibStructs.StakeData({
            staker: msg.sender,
            staked: true,
            farmableItemId: farmableItemId,
            stakeTimestamp: block.timestamp
        });
        // Get shadowcorn stats

        (uint256 might, uint256 wickedness, uint256 tenacity, uint256 cunning, uint256 arcana) = IShadowcornStatsFacet(
            LibResourceLocator.shadowcornNFT()
        ).getStats(tokenId);

        // set the farming data and farming bonus for the Shadowcorn
        stakingStorage().shadowcornFarmingData[tokenId] = calculateFarmingBonus(
            tokenId,
            farmableItemId,
            shadowcornOwner,
            (might + wickedness + tenacity + cunning + arcana)
        );

        // add the Shadowcorn to the staker's list of staked Shadowcorns
        stakingStorage().userToStakedShadowcorns[msg.sender].push(tokenId);

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        // emit the event
        emit ShadowcornStaked(
            tokenId,
            stakeData.staker,
            stakeData.staked,
            stakeData.farmableItemId,
            stakeData.stakeTimestamp
        );
        emit SetShadowcornFarmingData(stakeData.staker, tokenId, stakingStorage().shadowcornFarmingData[tokenId]);
    }

    //  Collect any goods that have been farmed by a Shadowcorn, and immediately
    //  re-stake the Shadowcorn back to yield farming. Progress toward incomplete
    //  items is carried over.
    //  @param tokenId - The NFT to unstake
    //  @custom:emits ShadowcornHarvest
    function harvestAndRestakeShadowcorn(uint256 tokenId) internal {
        // check that the Shadowcorn is staked
        require(stakingStorage().shadowcornStakeData[tokenId].staked == true, 'LibStaking: Shadowcorn not staked.');

        address shadowcornOwner = stakingStorage().shadowcornStakeData[tokenId].staker;
        require(shadowcornOwner == msg.sender, 'LibStaking: Not owner of Shadowcorn.');

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        // get the farming data
        LibStructs.FarmableItem memory farmingData = stakingStorage().shadowcornFarmingData[tokenId];

        uint256 stakingRewards = calculateStakingRewards(tokenId);

        // reset the staking data for the Shadowcorn
        stakingStorage().shadowcornStakeData[tokenId] = LibStructs.StakeData({
            staker: stakeData.staker,
            staked: true,
            farmableItemId: stakeData.farmableItemId,
            stakeTimestamp: block.timestamp
        });

        // Get shadowcorn stats

        (uint256 might, uint256 wickedness, uint256 tenacity, uint256 cunning, uint256 arcana) = IShadowcornStatsFacet(
            LibResourceLocator.shadowcornNFT()
        ).getStats(tokenId);

        // reset the farming data for the Shadowcorn
        stakingStorage().shadowcornFarmingData[tokenId] = calculateFarmingBonus(
            tokenId,
            stakeData.farmableItemId,
            shadowcornOwner,
            (might + wickedness + tenacity + cunning + arcana)
        );

        // setting terminus address
        TerminusFacet terminus = TerminusFacet(LibResourceLocator.shadowcornItems());

        // mint terminus tokens to the staker
        terminus.mint(msg.sender, farmingData.poolId, stakingRewards, '');

        // emit the harvest event
        emit ShadowcornHarvest(stakeData.staker, tokenId, farmingData.poolId, stakingRewards);
    }

    function returnShadowcorn(uint256 tokenId, address user) internal {
        IERC721 shadowcornContract = IERC721(LibResourceLocator.shadowcornNFT());
        // check that the Shadowcorn is owned by the sender
        require(
            shadowcornContract.ownerOf(tokenId) == address(this),
            'LibStaking: Hatchery not in possession of Shadowcorn.'
        );

        // transfer the Shadowcorn back to the owner
        shadowcornContract.transferFrom(address(this), user, tokenId);
    }

    //  Unstake a Shadowcorn, transfer it back to the owner's wallet, and collect
    //  any goods that have been farmed. Progress toward incomplete items are lost.
    //  @param tokenId - The NFT to unstake
    //  @custom:emits Transfer, ShadowcornUnstaked, ShadowcornHarvest
    function unstakeShadowcorn(uint256 tokenId) internal {
        // check that the Shadowcorn is staked
        require(stakingStorage().shadowcornStakeData[tokenId].staked == true, 'LibStaking: Shadowcorn not staked.');

        require(
            stakingStorage().shadowcornStakeData[tokenId].staker == msg.sender,
            'LibStaking: Not owner of Shadowcorn.'
        );

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        // get the farming data
        LibStructs.FarmableItem memory farmingData = stakingStorage().shadowcornFarmingData[tokenId];

        uint256 stakingRewards = calculateStakingRewards(tokenId);

        resetStakingData(tokenId);

        // setting terminus address
        TerminusFacet terminus = TerminusFacet(LibResourceLocator.shadowcornItems());

        // mint terminus tokens to the staker
        terminus.mint(msg.sender, farmingData.poolId, stakingRewards, '');

        // emit the harvest event
        emit ShadowcornHarvest(stakeData.staker, tokenId, farmingData.poolId, stakingRewards);

        IERC721 shadowcornContract = IERC721(LibResourceLocator.shadowcornNFT());

        // remove tokenId element from mapping user=>staked shadowcorns
        removeStakedShadowcornFromUserList(tokenId);

        // transfer the Shadowcorn back to the owner
        shadowcornContract.transferFrom(address(this), msg.sender, tokenId);

        // emit the event
        emit ShadowcornUnstaked(
            tokenId,
            stakingStorage().shadowcornStakeData[tokenId].staker,
            false,
            stakingStorage().shadowcornStakeData[tokenId].farmableItemId,
            block.timestamp - stakingStorage().shadowcornStakeData[tokenId].stakeTimestamp
        );
    }

    function resetStakingData(uint256 tokenId) internal {
        // reset the staking data for the Shadowcorn
        delete stakingStorage().shadowcornStakeData[tokenId];

        // reset the farming data for the Shadowcorn
        delete stakingStorage().shadowcornFarmingData[tokenId];
    }

    //  Transfer a Shadowcorn back to the owner immediately.
    //  No yields are rewarded.
    //  @custom:emits Transfer, ShadowcornUnstaked
    function forceUnstake(uint256 tokenId) internal {
        // check if ownerOf tokenId is msg.sender
        IERC721 shadowcornContract = IERC721(LibResourceLocator.shadowcornNFT());

        // check that the Shadowcorn is staked
        require(stakingStorage().shadowcornStakeData[tokenId].staked == true, 'LibStaking: Shadowcorn not staked');

        // check that the msg.sender is the owner of the Shadowcorn
        require(
            stakingStorage().shadowcornStakeData[tokenId].staker == msg.sender,
            'LibStaking: Not owner of Shadowcorn'
        );

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        resetStakingData(tokenId);

        // remove tokenId element from mapping user=>staked shadowcorns
        removeStakedShadowcornFromUserList(tokenId);

        // transfer the Shadowcorn back to the owner
        shadowcornContract.transferFrom(address(this), msg.sender, tokenId);

        // emit the event
        emit ShadowcornUnstaked(
            tokenId,
            stakeData.staker,
            false,
            stakeData.farmableItemId,
            block.timestamp - stakeData.stakeTimestamp
        );

        emit ForcedUnstakeExecuted(stakeData.staker, tokenId);
    }

    function calculateFarmingBonus(
        uint256 tokenId,
        uint256 farmableItemId
    ) internal view returns (LibStructs.FarmableItem memory shadowcornFarmingData) {
        // Get shadowcorn stats

        (uint256 might, uint256 wickedness, uint256 tenacity, uint256 cunning, uint256 arcana) = IShadowcornStatsFacet(
            LibResourceLocator.shadowcornNFT()
        ).getStats(tokenId);
        return
            calculateFarmingBonus(
                tokenId,
                farmableItemId,
                IERC721(LibResourceLocator.shadowcornNFT()).ownerOf(tokenId),
                (might + wickedness + tenacity + cunning + arcana)
            );
    }

    function calculateFarmingBonus(
        uint256 tokenId,
        uint256 farmableItemId,
        address shadowcornOwner,
        uint256 shadowcornTotalStats
    ) internal view returns (LibStructs.FarmableItem memory shadowcornFarmingData) {
        //check is shadowcorn is staked
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];
        if (stakeData.staked) {
            shadowcornOwner = stakeData.staker;
        } else {
            shadowcornOwner = IERC721(LibResourceLocator.shadowcornNFT()).ownerOf(tokenId);
        }

        // check that the Shadowcorn tokenId is valid
        LibValidate.enforceNonZeroAddress(shadowcornOwner);

        // get the farming storage
        LibFarming.LibFarmingStorage storage lfs = LibFarming.farmingStorage();
        LibStructs.FarmableItem memory farmableItem = lfs.farmableItemData[farmableItemId];
        shadowcornFarmingData = farmableItem;

        (uint256 class, uint256 rarity, uint256 stat) = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT())
            .getClassRarityAndStat(tokenId, farmableItem.stat);

        // Set the baseRate to the FarmableItem’s hourlyRate (ok if this is 0)
        uint256 baseRate = farmableItem.hourlyRate;

        // If the stat is non-zero, add 1/100th of the stat to the baseRate (will later by divided by 100 when calculating rewards)
        if (stat > 0) {
            baseRate = stat;
        }

        // If receivesHatcheryLevelBonus is true, add the cumulative level bonus (the same calculation we’re currently using)
        if (farmableItem.receivesHatcheryLevelBonus) {
            uint256 hatcheryLevel = LibLevel.getHatcheryLevelForAddress(shadowcornOwner);
            baseRate += stakingStorage().hatcheryLevelStakingCumulativeBonus[hatcheryLevel];
        }

        uint256 multiplierRate = 10;

        // If receivesRarityBonus is true, add the rarity multiplier bonus
        if (farmableItem.receivesRarityBonus) {
            multiplierRate += getMultiplicativeRarityBonus(rarity);
        }

        // If class is non-zero, and class matches the Shadowcorn, add the class multiplier bonus
        if (class > 0) {
            multiplierRate += getMultiplicativeClassBonus(class, farmableItem.class);
        }

        //This value is stored times 1000, it should be divided by 1000 when used
        shadowcornFarmingData.hourlyRate = baseRate * multiplierRate;

        // Calculate cap
        if (farmableItem.cap > 0) {
            // If the FarmableItem’s cap is non-zero, use that number
            shadowcornFarmingData.cap = farmableItem.cap;
        } else {
            // If the cap is zero, calculate the yield cap
            // yieldCap = (total shadowcorn stats / 10)
            uint256 yieldCap = shadowcornTotalStats;

            // yieldCap += hatchery Level Cumulative HuskLimit Bonus[level]
            yieldCap += getHatcheryLevelHuskLimitCumulative(LibLevel.getHatcheryLevelForAddress(shadowcornOwner)) * 10;

            // yieldCap *= getMultiplicativeRarityBonus
            yieldCap *= (10 + getMultiplicativeRarityBonus(rarity));

            shadowcornFarmingData.cap = yieldCap / 100;
        }
    }

    function getMultiplicativeClassBonus(
        uint256 shadowcornClass,
        uint256 farmableItemClass
    ) internal pure returns (uint256) {
        if (shadowcornClass == farmableItemClass) {
            //This is meant to be 0.5
            return 5;
        }
        return 0;
    }

    function getMultiplicativeRarityBonus(uint256 rarity) internal pure returns (uint256) {
        if (rarity == 1) {
            //This is meant to be 0.1
            return 1;
        } else if (rarity == 2) {
            //This is meant to be 1
            return 10;
        }
        //This is meant to be 4
        return 40;
    }

    function calculateStakingRewards(uint256 tokenId) internal view returns (uint256) {
        // get the staking storage
        LibStakingStorage storage lss = stakingStorage();

        // get the staking data
        LibStructs.StakeData memory stakeData = lss.shadowcornStakeData[tokenId];

        // get the farming data
        LibStructs.FarmableItem memory farmingData = lss.shadowcornFarmingData[tokenId];

        // get the time since the last update
        uint256 timeSinceStaking = block.timestamp - stakeData.stakeTimestamp;

        // calculate the staking reward
        uint256 stakingReward = (timeSinceStaking * farmingData.hourlyRate) / 3600;

        //Divide by 1000 to scale back to the original value
        stakingReward = stakingReward / 1000;

        // if the staking reward is greater than the cap, set it to the cap
        if (stakingReward > farmingData.cap) {
            stakingReward = farmingData.cap;
        }

        return stakingReward;
    }

    function stakingStorage() internal pure returns (LibStakingStorage storage lss) {
        bytes32 position = STAKING_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            lss.slot := position
        }
    }

    function removeStakedShadowcornFromUserList(uint256 _tokenId) internal {
        LibStakingStorage storage lss = stakingStorage();
        uint256[] storage arr = lss.userToStakedShadowcorns[msg.sender];

        for (uint i = 0; i < arr.length; i++) {
            if (arr[i] == _tokenId) {
                arr[i] = arr[arr.length - 1];
                arr.pop();
                return;
            }
        }
        revert('LibStaking: shadowcorn not found on user list.');
    }

    function getFarmingBonusBreakdown(
        uint256 tokenId
    ) internal view returns (uint256 baseRate, uint256 hatcheryLevelBonus, uint256 classBonus, uint256 rarityBonus) {
        // get the farming storage
        LibStructs.FarmableItem memory farmableItem = stakingStorage().shadowcornFarmingData[tokenId];

        (uint256 class, uint256 rarity, uint256 stat) = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT())
            .getClassRarityAndStat(tokenId, farmableItem.stat);

        baseRate = stat;
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];
        address shadowcornOwner;
        if (stakeData.staked) {
            shadowcornOwner = stakeData.staker;
        } else {
            shadowcornOwner = IERC721(LibResourceLocator.shadowcornNFT()).ownerOf(tokenId);
        }
        uint256 hatcheryLevel = LibLevel.getHatcheryLevelForAddress(shadowcornOwner);
        hatcheryLevelBonus = stakingStorage().hatcheryLevelStakingCumulativeBonus[hatcheryLevel];

        classBonus = getMultiplicativeClassBonus(class, farmableItem.class);
        rarityBonus = getMultiplicativeRarityBonus(rarity);
    }

    function getFarmingRateByFarmableItems(
        uint256 tokenId,
        uint256[] memory farmableItemIds
    ) internal view returns (uint256[] memory) {
        uint256 length = farmableItemIds.length;
        uint256[] memory husksPerFarmable = new uint256[](length);
        for (uint256 i = 0; i < length; i++) {
            husksPerFarmable[i] = calculateFarmingBonus(tokenId, farmableItemIds[i]).hourlyRate;
        }
        return husksPerFarmable;
    }

    function getFarmingHusksPerHour(
        uint256 tokenId
    )
        internal
        view
        returns (uint256 fireHusks, uint256 slimeHusks, uint256 voltHusks, uint256 soulHusks, uint256 nebulaHusks)
    {
        //This should then be divided by 1000 when used
        IERC721 shadowcornContract = IERC721(LibResourceLocator.shadowcornNFT());
        address shadowcornOwner = shadowcornContract.ownerOf(tokenId);
        uint256 hatcheryLevel = LibLevel.getHatcheryLevelForAddress(shadowcornOwner);
        uint256 hatcheryLevelBonus = stakingStorage().hatcheryLevelStakingCumulativeBonus[hatcheryLevel];
        uint256 rarityBonus = getMultiplicativeRarityBonus(
            IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getRarity(tokenId)
        );
        uint256 class = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getClass(tokenId);
        fireHusks = getFireHuskRate(tokenId, rarityBonus, hatcheryLevelBonus, class);
        slimeHusks = getSlimeHuskRate(tokenId, rarityBonus, hatcheryLevelBonus, class);
        voltHusks = getVoltHuskRate(tokenId, rarityBonus, hatcheryLevelBonus, class);
        soulHusks = getSoulHuskRate(tokenId, rarityBonus, hatcheryLevelBonus, class);
        nebulaHusks = getNebulaHuskRate(tokenId, rarityBonus, hatcheryLevelBonus, class);
    }

    function getFireHuskRate(
        uint256 tokenId,
        uint256 rarityBonus,
        uint256 hatcheryLevelBonus,
        uint256 class
    ) internal view returns (uint256) {
        uint256 might = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getMight(tokenId);
        uint256 fireClassBonus = getMultiplicativeClassBonus(class, 1);
        return (might + hatcheryLevelBonus) * (10 + rarityBonus + fireClassBonus);
    }

    function getSlimeHuskRate(
        uint256 tokenId,
        uint256 rarityBonus,
        uint256 hatcheryLevelBonus,
        uint256 class
    ) internal view returns (uint256) {
        uint256 wickedness = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getWickedness(tokenId);
        uint256 slimeClassBonus = getMultiplicativeClassBonus(class, 2);
        return (wickedness + hatcheryLevelBonus) * (10 + rarityBonus + slimeClassBonus);
    }

    function getVoltHuskRate(
        uint256 tokenId,
        uint256 rarityBonus,
        uint256 hatcheryLevelBonus,
        uint256 class
    ) internal view returns (uint256) {
        uint256 tenacity = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getTenacity(tokenId);
        uint256 voltClassBonus = getMultiplicativeClassBonus(class, 3);
        return (tenacity + hatcheryLevelBonus) * (10 + rarityBonus + voltClassBonus);
    }

    function getSoulHuskRate(
        uint256 tokenId,
        uint256 rarityBonus,
        uint256 hatcheryLevelBonus,
        uint256 class
    ) internal view returns (uint256) {
        uint256 cunning = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getCunning(tokenId);
        uint256 soulClassBonus = getMultiplicativeClassBonus(class, 4);
        return (cunning + hatcheryLevelBonus) * (10 + rarityBonus + soulClassBonus);
    }

    function getNebulaHuskRate(
        uint256 tokenId,
        uint256 rarityBonus,
        uint256 hatcheryLevelBonus,
        uint256 class
    ) internal view returns (uint256) {
        uint256 arcana = IShadowcornStatsFacet(LibResourceLocator.shadowcornNFT()).getArcana(tokenId);
        uint256 nebulaClassBonus = getMultiplicativeClassBonus(class, 5);
        return (arcana + hatcheryLevelBonus) * (10 + rarityBonus + nebulaClassBonus);
    }

    function getFarmableItemByShadowcornId(uint256 tokenId) internal view returns (LibStructs.FarmableItem memory) {
        return stakingStorage().shadowcornFarmingData[tokenId];
    }

    /// @notice Computes the time remaining until the cap is reached for a given token
    /// @param tokenId The ID of the token for which to calculate the time
    /// @return timeToReachCap The time remaining, in seconds, until the cap is reached
    function computeTimeToReachCap(uint256 tokenId) internal view returns (uint256 timeToReachCap) {
        // get the farming data
        LibStructs.FarmableItem memory farmingData = stakingStorage().shadowcornFarmingData[tokenId];

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        if (stakeData.stakeTimestamp == 0) {
            return 0;
        }

        // get the time since the last update
        uint256 timeSinceStaking = block.timestamp - stakeData.stakeTimestamp;

        // cap = hourlyRate * totalTimeToReachCap
        // totalTimeToReachCap = cap / hourlyRate
        // Convert totalTimeToReachCap to seconds and hourlyRate to decimals by multiplying with 3600 and 1000 respectively.
        uint256 totalTimeToReachCap = ((farmingData.cap * 3600 * 1000) / farmingData.hourlyRate);

        if (timeSinceStaking > totalTimeToReachCap) {
            return 0;
        }

        timeToReachCap = totalTimeToReachCap - timeSinceStaking;

        return (timeToReachCap);
    }

    /// @notice Computes the time remaining until the next husk is created for a given token
    /// @param tokenId The ID of the token for which to calculate the time
    /// @return timeUntilNextHusk The time remaining, in seconds, until the next husk is created
    function getTimeUntilNextHusk(uint256 tokenId) internal view returns (uint256 timeUntilNextHusk) {
        // get the farming data
        LibStructs.FarmableItem memory farmingData = stakingStorage().shadowcornFarmingData[tokenId];

        // get the staking data
        LibStructs.StakeData memory stakeData = stakingStorage().shadowcornStakeData[tokenId];

        // get hourly rate and time to get single husk
        uint256 hourlyRate = farmingData.hourlyRate;
        uint256 timeToGetSingleHusk = (3600 * 1000) / (hourlyRate);

        // get the time since the last update
        uint256 timeSinceStaking = block.timestamp - stakeData.stakeTimestamp;

        if (timeToGetSingleHusk >= timeSinceStaking) {
            return timeToGetSingleHusk - timeSinceStaking;
        }

        // get the time until next husk
        timeUntilNextHusk = timeToGetSingleHusk - (timeSinceStaking % timeToGetSingleHusk);

        return timeUntilNextHusk;
    }

    /// @notice Retrieves staking details including the husks created, time to reach cap, cap amount, and time until next husk
    /// @param tokenId The ID of the token for which to retrieve the details
    /// @return husksCreated The number of husks created since staking
    /// @return timeToReachCap The time remaining, in seconds, until the cap is reached
    /// @return capAmount The cap amount
    /// @return timeUntilNextHusk The time remaining, in seconds, until the next husk is created
    function getStakingDetails(
        uint256 tokenId
    )
        internal
        view
        returns (uint256 husksCreated, uint256 timeToReachCap, uint256 capAmount, uint256 timeUntilNextHusk)
    {
        husksCreated = calculateStakingRewards(tokenId);
        timeToReachCap = computeTimeToReachCap(tokenId);
        capAmount = stakingStorage().shadowcornFarmingData[tokenId].cap;
        timeUntilNextHusk = getTimeUntilNextHusk(tokenId);

        return (husksCreated, timeToReachCap, capAmount, timeUntilNextHusk);
    }

    /// @notice Retrieves the staking information for a specific Shadowcorn token
    /// @param tokenId The ID of the Shadowcorn token for which to retrieve the staking information
    /// @return A LibStructs.StakeData struct containing the staking details for the specified token
    function getStakingInfoByShadowcornId(uint256 tokenId) internal view returns (LibStructs.StakeData memory) {
        return stakingStorage().shadowcornStakeData[tokenId];
    }

    function getHatcheryLevelFullInfo(uint256 _hatcheryLevel) internal view returns (LevelFullInfo memory fullInfo) {
        fullInfo.unlockCosts = LibLevel.getHatcheryLevelUnlockCosts(_hatcheryLevel);
        fullInfo.cumulativeBonus = getHatcheryLevelCumulativeBonus(_hatcheryLevel);
        fullInfo.cumulativeHuskLimit = getHatcheryLevelHuskLimitCumulative(_hatcheryLevel);
        fullInfo.hatcheryLevel = _hatcheryLevel;
    }

    function getHatcheryLevelsFullInfo(uint8 page) internal view returns (LevelFullInfo[5] memory levelsInfo) {
        uint256 initialLevel = page * 5 + 1;
        for (uint256 i = 0; i < 5; i++) {
            levelsInfo[i] = getHatcheryLevelFullInfo(initialLevel + i);
        }
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol

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

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

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

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _ownerOf(tokenId);
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner or approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _safeTransfer(from, to, tokenId, data);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _ownerOf(tokenId) != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId, 1);

        // Check that tokenId was not minted by `_beforeTokenTransfer` hook
        require(!_exists(tokenId), "ERC721: token already minted");

        unchecked {
            // Will not overflow unless all 2**256 token ids are minted to the same owner.
            // Given that tokens are minted one by one, it is impossible in practice that
            // this ever happens. Might change if we allow batch minting.
            // The ERC fails to describe this case.
            _balances[to] += 1;
        }

        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId, 1);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId, 1);

        // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
        owner = ERC721.ownerOf(tokenId);

        // Clear approvals
        delete _tokenApprovals[tokenId];

        unchecked {
            // Cannot overflow, as that would require more tokens to be burned/transferred
            // out than the owner initially received through minting and transferring in.
            _balances[owner] -= 1;
        }
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId, 1);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId, 1);

        // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");

        // Clear approvals from the previous owner
        delete _tokenApprovals[tokenId];

        unchecked {
            // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
            // `from`'s balance is the number of token held, which is at least one before the current
            // transfer.
            // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
            // all 2**256 token ids to be minted, which in practice is impossible.
            _balances[from] -= 1;
            _balances[to] += 1;
        }
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId, 1);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
     * - When `from` is zero, the tokens will be minted for `to`.
     * - When `to` is zero, ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}

    /**
     * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
     * - When `from` is zero, the tokens were minted for `to`.
     * - When `to` is zero, ``from``'s tokens were burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
     * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
     * that `ownerOf(tokenId)` is `a`.
     */
    // solhint-disable-next-line func-name-mixedcase
    function __unsafe_increaseBalance(address account, uint256 amount) internal {
        _balances[account] += amount;
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

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

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

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

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

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

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

lib/@lagunagames/cu-common/lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol

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

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

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

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}
          

src/libraries/LibHatcheryRituals.sol

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

import {LibHatcheryRitualConstraints} from './LibHatcheryRitualConstraints.sol';
import {LibConstraints} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol';
import {LibHatcheryRitualComponents} from './LibHatcheryRitualComponents.sol';
import {LibRitualComponents} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualComponents.sol';
import {LibConstraintOperator} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraintOperator.sol';
import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {IRitualFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IRitualFacet.sol';
import {LibRNG} from '../../lib/@lagunagames/cu-common/src/libraries/LibRNG.sol';
import {LibRitualNames} from './LibRitualNames.sol';
import {LibArray} from './LibArray.sol';
import {LibRitualData} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualData.sol';
import {LibString} from '../../lib/@lagunagames/cu-common/src/libraries/LibString.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';

library LibHatcheryRituals {
    event RitualTemplateCreated(uint256 indexed id);
    event RitualTemplatePoolCreated(uint256 indexed id);
    event AffixCreated(uint256 indexed id);
    event AffixBucketCreated(uint256 indexed id, uint256[] affixIds);
    event BeginRitualCreation(
        address indexed playerWallet,
        uint256 indexed ritualTemplatePoolId,
        uint256 indexed vrfRequestId
    );
    event FinishRitualCreation(
        uint256 indexed vrfRequestId,
        uint256 ritualTemplateId,
        uint256 indexed ritualTokenId,
        uint256[] affixIdsApplied,
        address indexed user
    );

    event HatcheryAffixWarning(string warningText);

    struct HatcheryRitualStorage {
        uint256 lastTemplateId;
        uint256 lastPoolId;
        uint256 lastAffixId;
        mapping(uint256 => BasicRitualTemplate) basicRitualTemplateByRitualTemplateId;
        mapping(uint256 => LibConstraints.Constraint[]) consumptionConstraintsByRitualTemplateId;
        mapping(uint256 => LibRitualComponents.RitualCost[]) consumptionCostsByRitualTemplateId;
        mapping(uint256 => LibRitualComponents.RitualProduct[]) consumptionProductsByRitualTemplateId;
        mapping(uint256 => LibConstraints.Constraint[]) creationConstraintsByTemplatePoolId;
        mapping(uint256 => LibRitualComponents.RitualCost[]) creationCostsByTemplatePoolId;
        mapping(uint256 => uint256[]) affixBucketIdsByRitualTemplateId;
        mapping(uint256 => uint256[]) ritualTemplateIdsByTemplatePoolId;
        mapping(uint256 => uint256[]) ritualTemplateWeightsByTemplatePoolId;
        mapping(uint256 => uint256) ritualTemplateSumWeightByTemplatePoolId;
        mapping(uint256 => Affix) affixById;
        mapping(uint256 => uint256[]) affixIdsByAffixBucketId;
        mapping(uint256 => uint256) ritualTemplatePoolIdByVRFRequestId;
        mapping(uint256 => address) playerWalletByVRFRequestId;
        uint256 lastAffixBucketId;
        uint256 maxRitualsPerBatch;
    }

    enum AffixType {
        NONE,
        COST,
        PRODUCT,
        CHARGES,
        CONSTRAINT,
        SOULBOUND
    }

    struct Affix {
        AffixType affixType;
        bool isPositive;
        uint256 charges;
        LibRitualComponents.RitualCost cost;
        LibRitualComponents.RitualProduct product;
        LibConstraints.Constraint constraint;
        uint256 weight;
    }

    struct BasicRitualTemplate {
        uint8 rarity;
        uint256 charges;
        bool soulbound;
    }

    struct RitualTemplate {
        uint8 rarity;
        uint256 charges;
        bool soulbound;
        uint256[] affixBucketIds;
        LibConstraints.Constraint[] consumptionConstraints;
        LibRitualComponents.RitualCost[] consumptionCosts;
        LibRitualComponents.RitualProduct[] consumptionProducts;
    }

    uint256 private constant SALT_1 = 1;
    uint256 private constant SALT_PER_BUCKET = 100000;

    string private constant CALLBACK_SIGNATURE = 'fulfillCreateRitualRandomness(uint256,uint256[])';

    bytes32 private constant HATCHERY_RITUAL_STORAGE_POSITION = keccak256('CryptoUnicorns.HatcheryRitual.Storage');

    function hatcheryRitualStorage() internal pure returns (HatcheryRitualStorage storage ds) {
        bytes32 position = HATCHERY_RITUAL_STORAGE_POSITION;
        assembly {
            ds.slot := position
        }
    }

    // @notice This function is used to check if the user meets the constraints of a ritual.
    // @param constraints The constraints of the ritual.
    function checkConstraints(LibConstraints.Constraint[] memory constraints, address user) internal view {
        for (uint256 i = 0; i < constraints.length; i++) {
            //Max uint256 is used as a placeholder for the tokenId in the constraints.
            LibHatcheryRitualConstraints.checkConstraint(constraints[i], user);
        }
    }

    // @notice This function is used to pay the costs of a ritual.
    // @param costs The costs of the ritual.
    function payCosts(LibRitualComponents.RitualCost[] memory costs) internal {
        for (uint256 i = 0; i < costs.length; i++) {
            LibHatcheryRitualComponents.payCost(costs[i]);
        }
    }

    // @notice This function is used to mint the products of a ritual.
    // @param products The products of the ritual.
    function mintProducts(LibRitualComponents.RitualProduct[] memory products) internal {
        for (uint256 i = 0; i < products.length; i++) {
            LibHatcheryRitualComponents.mintProduct(products[i]);
        }
    }

    function consumeRitualCharge(uint256 ritualId) internal {
        address ritualsAddress = LibResourceLocator.ritualNFT();
        LibRitualData.Ritual memory ritual = IRitualFacet(ritualsAddress).validateChargesAndGetRitualDetailsForConsume(
            ritualId,
            msg.sender
        );
        checkConstraints(ritual.constraints, msg.sender);
        payCosts(ritual.costs);
        IRitualFacet(ritualsAddress).consumeRitualCharge(ritualId);
        mintProducts(ritual.products);
    }

    function canConsumeRitual(uint256 ritualId, address user) internal view returns (bool canConsume) {
        address ritualsAddress = LibResourceLocator.ritualNFT();
        LibRitualData.Ritual memory ritual = IRitualFacet(ritualsAddress).validateChargesAndGetRitualDetailsForConsume(
            ritualId,
            user
        );
        checkConstraints(ritual.constraints, user);
        return true;
    }

    function nextRitualTemplatePoolId() private returns (uint256) {
        return ++hatcheryRitualStorage().lastPoolId;
    }

    function nextRitualTemplateId() private returns (uint256) {
        return ++hatcheryRitualStorage().lastTemplateId;
    }

    function nextAffixId() private returns (uint256) {
        return ++hatcheryRitualStorage().lastAffixId;
    }

    function nextHatcheryBucketId() internal returns (uint256) {
        return ++hatcheryRitualStorage().lastAffixBucketId;
    }

    function setMaxRitualsPerBatch(uint256 maxRitualsPerBatch) internal {
        hatcheryRitualStorage().maxRitualsPerBatch = maxRitualsPerBatch;
    }

    function getMaxRitualsPerBatch() internal view returns (uint256) {
        return hatcheryRitualStorage().maxRitualsPerBatch;
    }

    function createBasicRitualTemplate(
        uint8 rarity,
        uint256 charges,
        bool soulbound
    ) internal returns (uint256 templateId) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        templateId = nextRitualTemplateId();

        hrs.basicRitualTemplateByRitualTemplateId[templateId] = BasicRitualTemplate({
            rarity: rarity,
            charges: charges,
            soulbound: soulbound
        });
    }

    function createRitualTemplate(RitualTemplate memory template) internal returns (uint256 id) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        id = nextRitualTemplateId();
        hrs.basicRitualTemplateByRitualTemplateId[id] = BasicRitualTemplate({
            rarity: template.rarity,
            charges: template.charges,
            soulbound: template.soulbound
        });

        for (uint256 i = 0; i < template.consumptionConstraints.length; ++i) {
            hrs.consumptionConstraintsByRitualTemplateId[id].push(template.consumptionConstraints[i]);
        }

        for (uint256 i = 0; i < template.consumptionCosts.length; ++i) {
            hrs.consumptionCostsByRitualTemplateId[id].push(template.consumptionCosts[i]);
        }

        for (uint256 i = 0; i < template.consumptionProducts.length; ++i) {
            hrs.consumptionProductsByRitualTemplateId[id].push(template.consumptionProducts[i]);
        }

        for (uint256 i = 0; i < template.affixBucketIds.length; ++i) {
            hrs.affixBucketIdsByRitualTemplateId[id].push(template.affixBucketIds[i]);
        }
        emit RitualTemplateCreated(id);
    }

    function createRitualTemplatePool(
        LibRitualComponents.RitualCost[] memory creationCosts,
        LibConstraints.Constraint[] memory creationConstraints
    ) internal returns (uint256 id) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        id = nextRitualTemplatePoolId();
        for (uint256 i = 0; i < creationConstraints.length; ++i) {
            hrs.creationConstraintsByTemplatePoolId[id].push(creationConstraints[i]);
        }

        for (uint256 i = 0; i < creationCosts.length; ++i) {
            hrs.creationCostsByTemplatePoolId[id].push(creationCosts[i]);
        }
        emit RitualTemplatePoolCreated(id);
    }

    function addRitualTemplateToPool(uint256 ritualTemplateId, uint256 ritualPoolId, uint256 rngWeight) internal {
        require(ritualTemplateExists(ritualTemplateId), 'LibHatcheryRituals: ritualTemplateId does not exist');
        //require(ritualPoolExists(ritualPoolId), "LibHatcheryRituals: Pool does not exist");
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        hrs.ritualTemplateIdsByTemplatePoolId[ritualPoolId].push(ritualTemplateId);
        hrs.ritualTemplateWeightsByTemplatePoolId[ritualPoolId].push(rngWeight);
        hrs.ritualTemplateSumWeightByTemplatePoolId[ritualPoolId] += rngWeight;
    }

    function removeRitualTemplateFromPool(uint256 ritualTemplateId, uint256 ritualPoolId) internal {
        //  TODO: Find the index of ritualTemplateId in ritualTemplateIdsByTemplatePoolId[ritualPoolId]
        //    subtract ritualTemplateWeightsByTemplatePoolId[ritualPoolId][index] from  ritualTemplateSumWeightByTemplatePoolId[ritualPoolId]
        //    delete index out of ritualTemplateIdsByTemplatePoolId[ritualPoolId]
        //    delete index out of hrs.ritualTemplateWeightsByTemplatePoolId[ritualPoolId]
        revert('Function not implemented');
    }

    function createAffix(Affix memory affix) internal returns (uint256 id) {
        id = nextAffixId();
        hatcheryRitualStorage().affixById[id] = affix;
        emit AffixCreated(id);
    }

    function createAffixBucket(uint256[] memory affixIds) internal returns (uint256 affixBucketId) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        affixBucketId = nextHatcheryBucketId();
        for (uint256 i = 0; i < affixIds.length; i++) {
            hatcheryRitualStorage().affixIdsByAffixBucketId[affixBucketId].push(affixIds[i]);
        }
        emit AffixBucketCreated(affixBucketId, affixIds);
    }

    function addAffixesToBucket(uint256[] memory affixIds, uint256 bucketId) internal {
        //This creates the bucket implicitly.
        require(affixBucketExists(bucketId), 'Bucket does not exist');
        require(affixesExist(affixIds), 'Some affixes do not exist');
        for (uint256 i = 0; i < affixIds.length; i++) {
            hatcheryRitualStorage().affixIdsByAffixBucketId[bucketId].push(affixIds[i]);
        }
    }

    function removeAffixFromBucket(uint256 affixId, uint256 bucketId) internal {
        require(affixBucketExists(bucketId), 'Bucket does not exist');
        require(affixExists(affixId), 'Affix does not exist');
        uint256[] storage affixIds = hatcheryRitualStorage().affixIdsByAffixBucketId[bucketId];
        for (uint256 i = 0; i < affixIds.length; i++) {
            if (affixIds[i] == affixId) {
                affixIds[i] = affixIds[affixIds.length - 1];
                affixIds.pop();
                break;
            }
        }
    }

    function affixBucketExists(uint256 bucketId) private view returns (bool) {
        return hatcheryRitualStorage().affixIdsByAffixBucketId[bucketId].length > 0;
    }

    function affixExists(uint256 affixId) private view returns (bool) {
        return hatcheryRitualStorage().affixById[affixId].affixType == AffixType.NONE;
    }

    function affixesExist(uint256[] memory affixIds) private view returns (bool) {
        for (uint256 i = 0; i < affixIds.length; i++) {
            if (affixExists(affixIds[i]) == false) {
                return false;
            }
        }
        return true;
    }

    function ritualPoolExists(uint256 ritualPoolId) private view returns (bool) {
        return hatcheryRitualStorage().ritualTemplateIdsByTemplatePoolId[ritualPoolId].length > 0;
    }

    function ritualTemplateExists(uint256 ritualTemplateId) private view returns (bool) {
        BasicRitualTemplate memory basicRitualTemplate = hatcheryRitualStorage().basicRitualTemplateByRitualTemplateId[
            ritualTemplateId
        ];
        return basicRitualTemplate.charges > 0 || basicRitualTemplate.rarity > 0;
    }

    function createRitual(uint256 ritualTemplatePoolId) internal {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        require(ritualPoolExists(ritualTemplatePoolId), 'LibHatcheryRituals: Pool does not exist');
        checkConstraints(hrs.creationConstraintsByTemplatePoolId[ritualTemplatePoolId], msg.sender);
        payCosts(hrs.creationCostsByTemplatePoolId[ritualTemplatePoolId]);
        uint256 vrfRequestId = LibRNG.requestRandomness(CALLBACK_SIGNATURE);
        saveCreateRitualData(ritualTemplatePoolId, msg.sender, vrfRequestId);
        emit BeginRitualCreation(msg.sender, ritualTemplatePoolId, vrfRequestId);
    }

    function selectAndApplyAffixesSimple(
        uint256 ritualTemplateId,
        uint256 randomness
    ) internal returns (RitualTemplate memory) {
        RitualTemplate memory ritualTemplate = getRitualTemplateById(ritualTemplateId);
        uint256[] memory affixIdsToApply = selectAffixes(ritualTemplateId, randomness);
        return applyAffixes(ritualTemplate, affixIdsToApply);
    }

    event ExternalErrorLog(bytes message, string key, uint256 keyvalue);

    function createRitualFulfillRandomness(uint256 vrfRequestId, uint256 randomness) internal {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        uint256 ritualTemplatePoolId = hrs.ritualTemplatePoolIdByVRFRequestId[vrfRequestId];
        address playerWallet = hrs.playerWalletByVRFRequestId[vrfRequestId];
        uint256 ritualTemplateId = selectRitualTemplateFromPool(randomness, ritualTemplatePoolId);
        (
            RitualTemplate memory ritualTemplateWithAffixesApplied,
            uint256[] memory affixIdsApplied
        ) = selectAndApplyAffixes(ritualTemplateId, randomness);
        string memory name = LibRitualNames.getRandomName(randomness);
        uint256 ritualTokenId = IRitualFacet(LibResourceLocator.ritualNFT()).createRitual(
            playerWallet, //to
            name, // name
            ritualTemplateWithAffixesApplied.rarity, //rarity
            ritualTemplateWithAffixesApplied.consumptionCosts, //costs
            ritualTemplateWithAffixesApplied.consumptionProducts, //products
            ritualTemplateWithAffixesApplied.consumptionConstraints, //constraints
            ritualTemplateWithAffixesApplied.charges, // charges
            ritualTemplateWithAffixesApplied.soulbound //soulbound
        );
        emit FinishRitualCreation(vrfRequestId, ritualTemplateId, ritualTokenId, affixIdsApplied, playerWallet);
    }

    function selectRitualTemplateFromPool(
        uint256 randomness,
        uint256 ritualTemplatePoolId
    ) internal view returns (uint256) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        uint256[] memory ritualTemplateIds = hrs.ritualTemplateIdsByTemplatePoolId[ritualTemplatePoolId];
        uint256[] memory weights = hrs.ritualTemplateWeightsByTemplatePoolId[ritualTemplatePoolId];
        uint256 totalWeight = hrs.ritualTemplateSumWeightByTemplatePoolId[ritualTemplatePoolId];
        uint256 target = LibRNG.expand(totalWeight, randomness, SALT_1);
        uint256 cumulativeWeight = 0;
        for (uint256 i = 0; i < ritualTemplateIds.length; i++) {
            cumulativeWeight += weights[i];
            if (target < cumulativeWeight) {
                return ritualTemplateIds[i];
            }
        }
    }

    function selectAffixFromBucket(
        uint256 randomness,
        uint256 affixBucketId,
        uint256 salt
    ) private view returns (uint256) {
        uint256[] memory affixIds = hatcheryRitualStorage().affixIdsByAffixBucketId[affixBucketId];
        mapping(uint256 => Affix) storage affixById = hatcheryRitualStorage().affixById;
        uint256 totalWeight = 0;
        for (uint256 i = 0; i < affixIds.length; i++) {
            totalWeight += affixById[affixIds[i]].weight;
        }

        uint256 target = LibRNG.expand(totalWeight, randomness, SALT_PER_BUCKET + salt);
        uint256 cumulativeWeight = 0;
        for (uint256 i = 0; i < affixIds.length; i++) {
            cumulativeWeight += affixById[affixIds[i]].weight;
            if (target < cumulativeWeight) {
                return affixIds[i];
            }
        }
    }

    function selectAffixes(
        uint256 ritualTemplateId,
        uint256 randomness
    ) internal view returns (uint256[] memory affixIdsToApply) {
        uint256[] storage affixBucketIds = hatcheryRitualStorage().affixBucketIdsByRitualTemplateId[ritualTemplateId];
        affixIdsToApply = new uint256[](affixBucketIds.length);
        for (uint256 i = 0; i < affixBucketIds.length; i++) {
            affixIdsToApply[i] = selectAffixFromBucket(randomness, affixBucketIds[i], i);
        }
    }

    function componentsAreEqual(
        LibRitualComponents.RitualComponent memory component1,
        LibRitualComponents.RitualComponent memory component2
    ) private pure returns (bool) {
        return
            component1.assetType == component2.assetType &&
            component1.poolId == component2.poolId &&
            component1.asset == component2.asset;
    }

    function searchIfProductIsPresent(
        RitualTemplate memory ritualTemplate,
        LibRitualComponents.RitualProduct memory product
    ) private pure returns (uint256) {
        for (uint256 i = 0; i < ritualTemplate.consumptionProducts.length; i++) {
            if (componentsAreEqual(ritualTemplate.consumptionProducts[i].component, product.component)) {
                return i;
            }
        }
        return type(uint256).max;
    }

    function searchIfCostIsPresent(
        RitualTemplate memory ritualTemplate,
        LibRitualComponents.RitualCost memory cost
    ) private pure returns (uint256) {
        for (uint256 i = 0; i < ritualTemplate.consumptionCosts.length; i++) {
            if (componentsAreEqual(ritualTemplate.consumptionCosts[i].component, cost.component)) {
                return i;
            }
        }
        return type(uint256).max;
    }

    function applyAffix(
        RitualTemplate memory ritualTemplate,
        Affix memory affix
    ) internal pure returns (RitualTemplate memory resultTemplate, bool shouldEmitWarning, string memory warningText) {
        shouldEmitWarning = false;
        warningText = '';
        if (affix.affixType == AffixType.CHARGES) {
            if (ritualTemplate.charges == type(uint256).max) {
                shouldEmitWarning = true;
                warningText = 'Ignoring error: LibHatcheryRituals: Cannot add nor subtract from a ritual with type(uint256).max charges, leaving ritualTemplate.charges as is';
            } else if (affix.isPositive) {
                if (affix.charges == type(uint256).max) {
                    // for unlimited charges
                    ritualTemplate.charges = type(uint256).max;
                } else {
                    ritualTemplate.charges += affix.charges;
                }
            } else {
                if (affix.charges == type(uint256).max) {
                    shouldEmitWarning = true;
                    warningText = 'Ignoring error: LibHatcheryRituals: Cannot have an affix were isPositive = false and charges = type(uint256).max, leaving ritualTemplate.charges as is';
                } else if (ritualTemplate.charges > affix.charges) {
                    // ritualTemplate.charges - affix.charges > 0, thus >= 1
                    ritualTemplate.charges -= affix.charges;
                } else {
                    // if ritualTemplate.charges - affix.charges == 0 or is negative, then:
                    if (affix.charges != ritualTemplate.charges) {
                        shouldEmitWarning = true;
                        warningText = 'Ignoring error: LibHatcheryRituals: Cannot subtract more than the amount of charges, leaving 1 as amount of charges instead';
                    }
                    ritualTemplate.charges = 1;
                }
            }
        } else if (affix.affixType == AffixType.CONSTRAINT) {
            //Constraints are only additive
            // Then => just add the constraint to the list
            ritualTemplate.consumptionConstraints = LibArray.pushToConstraintMemoryArray(
                ritualTemplate.consumptionConstraints,
                affix.constraint
            );
        } else if (affix.affixType == AffixType.COST) {
            // Costs can be new costs or old costs.
            uint256 costIndex = searchIfCostIsPresent(ritualTemplate, affix.cost);
            if (affix.isPositive) {
                if (costIndex == type(uint256).max) {
                    // If the cost is not present, we add it.
                    ritualTemplate.consumptionCosts = LibArray.pushToCostMemoryArray(
                        ritualTemplate.consumptionCosts,
                        affix.cost
                    );
                } else {
                    // If the cost is present, we add to that cost.
                    ritualTemplate.consumptionCosts[costIndex].component.amount += affix.cost.component.amount;
                }
            } else {
                if (costIndex == type(uint256).max) {
                    // If the cost is not present, we fail. Cannot subtract to a cost that is not present.
                    shouldEmitWarning = true;
                    warningText = 'Ignoring error: LibHatcheryRituals: Cannot subtract to a cost that is not present';
                } else {
                    if (ritualTemplate.consumptionCosts[costIndex].component.amount <= affix.cost.component.amount) {
                        // If the cost is present, there is at least one cost apart from this one
                        // and we subtract more than the cost, we remove cost from array
                        if (ritualTemplate.consumptionCosts.length > 1) {
                            LibArray.removeFromCostMemoryArray(ritualTemplate.consumptionCosts, costIndex);
                        } else {
                            // If the cost is present, there is only one cost and we subtract more than the cost,
                            // we ignore the removal and emit a warning.
                            shouldEmitWarning = true;
                            warningText = 'Ignoring error: LibHatcheryRituals: Cannot leave a ritual with no costs';
                        }
                    } else {
                        // If the cost is present and we subtract less than the cost, we subtract
                        ritualTemplate.consumptionCosts[costIndex].component.amount -= affix.cost.component.amount;
                    }
                }
            }
        } else if (affix.affixType == AffixType.PRODUCT) {
            // Product can be new product or old product.
            uint256 productIndex = searchIfProductIsPresent(ritualTemplate, affix.product);
            if (affix.isPositive) {
                if (productIndex == type(uint256).max) {
                    // If the product is not present, we add it.
                    ritualTemplate.consumptionProducts = LibArray.pushToProductMemoryArray(
                        ritualTemplate.consumptionProducts,
                        affix.product
                    );
                } else {
                    // If the product is present, we add to that product.
                    ritualTemplate.consumptionProducts[productIndex].component.amount += affix.product.component.amount;
                }
            } else {
                if (productIndex == type(uint256).max) {
                    // If the product is not present, we fail. Cannot subtract to a product that is not present.
                    shouldEmitWarning = true;
                    warningText = 'Ignoring error: LibHatcheryRituals: Cannot subtract to a product that is not present';
                } else {
                    if (
                        ritualTemplate.consumptionProducts[productIndex].component.amount <=
                        affix.product.component.amount
                    ) {
                        // If the product is present, there is at least one product apart from this one
                        // and we subtract more than the product, we remove product from array
                        if (ritualTemplate.consumptionProducts.length > 1) {
                            LibArray.removeFromProductMemoryArray(ritualTemplate.consumptionProducts, productIndex);
                        } else {
                            // If the product is present, there is only one product and we subtract more than the product,
                            // we ignore the removal and emit a warning.
                            shouldEmitWarning = true;
                            warningText = 'Ignoring error: LibHatcheryRituals: Cannot leave a ritual with no products';
                        }
                    } else {
                        // If the product is present and we subtract less than the product, we subtract
                        ritualTemplate.consumptionProducts[productIndex].component.amount -= affix
                            .product
                            .component
                            .amount;
                    }
                }
            }
        } else if (affix.affixType == AffixType.SOULBOUND) {
            //isPositive == true means that the ritual becomes soulbound
            //isPositive == false means that the ritual becomes not soulbound
            ritualTemplate.soulbound = affix.isPositive;
        }
        // implicitly: applyAffix(AffixType.NONE) => do nothing
        resultTemplate = ritualTemplate;
    }

    function applyAffixes(
        RitualTemplate memory ritualTemplate,
        uint256[] memory affixIdsToApply
    ) private returns (RitualTemplate memory) {
        string memory warningText = '';
        bool shouldEmitWarning = false;
        for (uint256 i = 0; i < affixIdsToApply.length; i++) {
            Affix memory affix = hatcheryRitualStorage().affixById[affixIdsToApply[i]];
            bool _shouldEmitWarning;
            string memory _warningText;
            RitualTemplate memory _ritualTemplate;
            (_ritualTemplate, _shouldEmitWarning, _warningText) = applyAffix(ritualTemplate, affix);

            if (_shouldEmitWarning) {
                shouldEmitWarning = true;
                warningText = string.concat(
                    warningText,
                    ' // ',
                    _warningText,
                    ', affixId:',
                    LibString.uintToString(affixIdsToApply[i])
                );
            }
        }
        if (shouldEmitWarning) {
            emit HatcheryAffixWarning(warningText);
        }
        return ritualTemplate;
    }

    function selectAndApplyAffixes(
        uint256 ritualTemplateId,
        uint256 randomness
    ) internal returns (RitualTemplate memory, uint256[] memory) {
        RitualTemplate memory ritualTemplate = getRitualTemplateById(ritualTemplateId);
        uint256[] memory affixIdsToApply = selectAffixes(ritualTemplateId, randomness);
        return (applyAffixes(ritualTemplate, affixIdsToApply), affixIdsToApply);
    }

    function getRitualTemplateById(
        uint256 ritualTemplateId
    ) internal view returns (RitualTemplate memory ritualTemplate) {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        BasicRitualTemplate memory basicRitualTemplate = hrs.basicRitualTemplateByRitualTemplateId[ritualTemplateId];
        ritualTemplate = RitualTemplate({
            rarity: basicRitualTemplate.rarity,
            charges: basicRitualTemplate.charges,
            soulbound: basicRitualTemplate.soulbound,
            affixBucketIds: hrs.affixBucketIdsByRitualTemplateId[ritualTemplateId],
            consumptionConstraints: hrs.consumptionConstraintsByRitualTemplateId[ritualTemplateId],
            consumptionCosts: hrs.consumptionCostsByRitualTemplateId[ritualTemplateId],
            consumptionProducts: hrs.consumptionProductsByRitualTemplateId[ritualTemplateId]
        });
    }

    function saveCreateRitualData(uint256 ritualTemplatePoolId, address sender, uint256 vrfRequestId) private {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        hrs.ritualTemplatePoolIdByVRFRequestId[vrfRequestId] = ritualTemplatePoolId;
        hrs.playerWalletByVRFRequestId[vrfRequestId] = sender;
    }

    function getCreationConstraintsAndCosts(
        uint256 ritualTemplatePoolId
    )
        internal
        view
        returns (LibConstraints.Constraint[] memory constraints, LibRitualComponents.RitualCost[] memory costs)
    {
        HatcheryRitualStorage storage hrs = hatcheryRitualStorage();
        constraints = hrs.creationConstraintsByTemplatePoolId[ritualTemplatePoolId];
        costs = hrs.creationCostsByTemplatePoolId[ritualTemplatePoolId];
    }

    //  Clone an Affix template into a generic instance for use in a ritual
    function instantiateAffixDefinition(uint256 affixId, uint256 weight) internal view returns (Affix memory) {
        return hatcheryRitualStorage().affixById[affixId];
        // return Affix(
        //     template.affixType,
        //     template.isPositive,
        //     template.charges,
        //     template.cost,
        //     template.product,
        //     template.constraint,
        //     template.weight
        // );
        // }
    }

    function mintInnateRitualsToInnateOwnerAccount(address _innateOwnerAccount) internal {
        /*
            Baseline template ids:
            - 1: Pyrofiend (T1 Baseline Fire Minion)
            - 2: Swamplix (T1 Baseline Slime Minion)
            - 3: Shockwisp (T1 Baseline Volt Minion)
            - 4: Sorciphant (T1 Baseline Soul Minion)
            - 5: Stargrub (T1 Baseline Nebula Minion)
            - 6: Shadow Forge Key
         */
        uint8[6] memory innateRitualTemplateIds = [1, 2, 3, 4, 5, 6];
        string[6] memory innateRitualTemplateNames = [
            'Pyrofiend Ritual',
            'Swamplix Ritual',
            'Shockwisp Ritual',
            'Sorciphant Ritual',
            'Stargrub Ritual',
            'Shadow Forge Key Ritual'
        ];

        for (uint256 i = 0; i < innateRitualTemplateIds.length; i++) {
            RitualTemplate memory ritualTemplate = getRitualTemplateById(innateRitualTemplateIds[i]);
            string memory name = innateRitualTemplateNames[i];
            IRitualFacet(LibResourceLocator.ritualNFT()).createRitual(
                _innateOwnerAccount, //to
                name, // name
                ritualTemplate.rarity, //rarity
                ritualTemplate.consumptionCosts, //costs
                ritualTemplate.consumptionProducts, //products
                ritualTemplate.consumptionConstraints, //constraints
                ritualTemplate.charges, // charges
                ritualTemplate.soulbound //soulbound
            );
        }
    }

    // function initializeAffixes() internal {

    // }
    // function initializeAffixBuckets() internal {
    //     // bucketId = nextAffixBucketId();
    //     // affixBucketIdToAffixIds
    // }
    // function initializeRitualTemplates() internal {

    // }
    // function initializeRitualPools() internal {

    // }
}
          

lib/web3/contracts/terminus/LibTerminus.sol

// SPDX-License-Identifier: Apache-2.0

/**
 * Authors: Moonstream Engineering ([email protected])
 * GitHub: https://github.com/bugout-dev/dao
 *
 * Common storage structure and internal methods for Moonstream DAO Terminus contracts.
 * As Terminus is an extension of ERC1155, this library can also be used to implement bare ERC1155 contracts
 * using the common storage pattern (e.g. for use in diamond proxies).
 */

// TODO(zomglings): Should we support EIP1761 in addition to ERC1155 or roll our own scopes and feature flags?
// https://eips.ethereum.org/EIPS/eip-1761

pragma solidity ^0.8.9;

library LibTerminus {
    bytes32 constant TERMINUS_STORAGE_POSITION =
        keccak256("moonstreamdao.eth.storage.terminus");

    struct TerminusStorage {
        // Terminus administration
        address controller;
        bool isTerminusActive;
        uint256 currentPoolID;
        address paymentToken;
        uint256 poolBasePrice;
        // Terminus pools
        mapping(uint256 => address) poolController;
        mapping(uint256 => string) poolURI;
        mapping(uint256 => uint256) poolCapacity;
        mapping(uint256 => uint256) poolSupply;
        mapping(uint256 => mapping(address => uint256)) poolBalances;
        mapping(uint256 => bool) poolNotTransferable;
        mapping(uint256 => bool) poolBurnable;
        mapping(address => mapping(address => bool)) globalOperatorApprovals;
        mapping(uint256 => mapping(address => bool)) globalPoolOperatorApprovals;
        // Contract metadata
        string contractURI;
    }

    function terminusStorage()
        internal
        pure
        returns (TerminusStorage storage es)
    {
        bytes32 position = TERMINUS_STORAGE_POSITION;
        assembly {
            es.slot := position
        }
    }

    event ControlTransferred(
        address indexed previousController,
        address indexed newController
    );

    event PoolControlTransferred(
        uint256 indexed poolID,
        address indexed previousController,
        address indexed newController
    );

    function setController(address newController) internal {
        TerminusStorage storage ts = terminusStorage();
        address previousController = ts.controller;
        ts.controller = newController;
        emit ControlTransferred(previousController, newController);
    }

    function enforceIsController() internal view {
        TerminusStorage storage ts = terminusStorage();
        require(msg.sender == ts.controller, "LibTerminus: Must be controller");
    }

    function setTerminusActive(bool active) internal {
        TerminusStorage storage ts = terminusStorage();
        ts.isTerminusActive = active;
    }

    function setPoolController(uint256 poolID, address newController) internal {
        TerminusStorage storage ts = terminusStorage();
        address previousController = ts.poolController[poolID];
        ts.poolController[poolID] = newController;
        emit PoolControlTransferred(poolID, previousController, newController);
    }

    function createSimplePool(uint256 _capacity) internal returns (uint256) {
        TerminusStorage storage ts = terminusStorage();
        uint256 poolID = ts.currentPoolID + 1;
        setPoolController(poolID, msg.sender);
        ts.poolCapacity[poolID] = _capacity;
        ts.currentPoolID++;
        return poolID;
    }

    function enforcePoolIsController(uint256 poolID, address maybeController)
        internal
        view
    {
        TerminusStorage storage ts = terminusStorage();
        require(
            ts.poolController[poolID] == maybeController,
            "LibTerminus: Must be pool controller"
        );
    }

    function _isApprovedForPool(uint256 poolID, address operator)
        internal
        view
        returns (bool)
    {
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        if (operator == ts.poolController[poolID]) {
            return true;
        } else if (ts.globalPoolOperatorApprovals[poolID][operator]) {
            return true;
        }
        return false;
    }

    function _approveForPool(uint256 poolID, address operator) internal {
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.globalPoolOperatorApprovals[poolID][operator] = true;
    }

    function _unapproveForPool(uint256 poolID, address operator) internal {
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.globalPoolOperatorApprovals[poolID][operator] = false;
    }
}
          

src/implementation/LevelFragment.sol

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

import {LevelUnlockCost} from '../entities/level/LevelUnlockCost.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract LevelFragment {
    event HatcheryLevelUnlocked(address indexed player, uint256 oldLevel, uint256 newLevel);

    /// @notice This function is used to get the current hatchery level for the sender address
    /// @param _user The address to get the hatchery level for
    /// @return uint256 The current hatchery level for the sender address (1 if not initialized)
    function getHatcheryLevel(address _user) external view returns (uint256) {}

    /// @dev Unlock the next hatchery level
    /// @notice This function is used to unlock the next hatchery level for an address if it's available
    function unlockNextHatcheryLevel() external {}

    /// @dev Get hatchery level unlocking costs
    /// @notice This function is used to get the costs for unlocking an specific level
    /// @param _hatcheryLevel The level to get the costs for
    /// @return LevelUnlockCost[] The costs for unlocking the level
    function getHatcheryLevelUnlockCosts(uint256 _hatcheryLevel) external view returns (LevelUnlockCost[] memory) {}

    /// @dev Add a cost for a hatchery level
    /// @notice This function is used to add a cost to an specific level
    /// @dev This function can only be called by the contract owner
    /// @param _hatcheryLevel The level to set the cost for
    /// @param _transferType The type of transfer that will be used for the cost (BURN, TRANSFER)
    /// @param _amount The amount of the cost
    /// @param _assetType The type of asset that will be used for the cost (0: ERC20, 1: ERC1155, 2: ERC721)
    /// @param _asset The asset of the cost
    /// @param _poolId The pool id of the cost (0 if the asset is not ERC1155/Terminus)
    function addHatcheryLevelUnlockCost(
        uint256 _hatcheryLevel,
        uint256 _transferType,
        uint128 _amount,
        uint128 _assetType,
        address _asset,
        uint256 _poolId
    ) external {}

    /// @dev Set hatchery level unlocking costs
    /// @notice This function is used to set the costs for unlocking an specific level
    /// @dev The arrays must be the same length
    /// @dev This function can only be called by the contract owner
    /// @dev When setting the costs for an specific level, the previous costs will be deleted
    /// @dev There are multiple parallel arrays instead of a single array to prevent big nested structs array.
    /// @param _hatcheryLevel The level to set the costs for
    /// @param _transferTypes The types of transfer that will be used for the cost (BURN, TRANSFER)
    /// @param _amounts The amounts of the costs
    /// @param _assetTypes The types of asset that will be used for the costs (0: ERC20, 1: ERC1155, 2: ERC721)
    /// @param _assets The assets of the costs
    /// @param _poolIds The pool ids of the costs (0 if the asset is not ERC1155/Terminus)
    function setHatcheryLevelUnlockCosts(
        uint256 _hatcheryLevel,
        uint256[] memory _transferTypes,
        uint128[] memory _amounts,
        uint128[] memory _assetTypes,
        address[] memory _assets,
        uint256[] memory _poolIds
    ) external {}

    /// @dev Get the hatchery level cap
    /// @notice This function is used to get the level cap for all the hatcheries
    /// @return uint256 The level cap for all the hatcheries
    function getHatcheryLevelCap() external view returns (uint256) {}

    /// @dev Set the hatchery level cap
    /// @notice This function is used to set the level cap for all the hatcheries
    /// @dev This function can only be called by the contract owner
    /// @param _hatcheryLevelCap The level cap for all the hatcheries
    function setHatcheryLevelCap(uint256 _hatcheryLevelCap) external {}
}
          

lib/@lagunagames/cu-common/src/interfaces/IRitualFacet.sol

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

import {LibRitualComponents} from '../libraries/LibRitualComponents.sol';
import {LibConstraints} from '../libraries/LibConstraints.sol';
import {LibRitualData} from '../libraries/LibRitualData.sol';

interface IRitualFacet {
    /// @notice This function creates a ritual and mints it to the target user,
    /// can only be called by minion hatchery.
    /// @param to The user that the ritual should be minted to.
    /// @param name The ritual's name.
    /// @param rarity The ritual's rarity expressed in integer value.
    /// @param costs The costs to pay to consume a ritual charge expressed in components.
    /// @param products The outcome of consuming a ritual charge expressed in components.
    /// @param constraints The restrictions to consume a ritual charge expressed in constraints.
    /// @param charges The amount of times the ritual can consume a charge to exchange costs for products.
    /// @param soulbound Flag to indicate if the ritual is soulbound or not.
    function createRitual(
        address to,
        string calldata name,
        uint8 rarity,
        LibRitualComponents.RitualCost[] memory costs,
        LibRitualComponents.RitualProduct[] memory products,
        LibConstraints.Constraint[] memory constraints,
        uint256 charges,
        bool soulbound
    ) external returns (uint256 ritualTokenId);

    /// @notice This function returns the entire ritual data that is relevant for the minion hatchery.
    /// @param ritualId The id of the ritual.
    /// @return Ritual (name, rarity, costs, products, constraints, charges and soulbound).
    function getRitualDetails(uint256 ritualId) external view returns (LibRitualData.Ritual memory);

    /// @notice This function consumes a ritual charge, can only be called by minion hatchery.
    /// @param ritualId The id of the ritual.
    function consumeRitualCharge(uint256 ritualId) external;

    /// @notice This function validates checks that the address that wants to consume a charge
    /// is the owner of the ritual and that is has charges left, then
    /// returns the ritual details, can only be called by minion hatchery.
    /// @param ritualId The id of the ritual.
    /// @param ritualOwner The owner of the ritual.
    /// @return Ritual (name, rarity, costs, products, constraints, charges and soulbound).
    function validateChargesAndGetRitualDetailsForConsume(
        uint256 ritualId,
        address ritualOwner
    ) external view returns (LibRitualData.Ritual memory);
}
          

lib/openzeppelin-contracts/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol

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

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}
          

lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol

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

library LibConstraints {

    /// @notice This Constraints are used to check if the user meets 
    //  certain requirements to mint rituals or consume charges from 
    //  a ritual in the minion hatchery
    //  @param constraintType What will the constraint check against
    //  @param operator The conditional operator that will be checked against the constraintType
    //  @param value The value that will be checked with the operator against the constraintType
    struct Constraint {
        uint128 constraintType;
        uint128 operator;
        uint256 value;
    }

    //DO NOT REORDER THIS VALUES
    enum ConstraintType {
        NONE,                           //0
        HATCHERY_LEVEL,                 //1
        SHADOWCORN_RARITY,              //2
        SHADOWCORN_CLASS,               //3
        SHADOWCORN_BALANCE,             //4
        SHADOWCORN_MIGHT,               //5
        SHADOWCORN_WICKEDNESS,          //6
        SHADOWCORN_TENACITY,            //7
        SHADOWCORN_CUNNING,             //8
        SHADOWCORN_ARCANA,              //9
        BALANCE_UNICORN,                //10
        BALANCE_SHADOWCORN              //11
    }

    // @notice This function is used to check if a constraint type is valid.
    // @param constraintType The constraint type to check.
    function enforceValidConstraintType(uint256 constraintType) internal pure {
        require(constraintType <= uint(type(ConstraintType).max), "LibConstraints: invalid constraint type.");
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibConstraintOperator.sol

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

library LibConstraintOperator {
    //DO NOT REORDER THIS VALUES
    enum ConstraintOperator {
        NONE, //0
        LESS_THAN, //1 (<)
        LESS_THAN_OR_EQUAL, //2 (<=)
        EQUAL, //3 (=)
        GREATER_THAN_OR_EQUAL, //4 (>=)
        GREATER_THAN, //5 (>)
        NOT_EQUAL //6 (!=)
    }

    /*
        @title Enforce valid operator
        @notice This function will revert if the operator is not valid
        @param operator The operator to check
     */
    function enforceValidOperator(uint256 operator) internal pure {
        require(operator <= uint(type(ConstraintOperator).max), 'LibConstraintOperator: invalid constraint operator.');
    }

    /*
        @title Check mathematical operator against two values
        @notice This function will return whether the condition for the mathematical operator and the given values is true or false.
        @dev This function will revert if the operator is NONE or not valid.
        @param leftValue The first value that we need to compare with the other value parameter for the given mathematical operator.
        @param operator The operator to check (=, >, <, >=, <=, !=)
        @param rightValue The second value to check
        @return bool Whether the operator condition for the given values is true or false.
     */
    function checkOperator(uint256 leftValue, uint256 operator, uint256 rightValue) internal pure returns (bool) {
        enforceValidOperator(operator);

        ConstraintOperator castedOperator = ConstraintOperator(operator);
        require(castedOperator != ConstraintOperator.NONE, 'LibConstraintOperator: Operator should not be NONE.');

        if (castedOperator == ConstraintOperator.LESS_THAN) {
            return leftValue < rightValue;
        }
        if (castedOperator == ConstraintOperator.LESS_THAN_OR_EQUAL) {
            return leftValue <= rightValue;
        }
        if (castedOperator == ConstraintOperator.EQUAL) {
            return leftValue == rightValue;
        }
        if (castedOperator == ConstraintOperator.GREATER_THAN_OR_EQUAL) {
            return leftValue >= rightValue;
        }
        if (castedOperator == ConstraintOperator.GREATER_THAN) {
            return leftValue > rightValue;
        }
        if (castedOperator == ConstraintOperator.NOT_EQUAL) {
            return leftValue != rightValue;
        }

        revert('LibConstraintOperator: Invalid operator.');
    }
}
          

lib/@lagunagames/cu-common/src/interfaces/IDarkMarksControllerFacet.sol

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

interface IDarkMarksControllerFacet {
    /*
     * @notice Whitelist a user or contract to create new DarkMarks tokens.
     * @dev Can only be called by the Diamond owner of the DarkMarks contract.
     * @param minter - Public address of the user or contract allow
     */
    function allowAddressToMintDarkMarks(address minter) external;

    /*
     * @notice Revoke a user's or contract's permission to create new DarkMarks tokens.
     * @dev Can only be called by the Diamond owner of the DarkMarks contract.
     * @param minter - Public address of the user or contract to revoke
     */
    function denyAddressToMintDarkMarks(address minter) external;

    /*
     * @notice Print the list of wallets and contracts who can create DarkMarks tokens.
     * @return The full list of permitted addresses
     */
    function getAddressesPermittedToMintDarkMarks() external view returns (address[] memory);

    /*
     * @notice Reports the lifetime number of DarkMarks that an address has minted and burned.
     * @param controller - Public address of the minter
     * @return minted - The grand total number of DarkMarks this address has minted
     * @return burned - The grand total number of DarkMarks this address has burned
     */
    function getDarkMarksOperationsForAddress(
        address controller
    ) external view returns (uint256 minted, uint256 burned);

    /*
     * @notice Create new DarkMarks tokens for a target wallet.
     * @dev Can only be called by an address allowed via allowAddressToMintDarkMarks
     * @param account - The address receiving the funds
     * @param amount - The number of DarkMarks tokens to create
     */
    function mintDarkMarks(address account, uint256 amount) external;

    /*
     * @notice Destroy DarkMarks tokens from a target wallet.
     * @dev Can only be called by an address allowed via allowAddressToMintDarkMarks
     * @dev This method uses the player's spend/burn allowance granted to GameBank,
     *     rather than allowance for msgSender, so this may have better permission.
     * @param account - The wallet to remove DarkMarks from
     * @param amount - The number of DarkMarks tokens to destroy
     */
    function burnDarkMarksFrom(address account, uint256 amount) external;
}
          

src/libraries/LibLevel.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';
import {ERC20Burnable} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol';
import {IERC721} from '../../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol';
import {ERC721Burnable} from '../../lib/openzeppelin-contracts/contracts/token/ERC721/extensions/ERC721Burnable.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';
import {LevelUnlockCost} from '../entities/level/LevelUnlockCost.sol';
import {LibConstraintOperator} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraintOperator.sol';
import {LevelUpgradeBonus} from '../entities/level/LevelUpgradeBonus.sol';
import {LevelUpgradeBonusType} from '../entities/level/LevelUpgradeBonusType.sol';
import {LevelUpgradeBonusFrequency} from '../entities/level/LevelUpgradeBonusFrequency.sol';
import {LibRitualData} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualData.sol';
import {LevelUnlockCostTokenTransferType} from '../entities/level/LevelUnlockCostTokenTransferType.sol';
import {Component} from '../entities/common/Component.sol';
import {TerminusFacet} from '../../lib/web3/contracts/terminus/TerminusFacet.sol';
import {ERC1155, IERC1155} from '../../lib/openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol';
import {LibValidate} from '../../lib/@lagunagames/cu-common/src/libraries/LibValidate.sol';
import {LibRitualValidate} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualValidate.sol';
import {LibToken} from '../../lib/@lagunagames/cu-common/src/libraries/LibToken.sol';
import {IUNIMControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IUNIMControllerFacet.sol';
import {IDarkMarksControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IDarkMarksControllerFacet.sol';

/// @notice LibLevel
/// @author Facundo Vidal
/// @dev Implementation of Minion Hatchery leveling
library LibLevel {
    event HatcheryLevelUnlocked(address indexed player, uint256 oldLevel, uint256 newLevel);

    uint256 private constant HATCHERY_LEVEL_MIN = 1;

    bytes32 private constant HATCHERY_LEVEL_STORAGE_POSITION = keccak256('CryptoUnicorns.HatcheryLevel.Storage');

    /// @dev Do not modify the ordering of this struct once it has been deployed.
    /// @dev If you need to add new fields, add them to the end.
    struct LibLevelStorage {
        // Maps each user to their current level. If the user is level 1, the user might not be in this map.
        mapping(address => uint256) userToHatcheryLevel;
        // The maximum level that the hatchery can reach.
        uint256 hatcheryLevelCap;
        // Unlocking costs for each level
        mapping(uint256 => LevelUnlockCost[]) hatcheryLevelToUnlockCosts;
        // **DEPRECATED: DONT DELETE THIS, KEEP STORAGE ORDER!** Available farm slots and bonuses for each level
        mapping(uint256 => uint256) hatcheryLevelToUpgradeFarmSlots;
        // **DEPRECATED: DONT DELETE THIS, KEEP STORAGE ORDER!** Bonuses for each level
        mapping(uint256 => LevelUpgradeBonus[]) hatcheryLevelToUpgradeBonuses;
        // **DEPRECATED: DONT DELETE THIS, KEEP STORAGE ORDER!** Available rituals for each level
        mapping(uint256 => LibRitualData.BasicRitual[]) hatcheryLevelToUpgradeRituals;
    }

    function levelStorage() internal pure returns (LibLevelStorage storage lrs) {
        bytes32 position = HATCHERY_LEVEL_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            lrs.slot := position
        }
    }

    /// @notice Set the unlocking costs for an specific hatchery level
    /// @notice This function will be used to set the unlocking costs for each level.
    /// @dev When setting the costs for an specific level, the previous costs will be deleted
    /// @dev There are multiple parallel arrays instead of a single array to prevent big nested structs array.
    /// @param _hatcheryLevel The level that will be unlocked
    /// @param _transferTypes The type of transfer that will be used for the cost (BURN, TRANSFER)
    /// @param _amounts The amount of tokens that will be used for the cost
    /// @param _assetTypes The type of asset that will be used for the cost (ERC20, ERC721, ERC1155)
    /// @param _assets The address of the asset that will be used for the cost
    /// @param _poolIds The pool id of the asset that will be used for the cost
    function setHatcheryLevelUnlockCosts(
        uint256 _hatcheryLevel,
        uint256[] memory _transferTypes,
        uint128[] memory _amounts,
        uint128[] memory _assetTypes,
        address[] memory _assets,
        uint256[] memory _poolIds
    ) internal {
        // Enforce that all the parameter array lengths are the same
        require(
            _amounts.length == _assetTypes.length &&
                _assetTypes.length == _assets.length &&
                _assets.length == _poolIds.length &&
                _poolIds.length == _transferTypes.length,
            'LibLevel: parameter array lengths must be the same.'
        );

        // Clear old costs for level
        delete levelStorage().hatcheryLevelToUnlockCosts[_hatcheryLevel];

        for (uint256 i = 0; i < _amounts.length; i++) {
            enforceValidTransferType(_transferTypes[i]);
            enforceValidAssetType(_assetTypes[i]);
            addHatcheryLevelUnlockCost(
                _hatcheryLevel,
                LevelUnlockCostTokenTransferType(_transferTypes[i]),
                _amounts[i],
                _assetTypes[i],
                _assets[i],
                _poolIds[i]
            );
        }
    }

    /// @notice Enforce valid asset type
    /// @notice This function will revert if the asset type is not valid
    /// @param _assetType The asset type
    function enforceValidAssetType(uint256 _assetType) private pure {
        require(
            _assetType == LibToken.TYPE_ERC20 ||
                _assetType == LibToken.TYPE_ERC721 ||
                _assetType == LibToken.TYPE_ERC1155,
            'LibComponents: Invalid asset type.'
        );
    }

    ///  @notice Unlock next hatchery level
    ///  @dev This function is called when a user wants to unlock the next hatchery level
    ///  @dev This function will revert if the user has reached the level cap
    ///  @dev This function will revert if the user has not unlocked the previous level
    function unlockNextHatcheryLevel() internal {
        // Get the current level
        uint256 currentLevel = getHatcheryLevelForAddress(msg.sender);

        // Get the next level
        uint256 nextLevel = currentLevel + 1;

        // Get the level cap
        uint256 levelCap = getHatcheryLevelCap();

        // Check if the user has reached the level cap
        require(currentLevel < levelCap, 'LibLevel: User has reached the level cap');

        LibLevelStorage storage llStorage = levelStorage();

        // Get new level costs
        LevelUnlockCost[] memory newLevelCosts = llStorage.hatcheryLevelToUnlockCosts[nextLevel];

        for (uint256 i = 0; i < newLevelCosts.length; i++) {
            // Get the cost
            LevelUnlockCost memory newLevelCost = newLevelCosts[i];

            if (newLevelCost.component.assetType == LibToken.TYPE_ERC20) {
                consumeERC20Cost(
                    newLevelCost.component.amount,
                    newLevelCost.component.asset,
                    newLevelCost.transferType
                );
            } else if (newLevelCost.component.assetType == LibToken.TYPE_ERC721) {
                // TODO: implement ERC721 usage and adapt Component structure to support it.
                revert('LibLevel: ERC721 for costs was not implemented yet');
            } else if (newLevelCost.component.assetType == LibToken.TYPE_ERC1155) {
                consumeERC1155Cost(
                    newLevelCost.component.amount,
                    newLevelCost.component.asset,
                    newLevelCost.transferType,
                    newLevelCost.component.poolId
                );
            }
        }

        // Unlock the next level
        llStorage.userToHatcheryLevel[msg.sender] = nextLevel;

        emit HatcheryLevelUnlocked(msg.sender, currentLevel, nextLevel);
    }

    /// @notice Consume ERC20 cost for hatchery leveling
    /// @notice This function will consume the ERC20 cost for hatchery leveling
    /// @dev This function will revert if the transfer type is invalid
    /// @param _amount The amount of ERC20 tokens to consume
    /// @param _asset The address of the ERC20 token
    /// @param _transferType The transfer type (BURN, TRANSFER)
    function consumeERC20Cost(
        uint256 _amount,
        address _asset,
        LevelUnlockCostTokenTransferType _transferType
    ) internal {
        if (_transferType == LevelUnlockCostTokenTransferType.BURN) {
            // Burn the tokens
            if (_asset == LibResourceLocator.unimToken()) {
                IUNIMControllerFacet(LibResourceLocator.gameBank()).burnUNIMFrom(msg.sender, _amount);
            } else if (_asset == LibResourceLocator.darkMarkToken()) {
                IDarkMarksControllerFacet(LibResourceLocator.gameBank()).burnDarkMarksFrom(msg.sender, _amount);
            } else {
                ERC20Burnable(_asset).burnFrom(msg.sender, _amount);
            }
            return;
        } else if (_transferType == LevelUnlockCostTokenTransferType.TRANSFER) {
            // Transfer the tokens to the game bank
            IERC20(_asset).transferFrom(msg.sender, LibResourceLocator.gameBank(), _amount);
            return;
        }

        revert('LibLevel: Invalid transfer type');
    }

    /// @notice Consume ERC1155 cost for hatchery leveling
    /// @notice This function will consume the ERC1155 or Terminus token cost for hatchery leveling
    /// @dev This function will revert if the transfer type is invalid
    /// @param _amount The amount of ERC1155/Terminus tokens to consume
    /// @param _asset The address of the ERC1155/Terminus token
    /// @param _transferType The transfer type (Only BURN is supported)
    /// @param _poolId The pool id
    function consumeERC1155Cost(
        uint256 _amount,
        address _asset,
        LevelUnlockCostTokenTransferType _transferType,
        uint256 _poolId
    ) internal {
        if (_transferType == LevelUnlockCostTokenTransferType.BURN) {
            // Burn the tokens
            TerminusFacet(_asset).burn(msg.sender, _poolId, _amount);
            return;
        }

        revert('LibLevel: Invalid transfer type');
    }

    /// @notice Get the costs to unlock an specific hatchery level
    /// @param _hatcheryLevel The level
    /// @return Array of costs with token type and quantity
    function getHatcheryLevelUnlockCosts(uint256 _hatcheryLevel) internal view returns (LevelUnlockCost[] memory) {
        return levelStorage().hatcheryLevelToUnlockCosts[_hatcheryLevel];
    }

    /// @notice Add a cost for a hatchery level
    /// @notice This function is used to add a cost to an specific level
    /// @dev This function can only be called by the contract owner
    /// @param _hatcheryLevel The level to set the cost for
    /// @param _transferType The type of transfer that will be used for the cost (BURN, TRANSFER)
    /// @param _amount The amount of the cost
    /// @param _assetType The type of asset that will be used for the cost (0: ERC20, 1: ERC1155, 2: ERC721)
    /// @param _asset The asset of the cost
    /// @param _poolId The pool id of the cost (0 if the asset is not ERC1155/Terminus)
    function addHatcheryLevelUnlockCost(
        uint256 _hatcheryLevel,
        LevelUnlockCostTokenTransferType _transferType,
        uint128 _amount,
        uint128 _assetType,
        address _asset,
        uint256 _poolId
    ) internal {
        enforceValidAssetType(_assetType);

        LibValidate.enforceNonZeroAddress(_asset);

        if (_assetType == LibToken.TYPE_ERC1155) {
            // Require transferType = BURN since we are not supporting transfer for ERC1155/Terminus tokens.
            require(
                _transferType == LevelUnlockCostTokenTransferType.BURN,
                'LibLevel: must use transfer type BURN for ERC1155 or Terminus assets.'
            );

            require(_poolId > 0, 'LibLevel: must assign a pool id to a level cost with asset type ERC1155.');
        }

        // Push unlock cost to level
        levelStorage().hatcheryLevelToUnlockCosts[_hatcheryLevel].push(
            LevelUnlockCost({
                component: Component({amount: _amount, assetType: _assetType, asset: _asset, poolId: _poolId}),
                transferType: _transferType
            })
        );
    }

    /// @notice Get the hatchery level cap
    /// @dev This function will return the minimum level if the level cap is 0
    /// @return The level cap
    function getHatcheryLevelCap() internal view returns (uint256) {
        // Get the level cap
        uint256 levelCap = levelStorage().hatcheryLevelCap;

        // If the level cap is 0, return the minimum level cap
        return (levelCap == 0) ? HATCHERY_LEVEL_MIN : levelCap;
    }

    /// @notice Set the hatchery level cap
    /// @dev This function will revert if the level cap is 0
    /// @param _hatcheryLevelCap The level cap
    function setHatcheryLevelCap(uint256 _hatcheryLevelCap) internal {
        require(_hatcheryLevelCap > HATCHERY_LEVEL_MIN, 'LibLevel: Level cap must be greater than the minimum level');

        levelStorage().hatcheryLevelCap = _hatcheryLevelCap;
    }

    /// @notice Get the hatchery level for an address
    /// @param _user The address of the user
    /// @dev If the user has not been initialized, it returns 1.
    /// @return The current level of the user
    function getHatcheryLevelForAddress(address _user) internal view returns (uint256) {
        uint256 level = levelStorage().userToHatcheryLevel[_user];

        // If the user has not been initialized, it has level 1
        return (level == 0) ? 1 : level;
    }

    /// @notice Enforces valid level
    /// @dev This function will revert if the level is not valid
    /// @param _hatcheryLevel The level to validate
    function enforceValidLevel(uint256 _hatcheryLevel) internal view {
        require(
            _hatcheryLevel >= HATCHERY_LEVEL_MIN && _hatcheryLevel <= getHatcheryLevelCap(),
            'LibLevel: Level must be greater than or equal to the minimum level'
        );
    }

    /// @notice Enforces valid transfer type for token
    /// @dev This function will revert if the transfer type is not valid
    /// @param _transferType The transfer type to validate
    function enforceValidTransferType(uint256 _transferType) internal pure {
        require(
            _transferType == uint(LevelUnlockCostTokenTransferType.BURN) ||
                _transferType == uint(LevelUnlockCostTokenTransferType.TRANSFER),
            'LibLevel: Invalid transfer type'
        );
    }

    /// @notice Resets hatchery level
    /// @dev This function should only be called on local or testnet for debugging purposes
    /// @param user The address of the user
    function resetHatcheryLevel(address user) internal {
        levelStorage().userToHatcheryLevel[user] = 1;
    }
}
          

lib/@lagunagames/cu-common/lib/openzeppelin-contracts/contracts/utils/Strings.sol

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

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

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

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

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

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

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
          

lib/web3/contracts/terminus/TerminusFacet.sol

// SPDX-License-Identifier: Apache-2.0

/**
 * Authors: Moonstream Engineering ([email protected])
 * GitHub: https://github.com/bugout-dev/dao
 *
 * This is an implementation of the Terminus decentralized authorization contract.
 *
 * Terminus users can create authorization pools. Each authorization pool has the following properties:
 * 1. Controller: The address that controls the pool. Initially set to be the address of the pool creator.
 * 2. Pool URI: Metadata URI for the authorization pool.
 * 3. Pool capacity: The total number of tokens that can be minted in that authorization pool.
 * 4. Pool supply: The number of tokens that have actually been minted in that authorization pool.
 * 5. Transferable: A boolean value which denotes whether or not tokens from that pool can be transfered
 *    between addresses. (Note: Implemented by TerminusStorage.poolNotTransferable since we expect most
 *    pools to be transferable. This negation is better for storage + gas since false is default value
 *    in map to bool.)
 * 6. Burnable: A boolean value which denotes whether or not tokens from that pool can be burned.
 */

pragma solidity ^0.8.0;

import "@openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import "./ERC1155WithTerminusStorage.sol";
import "./LibTerminus.sol";
import {LibDiamondMoonstream as LibDiamond} from "../diamond/libraries/LibDiamondMoonstream.sol";

contract TerminusFacet is ERC1155WithTerminusStorage {
    constructor() {
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.controller = msg.sender;
    }

    event PoolMintBatch(
        uint256 indexed id,
        address indexed operator,
        address from,
        address[] toAddresses,
        uint256[] amounts
    );

    function setController(address newController) external {
        LibTerminus.enforceIsController();
        LibTerminus.setController(newController);
    }

    function poolMintBatch(
        uint256 id,
        address[] memory toAddresses,
        uint256[] memory amounts
    ) public {
        require(
            toAddresses.length == amounts.length,
            "TerminusFacet: _poolMintBatch -- toAddresses and amounts length mismatch"
        );
        address operator = _msgSender();
        require(
            isApprovedForPool(id, operator),
            "TerminusFacet: poolMintBatch -- caller is neither owner nor approved"
        );

        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();

        uint256 i = 0;
        uint256 totalAmount = 0;

        for (i = 0; i < toAddresses.length; i++) {
            address to = toAddresses[i];
            uint256 amount = amounts[i];
            require(
                to != address(0),
                "TerminusFacet: _poolMintBatch -- cannot mint to zero address"
            );
            totalAmount += amount;
            ts.poolBalances[id][to] += amount;
            emit TransferSingle(operator, address(0), to, id, amount);
        }

        require(
            ts.poolSupply[id] + totalAmount <= ts.poolCapacity[id],
            "TerminusFacet: _poolMintBatch -- Minted tokens would exceed pool capacity"
        );
        ts.poolSupply[id] += totalAmount;

        emit PoolMintBatch(id, operator, address(0), toAddresses, amounts);
    }

    function terminusController() external view returns (address) {
        return LibTerminus.terminusStorage().controller;
    }

    function paymentToken() external view returns (address) {
        return LibTerminus.terminusStorage().paymentToken;
    }

    function setPaymentToken(address newPaymentToken) external {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.paymentToken = newPaymentToken;
    }

    function poolBasePrice() external view returns (uint256) {
        return LibTerminus.terminusStorage().poolBasePrice;
    }

    function setPoolBasePrice(uint256 newBasePrice) external {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.poolBasePrice = newBasePrice;
    }

    function _paymentTokenContract() internal view returns (IERC20) {
        address paymentTokenAddress = LibTerminus
            .terminusStorage()
            .paymentToken;
        require(
            paymentTokenAddress != address(0),
            "TerminusFacet: Payment token has not been set"
        );
        return IERC20(paymentTokenAddress);
    }

    function withdrawPayments(address toAddress, uint256 amount) external {
        LibTerminus.enforceIsController();
        require(
            _msgSender() == toAddress,
            "TerminusFacet: withdrawPayments -- Controller can only withdraw to self"
        );
        IERC20 paymentTokenContract = _paymentTokenContract();
        paymentTokenContract.transfer(toAddress, amount);
    }

    function contractURI() public view returns (string memory) {
        return LibTerminus.terminusStorage().contractURI;
    }

    function setContractURI(string memory _contractURI) external {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.contractURI = _contractURI;
    }

    function setURI(uint256 poolID, string memory poolURI) external {
        LibTerminus.enforcePoolIsController(poolID, _msgSender());
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.poolURI[poolID] = poolURI;
    }

    function totalPools() external view returns (uint256) {
        return LibTerminus.terminusStorage().currentPoolID;
    }

    function setPoolController(uint256 poolID, address newController) external {
        LibTerminus.enforcePoolIsController(poolID, msg.sender);
        LibTerminus.setPoolController(poolID, newController);
    }

    function terminusPoolController(
        uint256 poolID
    ) external view returns (address) {
        return LibTerminus.terminusStorage().poolController[poolID];
    }

    function terminusPoolCapacity(
        uint256 poolID
    ) external view returns (uint256) {
        return LibTerminus.terminusStorage().poolCapacity[poolID];
    }

    function terminusPoolSupply(
        uint256 poolID
    ) external view returns (uint256) {
        return LibTerminus.terminusStorage().poolSupply[poolID];
    }

    function poolIsTransferable(uint256 poolID) external view returns (bool) {
        return !LibTerminus.terminusStorage().poolNotTransferable[poolID];
    }

    function poolIsBurnable(uint256 poolID) external view returns (bool) {
        return LibTerminus.terminusStorage().poolBurnable[poolID];
    }

    function setPoolTransferable(uint256 poolID, bool transferable) external {
        LibTerminus.enforcePoolIsController(poolID, msg.sender);
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.poolNotTransferable[poolID] = !transferable;
    }

    function setPoolBurnable(uint256 poolID, bool burnable) external {
        LibTerminus.enforcePoolIsController(poolID, msg.sender);
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        ts.poolBurnable[poolID] = burnable;
    }

    function createSimplePool(uint256 _capacity) external returns (uint256) {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        uint256 requiredPayment = ts.poolBasePrice;

        if (requiredPayment > 0) {
            IERC20 paymentTokenContract = _paymentTokenContract();
            require(
                paymentTokenContract.allowance(_msgSender(), address(this)) >=
                    requiredPayment,
                "TerminusFacet: createSimplePool -- Insufficient allowance on payment token"
            );
            require(
                paymentTokenContract.transferFrom(
                    msg.sender,
                    address(this),
                    requiredPayment
                ),
                "TerminusFacet: createSimplePool -- Transfer of payment token was unsuccessful"
            );
        }
        return LibTerminus.createSimplePool(_capacity);
    }

    function createPoolV1(
        uint256 _capacity,
        bool _transferable,
        bool _burnable
    ) external returns (uint256) {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        uint256 requiredPayment = ts.poolBasePrice;
        if (requiredPayment > 0) {
            IERC20 paymentTokenContract = _paymentTokenContract();
            require(
                paymentTokenContract.allowance(_msgSender(), address(this)) >=
                    requiredPayment,
                "TerminusFacet: createPoolV1 -- Insufficient allowance on payment token"
            );
            require(
                paymentTokenContract.transferFrom(
                    msg.sender,
                    address(this),
                    requiredPayment
                ),
                "TerminusFacet: createPoolV1 -- Transfer of payment token was unsuccessful"
            );
        }
        uint256 poolID = LibTerminus.createSimplePool(_capacity);
        if (!_transferable) {
            ts.poolNotTransferable[poolID] = true;
        }
        if (_burnable) {
            ts.poolBurnable[poolID] = true;
        }
        return poolID;
    }

    function createPoolV2(
        uint256 _capacity,
        bool _transferable,
        bool _burnable,
        string memory poolURI
    ) external returns (uint256) {
        LibTerminus.enforceIsController();
        LibTerminus.TerminusStorage storage ts = LibTerminus.terminusStorage();
        uint256 requiredPayment = ts.poolBasePrice;
        if (requiredPayment > 0) {
            IERC20 paymentTokenContract = _paymentTokenContract();
            require(
                paymentTokenContract.allowance(_msgSender(), address(this)) >=
                    requiredPayment,
                "TerminusFacet: createPoolV2 -- Insufficient allowance on payment token"
            );
            require(
                paymentTokenContract.transferFrom(
                    msg.sender,
                    address(this),
                    requiredPayment
                ),
                "TerminusFacet: createPoolV2 -- Transfer of payment token was unsuccessful"
            );
        }
        uint256 poolID = LibTerminus.createSimplePool(_capacity);
        if (!_transferable) {
            ts.poolNotTransferable[poolID] = true;
        }
        if (_burnable) {
            ts.poolBurnable[poolID] = true;
        }
        ts.poolURI[poolID] = poolURI;
        return poolID;
    }

    function mint(
        address to,
        uint256 poolID,
        uint256 amount,
        bytes memory data
    ) external {
        require(
            isApprovedForPool(poolID, msg.sender),
            "TerminusFacet: mint -- caller is neither owner nor approved"
        );
        _mint(to, poolID, amount, data);
    }

    function mintBatch(
        address to,
        uint256[] memory poolIDs,
        uint256[] memory amounts,
        bytes memory data
    ) external {
        for (uint256 i = 0; i < poolIDs.length; i++) {
            require(
                isApprovedForPool(poolIDs[i], msg.sender),
                "TerminusFacet: mintBatch -- caller is neither owner nor approved"
            );
        }
        _mintBatch(to, poolIDs, amounts, data);
    }

    function burn(address from, uint256 poolID, uint256 amount) external {
        address operator = _msgSender();
        require(
            operator == from || isApprovedForPool(poolID, operator),
            "TerminusFacet: burn -- caller is neither owner nor approved"
        );
        _burn(from, poolID, amount);
    }
}
          

lib/@lagunagames/lg-diamond-template/src/interfaces/IERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.19;

/// @title ERC-20 standard interface
/// @notice Interface of the ERC-20 standard as defined in the ERC.
/// @dev Adapted from OpenZeppelin: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}
          

lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol

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

/// @title Library for the common LG implementation of ERC-173 Contract Ownership Standard
/// @author [email protected]
/// @custom:storage-location erc1967:eip1967.proxy.admin
library LibContractOwner {
    error CallerIsNotContractOwner();

    /// @notice This emits when ownership of a contract changes.
    /// @dev ERC-173
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice Emitted when the admin account has changed.
    /// @dev ERC-1967
    event AdminChanged(address previousAdmin, address newAdmin);

    //  @dev Standard storage slot for the ERC-1967 admin address
    //  @dev bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
    bytes32 private constant ADMIN_SLOT_POSITION = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    struct LibOwnerStorage {
        address contractOwner;
    }

    /// @notice Storage slot for Contract Owner state data
    function ownerStorage() internal pure returns (LibOwnerStorage storage storageSlot) {
        bytes32 position = ADMIN_SLOT_POSITION;

        // solhint-disable-next-line no-inline-assembly
        assembly {
            storageSlot.slot := position
        }
    }

    /// @notice Sets the contract owner
    /// @param newOwner The new owner
    /// @custom:emits OwnershipTransferred
    function setContractOwner(address newOwner) internal {
        LibOwnerStorage storage ls = ownerStorage();
        address previousOwner = ls.contractOwner;
        ls.contractOwner = newOwner;
        emit OwnershipTransferred(previousOwner, newOwner);
        emit AdminChanged(previousOwner, newOwner);
    }

    /// @notice Gets the contract owner wallet
    /// @return owner The contract owner
    function contractOwner() internal view returns (address owner) {
        owner = ownerStorage().contractOwner;
    }

    /// @notice Ensures that the caller is the contract owner, or throws an error.
    /// @custom:throws LibAccess: Must be contract owner
    function enforceIsContractOwner() internal view {
        if (msg.sender != ownerStorage().contractOwner) revert CallerIsNotContractOwner();
    }
}
          

src/entities/common/Component.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

struct Component {
    address asset; //address of the asset
    uint256 poolId; //poolId is for ERC1155 assets, doesn't need to be assigned for non 1155
    uint128 amount;
    uint128 assetType; //20 = ERC20, 721 = ERC721, 1155 = ERC1155
}
          

src/implementation/MigrationFragment.sol

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

import {LibStructs} from '../libraries/LibStructs.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract MigrationFragment {
    function mig_getLastTemplateId() external view returns (uint256) {}

    function mig_setLastTemplateId(uint256 n) external {}

    function mig_getLastPoolId() external view returns (uint256) {}

    function mig_setLastPoolId(uint256 n) external {}

    function mig_getLastAffixId() external view returns (uint256) {}

    function mig_setLastAffixId(uint256 n) external {}

    function mig_getLastAffixBucketId() external view returns (uint256) {}

    function mig_setLastAffixBucketId(uint256 n) external {}

    function mig_getMaxRitualsPerBatch() external view returns (uint256) {}

    function mig_setMaxRitualsPerBatch(uint256 n) external {}

    function mig_setFarmableItemCount(uint256 count) external {}

    function mig_getHatcheryLevelForAddress(address[] calldata users) external view returns (uint256[] memory levels) {}

    function mig_setHatcheryLevelForAddress(address[] calldata users, uint256[] calldata levels) external {}

    function mig_getStakingInfoByShadowcornIds(
        uint256[] calldata shadowcornIds
    ) external view returns (LibStructs.StakeData[] memory stakeData) {}

    function mig_setStakingInfoByShadowcornIds(
        uint256[] calldata shadowcornIds,
        LibStructs.StakeData[] calldata stakeData
    ) external {}

    function mig_getShadowcornFarmingData(
        uint256[] calldata shadowcornIds
    ) external view returns (LibStructs.FarmableItem[] memory farmableItems) {}

    function mig_setShadowcornFarmingData(
        uint256[] calldata shadowcornIds,
        LibStructs.FarmableItem[] calldata farmableItems
    ) external {}

    function mig_setUserToStakedShadowcorns(address user, uint256[] calldata stakedTokenIds) external {}

    function mig_setHatcheryLevelCumulativeBonus(uint256 hatcheryLevel, uint256 cumulativeBonus) external {}
}
          

src/libraries/LibGlobalQueue.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @title LibGlobalQueue
/// @author Shiva Shanmuganathan
/// @dev Implementation of the queue data structure, providing a library with struct definition for queue storage in consuming contracts.
/// @notice This library provides functionalities to manage a queue data structure, allowing contracts to enqueue and dequeue items.
library LibGlobalQueue {
    struct QueueStorage {
        mapping(uint256 idx => uint256 waveId) idxToWaveId;
        mapping(uint256 waveId => TxData txData) waveIdToTxData;
        uint256 first;
        uint256 last;
    }

    // NOTE: This structure is a STORAGE STRUCT (used in storage), modifying it can have significant implications.
    // Please exercise caution and avoid modifying this structure unless absolutely necessary.
    struct TxData {
        uint256 quantity;
        uint256 waveUNIM;
        uint256 waveDarkMarks;
        uint256 unclaimedUnim;
        uint256 unclaimedDarkmarks;
    }

    /// @dev Initializes the queue by setting the first and last indices.
    /// @param queue The queue to initialize.
    function initialize(QueueStorage storage queue) internal {
        queue.first = 1;
        queue.last = 0;
    }

    /// @dev Enqueues a new item into the queue.
    /// @param queue The queue to enqueue the item into.
    /// @param waveId The waveId of the item.
    /// @param quantity The quantity associated with the item.
    /// @param waveUNIM The unim pool associated with the item.
    /// @param waveDarkMarks The darkmarks pool associated with the item.
    /// @param unclaimedUnim The unclaimed unim associated with the item.
    /// @param unclaimedDarkmarks The unclaimed darkmarks associated with the item.
    function enqueue(
        QueueStorage storage queue,
        uint256 waveId,
        uint256 quantity,
        uint256 waveUNIM,
        uint256 waveDarkMarks,
        uint256 unclaimedUnim,
        uint256 unclaimedDarkmarks
    ) internal {
        enforceQueueInitialized(queue);
        queue.idxToWaveId[++queue.last] = waveId;
        queue.waveIdToTxData[waveId] = TxData(quantity, waveUNIM, waveDarkMarks, unclaimedUnim, unclaimedDarkmarks);
    }

    /// @dev Dequeues an item from the front of the queue.
    /// @param queue The queue to dequeue an item from.
    /// @return waveId The waveId of the dequeued item.
    /// @return quantity The quantity associated with the dequeued item.
    /// @return waveUNIM The unim pool associated with the dequeued item.
    /// @return waveDarkMarks The darkmarks pool associated with the dequeued item.
    /// @return unclaimedUnim The unclaimed unim associated with the dequeued item.
    /// @return unclaimedDarkmarks The unclaimed darkmarks associated with the dequeued item.
    function dequeue(
        QueueStorage storage queue
    )
        internal
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        enforceQueueInitialized(queue);
        enforceNonEmptyQueue(queue);
        waveId = queue.idxToWaveId[queue.first];
        TxData memory txData = queue.waveIdToTxData[waveId];
        quantity = txData.quantity;
        waveUNIM = txData.waveUNIM;
        waveDarkMarks = txData.waveDarkMarks;
        unclaimedUnim = txData.unclaimedUnim;
        unclaimedDarkmarks = txData.unclaimedDarkmarks;

        delete queue.waveIdToTxData[waveId];
        delete queue.idxToWaveId[queue.first];
        queue.first = queue.first + 1;
    }

    /// @notice Updates the quantity of rewards associated with a given wave ID in the queue.
    /// @dev Ensures that the specified wave ID matches the last wave ID in the queue.
    /// @param queue The storage reference to the queue being updated.
    /// @param waveId The wave ID for which the quantity is being updated.
    /// @param quantity The amount to add to the existing quantity associated with the wave ID.
    /// require The specified wave ID must match the last wave ID in the queue.
    function updateQty(QueueStorage storage queue, uint256 waveId, uint256 quantity) internal {
        require(queue.idxToWaveId[queue.last] == waveId, 'LibGlobalQueue: Wave does not exist in Global Queue.');
        queue.waveIdToTxData[waveId].quantity += quantity;
    }

    /// @notice Deducts the claimed UNIM and Dark Marks rewards for a specific wave ID in the provided queue.
    /// @dev This function updates the unclaimed rewards within the queue's transaction data, reducing them by the amounts claimed.
    /// @param queue The storage reference to the queue where the claimed rewards are being deducted.
    /// @param waveId The wave ID for which the rewards are being deducted.
    /// @param claimedUnim The amount of UNIM rewards that have been claimed and should be deducted.
    /// @param claimedDarkmarks The amount of Dark Marks rewards that have been claimed and should be deducted.
    function deductClaimedRewards(
        QueueStorage storage queue,
        uint256 waveId,
        uint256 claimedUnim,
        uint256 claimedDarkmarks
    ) internal {
        TxData storage txData = queue.waveIdToTxData[waveId];
        txData.unclaimedUnim -= claimedUnim;
        txData.unclaimedDarkmarks -= claimedDarkmarks;
    }

    /// @dev Checks if the queue has been initialized.
    /// @param queue The queue to check.
    /// @return isQueueInitialized True if the queue is initialized, false otherwise.
    function isInitialized(QueueStorage storage queue) internal view returns (bool isQueueInitialized) {
        return queue.first != 0;
    }

    /// @dev Checks if the queue is initialized and raises an error if not.
    /// @param queue The queue to check for initialization.
    function enforceQueueInitialized(QueueStorage storage queue) internal view {
        require(isInitialized(queue), 'LibGlobalQueue: Queue is not initialized.');
    }

    /// @dev Function to check if the queue is not empty.
    /// @param queue The queue to check.
    function enforceNonEmptyQueue(QueueStorage storage queue) internal view {
        require(!isEmpty(queue), 'LibGlobalQueue: Queue is empty.');
    }

    /// @dev Returns the length of the queue.
    /// @param queue The queue to get the length of.
    /// @return queueLength The length of the queue.
    function length(QueueStorage storage queue) internal view returns (uint256 queueLength) {
        if (queue.last < queue.first) {
            return 0;
        }
        return queue.last - queue.first + 1;
    }

    /// @dev Checks if the queue is empty.
    /// @param queue The queue to check.
    /// @return isQueueEmpty True if the queue is empty, false otherwise.
    function isEmpty(QueueStorage storage queue) internal view returns (bool isQueueEmpty) {
        return length(queue) == 0;
    }

    /// @dev Returns the item at the front of the queue without dequeuing it.
    /// @param queue The queue to get the front item from.
    /// @return waveId The waveId of the front item.
    /// @return quantity The quantity associated with the front item.
    /// @return waveUNIM The unim pool associated with the front item.
    /// @return waveDarkMarks The darkmarks pool associated with the front item.
    /// @return unclaimedUnim The unclaimed unim associated with the front item.
    /// @return unclaimedDarkmarks The unclaimed darkmarks associated with the front item.
    function peek(
        QueueStorage storage queue
    )
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        waveId = queue.idxToWaveId[queue.first];
        TxData memory txData = queue.waveIdToTxData[waveId];
        quantity = txData.quantity;
        waveUNIM = txData.waveUNIM;
        waveDarkMarks = txData.waveDarkMarks;
        unclaimedUnim = txData.unclaimedUnim;
        unclaimedDarkmarks = txData.unclaimedDarkmarks;
    }

    /// @dev Returns the item at the end of the queue without dequeuing it.
    /// @param queue The queue to get the last item from.
    /// @return waveId The waveId of the last item.
    /// @return quantity The quantity associated with the last item.
    /// @return waveUNIM The unim pool associated with the last item.
    /// @return waveDarkMarks The darkmarks pool associated with the last item.
    /// @return unclaimedUnim The unclaimed unim associated with the last item.
    /// @return unclaimedDarkmarks The unclaimed darkmarks associated with the last item.
    function peekLast(
        QueueStorage storage queue
    )
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        waveId = queue.idxToWaveId[queue.last];
        TxData memory txData = queue.waveIdToTxData[waveId];
        quantity = txData.quantity;
        waveUNIM = txData.waveUNIM;
        waveDarkMarks = txData.waveDarkMarks;
        unclaimedUnim = txData.unclaimedUnim;
        unclaimedDarkmarks = txData.unclaimedDarkmarks;
    }

    /// @dev Returns the item at the given index in the queue.
    /// @param queue The queue to get the item from.
    /// @param idx The index of the item to retrieve.
    /// @return waveId The waveId of the item at the given index.
    /// @return quantity The quantity associated with the item at the given index.
    /// @return waveUNIM The unim pool associated with the item at the given index.
    /// @return waveDarkMarks The darkmarks pool associated with the item at the given index.
    /// @return unclaimedUnim The unclaimed unim associated with the item at the given index.
    /// @return unclaimedDarkmarks The unclaimed darkmarks associated with the item at the given index.
    function at(
        QueueStorage storage queue,
        uint256 idx
    )
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        idx = idx + queue.first;
        waveId = queue.idxToWaveId[idx];
        TxData memory txData = queue.waveIdToTxData[waveId];
        quantity = txData.quantity;
        waveUNIM = txData.waveUNIM;
        waveDarkMarks = txData.waveDarkMarks;
        unclaimedUnim = txData.unclaimedUnim;
        unclaimedDarkmarks = txData.unclaimedDarkmarks;
    }

    /// @notice Checks if a given wave ID exists in the specified queue.
    /// @param queue The storage reference to the queue being checked.
    /// @param waveId The wave ID to check for existence in the queue.
    /// @return waveExists A boolean value indicating whether the specified wave ID exists in the queue.
    function waveIdExistsInQueue(QueueStorage storage queue, uint256 waveId) internal view returns (bool waveExists) {
        if (queue.waveIdToTxData[waveId].quantity == 0) {
            return false;
        }
        return true;
    }

    /// @notice Retrieves the quantity associated with a given wave ID in the specified queue.
    /// @param queue The storage reference to the queue from which the quantity is being retrieved.
    /// @param waveId The wave ID for which the quantity is being fetched.
    /// @return quantity The quantity associated with the specified wave ID.
    function getQtyByWaveId(QueueStorage storage queue, uint256 waveId) internal view returns (uint256 quantity) {
        return queue.waveIdToTxData[waveId].quantity;
    }

    /// @dev Returns the dequeue count for waves more than 7 days old (or 7 waves ago) from the provided waveId.
    /// @param queue The queue to get the dequeue count from.
    /// @param waveId The waveId from which to count older waves.
    /// @return dequeueCount The count of waves more than 7 days old.
    function getDequeueCount(QueueStorage storage queue, uint256 waveId) internal view returns (uint256) {
        uint256 dequeueCount = 0;
        uint256 waveIdFromQueue;

        // If queue length is 0, there's nothing to dequeue.
        if (length(queue) == 0) {
            return 0;
        }

        // Loop over the queue from the end to the beginning.
        for (uint256 i = 0; i < length(queue); i++) {
            // Get the waveIdFromQueue at index i
            (waveIdFromQueue, , , , , ) = at(queue, i);

            // If the waveIdFromQueue is more than 7 days old, increment dequeueCount and continue.
            if (waveId - 6 > waveIdFromQueue) {
                dequeueCount++;
            } else {
                // If the waveIdFromQueue is within 7 days, break the loop.
                break;
            }
        }

        return dequeueCount;
    }

    /// @notice Checks if the queue has any waves that are considered outdated based on the given wave ID.
    /// @param queue The storage reference to the queue being checked.
    /// @param waveId The wave ID used to determine if there are any outdated waves in the queue.
    /// @return waveExists A boolean value indicating whether there are outdated waves in the queue.
    function hasOutdatedWaves(QueueStorage storage queue, uint256 waveId) internal view returns (bool waveExists) {
        (uint256 waveIdFromQueueStart, , , , , ) = peek(queue);
        if (waveId - waveIdFromQueueStart + 1 > 7) {
            return true;
        }
        return false;
    }

    /// @notice Calculates the total unclaimed rewards in UNIM and DarkMarks for outdated waves in the queue.
    /// @param queue The storage reference to the queue being examined.
    /// @param waveId The wave ID used to determine if there are any outdated waves in the queue.
    /// @return totalUnclaimedUNIM The total unclaimed rewards in UNIM for the outdated waves.
    /// @return totalUnclaimedDarkMarks The total unclaimed rewards in DarkMarks for the outdated waves.
    function getUnclaimedRewardsInQueue(
        QueueStorage storage queue,
        uint256 waveId
    ) internal view returns (uint256 totalUnclaimedUNIM, uint256 totalUnclaimedDarkMarks) {
        for (uint256 i = 0; i < length(queue); i++) {
            (uint256 waveIdFromQueue, , , , uint256 unclaimedUnim, uint256 unclaimedDarkmarks) = at(queue, i);
            if (waveId - 6 > waveIdFromQueue) {
                totalUnclaimedUNIM += (unclaimedUnim);
                totalUnclaimedDarkMarks += (unclaimedDarkmarks);
            } else {
                break;
            }
        }
        return (totalUnclaimedUNIM, totalUnclaimedDarkMarks);
    }

    /// @notice Retrieves the claimable UNIM reward for a given wave ID from the queue.
    /// @param queue The storage reference to the queue being examined.
    /// @param waveId The wave ID for which the claimable UNIM is being retrieved.
    /// @return claimableUNIM The amount of claimable UNIM for the specified wave ID.
    function getClaimableUNIM(
        QueueStorage storage queue,
        uint256 waveId
    ) internal view returns (uint256 claimableUNIM) {
        return queue.waveIdToTxData[waveId].unclaimedUnim;
    }

    /// @notice Retrieves the claimable DarkMarks reward for a given wave ID from the queue.
    /// @param queue The storage reference to the queue being examined.
    /// @param waveId The wave ID for which the claimable DarkMarks is being retrieved.
    /// @return claimableDarkMarks The amount of claimable DarkMarks for the specified wave ID.
    function getClaimableDarkMarks(
        QueueStorage storage queue,
        uint256 waveId
    ) internal view returns (uint256 claimableDarkMarks) {
        return queue.waveIdToTxData[waveId].unclaimedDarkmarks;
    }

    /// @notice Retrieves the UNIM rewards associated with a given wave ID in the specified queue.
    /// @param queue The storage reference to the queue from which the UNIM rewards are being retrieved.
    /// @param waveId The wave ID for which the UNIM rewards are being fetched.
    /// @return waveUNIM The UNIM rewards associated with the specified wave ID.
    function getWaveUNIMByWaveId(QueueStorage storage queue, uint256 waveId) internal view returns (uint256 waveUNIM) {
        return queue.waveIdToTxData[waveId].waveUNIM;
    }

    /// @notice Retrieves the Dark Marks rewards associated with a given wave ID in the specified queue.
    /// @param queue The storage reference to the queue from which the Dark Marks rewards are being retrieved.
    /// @param waveId The wave ID for which the Dark Marks rewards are being fetched.
    /// @return waveDarkMarks The Dark Marks rewards associated with the specified wave ID.
    function getWaveDarkMarksByWaveId(
        QueueStorage storage queue,
        uint256 waveId
    ) internal view returns (uint256 waveDarkMarks) {
        return queue.waveIdToTxData[waveId].waveDarkMarks;
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibValidate.sol

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

library LibValidate {
    function enforceNonEmptyString(string memory str) internal pure {
        require(bytes(str).length > 0, 'String cannot be empty');
    }

    function enforceNonZeroAddress(address addr) internal pure {
        require(addr != address(0), 'Address cannot be zero address');
    }

    function enforceNonEmptyAddressArray(address[] memory array) internal pure {
        require(array.length != 0, 'Address array cannot be empty.');
    }

    function enforceNonEmptyStringArray(string[] memory array) internal pure {
        require(array.length != 0, 'String array cannot be empty.');
    }

    function enforceNonEmptyUintArray(uint256[] memory array) internal pure {
        require(array.length != 0, 'uint256 array cannot be empty.');
    }

    function enforceNonEmptyBoolArray(bool[] memory array) internal pure {
        require(array.length != 0, 'bool array cannot be empty.');
    }
}
          

lib/@lagunagames/cu-common/src/interfaces/IResourceLocator.sol

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

/// @title Resource Locator for Crypto Unicorns
/// @author [email protected]
interface IResourceLocator {
    /// @notice Returns the Unicorn NFT contract address
    function unicornNFTAddress() external view returns (address);

    /// @notice Returns the Land NFT contract address
    function landNFTAddress() external view returns (address);

    /// @notice Returns the Shadowcorn NFT contract address
    function shadowcornNFTAddress() external view returns (address);

    /// @notice Returns the Gem NFT contract address
    function gemNFTAddress() external view returns (address);

    /// @notice Returns the Ritual NFT contract address
    function ritualNFTAddress() external view returns (address);

    /// @notice Returns the RBW Token contract address
    function rbwTokenAddress() external view returns (address);

    /// @notice Returns the CU Token contract address
    function cuTokenAddress() external view returns (address);

    /// @notice Returns the UNIM Token contract address
    function unimTokenAddress() external view returns (address);

    /// @notice Returns the WETH Token contract address
    function wethTokenAddress() external view returns (address);

    /// @notice Returns the DarkMark Token contract address
    function darkMarkTokenAddress() external view returns (address);

    /// @notice Returns the Unicorn Items contract address
    function unicornItemsAddress() external view returns (address);

    /// @notice Returns the Shadowcorn Items contract address
    function shadowcornItemsAddress() external view returns (address);

    /// @notice Returns the Access Control Badge contract address
    function accessControlBadgeAddress() external view returns (address);

    /// @notice Returns the GameBank contract address
    function gameBankAddress() external view returns (address);

    /// @notice Returns the SatelliteBank contract address
    function satelliteBankAddress() external view returns (address);

    /// @notice Returns the PlayerProfile contract address
    function playerProfileAddress() external view returns (address);

    /// @notice Returns the Shadow Forge contract address
    function shadowForgeAddress() external view returns (address);

    /// @notice Returns the Dark Forest contract address
    function darkForestAddress() external view returns (address);

    /// @notice Returns the Game Server SSS contract address
    function gameServerSSSAddress() external view returns (address);

    /// @notice Returns the Game Server Oracle contract address
    function gameServerOracleAddress() external view returns (address);

    /// @notice Returns the VRF Oracle contract address
    function vrfOracleAddress() external view returns (address);

    /// @notice Returns the VRF Client Wallet address
    function vrfClientWalletAddress() external view returns (address);

    /// @notice Returns the Testnet Debug Registry address
    /// @dev Available on testnet deployments only
    function testnetDebugRegistryAddress() external view returns (address);
}
          

src/entities/level/LevelUnlockCost.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import {Component} from '../common/Component.sol';
import {LevelUnlockCostTokenTransferType} from './LevelUnlockCostTokenTransferType.sol';

/*
    @title Level Unlock Cost
    @notice This struct is used to define the cost of unlocking each level
    @param component The component that is required to unlock the level
    @param transferType The type of transfer that will be used for the cost (BURN, TRANSFER)
 */
struct LevelUnlockCost {
    Component component;
    LevelUnlockCostTokenTransferType transferType;
}
          

lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol

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

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
          

lib/@lagunagames/cu-common/src/interfaces/IArbSys.sol

// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE
// SPDX-License-Identifier: BUSL-1.1

pragma solidity >=0.4.21 <0.9.0;

/**
 * @title System level functionality
 * @notice For use by contracts to interact with core L2-specific functionality.
 * Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064.
 */
/// @custom:see https://docs.arbitrum.io/build-decentralized-apps/precompiles/reference
/// @custom:see https://github.com/OffchainLabs/nitro-contracts/blob/1cab72ff3dfcfe06ceed371a9db7a54a527e3bfb/src/precompiles/ArbSys.sol
interface IArbSys {
    /**
     * @notice Get Arbitrum block number (distinct from L1 block number; Arbitrum genesis block has block number 0)
     * @return block number as int
     */
    function arbBlockNumber() external view returns (uint256);

    /**
     * @notice Get Arbitrum block hash (reverts unless currentBlockNum-256 <= arbBlockNum < currentBlockNum)
     * @return block hash
     */
    function arbBlockHash(uint256 arbBlockNum) external view returns (bytes32);

    /**
     * @notice Gets the rollup's unique chain identifier
     * @return Chain identifier as int
     */
    function arbChainID() external view returns (uint256);

    /**
     * @notice Get internal version number identifying an ArbOS build
     * @return version number as int
     */
    function arbOSVersion() external view returns (uint256);

    /**
     * @notice Returns 0 since Nitro has no concept of storage gas
     * @return uint 0
     */
    function getStorageGasAvailable() external view returns (uint256);

    /**
     * @notice (deprecated) check if current call is top level (meaning it was triggered by an EoA or a L1 contract)
     * @dev this call has been deprecated and may be removed in a future release
     * @return true if current execution frame is not a call by another L2 contract
     */
    function isTopLevelCall() external view returns (bool);

    /**
     * @notice map L1 sender contract address to its L2 alias
     * @param sender sender address
     * @param unused argument no longer used
     * @return aliased sender address
     */
    function mapL1SenderContractAddressToL2Alias(address sender, address unused) external pure returns (address);

    /**
     * @notice check if the caller (of this caller of this) is an aliased L1 contract address
     * @return true iff the caller's address is an alias for an L1 contract address
     */
    function wasMyCallersAddressAliased() external view returns (bool);

    /**
     * @notice return the address of the caller (of this caller of this), without applying L1 contract address aliasing
     * @return address of the caller's caller, without applying L1 contract address aliasing
     */
    function myCallersAddressWithoutAliasing() external view returns (address);

    /**
     * @notice Send given amount of Eth to dest from sender.
     * This is a convenience function, which is equivalent to calling sendTxToL1 with empty data.
     * @param destination recipient address on L1
     * @return unique identifier for this L2-to-L1 transaction.
     */
    function withdrawEth(address destination) external payable returns (uint256);

    /**
     * @notice Send a transaction to L1
     * @dev it is not possible to execute on the L1 any L2-to-L1 transaction which contains data
     * to a contract address without any code (as enforced by the Bridge contract).
     * @param destination recipient address on L1
     * @param data (optional) calldata for L1 contract call
     * @return a unique identifier for this L2-to-L1 transaction.
     */
    function sendTxToL1(address destination, bytes calldata data) external payable returns (uint256);

    /**
     * @notice Get send Merkle tree state
     * @return size number of sends in the history
     * @return root root hash of the send history
     * @return partials hashes of partial subtrees in the send history tree
     */
    function sendMerkleTreeState() external view returns (uint256 size, bytes32 root, bytes32[] memory partials);

    /**
     * @notice creates a send txn from L2 to L1
     * @param position = (level << 192) + leaf = (0 << 192) + leaf = leaf
     */
    event L2ToL1Tx(
        address caller,
        address indexed destination,
        uint256 indexed hash,
        uint256 indexed position,
        uint256 arbBlockNum,
        uint256 ethBlockNum,
        uint256 timestamp,
        uint256 callvalue,
        bytes data
    );

    /// @dev DEPRECATED in favour of the new L2ToL1Tx event above after the nitro upgrade
    event L2ToL1Transaction(
        address caller,
        address indexed destination,
        uint256 indexed uniqueId,
        uint256 indexed batchNumber,
        uint256 indexInBatch,
        uint256 arbBlockNum,
        uint256 ethBlockNum,
        uint256 timestamp,
        uint256 callvalue,
        bytes data
    );

    /**
     * @notice logs a merkle branch for proof synthesis
     * @param reserved an index meant only to align the 4th index with L2ToL1Transaction's 4th event
     * @param hash the merkle hash
     * @param position = (level << 192) + leaf
     */
    event SendMerkleUpdate(uint256 indexed reserved, bytes32 indexed hash, uint256 indexed position);

    error InvalidBlockNumber(uint256 requested, uint256 current);
}
          

src/libraries/LibTime.sol

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

// ----------------------------------------------------------------------------
// BokkyPooBah's DateTime Library v1.01
//
// A gas-efficient Solidity date and time library
//
// https://github.com/bokkypoobah/BokkyPooBahsDateTimeLibrary
//
// Tested date range 1970/01/01 to 2345/12/31
//
// Conventions:
// Unit      | Range         | Notes
// :-------- |:-------------:|:-----
// timestamp | >= 0          | Unix timestamp, number of seconds since 1970/01/01 00:00:00 UTC
// year      | 1970 ... 2345 |
// month     | 1 ... 12      |
// day       | 1 ... 31      |
// hour      | 0 ... 23      |
// minute    | 0 ... 59      |
// second    | 0 ... 59      |
// dayOfWeek | 1 ... 7       | 1 = Monday, ..., 7 = Sunday
//
//
// Enjoy. (c) BokkyPooBah / Bok Consulting Pty Ltd 2018-2019. The MIT Licence.
// ----------------------------------------------------------------------------

library LibTime {
    uint constant SECONDS_PER_WEEK = 7 * 24 * 60 * 60;
    uint constant SECONDS_PER_DAY = 24 * 60 * 60;
    uint constant SECONDS_PER_HOUR = 60 * 60;
    uint constant SECONDS_PER_MINUTE = 60;
    int constant OFFSET19700101 = 2440588;

    uint constant DOW_MON = 1;
    uint constant DOW_TUE = 2;
    uint constant DOW_WED = 3;
    uint constant DOW_THU = 4;
    uint constant DOW_FRI = 5;
    uint constant DOW_SAT = 6;
    uint constant DOW_SUN = 7;

    // ------------------------------------------------------------------------
    // Calculate the number of days from 1970/01/01 to year/month/day using
    // the date conversion algorithm from
    //   https://aa.usno.navy.mil/faq/JD_formula.html
    // and subtracting the offset 2440588 so that 1970/01/01 is day 0
    //
    // days = day
    //      - 32075
    //      + 1461 * (year + 4800 + (month - 14) / 12) / 4
    //      + 367 * (month - 2 - (month - 14) / 12 * 12) / 12
    //      - 3 * ((year + 4900 + (month - 14) / 12) / 100) / 4
    //      - offset
    // ------------------------------------------------------------------------
    function _daysFromDate(uint year, uint month, uint day) internal pure returns (uint _days) {
        require(year >= 1970);
        int _year = int(year);
        int _month = int(month);
        int _day = int(day);

        int __days = _day -
            32075 +
            (1461 * (_year + 4800 + (_month - 14) / 12)) /
            4 +
            (367 * (_month - 2 - ((_month - 14) / 12) * 12)) /
            12 -
            (3 * ((_year + 4900 + (_month - 14) / 12) / 100)) /
            4 -
            OFFSET19700101;

        _days = uint(__days);
    }

    // ------------------------------------------------------------------------
    // Calculate year/month/day from the number of days since 1970/01/01 using
    // the date conversion algorithm from
    //   http://aa.usno.navy.mil/faq/docs/JD_Formula.php
    // and adding the offset 2440588 so that 1970/01/01 is day 0
    //
    // int L = days + 68569 + offset
    // int N = 4 * L / 146097
    // L = L - (146097 * N + 3) / 4
    // year = 4000 * (L + 1) / 1461001
    // L = L - 1461 * year / 4 + 31
    // month = 80 * L / 2447
    // dd = L - 2447 * month / 80
    // L = month / 11
    // month = month + 2 - 12 * L
    // year = 100 * (N - 49) + year + L
    // ------------------------------------------------------------------------
    function _daysToDate(uint _days) internal pure returns (uint year, uint month, uint day) {
        int __days = int(_days);

        int L = __days + 68569 + OFFSET19700101;
        int N = (4 * L) / 146097;
        L = L - (146097 * N + 3) / 4;
        int _year = (4000 * (L + 1)) / 1461001;
        L = L - (1461 * _year) / 4 + 31;
        int _month = (80 * L) / 2447;
        int _day = L - (2447 * _month) / 80;
        L = _month / 11;
        _month = _month + 2 - 12 * L;
        _year = 100 * (N - 49) + _year + L;

        year = uint(_year);
        month = uint(_month);
        day = uint(_day);
    }

    function timestampFromDate(uint year, uint month, uint day) internal pure returns (uint timestamp) {
        timestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY;
    }

    function timestampFromDateTime(
        uint year,
        uint month,
        uint day,
        uint hour,
        uint minute,
        uint second
    ) internal pure returns (uint timestamp) {
        timestamp =
            _daysFromDate(year, month, day) *
            SECONDS_PER_DAY +
            hour *
            SECONDS_PER_HOUR +
            minute *
            SECONDS_PER_MINUTE +
            second;
    }

    function timestampToDate(uint timestamp) internal pure returns (uint year, uint month, uint day) {
        (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    }

    function timestampToDateTime(
        uint timestamp
    ) internal pure returns (uint year, uint month, uint day, uint hour, uint minute, uint second) {
        (year, month, day) = _daysToDate(timestamp / SECONDS_PER_DAY);
        uint secs = timestamp % SECONDS_PER_DAY;
        hour = secs / SECONDS_PER_HOUR;
        secs = secs % SECONDS_PER_HOUR;
        minute = secs / SECONDS_PER_MINUTE;
        second = secs % SECONDS_PER_MINUTE;
    }

    function isValidDate(uint year, uint month, uint day) internal pure returns (bool valid) {
        if (year >= 1970 && month > 0 && month <= 12) {
            uint daysInMonth = _getDaysInMonth(year, month);
            if (day > 0 && day <= daysInMonth) {
                valid = true;
            }
        }
    }

    function isValidDateTime(
        uint year,
        uint month,
        uint day,
        uint hour,
        uint minute,
        uint second
    ) internal pure returns (bool valid) {
        if (isValidDate(year, month, day)) {
            if (hour < 24 && minute < 60 && second < 60) {
                valid = true;
            }
        }
    }

    function isLeapYear(uint timestamp) internal pure returns (bool leapYear) {
        (uint year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY);
        leapYear = _isLeapYear(year);
    }

    function _isLeapYear(uint year) internal pure returns (bool leapYear) {
        leapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
    }

    function isWeekDay(uint timestamp) internal pure returns (bool weekDay) {
        weekDay = getDayOfWeek(timestamp) <= DOW_FRI;
    }

    function isWeekEnd(uint timestamp) internal pure returns (bool weekEnd) {
        weekEnd = getDayOfWeek(timestamp) >= DOW_SAT;
    }

    function getDaysInMonth(uint timestamp) internal pure returns (uint daysInMonth) {
        (uint year, uint month, ) = _daysToDate(timestamp / SECONDS_PER_DAY);
        daysInMonth = _getDaysInMonth(year, month);
    }

    function _getDaysInMonth(uint year, uint month) internal pure returns (uint daysInMonth) {
        if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
            daysInMonth = 31;
        } else if (month != 2) {
            daysInMonth = 30;
        } else {
            daysInMonth = _isLeapYear(year) ? 29 : 28;
        }
    }

    // 1 = Monday, 7 = Sunday
    function getDayOfWeek(uint timestamp) internal pure returns (uint dayOfWeek) {
        uint _days = timestamp / SECONDS_PER_DAY;
        dayOfWeek = ((_days + 3) % 7) + 1;
    }

    function getYear(uint timestamp) internal pure returns (uint year) {
        (year, , ) = _daysToDate(timestamp / SECONDS_PER_DAY);
    }

    function getMonth(uint timestamp) internal pure returns (uint month) {
        (, month, ) = _daysToDate(timestamp / SECONDS_PER_DAY);
    }

    function getDay(uint timestamp) internal pure returns (uint day) {
        (, , day) = _daysToDate(timestamp / SECONDS_PER_DAY);
    }

    function getHour(uint timestamp) internal pure returns (uint hour) {
        uint secs = timestamp % SECONDS_PER_DAY;
        hour = secs / SECONDS_PER_HOUR;
    }

    function getMinute(uint timestamp) internal pure returns (uint minute) {
        uint secs = timestamp % SECONDS_PER_HOUR;
        minute = secs / SECONDS_PER_MINUTE;
    }

    function getSecond(uint timestamp) internal pure returns (uint second) {
        second = timestamp % SECONDS_PER_MINUTE;
    }

    function addYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) {
        (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY);
        year += _years;
        uint daysInMonth = _getDaysInMonth(year, month);
        if (day > daysInMonth) {
            day = daysInMonth;
        }
        newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
        require(newTimestamp >= timestamp);
    }

    function addMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) {
        (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY);
        month += _months;
        year += (month - 1) / 12;
        month = ((month - 1) % 12) + 1;
        uint daysInMonth = _getDaysInMonth(year, month);
        if (day > daysInMonth) {
            day = daysInMonth;
        }
        newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
        require(newTimestamp >= timestamp);
    }

    function addDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp + _days * SECONDS_PER_DAY;
        require(newTimestamp >= timestamp);
    }

    function addHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp + _hours * SECONDS_PER_HOUR;
        require(newTimestamp >= timestamp);
    }

    function addMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp + _minutes * SECONDS_PER_MINUTE;
        require(newTimestamp >= timestamp);
    }

    function addSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp + _seconds;
        require(newTimestamp >= timestamp);
    }

    function subYears(uint timestamp, uint _years) internal pure returns (uint newTimestamp) {
        (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY);
        year -= _years;
        uint daysInMonth = _getDaysInMonth(year, month);
        if (day > daysInMonth) {
            day = daysInMonth;
        }
        newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
        require(newTimestamp <= timestamp);
    }

    function subMonths(uint timestamp, uint _months) internal pure returns (uint newTimestamp) {
        (uint year, uint month, uint day) = _daysToDate(timestamp / SECONDS_PER_DAY);
        uint yearMonth = year * 12 + (month - 1) - _months;
        year = yearMonth / 12;
        month = (yearMonth % 12) + 1;
        uint daysInMonth = _getDaysInMonth(year, month);
        if (day > daysInMonth) {
            day = daysInMonth;
        }
        newTimestamp = _daysFromDate(year, month, day) * SECONDS_PER_DAY + (timestamp % SECONDS_PER_DAY);
        require(newTimestamp <= timestamp);
    }

    function subDays(uint timestamp, uint _days) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp - _days * SECONDS_PER_DAY;
        require(newTimestamp <= timestamp);
    }

    function subHours(uint timestamp, uint _hours) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp - _hours * SECONDS_PER_HOUR;
        require(newTimestamp <= timestamp);
    }

    function subMinutes(uint timestamp, uint _minutes) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp - _minutes * SECONDS_PER_MINUTE;
        require(newTimestamp <= timestamp);
    }

    function subSeconds(uint timestamp, uint _seconds) internal pure returns (uint newTimestamp) {
        newTimestamp = timestamp - _seconds;
        require(newTimestamp <= timestamp);
    }

    function diffYears(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _years) {
        require(fromTimestamp <= toTimestamp);
        (uint fromYear, , ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY);
        (uint toYear, , ) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
        _years = toYear - fromYear;
    }

    function diffMonths(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _months) {
        require(fromTimestamp <= toTimestamp);
        (uint fromYear, uint fromMonth, ) = _daysToDate(fromTimestamp / SECONDS_PER_DAY);
        (uint toYear, uint toMonth, ) = _daysToDate(toTimestamp / SECONDS_PER_DAY);
        _months = toYear * 12 + toMonth - fromYear * 12 - fromMonth;
    }

    function diffDays(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _days) {
        require(fromTimestamp <= toTimestamp);
        _days = (toTimestamp - fromTimestamp) / SECONDS_PER_DAY;
    }

    function diffHours(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _hours) {
        require(fromTimestamp <= toTimestamp);
        _hours = (toTimestamp - fromTimestamp) / SECONDS_PER_HOUR;
    }

    function diffMinutes(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _minutes) {
        require(fromTimestamp <= toTimestamp);
        _minutes = (toTimestamp - fromTimestamp) / SECONDS_PER_MINUTE;
    }

    function diffSeconds(uint fromTimestamp, uint toTimestamp) internal pure returns (uint _seconds) {
        require(fromTimestamp <= toTimestamp);
        _seconds = toTimestamp - fromTimestamp;
    }
}
          

src/entities/level/LevelUpgradeBonusType.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

enum LevelUpgradeBonusType {
    NONE,
    HUSK_GENERATION,
    DARK_MARK_GENERATION,
    HUSK_STORAGE
}
          

lib/openzeppelin-contracts/contracts/utils/math/SignedMath.sol

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

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

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

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}
          

src/libraries/LibPlayerQueue.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

/// @title LibPlayerQueue
/// @author Shiva Shanmuganathan
/// @dev Implementation of the queue data structure, providing a library with struct definition for queue storage in consuming contracts.
/// @notice This library provides functionalities to manage a queue data structure, allowing contracts to enqueue and dequeue items.
library LibPlayerQueue {
    struct QueueStorage {
        mapping(uint256 idx => uint256 waveId) idxToWaveId;
        mapping(uint256 waveId => uint256 quantity) waveIdToQty;
        uint256 first;
        uint256 last;
    }

    /// @dev Initializes the queue by setting the first and last indices.
    /// @param queue The queue to initialize.
    function initialize(QueueStorage storage queue) internal {
        queue.first = 1;
        queue.last = 0;
    }

    /// @dev Enqueues a new item into the queue.
    /// @param queue The queue to enqueue the item into.
    /// @param waveId The waveId of the item.
    /// @param quantity The quantity associated with the item.
    function enqueue(QueueStorage storage queue, uint256 waveId, uint256 quantity) internal {
        enforceQueueInitialized(queue);
        queue.idxToWaveId[++queue.last] = waveId;
        queue.waveIdToQty[waveId] = quantity;
    }

    /// @dev Dequeues an item from the front of the queue.
    /// @param queue The queue to dequeue an item from.
    /// @return waveId The waveId of the dequeued item.
    /// @return quantity The quantity associated with the dequeued item.
    function dequeue(QueueStorage storage queue) internal returns (uint256 waveId, uint256 quantity) {
        enforceQueueInitialized(queue);
        enforceNonEmptyQueue(queue);

        waveId = queue.idxToWaveId[queue.first];
        quantity = queue.waveIdToQty[waveId];

        delete queue.waveIdToQty[waveId];
        delete queue.idxToWaveId[queue.first];

        queue.first = queue.first + 1;
    }

    /// @notice Updates the quantity of rewards associated with a given wave ID in the queue.
    /// @dev Ensures that the specified wave ID matches the last wave ID in the queue.
    /// @param queue The storage reference to the queue being updated.
    /// @param waveId The wave ID for which the quantity is being updated.
    /// @param quantity The amount to add to the existing quantity associated with the wave ID.
    /// require The specified wave ID must match the last wave ID in the queue.
    function updateQty(QueueStorage storage queue, uint256 waveId, uint256 quantity) internal {
        require(queue.idxToWaveId[queue.last] == waveId, 'LibPlayerQueue: Wave does not exist in Player Queue.');
        queue.waveIdToQty[waveId] += quantity;
    }

    /// @dev Checks if the queue has been initialized.
    /// @param queue The queue to check.
    /// @return isQueueInitialized True if the queue is initialized, false otherwise.
    function isInitialized(QueueStorage storage queue) internal view returns (bool isQueueInitialized) {
        return queue.first != 0;
    }

    /// @dev Checks if the queue is initialized and raises an error if not.
    /// @param queue The queue to check for initialization.
    function enforceQueueInitialized(QueueStorage storage queue) internal view {
        require(isInitialized(queue), 'LibPlayerQueue: Queue is not initialized.');
    }

    /// @dev Function to check if the queue is not empty.
    /// @param queue The queue to check.
    function enforceNonEmptyQueue(QueueStorage storage queue) internal view {
        require(!isEmpty(queue), 'LibPlayerQueue: Queue is empty.');
    }

    /// @dev Returns the length of the queue.
    /// @param queue The queue to get the length of.
    /// @return queueLength The length of the queue.
    function length(QueueStorage storage queue) internal view returns (uint256 queueLength) {
        if (queue.last < queue.first) {
            return 0;
        }
        return queue.last - queue.first + 1;
    }

    /// @dev Checks if the queue is empty.
    /// @param queue The queue to check.
    /// @return isQueueEmpty True if the queue is empty, false otherwise.
    function isEmpty(QueueStorage storage queue) internal view returns (bool isQueueEmpty) {
        return length(queue) == 0;
    }

    /// @dev Returns the item at the front of the queue without dequeuing it.
    /// @param queue The queue to get the front item from.
    /// @return waveId The waveId of the front item.
    /// @return quantity The quantity associated with the front item.
    function peek(QueueStorage storage queue) internal view returns (uint256 waveId, uint256 quantity) {
        enforceNonEmptyQueue(queue);
        waveId = queue.idxToWaveId[queue.first];
        quantity = queue.waveIdToQty[waveId];
    }

    /// @dev Returns the item at the end of the queue without dequeuing it.
    /// @param queue The queue to get the last item from.
    /// @return waveId The waveId of the last item.
    /// @return quantity The quantity associated with the last item.
    function peekLast(QueueStorage storage queue) internal view returns (uint256 waveId, uint256 quantity) {
        enforceNonEmptyQueue(queue);
        waveId = queue.idxToWaveId[queue.last];
        quantity = queue.waveIdToQty[waveId];
    }

    /// @dev Returns the item at the given index in the queue.
    /// @param queue The queue to get the item from.
    /// @param idx The index of the item to retrieve.
    /// @return waveId The waveId of the item at the given index.
    /// @return quantity The quantity associated with the item at the given index.
    function at(QueueStorage storage queue, uint256 idx) internal view returns (uint256 waveId, uint256 quantity) {
        idx = idx + queue.first;
        waveId = queue.idxToWaveId[idx];
        quantity = queue.waveIdToQty[waveId];
    }

    /// @notice Checks if a given wave ID exists in the specified queue.
    /// @param queue The storage reference to the queue being checked.
    /// @param waveId The wave ID to check for existence in the queue.
    /// @return waveExists A boolean value indicating whether the specified wave ID exists in the queue.
    function waveIdExistsInQueue(QueueStorage storage queue, uint256 waveId) internal view returns (bool waveExists) {
        if (queue.waveIdToQty[waveId] == 0) {
            return false;
        }
        return true;
    }

    /// @notice Retrieves the quantity associated with a given wave ID in the specified queue.
    /// @param queue The storage reference to the queue from which the quantity is being retrieved.
    /// @param waveId The wave ID for which the quantity is being fetched.
    /// @return quantity The quantity associated with the specified wave ID.
    function getQtyByWaveId(QueueStorage storage queue, uint256 waveId) internal view returns (uint256 quantity) {
        return queue.waveIdToQty[waveId];
    }

    /// @notice Calculates the number of elements that can be dequeued from the provided queue based on the given wave ID.
    /// @dev This function checks the last element in the queue to determine how many elements can be dequeued.
    ///      The dequeuing logic is dependent on whether the last wave ID in the queue matches the given wave ID.
    /// @param queue The storage reference to the queue from which the dequeue count is being calculated.
    /// @param waveId The wave ID used as the reference point for calculating the dequeue count.
    /// @return dequeueCount The calculated number of elements that can be dequeued from the queue.
    function getDequeueCount(QueueStorage storage queue, uint256 waveId) internal view returns (uint256 dequeueCount) {
        uint256 queueLen = length(queue);
        (uint256 waveIdFromQueue, ) = peekLast(queue);
        if (waveIdFromQueue == waveId) {
            dequeueCount = queueLen - 1;
        } else {
            dequeueCount = queueLen;
        }
        return dequeueCount;
    }
}
          

src/implementation/HatcheryRitualsFragment.sol

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

import {LibHatcheryRituals} from '../libraries/LibHatcheryRituals.sol';
import {LibRitualComponents} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualComponents.sol';
import {LibConstraints} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract HatcheryRitualsFragment {
    event RitualTemplateCreated(uint256 indexed id);
    event RitualTemplatePoolCreated(uint256 indexed id);
    event AffixCreated(uint256 indexed id);
    event AffixBucketCreated(uint256 indexed id, uint256[] affixIds);
    event BeginRitualCreation(
        address indexed playerWallet,
        uint256 indexed ritualTemplatePoolId,
        uint256 indexed vrfRequestId
    );
    event FinishRitualCreation(
        uint256 indexed vrfRequestId,
        uint256 ritualTemplateId,
        uint256 indexed ritualTokenId,
        uint256[] affixIdsApplied,
        address indexed user
    );

    event HatcheryAffixWarning(string warningText);

    // @notice This function is used to consume a ritual charge.
    // @param ritualId The id of the ritual to consume a charge from.
    function consumeRitualCharge(uint256 ritualId) external {}

    function batchConsumeRitualCharges(uint256[] memory ritualIds) external {}

    function setMaxRitualsPerBatch(uint256 maxRitualsPerBatch) external {}

    function getMaxRitualsPerBatch() external view returns (uint256 maxRituals) {}

    function canConsumeRitual(uint256 ritualId, address user) external view returns (bool canConsume) {}

    function createRitual(uint256 ritualTemplatePoolId) external {}

    function getRitualTemplateById(
        uint256 ritualTemplateId
    ) external view returns (LibHatcheryRituals.RitualTemplate memory ritualTemplate) {}

    function removeAffixFromBucket(uint256 affixId, uint256 bucketId) external {}

    function addAffixesToBucket(uint256[] memory affixesIds, uint256 bucketId) external {}

    function createAffix(LibHatcheryRituals.Affix memory affix) external returns (uint256 id) {}

    function addRitualTemplateToPool(uint256 ritualTemplateId, uint256 ritualPoolId, uint256 rngWeight) external {}

    function createRitualTemplatePool(
        LibRitualComponents.RitualCost[] memory creationCosts,
        LibConstraints.Constraint[] memory creationConstraints
    ) external returns (uint256 id) {}

    function createRitualTemplate(LibHatcheryRituals.RitualTemplate memory template) external returns (uint256 id) {}

    function getCreationConstraintsAndCosts(
        uint256 ritualTemplatePoolId
    )
        external
        view
        returns (LibConstraints.Constraint[] memory constraints, LibRitualComponents.RitualCost[] memory costs)
    {}

    function createAffixBucket(uint256[] memory affixIds) external {}
}
          

src/implementation/GlueFactoryFragment.sol

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

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract GlueFactoryFragment {
    event BatchSacrificeUnicorns(uint256[] tokenIds, address user);

    function batchSacrificeUnicorns(uint256[] memory tokenIds) external {}

    function setUnicornSoulsPoolId(uint256 poolId) external {}

    function getUnicornSoulsPoolId() external view returns (uint256 poolId) {}

    function setMaxBatchSacrificeUnicornsAmount(uint256 maxBatchAmount) external {}

    function getMaxBatchSacrificeUnicornsAmount() external view returns (uint256 maxBatchAmount) {}
}
          

src/libraries/LibArray.sol

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

import {LibRitualComponents} from '../../lib/@lagunagames/cu-common/src/libraries/LibRitualComponents.sol';
import {LibConstraints} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol';

library LibArray {
    function popFromMemoryArray(uint256[] memory array) internal pure returns (uint256[] memory) {
        require(array.length > 0, 'LibArray: Cannot pop from empty array');
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
        // Some important points to remember:

        // Make sure this assembly code never runs when backerList.length == 0 (don't allow the array length to underflow)

        // Don't try to use this to increase the size of an array (by replacing sub with add)

        // Only use it on variables with a type like ...[] memory (for example, don't use it on a address[10] memory or address)

        // Disclaimer: The use of inline assembly is usually not recommended. Use it with caution and at your own risk :)
        // source: https://ethereum.stackexchange.com/questions/51891/how-to-pop-from-decrease-the-length-of-a-memory-array-in-solidity
    }

    function removeFromMemoryArray(
        uint256[] memory array,
        uint256 positionToRemove
    ) internal pure returns (uint256[] memory) {
        require(array.length > 0, 'LibArray: Cannot remove from empty array');
        require(
            positionToRemove < array.length,
            'LibArray: Cannot remove from array at position greater than array length'
        );
        array[positionToRemove] = array[array.length - 1];
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function pushToMemoryArray(uint256[] memory array, uint256 element) internal pure returns (uint256[] memory) {
        uint256[] memory newArray = new uint256[](array.length + 1);
        for (uint256 i = 0; i < array.length; i++) {
            newArray[i] = array[i];
        }
        newArray[array.length] = element;
        return newArray;
    }

    function pushToConstraintMemoryArray(
        LibConstraints.Constraint[] memory array,
        LibConstraints.Constraint memory element
    ) internal pure returns (LibConstraints.Constraint[] memory) {
        LibConstraints.Constraint[] memory newArray = new LibConstraints.Constraint[](array.length + 1);
        for (uint256 i = 0; i < array.length; i++) {
            newArray[i] = array[i];
        }
        newArray[array.length] = element;
        return newArray;
    }

    function removeFromConstraintMemoryArray(
        LibConstraints.Constraint[] memory array,
        uint256 positionToRemove
    ) internal pure returns (LibConstraints.Constraint[] memory) {
        require(array.length > 0, 'LibArray: Cannot remove from empty array');
        require(
            positionToRemove < array.length,
            'LibArray: Cannot remove from array at position greater than array length'
        );
        array[positionToRemove] = array[array.length - 1];
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function popFromConstraintMemoryArray(
        LibConstraints.Constraint[] memory array
    ) internal pure returns (LibConstraints.Constraint[] memory) {
        require(array.length > 0, 'LibArray: Cannot pop from empty array');
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function pushToProductMemoryArray(
        LibRitualComponents.RitualProduct[] memory array,
        LibRitualComponents.RitualProduct memory element
    ) internal pure returns (LibRitualComponents.RitualProduct[] memory) {
        LibRitualComponents.RitualProduct[] memory newArray = new LibRitualComponents.RitualProduct[](array.length + 1);
        for (uint256 i = 0; i < array.length; i++) {
            newArray[i] = array[i];
        }
        newArray[array.length] = element;
        return newArray;
    }

    function removeFromProductMemoryArray(
        LibRitualComponents.RitualProduct[] memory array,
        uint256 positionToRemove
    ) internal pure returns (LibRitualComponents.RitualProduct[] memory) {
        require(array.length > 0, 'LibArray: Cannot remove from empty array');
        require(
            positionToRemove < array.length,
            'LibArray: Cannot remove from array at position greater than array length'
        );
        array[positionToRemove] = array[array.length - 1];
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function popFromProductMemoryArray(
        LibRitualComponents.RitualProduct[] memory array
    ) internal pure returns (LibRitualComponents.RitualProduct[] memory) {
        require(array.length > 0, 'LibArray: Cannot pop from empty array');
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function pushToCostMemoryArray(
        LibRitualComponents.RitualCost[] memory array,
        LibRitualComponents.RitualCost memory element
    ) internal pure returns (LibRitualComponents.RitualCost[] memory) {
        LibRitualComponents.RitualCost[] memory newArray = new LibRitualComponents.RitualCost[](array.length + 1);
        for (uint256 i = 0; i < array.length; i++) {
            newArray[i] = array[i];
        }
        newArray[array.length] = element;
        return newArray;
    }

    function removeFromCostMemoryArray(
        LibRitualComponents.RitualCost[] memory array,
        uint256 positionToRemove
    ) internal pure returns (LibRitualComponents.RitualCost[] memory) {
        require(array.length > 0, 'LibArray: Cannot remove from empty array');
        require(
            positionToRemove < array.length,
            'LibArray: Cannot remove from array at position greater than array length'
        );
        array[positionToRemove] = array[array.length - 1];
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }

    function popFromCostMemoryArray(
        LibRitualComponents.RitualCost[] memory array
    ) internal pure returns (LibRitualComponents.RitualCost[] memory) {
        require(array.length > 0, 'LibArray: Cannot pop from empty array');
        assembly {
            mstore(array, sub(mload(array), 1))
        }
        return array;
    }
}
          

src/implementation/StakingFragment.sol

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

import {LibStructs} from '../libraries/LibStructs.sol';
import {LevelFullInfo} from '../entities/level/LevelFullInfo.sol';

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract StakingFragment {
    event ShadowcornStaked(
        uint256 indexed tokenId,
        address indexed staker,
        bool staked,
        uint256 farmableItemId,
        uint256 stakeTimestamp
    );

    event SetShadowcornFarmingData(
        address indexed player,
        uint256 indexed tokenId,
        LibStructs.FarmableItem farmingData
    );

    event ShadowcornUnstaked(
        uint256 indexed tokenId,
        address indexed staker,
        bool staked,
        uint256 farmableItemId,
        uint256 stakedTime
    );

    event ForcedUnstakeExecuted(address indexed player, uint256 indexed tokenId);

    event ShadowcornHarvest(
        address indexed player,
        uint256 indexed tokenId,
        uint256 indexed poolId,
        uint256 stakingRewards
    );

    /// Transfer a Shadowcorn into the Hatchery contract to begin yield farming.
    /// @param tokenId - The NFT to transfer
    /// @param farmableItemId - Id for the product to farm
    /// @custom:emits Transfer, ShadowcornStaked
    function stakeShadowcorn(uint256 tokenId, uint256 farmableItemId) external {}

    /// @notice Get staked shadowcorns by user
    /// @dev the first page is 0
    /// @return stakedShadowcorns - Array of staked shadowcorns
    function getStakedShadowcornsByUser(
        address user,
        uint32 _pageNumber
    ) external view returns (uint256[] memory stakedShadowcorns, bool moreEntriesExist) {}

    //  Unstake a Shadowcorn, transfer it back to the owner's wallet, and collect
    //  any goods that have been farmed. Progress toward incomplete items are lost.
    //  @param tokenId - The NFT to unstake
    //  @custom:emits Transfer, ShadowcornUnstaked, ShadowcornHarvest
    function unstakeShadowcorn(uint256 tokenId) external {}

    //  Collect any goods that have been farmed by a Shadowcorn, and immediately
    //  re-stake the Shadowcorn back to yield farming. Progress toward incomplete
    //  items is carried over.
    //  @param tokenId - The NFT to unstake
    //  @custom:emits ShadowcornHarvest
    function harvestAndRestake(uint256 tokenId) external {}

    //  Transfer a Shadowcorn back to the owner immediately.
    //  No yields are rewarded.
    //  @custom:emits Transfer
    function forceUnstake(uint256 tokenId) external {}

    function calculateFarmingBonus(
        uint256 tokenId,
        uint256 farmableItemId
    ) external view returns (LibStructs.FarmableItem memory shadowcornFarmingData) {}

    /// @notice Sets the cumulative husk limit for a given hatchery level
    /// @param hatcheryLevel - The hatchery level
    /// @param cumulativeHuskLimit - The cumulative husk limit
    /// @dev the limit should be set times * 100: example: if the limit is meant to be 0.2, the value should be set to 20.
    function setHatcheryLevelHuskLimitCumulative(uint256 hatcheryLevel, uint256 cumulativeHuskLimit) external {}

    /// @notice Gets the cumulative husk limit for a given hatchery level
    /// @return cumulativeHuskLimit - The cumulative husk limit
    function getHatcheryLevelHuskLimitCumulative(
        uint256 hatcheryLevel
    ) external view returns (uint256 cumulativeHuskLimit) {}

    /// @notice Gets the cumulative staking bonus for a given hatchery level
    /// @param hatcheryLevel - The hatchery level
    function getHatcheryLevelCumulativeBonus(uint256 hatcheryLevel) external view returns (uint256) {}

    function setHatcheryLevelCumulativeBonus(uint256 hatcheryLevel, uint256 cumulativeBonus) external {}

    function calculateStakingRewards(uint256 tokenId) external view returns (uint256 stakingReward) {}

    /// @notice Computes the time remaining until the cap is reached for a given token
    /// @param tokenId The ID of the token for which to calculate the time
    /// @return timeToReachCap The time remaining, in seconds, until the cap is reached
    function computeTimeToReachCap(uint256 tokenId) external view returns (uint256 timeToReachCap) {}

    /// @notice Computes the time remaining until the next husk is created for a given token
    /// @param tokenId The ID of the token for which to calculate the time
    /// @return timeUntilNextHusk The time remaining, in seconds, until the next husk is created
    function getTimeUntilNextHusk(uint256 tokenId) external view returns (uint256 timeUntilNextHusk) {}

    /// @notice Retrieves staking details including the husks created, time to reach cap, cap amount, and time until next husk
    /// @param tokenId The ID of the token for which to retrieve the details
    /// @return husksCreated The number of husks created since staking
    /// @return timeToReachCap The time remaining, in hours, until the cap is reached
    /// @return capAmount The cap amount, divided by 1000
    /// @return timeUntilNextHusk The time remaining, in seconds, until the next husk is created
    function getStakingDetails(
        uint256 tokenId
    )
        external
        view
        returns (uint256 husksCreated, uint256 timeToReachCap, uint256 capAmount, uint256 timeUntilNextHusk)
    {}

    /// @notice Retrieves the staking information for a specific Shadowcorn token
    /// @param tokenId The ID of the Shadowcorn token for which to retrieve the staking information
    /// @return A LibStructs.StakeData struct containing the staking details for the specified token
    function getStakingInfoByShadowcornId(uint256 tokenId) external view returns (LibStructs.StakeData memory) {}

    function getFarmingBonusBreakdown(
        uint256 tokenId
    ) external view returns (uint256 baseRate, uint256 hatcheryLevelBonus, uint256 classBonus, uint256 rarityBonus) {}

    function getFarmingRateByFarmableItems(
        uint256 tokenId,
        uint256[] memory farmableItemIds
    ) external view returns (uint256[] memory) {}

    function getFarmingHusksPerHour(
        uint256 tokenId
    )
        external
        view
        returns (uint256 fireHusks, uint256 slimeHusks, uint256 voltHusks, uint256 soulHusks, uint256 nebulaHusks)
    {}

    /// @notice Get farmable item by shadowcorn id
    /// @param shadowcornId The shadowcorn id to get the farmable item for
    /// @return farmableItem The farmable item
    function getFarmableItemByShadowcornId(
        uint256 shadowcornId
    ) external view returns (LibStructs.FarmableItem memory farmableItem) {}

    /// @notice Get levels unlockCosts, cumulativeBonus, and cumulativeHuskLimit paginated (5 levels per page)
    /// @param page The page number to retrieve
    /// @return LevelFullInfo[] The levels info
    function getHatcheryLevelsFullInfo(uint8 page) external view returns (LevelFullInfo[5] memory) {}
}
          

src/libraries/LibRewards.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

import '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol';

import {LibPlayerQueue} from './LibPlayerQueue.sol';
import {LibGlobalQueue} from './LibGlobalQueue.sol';
import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {LibTime} from './LibTime.sol';
import {IUNIMControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IUNIMControllerFacet.sol';
import {IDarkMarksControllerFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IDarkMarksControllerFacet.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';

/// @title LibRewards
/// @author Shiva Shanmuganathan
/// @dev Implementation of the daily rewards in minion hatchery
library LibRewards {
    event ClaimedRewards(address indexed player, uint256 rewardUNIM, uint256 rewardDarkMarks);
    event AddedToQueue(address indexed player, uint256 waveId, uint256 quantity);
    event BegunNewWave(uint256 newWaveId, uint256 prevWaveTime, uint256 newWaveTime);

    // Position to store the rewards storage
    bytes32 private constant REWARD_STORAGE_POSITION = keccak256('CryptoUnicorns.Rewards.Storage');

    using LibPlayerQueue for LibPlayerQueue.QueueStorage;
    using LibGlobalQueue for LibGlobalQueue.QueueStorage;

    // Reward storage struct that holds all relevant reward data
    struct LibRewardsStorage {
        mapping(address => LibPlayerQueue.QueueStorage) playerQueue; // Player-specific reward queue
        LibGlobalQueue.QueueStorage globalQueue; // Global reward queue
        uint256 waveUNIM; // Amount of UNIM tokens to be released per wave
        uint256 waveDarkMarks; // Amount of DarkMarks tokens to be released per wave
        uint256 waveTime; // Timestamp of the current reward wave
        uint256 waveCount; // Number of reward waves
        bool initialized; // Whether the reward system has been initialized
        uint256 distributedUNIM; // Amount of UNIM tokens distributed
        uint256 distributedDarkMarks; // Amount of DarkMarks tokens distributed
    }

    /// @notice Initializes the global reward queue.
    /// @dev Must be called once before using the reward system. This function sets the reward wave.
    function initializeGlobalQueue() internal {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        // initialize
        require(globalQueue.isInitialized() == false, 'Global Queue already initialised');
        globalQueue.initialize();
        beginNewWave();
        lrs.initialized = true;
    }

    /// @notice Begins the current reward wave
    /// @dev Should be called whenever a new reward wave starts.
    /// @dev This should happen approximately every 24 hours depending on user minion creation.
    /// @dev Updates unclaimed reward quantities.
    /// @custom:emits BegunNewWave
    function beginNewWave() internal {
        LibRewardsStorage storage lrs = rewardStorage();
        uint256 lastWaveTime = lrs.waveTime;
        if (lrs.initialized) {
            (lrs.waveCount, lrs.waveTime) = calculateCurrentWaveData();
        } else {
            lrs.waveCount = lrs.waveCount + 1;
            lrs.waveTime = calculateMidnightUTCTime();
        }

        emit BegunNewWave(lrs.waveCount, lastWaveTime, lrs.waveTime);
    }

    /// @notice Calculate Current Wave Data
    /// @dev Calculates the current wave count and the corresponding wave time in UTC, considering midnight as the beginning of a new wave.
    /// @return currentWaveCount The current wave count, including any new waves since the last recorded wave time.
    /// @return currentWaveTime The timestamp of the beginning of the current wave, corresponding to midnight UTC on the current day.
    function calculateCurrentWaveData() internal view returns (uint256 currentWaveCount, uint256 currentWaveTime) {
        LibRewardsStorage storage lrs = rewardStorage();
        uint256 lastWaveTime = lrs.waveTime;
        currentWaveTime = calculateMidnightUTCTime();
        uint256 waveCountToAdd = (currentWaveTime - lastWaveTime) / LibTime.SECONDS_PER_DAY;
        currentWaveCount = lrs.waveCount + waveCountToAdd;
    }

    /// @notice Calculate Midnight UTC Time
    /// @dev Calculates the timestamp corresponding to midnight UTC of the current day. This is used to identify the beginning of a new wave.
    /// @return newWaveTime The timestamp representing midnight UTC on the current day.
    function calculateMidnightUTCTime() internal view returns (uint256 newWaveTime) {
        (uint year, uint month, uint day) = LibTime._daysToDate(block.timestamp / LibTime.SECONDS_PER_DAY);
        newWaveTime = LibTime.timestampFromDate(year, month, day);
    }

    /// @notice Adds player's contribution to player and global queue
    /// @dev This function should be triggered by a function in the facet layer when minions are created using recipes, passing the number of minions and the user creating them.
    /// @dev Adds a player to the reward queue for a given quantity of tokens.
    /// @param quantity The quantity of tokens to add to the player's reward queue.
    /// @param user The address of the player to add to the queue.
    function addToQueue(uint256 quantity, address user) internal {
        checkAndInitializePlayerQueue(user);

        (uint256 waveUNIM, uint256 waveDarkMarks, bool globalQueueUpdated) = handleExpiredWave(
            rewardStorage().waveUNIM,
            rewardStorage().waveDarkMarks,
            quantity
        );

        handleQueueUpdates(user, rewardStorage().waveCount, quantity, waveUNIM, waveDarkMarks, globalQueueUpdated);

        emit AddedToQueue(user, rewardStorage().waveCount, quantity);
    }

    /// @notice Get the wave IDs for the start and end of a specified range based on the current wave count.
    /// @dev If the current wave count is greater than 1, the startWaveId is set to currentWaveCount - 1.
    ///      If the current wave count is greater or equal to 7, the endWaveId is set to currentWaveCount - 7.
    ///      Otherwise, startWaveId and endWaveId are set to 0.
    /// @return startWaveId The starting wave ID of the range.
    /// @return endWaveId The ending wave ID of the range.
    /// @return currentWaveCount The current wave count at the time of calling this function.
    function getWaveIdRange() internal view returns (uint256 startWaveId, uint256 endWaveId, uint256 currentWaveCount) {
        (currentWaveCount, ) = calculateCurrentWaveData();

        if (currentWaveCount > 1) {
            startWaveId = currentWaveCount - 1;
        } else {
            startWaveId = 0;
        }

        if (currentWaveCount >= 7) {
            endWaveId = currentWaveCount - 7;
        } else {
            endWaveId = 0;
        }

        return (startWaveId, endWaveId, currentWaveCount);
    }

    /// @notice Calculates the rewards owed to a player for a specific wave ID.
    /// @param waveId The ID of the wave for which to calculate the rewards.
    /// @param player The address of the player for whom to calculate the rewards.
    /// @return owedUNIM The amount of UNIM tokens owed to the player for the given wave ID.
    /// @return owedDarkMarks The amount of DarkMarks tokens owed to the player for the given wave ID.
    function calculateRewardsByWaveId(
        uint256 waveId,
        address player
    ) internal view returns (uint256 owedUNIM, uint256 owedDarkMarks) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[player];
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;

        uint256 playerMinionQty = playerQueue.getQtyByWaveId(waveId);
        uint256 globalMinionQty = globalQueue.getQtyByWaveId(waveId);

        uint256 totalUNIM = globalQueue.getWaveUNIMByWaveId(waveId);
        uint256 totalDarkMarks = globalQueue.getWaveDarkMarksByWaveId(waveId);

        owedUNIM = (totalUNIM * playerMinionQty) / globalMinionQty;
        owedDarkMarks = (totalDarkMarks * playerMinionQty) / globalMinionQty;

        return (owedUNIM, owedDarkMarks);
    }

    /// @notice Handles the updates to both the player queue and global queue, enqueuing or updating the quantity as required.
    /// @param user The address of the user whose queue is to be updated.
    /// @param waveCount The current wave count at the time of the update.
    /// @param quantity The quantity to be added or updated in the queues.
    /// @param waveUNIM The amount of UNIM tokens associated with the wave.
    /// @param waveDarkMarks The amount of DarkMarks tokens associated with the wave.
    /// @param globalQueueUpdated A boolean flag indicating whether the global queue has already been updated for the wave.
    function handleQueueUpdates(
        address user,
        uint256 waveCount,
        uint256 quantity,
        uint256 waveUNIM,
        uint256 waveDarkMarks,
        bool globalQueueUpdated
    ) internal {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;

        if (!playerQueue.waveIdExistsInQueue(waveCount)) {
            // add to playerQueue
            playerQueue.enqueue(waveCount, quantity);
        } else {
            // update playerQueue
            playerQueue.updateQty(waveCount, quantity);
        }

        if (!globalQueue.waveIdExistsInQueue(waveCount)) {
            // add to globalQueue
            globalQueue.enqueue(waveCount, quantity, waveUNIM, waveDarkMarks, waveUNIM, waveDarkMarks);
        } else {
            if (!globalQueueUpdated) {
                // update globalQueue
                globalQueue.updateQty(waveCount, quantity);
            }
        }
    }

    /// @notice Ensures the player queue for a specific user is initialized, calling the initialize function if it has not been done yet.
    /// @param user The address of the user whose queue needs to be initialized if not already done.
    function checkAndInitializePlayerQueue(address user) internal {
        LibPlayerQueue.QueueStorage storage playerQueue = rewardStorage().playerQueue[user];
        if (playerQueue.isInitialized() == false) {
            playerQueue.initialize();
        }
    }

    /// @notice Handle processing of an expired wave.
    /// @param waveUNIM The initial amount of UNIM for the wave.
    /// @param waveDarkMarks The initial amount of DarkMarks for the wave.
    /// @param quantity The quantity to be processed.
    /// @return _waveUNIM The updated amount of UNIM after handling the expired wave.
    /// @return _waveDarkMarks The updated amount of DarkMarks after handling the expired wave.
    /// @return _globalQueueUpdated A boolean flag indicating if the global queue was updated.
    ///
    /// This function checks if the current wave is expired and performs necessary operations
    /// such as dequeuing outdated waves, updating wave rewards, and adding new entries to the
    /// global queue. If the wave is not expired, the original parameters are returned unchanged.
    function handleExpiredWave(
        uint256 waveUNIM,
        uint256 waveDarkMarks,
        uint256 quantity
    ) internal returns (uint256 _waveUNIM, uint256 _waveDarkMarks, bool _globalQueueUpdated) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        if (isWaveExpired()) {
            (uint256 currentWaveCount, ) = calculateCurrentWaveData();
            // check if global queue has more than 7 days of txs
            if (globalQueue.hasOutdatedWaves(currentWaveCount)) {
                // getDequeueCount
                uint256 dequeueCount = globalQueue.getDequeueCount(currentWaveCount);

                // get total unclaimed rewards in dequeued txs
                (uint256 totalUnclaimedUNIM, uint256 totalUnclaimedDarkMarks) = globalQueue.getUnclaimedRewardsInQueue(
                    currentWaveCount
                );

                for (uint256 i = 0; i < dequeueCount; i++) {
                    // dequeue
                    globalQueue.dequeue();
                }

                // update wave rewards
                waveUNIM = waveUNIM + totalUnclaimedUNIM;
                waveDarkMarks = waveDarkMarks + totalUnclaimedDarkMarks;
            }

            beginNewWave();

            // add to globalQueue
            globalQueue.enqueue(lrs.waveCount, quantity, waveUNIM, waveDarkMarks, waveUNIM, waveDarkMarks);
            return (waveUNIM, waveDarkMarks, true);
        }
        return (waveUNIM, waveDarkMarks, false);
    }

    /// @notice Claims the rewards for the calling user and transfers the corresponding tokens.
    /// @dev Dequeues contributions older than a day in player queue
    /// @dev Calculate rewards with the playerMinionsQty and globalMinionsQty in the last 7 days except the last 24 hours
    /// @return rewardUNIM The total amount of UNIM tokens that were claimed.
    /// @return rewardDarkMarks The total amount of DarkMarks tokens that were claimed.
    function claimRewards() internal returns (uint256 rewardUNIM, uint256 rewardDarkMarks) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[msg.sender];
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;

        (uint256 startWaveId, uint256 endWaveId, uint256 currentWaveCount) = getWaveIdRange();

        // loop from today's wave id back to the 6 waves before
        for (uint256 waveId = startWaveId; waveId > endWaveId; waveId--) {
            // check if wave id exists in both queues
            if (playerQueue.waveIdExistsInQueue(waveId) && globalQueue.waveIdExistsInQueue(waveId)) {
                // calculate rewards by wave id
                (uint256 owedUNIM, uint256 owedDarkMarks) = calculateRewardsByWaveId(waveId, msg.sender);

                globalQueue.deductClaimedRewards(waveId, owedUNIM, owedDarkMarks);

                rewardUNIM += owedUNIM;
                rewardDarkMarks += owedDarkMarks;
            }
        }

        uint256 playerDequeueCount = playerQueue.getDequeueCount(currentWaveCount);
        // dequeue txs older than 1 day in player queue
        for (uint256 i = 0; i < playerDequeueCount; i++) {
            playerQueue.dequeue();
        }

        // add distributed rewards
        lrs.distributedUNIM += rewardUNIM;
        lrs.distributedDarkMarks += rewardDarkMarks;

        // check if player has any rewards to redeem
        if (rewardUNIM > 0) {
            IUNIMControllerFacet(LibResourceLocator.gameBank()).mintUNIM(msg.sender, rewardUNIM);
        }
        if (rewardDarkMarks > 0) {
            IDarkMarksControllerFacet(LibResourceLocator.gameBank()).mintDarkMarks(msg.sender, rewardDarkMarks);
        }

        emit ClaimedRewards(msg.sender, rewardUNIM, rewardDarkMarks);
    }

    /// @notice Retrieves the current wave count from the rewards storage.
    /// @return waveCount The current wave count value.
    function getWaveCount() internal view returns (uint256 waveCount) {
        return rewardStorage().waveCount;
    }

    /// @notice Retrieves the current wave time from the rewards storage.
    /// @return waveTime The current wave time value.
    function getWaveTime() internal view returns (uint256 waveTime) {
        return rewardStorage().waveTime;
    }

    /// @notice Calculates the total rewards (UNIM and Dark Marks) for a specific user based on wave IDs.
    /// @param user The address of the user whose rewards are to be calculated.
    /// @return rewardUNIM The total UNIM rewards owed to the user.
    /// @return rewardDarkMarks The total Dark Marks rewards owed to the user.
    function getPlayerRewards(address user) internal view returns (uint256 rewardUNIM, uint256 rewardDarkMarks) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;

        (uint256 startWaveId, uint256 endWaveId, uint256 currentWaveCount) = getWaveIdRange();

        // loop from today's wave id back to the 6 waves before
        for (uint256 waveId = startWaveId; waveId > endWaveId; waveId--) {
            // check if wave id exists in both queues
            if (playerQueue.waveIdExistsInQueue(waveId) && globalQueue.waveIdExistsInQueue(waveId)) {
                // calculate rewards by wave id
                (uint256 owedUNIM, uint256 owedDarkMarks) = calculateRewardsByWaveId(waveId, user);

                rewardUNIM += owedUNIM;
                rewardDarkMarks += owedDarkMarks;
            }
        }
        return (rewardUNIM, rewardDarkMarks);
    }

    /// @dev Internal function to get the length of the player-specific queue.
    /// @param user The address of the player whose queue length is to be retrieved.
    /// @return length The number of items in the player's queue.
    function getPlayerQueueLength(address user) internal view returns (uint256 length) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        return playerQueue.length();
    }

    /// @dev Internal function to get the length of the global queue.
    /// @return length The number of items in the global queue.
    function getGlobalQueueLength() internal view returns (uint256 length) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        return globalQueue.length();
    }

    /// @dev Internal function to get the front (oldest) item of the player's queue.
    /// @param user The address of the player whose queue is being accessed.
    /// @return waveId The waveId of the front item in the player's queue.
    /// @return quantity The quantity associated with the front item in the player's queue.
    function getPlayerQueueFront(address user) internal view returns (uint256 waveId, uint256 quantity) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        return playerQueue.peek();
    }

    /// @dev Internal function to get the front (oldest) item of the global queue.
    /// @return waveId The waveId of the front item in the global queue.
    /// @return quantity The quantity associated with the front item in the global queue.
    function getGlobalQueueFront()
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        return globalQueue.peek();
    }

    /// @dev Internal function to get the tail (newest) item of the player-specific queue.
    /// @param user The address of the player whose queue is being accessed.
    /// @return waveId The waveId of the tail item in the player-specific queue.
    /// @return quantity The quantity associated with the tail item in the player-specific queue.
    function getPlayerQueueTail(address user) internal view returns (uint256 waveId, uint256 quantity) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        return playerQueue.peekLast();
    }

    /// @dev Internal function to get the tail (newest) item of the global queue.
    /// @return waveId The waveId of the tail item in the global queue.
    /// @return quantity The quantity associated with the tail item in the global queue.
    function getGlobalQueueTail()
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        return globalQueue.peekLast();
    }

    /// @dev Internal function to get the item at the specified index in the player-specific queue.
    /// @param user The address of the player whose queue is being accessed.
    /// @param index The index of the item to retrieve from the player-specific queue.
    /// @return waveId The waveId of the item at the specified index in the player-specific queue.
    /// @return quantity The quantity associated with the item at the specified index in the player-specific queue.
    function getPlayerQueueAtIndex(
        address user,
        uint256 index
    ) internal view returns (uint256 waveId, uint256 quantity) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        return playerQueue.at(index);
    }

    /// @dev Internal function to get the item at the specified index in the global queue.
    /// @param index The index of the item to retrieve from the global queue.
    /// @return waveId The waveId of the item at the specified index in the global queue.
    /// @return quantity The quantity associated with the item at the specified index in the global queue.
    function getGlobalQueueAtIndex(
        uint256 index
    )
        internal
        view
        returns (
            uint256 waveId,
            uint256 quantity,
            uint256 waveUNIM,
            uint256 waveDarkMarks,
            uint256 unclaimedUnim,
            uint256 unclaimedDarkmarks
        )
    {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        return globalQueue.at(index);
    }

    /// @dev Internal function to retrieve the player-specific queue for the given user.
    /// @param user The address of the player whose queue is being accessed.
    /// @return waveIdsArray An array containing the waveIds of items in the player-specific queue.
    /// @return quantityArray An array containing the quantities associated with items in the player-specific queue.
    function getPlayerQueue(
        address user
    ) internal view returns (uint256[] memory waveIdsArray, uint256[] memory quantityArray) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage queue = lrs.playerQueue[user];

        uint256 queueLen = queue.length();
        waveIdsArray = new uint256[](queueLen);
        quantityArray = new uint256[](queueLen);

        for (uint256 i = 0; i < queueLen; i++) {
            (uint256 waveId, uint256 quantity) = queue.at(i);
            waveIdsArray[i] = waveId;
            quantityArray[i] = quantity;
        }

        return (waveIdsArray, quantityArray);
    }

    /// @dev Internal function to retrieve the global queue.
    /// @return waveIdsArray An array containing the waveIds of items in the global queue.
    /// @return quantityArray An array containing the quantities associated with items in the global queue.
    function getGlobalQueue() internal view returns (uint256[] memory waveIdsArray, uint256[] memory quantityArray) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage queue = lrs.globalQueue;

        uint256 queueLen = queue.length();
        waveIdsArray = new uint256[](queueLen);
        quantityArray = new uint256[](queueLen);
        for (uint256 i = 0; i < queueLen; i++) {
            (uint256 waveId, uint256 quantity, , , , ) = queue.at(i);
            waveIdsArray[i] = waveId;
            quantityArray[i] = quantity;
        }

        return (waveIdsArray, quantityArray);
    }

    /// @dev Internal function to set the wave UNIM tokens available for rewards.
    /// @param _waveUNIM The new wave UNIM token value to be set.
    function setWaveUNIM(uint256 _waveUNIM) internal {
        rewardStorage().waveUNIM = _waveUNIM;
    }

    /// @dev Internal function to get the wave UNIM tokens available for rewards.
    /// @return waveUNIM The wave UNIM token value.
    function getWaveUNIM() internal view returns (uint256 waveUNIM) {
        return rewardStorage().waveUNIM;
    }

    /// @dev Internal function to set the wave Dark Marks available for rewards.
    /// @param _waveDarkMarks The new wave Dark Marks value to be set.
    function setWaveDarkMarks(uint256 _waveDarkMarks) internal {
        rewardStorage().waveDarkMarks = _waveDarkMarks;
    }

    /// @dev Internal function to get the wave Dark Marks available for rewards.
    /// @return waveDarkMarks The wave Dark Marks value.
    function getWaveDarkMarks() internal view returns (uint256 waveDarkMarks) {
        return rewardStorage().waveDarkMarks;
    }

    /// @dev Internal function to check if the player's queue is initialized.
    /// @param user The address of the player whose queue is to be checked.
    /// @return isInitialized True if the player's queue is initialized, false otherwise.
    function isPlayerQueueInitialized(address user) internal view returns (bool isInitialized) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        return playerQueue.isInitialized();
    }

    /// @dev Internal function to check if the global queue is initialized.
    /// @return isInitialized True if the global queue is initialized, false otherwise.
    function isGlobalQueueInitialized() internal view returns (bool isInitialized) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;
        return globalQueue.isInitialized();
    }

    /// @notice Checks if the current reward wave has expired.
    /// @dev Returns true if the time elapsed since the last wave started is greater than or equal to the duration of a reward wave (24 hours).
    /// @return isExpired True if the current reward wave is expired, otherwise false.
    function isWaveExpired() internal view returns (bool isExpired) {
        LibRewardsStorage storage lrs = rewardStorage();
        return block.timestamp - lrs.waveTime >= LibTime.SECONDS_PER_DAY;
    }

    /// @notice Retrieves the wave rewards (UNIM and Dark Marks) from the global queue.
    /// @return waveUNIMArray An array containing the UNIM rewards for each wave in the global queue.
    /// @return waveDarkMarksArray An array containing the Dark Marks rewards for each wave in the global queue.
    function getGlobalQueueWaveRewards()
        internal
        view
        returns (uint256[] memory waveUNIMArray, uint256[] memory waveDarkMarksArray)
    {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage queue = lrs.globalQueue;

        uint256 queueLen = queue.length();
        waveUNIMArray = new uint256[](queueLen);
        waveDarkMarksArray = new uint256[](queueLen);
        for (uint256 i = 0; i < queueLen; i++) {
            (, , uint256 waveUNIM, uint256 waveDarkMarks, , ) = queue.at(i);
            waveUNIMArray[i] = waveUNIM;
            waveDarkMarksArray[i] = waveDarkMarks;
        }

        return (waveUNIMArray, waveDarkMarksArray);
    }

    /// @notice Retrieves the unclaimed rewards (UNIM and Dark Marks) from the global queue.
    /// @return unclaimedUnimArray An array containing the unclaimed UNIM for each wave in the global queue.
    /// @return unclaimedDarkmarksArray An array containing the unclaimed Dark Marks for each wave in the global queue.
    function getGlobalQueueUnclaimedRewards()
        internal
        view
        returns (uint256[] memory unclaimedUnimArray, uint256[] memory unclaimedDarkmarksArray)
    {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage queue = lrs.globalQueue;

        uint256 queueLen = queue.length();
        unclaimedUnimArray = new uint256[](queueLen);
        unclaimedDarkmarksArray = new uint256[](queueLen);

        for (uint256 i = 0; i < queueLen; i++) {
            (, , , , uint256 unclaimedUnim, uint256 unclaimedDarkmarks) = queue.at(i);
            unclaimedUnimArray[i] = unclaimedUnim;
            unclaimedDarkmarksArray[i] = unclaimedDarkmarks;
        }

        return (unclaimedUnimArray, unclaimedDarkmarksArray);
    }

    /// @notice Retrieves the claimable UNIM (Universal Incentive Mechanism) tokens for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the claimable UNIM.
    /// @return claimableUNIM The amount of claimable UNIM tokens for the specified wave ID.
    function getClaimableUNIM(uint256 waveId) internal view returns (uint256 claimableUNIM) {
        return rewardStorage().globalQueue.getClaimableUNIM(waveId);
    }

    /// @notice Retrieves the claimable Dark Marks tokens for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the claimable Dark Marks.
    /// @return claimableDarkMarks The amount of claimable Dark Marks tokens for the specified wave ID.
    function getClaimableDarkMarks(uint256 waveId) internal view returns (uint256 claimableDarkMarks) {
        return rewardStorage().globalQueue.getClaimableDarkMarks(waveId);
    }

    /// @notice Retrieves the UNIM tokens for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the UNIM tokens.
    /// @return waveUNIM The amount of UNIM tokens for the specified wave ID.
    function getWaveUNIMByWaveId(uint256 waveId) internal view returns (uint256 waveUNIM) {
        return rewardStorage().globalQueue.getWaveUNIMByWaveId(waveId);
    }

    /// @notice Retrieves the Dark Marks for a given wave ID.
    /// @param waveId The wave ID for which to retrieve the Dark Marks.
    /// @return waveDarkMarks The amount of Dark Marks for the specified wave ID.
    function getWaveDarkMarksByWaveId(uint256 waveId) internal view returns (uint256 waveDarkMarks) {
        return rewardStorage().globalQueue.getWaveDarkMarksByWaveId(waveId);
    }

    /// @dev Retrieves the total unclaimed rewards in the global queue.
    /// Iterates through the queue, summing the unclaimed UNIM and Darkmarks rewards.
    /// @return unclaimedUnim The total amount of unclaimed UNIM tokens.
    /// @return unclaimedDarkmarks The total amount of unclaimed Darkmarks.
    function getUnclaimedRewards() internal view returns (uint256 unclaimedUnim, uint256 unclaimedDarkmarks) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibGlobalQueue.QueueStorage storage queue = lrs.globalQueue;

        uint256 queueLen = queue.length();
        for (uint256 i = 0; i < queueLen; i++) {
            (, , , , uint256 _unclaimedUnim, uint256 _unclaimedDarkmarks) = queue.at(i);
            unclaimedUnim += _unclaimedUnim;
            unclaimedDarkmarks += _unclaimedDarkmarks;
        }

        return (unclaimedUnim, unclaimedDarkmarks);
    }

    /// @dev Retrieves the total distributed rewards.
    /// @return distributedUNIM The total amount of distributed UNIM tokens.
    /// @return distributedDarkMarks The total amount of distributed Darkmarks.
    function getDistributedRewards() internal view returns (uint256 distributedUNIM, uint256 distributedDarkMarks) {
        return (rewardStorage().distributedUNIM, rewardStorage().distributedDarkMarks);
    }

    /// @dev Calculates the contribution percentage of a specific user over the last 7 waves (including the current wave).
    /// It calculates the total contribution of a user in comparison to the global contributions.
    /// @param user The address of the user whose contribution percentage is being calculated.
    /// @return contributionPercentage The percentage of the user's contribution in the last 7 waves.
    function getContributionPercentage(address user) internal view returns (uint256 contributionPercentage) {
        LibRewardsStorage storage lrs = rewardStorage();
        LibPlayerQueue.QueueStorage storage playerQueue = lrs.playerQueue[user];
        LibGlobalQueue.QueueStorage storage globalQueue = lrs.globalQueue;

        // get today's wave id
        (uint256 currentWaveCount, ) = calculateCurrentWaveData();

        uint256 startWaveId;
        if (currentWaveCount > 1) {
            startWaveId = currentWaveCount - 1;
        } else {
            startWaveId = 0;
        }

        uint256 endWaveId;
        if (currentWaveCount >= 7) {
            endWaveId = currentWaveCount - 7;
        } else {
            endWaveId = 0;
        }

        uint256 totalPlayerMinions;
        uint256 totalGlobalMinions;

        // loop from today's wave id back to the 6 waves before
        for (uint256 waveId = startWaveId; waveId > endWaveId; waveId--) {
            // check if wave id exists in both queues
            if (playerQueue.waveIdExistsInQueue(waveId) && globalQueue.waveIdExistsInQueue(waveId)) {
                // calculate player and global contribution
                totalPlayerMinions += playerQueue.getQtyByWaveId(waveId);
                totalGlobalMinions += globalQueue.getQtyByWaveId(waveId);
            }
        }

        if (totalGlobalMinions == 0) {
            return (0);
        }

        contributionPercentage = (totalPlayerMinions * 100) / totalGlobalMinions;

        return (contributionPercentage);
    }

    /// @notice Returns the daily UNIM rewards.
    /// @return dailyUNIM The quantity of UNIM allocated for the current wave.
    function getDailyUNIM() internal view returns (uint256 dailyUNIM) {
        (uint256 waveId, ) = calculateCurrentWaveData();
        dailyUNIM = getWaveUNIMByWaveId(waveId);
        if (dailyUNIM == 0) {
            dailyUNIM = getWaveUNIM();
        }
        return dailyUNIM;
    }

    /// @notice Returns the daily DarkMarks rewards.
    /// @return dailyDarkMarks The quantity of DarkMarks allocated for the current wave.
    function getDailyDarkMarks() internal view returns (uint256 dailyDarkMarks) {
        (uint256 waveId, ) = calculateCurrentWaveData();
        dailyDarkMarks = getWaveDarkMarksByWaveId(waveId);
        if (dailyDarkMarks == 0) {
            dailyDarkMarks = getWaveDarkMarks();
        }
        return dailyDarkMarks;
    }

    /// @notice Returns both the daily UNIM and DarkMarks rewards.
    /// @return dailyUNIM The quantity of UNIM allocated for the current wave.
    /// @return dailyDarkMarks The quantity of DarkMarks allocated for the current wave.
    function getDailyRewards() internal view returns (uint256 dailyUNIM, uint256 dailyDarkMarks) {
        return (getDailyUNIM(), getDailyDarkMarks());
    }

    /// @dev Retrieves the storage position for LibRewardsStorage using inline assembly.
    /// This function accesses the storage location associated with the constant REWARD_STORAGE_POSITION.
    /// @return lrs The reference to LibRewardsStorage structure in storage.
    function rewardStorage() internal pure returns (LibRewardsStorage storage lrs) {
        bytes32 position = REWARD_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            lrs.slot := position
        }
    }
}
          

src/libraries/LibMinion.sol

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

/// @custom:storage-location erc7201:games.laguna.ShadowForge.LibMinion
library LibMinion {
    // Position to store the minion storage
    bytes32 constant MINION_STORAGE_POSITION =
        keccak256(abi.encode(uint256(keccak256('games.laguna.ShadowForge.LibMinion')) - 1)) & ~bytes32(uint256(0xff));

    struct LibMinionStorage {
        mapping(uint256 poolId => uint256 multiplier) minionMultiplierForContribution;
    }

    function minionStorage() internal pure returns (LibMinionStorage storage lms) {
        bytes32 position = MINION_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            lms.slot := position
        }
    }

    function getMinionMultiplierForContribution(uint256 poolId) internal view returns (uint256) {
        uint256 multiplier = minionStorage().minionMultiplierForContribution[poolId];

        if (multiplier == 0) {
            multiplier = 1;
        }

        return multiplier;
    }

    function setMinionMultiplierForContribution(uint256[] memory poolIds, uint256[] memory multipliers) internal {
        require(poolIds.length == multipliers.length, 'LibMinion: Invalid input lengths');

        for (uint256 i = 0; i < poolIds.length; i++) {
            minionStorage().minionMultiplierForContribution[poolIds[i]] = multipliers[i];
        }
    }
}
          

src/entities/level/LevelFullInfo.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {LevelUnlockCost} from './LevelUnlockCost.sol';

struct LevelFullInfo {
    LevelUnlockCost[] unlockCosts;
    uint256 cumulativeBonus;
    uint256 cumulativeHuskLimit;
    uint256 hatcheryLevel;
}
          

src/entities/level/LevelUnlockCostTokenTransferType.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.19;

enum LevelUnlockCostTokenTransferType {
    NONE, //0
    BURN, //1
    TRANSFER //2
}
          

src/implementation/VRFCallbackCreateRitualFragment.sol

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

/// @title Dummy "implementation" contract for LG Diamond interface for ERC-1967 compatibility
/// @dev adapted from https://github.com/zdenham/diamond-etherscan?tab=readme-ov-file
/// @dev This interface is used internally to call endpoints on a deployed diamond cluster.
contract VRFCallbackCreateRitualFragment {
    /// @notice Callback for VRF fulfillRandomness
    /// @dev This method MUST check `LibRNG.rngReceived(nonce)`
    /// @dev For multiple RNG callbacks, copy and rename this function
    /// @custom:see https://gb-docs.supraoracles.com/docs/vrf/v2-guide
    /// @param nonce The vrfRequestId
    /// @param rngList The random numbers
    function fulfillCreateRitualRandomness(uint256 nonce, uint256[] calldata rngList) external {}
}
          

lib/openzeppelin-contracts/contracts/utils/math/Math.sol

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

pragma solidity ^0.8.0;

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

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

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

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

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

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

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

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

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

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

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

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

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

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

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

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

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

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

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

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

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

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

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

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}
          

lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol

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

src/libraries/LibFarming.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.19;

import {LibContractOwner} from '../../lib/@lagunagames/lg-diamond-template/src/libraries/LibContractOwner.sol';
import {LibStructs} from './LibStructs.sol';
import {IERC721} from '../../lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol';
import {TerminusFacet} from '../../lib/web3/contracts/terminus/TerminusFacet.sol';
import {LibConstraints} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraints.sol';
import {LibConstraintOperator} from '../../lib/@lagunagames/cu-common/src/libraries/LibConstraintOperator.sol';
import {IConstraintFacet} from '../../lib/@lagunagames/cu-common/src/interfaces/IConstraintFacet.sol';
import {LibValidate} from '../../lib/@lagunagames/cu-common/src/libraries/LibValidate.sol';
import {LibResourceLocator} from '../../lib/@lagunagames/cu-common/src/libraries/LibResourceLocator.sol';

/// @title LibFarming
/// @author Shiva Shanmuganathan
/// @dev Library implementation of the yield farming in minion hatchery.
/// @custom:storage-location erc7201:games.laguna.cryptounicorns.farming
library LibFarming {
    event FarmableItemRegistered(
        address indexed admin,
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        string indexed uri
    );
    event FarmableItemModified(
        address indexed admin,
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat
    );

    event FarmableItemActivated(address indexed admin, uint256 farmableItemId);
    event FarmableItemDeactivated(address indexed admin, uint256 farmableItemId);
    event FarmableItemDeleted(address indexed admin, uint256 farmableItemId);

    // Position to store the farming storage
    bytes32 private constant FARMING_STORAGE_POSITION = keccak256('games.laguna.cryptounicorns.farming');

    // Farming storage struct that holds all relevant stake data
    struct LibFarmingStorage {
        uint256 farmableItemCount;
        mapping(uint256 farmableItemId => LibStructs.FarmableItem farmableItemData) farmableItemData;
        uint256[] huskPoolIds;
        uint256[] minionPoolIds;
        mapping(uint256 huskPoolId => uint256 index) huskPoolIdToIndex;
        mapping(uint256 minionPoolId => uint256 index) minionPoolIdToIndex;
    }

    function getFarmableItemData(uint256 farmableItemId) internal view returns (LibStructs.FarmableItem memory) {
        LibFarmingStorage storage lfs = farmingStorage();
        return lfs.farmableItemData[farmableItemId];
    }

    /// Setup a new item for yield farming by the Shadowcorns.
    /// @param poolId - Terminus pool (Shadowcorns item collection only)
    /// @param hourlyRate - Number of items created in one batch per hour (3 decimals)
    /// @param cap - Max number of items that can be farmed per session
    /// @param class - Class of the Shadowcorn
    /// @param stat - Stat of the Shadowcorn to check when farming
    /// @param receivesHatcheryLevelBonus - Whether the item receives a bonus based on the hatchery level
    /// @param receivesRarityBonus - Whether the item receives a bonus based on the rarity of the minion
    function registerNewFarmableItem(
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat,
        bool receivesHatcheryLevelBonus,
        bool receivesRarityBonus
    ) internal {
        // check valid farmable item ID by checking if terminus pool exists
        require(
            poolId <= TerminusFacet(LibResourceLocator.shadowcornItems()).totalPools(),
            'LibFarming: Terminus pool does not exist.'
        );

        require(class >= 0 && class < 6, 'LibFarming: Invalid class.'); // We can have no class (0)
        require(stat >= 0 && stat < 6, 'LibFarming: Invalid stat.');

        LibFarmingStorage storage lfs = farmingStorage();

        // increment farmable item count
        lfs.farmableItemCount++;

        // get uri from terminus
        string memory uri = TerminusFacet(LibResourceLocator.shadowcornItems()).uri(poolId);

        // register the new farmable item
        lfs.farmableItemData[lfs.farmableItemCount] = LibStructs.FarmableItem({
            active: false,
            poolId: poolId,
            hourlyRate: hourlyRate,
            cap: cap,
            uri: uri,
            class: class,
            stat: stat,
            receivesHatcheryLevelBonus: receivesHatcheryLevelBonus,
            receivesRarityBonus: receivesRarityBonus
        });

        // emit event
        emit FarmableItemRegistered(msg.sender, lfs.farmableItemCount, poolId, hourlyRate, cap, uri);
    }

    /// Change the details of a farmableItem
    /// @param farmableItemId - Item to modify
    /// @param hourlyRate - Number of items created in one batch per hour (3 decimals)
    /// @param cap - Max number of items that can be farmed per session
    /// @param receivesHatcheryLevelBonus - Whether the item receives a bonus based on the hatchery level
    /// @param receivesRarityBonus - Whether the item receives a bonus based on the rarity of the minion
    function modifyFarmableItem(
        uint256 farmableItemId,
        uint256 poolId,
        uint256 hourlyRate,
        uint256 cap,
        uint256 class,
        uint256 stat,
        bool receivesHatcheryLevelBonus,
        bool receivesRarityBonus
    ) internal {
        // check valid farmable item ID
        enforceValidFarmableItemId(farmableItemId);

        // get the farming storage
        LibFarmingStorage storage lfs = farmingStorage();

        // get the farmable item
        LibStructs.FarmableItem storage fi = lfs.farmableItemData[farmableItemId];

        // check that the farmable item is not active
        require(fi.active == false, 'LibFarming: Cannot modify active farmable item.');

        // modify the farmable item
        lfs.farmableItemData[farmableItemId].poolId = poolId;
        lfs.farmableItemData[farmableItemId].hourlyRate = hourlyRate;
        lfs.farmableItemData[farmableItemId].cap = cap;
        lfs.farmableItemData[farmableItemId].class = class;
        lfs.farmableItemData[farmableItemId].stat = stat;
        lfs.farmableItemData[farmableItemId].receivesHatcheryLevelBonus = receivesHatcheryLevelBonus;
        lfs.farmableItemData[farmableItemId].receivesRarityBonus = receivesRarityBonus;

        // emit event
        emit FarmableItemModified(msg.sender, farmableItemId, poolId, hourlyRate, cap, class, stat);
    }

    /// Turn on a Farmable item for use
    function activateFarmableItem(uint256 farmableItemId) internal {
        // check valid farmable item ID
        enforceValidFarmableItemId(farmableItemId);

        LibFarmingStorage storage lfs = farmingStorage();

        LibStructs.FarmableItem storage fi = lfs.farmableItemData[farmableItemId];
        require(fi.active == false, 'LibFarming: Farmable item already active.');
        lfs.farmableItemData[farmableItemId].active = true;
        emit FarmableItemActivated(msg.sender, farmableItemId);
    }

    /// Turn off a Farmable item for use
    function deactivateFarmableItem(uint256 farmableItemId) internal {
        // check valid farmable item ID
        enforceValidFarmableItemId(farmableItemId);

        LibFarmingStorage storage lfs = farmingStorage();

        LibStructs.FarmableItem storage fi = lfs.farmableItemData[farmableItemId];
        require(fi.active, 'LibFarming: Farmable item already inactive.');
        lfs.farmableItemData[farmableItemId].active = false;
        emit FarmableItemDeactivated(msg.sender, farmableItemId);
    }

    /// Returns a list of Farmable items that are currently active.
    function getActiveFarmables() internal view returns (LibStructs.FarmableItem[] memory, uint256[] memory) {
        LibFarmingStorage storage lfs = farmingStorage();
        uint256 activeCount = getActiveFarmableCount();
        LibStructs.FarmableItem[] memory activeFarmables = new LibStructs.FarmableItem[](activeCount);
        uint256 activeIndex = 0;
        uint256[] memory farmableItemIds = new uint256[](activeCount);
        for (uint256 i = 1; i <= lfs.farmableItemCount; i++) {
            if (lfs.farmableItemData[i].active) {
                activeFarmables[activeIndex] = lfs.farmableItemData[i];
                farmableItemIds[activeIndex] = i;

                activeIndex++;
            }
        }

        return (activeFarmables, farmableItemIds);
    }

    /// Returns the number of FarmableItem objects registered
    function getFarmableItemCount() internal view returns (uint256) {
        return farmingStorage().farmableItemCount;
    }

    /// Returns the number of FarmableItem objects that are active=true
    function getActiveFarmableCount() internal view returns (uint256) {
        LibFarmingStorage storage lfs = farmingStorage();
        uint256 activeCount = 0;
        for (uint256 i = 1; i <= lfs.farmableItemCount; i++) {
            if (lfs.farmableItemData[i].active) {
                activeCount++;
            }
        }
        return activeCount;
    }

    // check valid farmable item ID by checking it is below farmableItemCount
    function enforceValidFarmableItemId(uint256 farmableItemId) internal view {
        require(
            farmableItemId > 0 && farmableItemId <= farmingStorage().farmableItemCount,
            'LibFarming: Invalid farmable item ID.'
        );
    }

    /// @notice Adds a new minion pool ID.
    /// @param poolId The ID of the minion pool to add.
    function addMinionPoolId(uint256 poolId) internal {
        LibFarmingStorage storage lfs = farmingStorage();
        require(minionPoolExists(poolId) == false, 'LibFarming: Minion pool already exists.');
        lfs.minionPoolIds.push(poolId);
        lfs.minionPoolIdToIndex[poolId] = lfs.minionPoolIds.length;
    }

    /// @notice Checks if a given minion pool ID exists.
    /// @param poolId The ID of the minion pool to check.
    /// @return True if the pool ID exists, false otherwise.
    function minionPoolExists(uint256 poolId) internal view returns (bool) {
        return farmingStorage().minionPoolIdToIndex[poolId] > 0;
    }

    /// @notice Returns the index of a given minion pool ID.
    /// @param poolId The ID of the minion pool to find.
    /// @return The index of the given minion pool ID.
    function getMinionPoolIndex(uint256 poolId) internal view returns (uint256) {
        return farmingStorage().minionPoolIdToIndex[poolId] - 1;
    }

    /// @notice Removes a given minion pool ID.
    /// @param poolId The ID of the minion pool to remove.
    function removeMinionPoolId(uint256 poolId) internal {
        LibFarmingStorage storage lfs = farmingStorage();
        require(minionPoolExists(poolId), 'LibFarming: Minion pool does not exist.');
        uint256 index = getMinionPoolIndex(poolId);
        lfs.minionPoolIds[index] = lfs.minionPoolIds[lfs.minionPoolIds.length - 1];
        lfs.minionPoolIds.pop();
        lfs.minionPoolIdToIndex[poolId] = 0;
    }

    /// @notice Adds a new husk pool ID.
    /// @param poolId The ID of the husk pool to add.
    function addHuskPoolId(uint256 poolId) internal {
        LibFarmingStorage storage lfs = farmingStorage();
        require(huskPoolExists(poolId) == false, 'LibFarming: Husk pool already exists.');
        lfs.huskPoolIds.push(poolId);
        lfs.huskPoolIdToIndex[poolId] = lfs.huskPoolIds.length;
    }

    /// @notice Checks if a given husk pool ID exists.
    /// @param poolId The ID of the husk pool to check.
    /// @return True if the pool ID exists, false otherwise.
    function huskPoolExists(uint256 poolId) internal view returns (bool) {
        return farmingStorage().huskPoolIdToIndex[poolId] > 0;
    }

    /// @notice Removes a given husk pool ID.
    /// @param poolId The ID of the husk pool to remove.
    function removeHuskPoolId(uint256 poolId) internal {
        LibFarmingStorage storage lfs = farmingStorage();
        require(huskPoolExists(poolId), 'LibFarming: Husk pool does not exist.');
        uint256 index = getHuskPoolIndex(poolId);
        lfs.huskPoolIds[index] = lfs.huskPoolIds[lfs.huskPoolIds.length - 1];
        lfs.huskPoolIds.pop();
        lfs.huskPoolIdToIndex[poolId] = 0;
    }

    /// @notice Returns the index of a given husk pool ID.
    /// @param poolId The ID of the husk pool to find.
    /// @return The index of the given husk pool ID.
    function getHuskPoolIndex(uint256 poolId) internal view returns (uint256) {
        return farmingStorage().huskPoolIdToIndex[poolId] - 1;
    }

    /// @notice Returns the list of husk pool IDs.
    /// @return huskPoolIds An array containing all the husk pool IDs.
    function getHuskPoolIds() internal view returns (uint256[] memory huskPoolIds) {
        return farmingStorage().huskPoolIds;
    }

    /// @notice Returns the list of minion pool IDs.
    /// @return minionPoolIds An array containing all the minion pool IDs.
    function getMinionPoolIds() internal view returns (uint256[] memory minionPoolIds) {
        return farmingStorage().minionPoolIds;
    }

    /// @notice Retrieves the total supply of husks by iterating through all husk pool IDs.
    /// @return totalHusks The total supply of husks across all husk pools.
    function getTotalHusks() internal view returns (uint256 totalHusks) {
        LibFarmingStorage storage lfs = farmingStorage();
        TerminusFacet terminusFacet = TerminusFacet(LibResourceLocator.shadowcornItems());

        for (uint256 i = 0; i < lfs.huskPoolIds.length; i++) {
            totalHusks += terminusFacet.terminusPoolSupply(lfs.huskPoolIds[i]);
        }
    }

    /// @notice Retrieves the total supply of minions by iterating through all minion pool IDs.
    /// @return totalMinions The total supply of minions across all minion pools.
    function getTotalMinions() internal view returns (uint256 totalMinions) {
        LibFarmingStorage storage lfs = farmingStorage();
        TerminusFacet terminusFacet = TerminusFacet(LibResourceLocator.shadowcornItems());

        for (uint256 i = 0; i < lfs.minionPoolIds.length; i++) {
            totalMinions += terminusFacet.terminusPoolSupply(lfs.minionPoolIds[i]);
        }
    }

    /// @notice Enforces the validity of a Terminus pool ID.
    /// @param poolId The ID of the Terminus pool to validate.
    /// @dev Reverts if the pool ID is greater than the total number of Terminus pools.
    function enforceValidTerminusPoolId(uint256 poolId) internal view {
        require(
            poolId <= TerminusFacet(LibResourceLocator.shadowcornItems()).totalPools(),
            'LibFarming: Terminus pool does not exist.'
        );
    }

    /// @notice Returns the farming storage structure.
    function farmingStorage() internal pure returns (LibFarmingStorage storage lfs) {
        bytes32 position = FARMING_STORAGE_POSITION;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            lfs.slot := position
        }
    }
}
          

lib/@lagunagames/cu-common/src/libraries/LibString.sol

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

import {Strings} from '../../lib/openzeppelin-contracts/contracts/utils/Strings.sol';

/// @author Uriel Chami
library LibString {
    /// @notice Converts uint to readable string
    /// @dev Any uint from uint8 up to uint256
    /// @param i uint8 to uint256 number to convert
    /// @return number as string
    function uintToString(uint256 i) internal pure returns (string memory) {
        // https://neznein9.medium.com/the-fastest-way-to-convert-uint256-to-string-in-solidity-b880cfa5f377
        return Strings.toString(i);
    }

    /// @notice Converts address to readable string
    /// @dev the final string includes "0x"
    /// @param a address to convert
    /// @return address as string including "0x" at the start
    function addressToString(address a) internal pure returns (string memory) {
        //  https://gist.github.com/Rob-lg/bd4504213cf1c6fa3db3c2737196dad6
        return Strings.toHexString(a);
    }

    function char(bytes1 b) internal pure returns (bytes1 c) {
        if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
        else return bytes1(uint8(b) + 0x57);
    }

    function toHexDigit(uint8 d) internal pure returns (bytes1) {
        if (0 <= d && d <= 9) {
            return bytes1(uint8(bytes1('0')) + d);
        } else if (10 <= uint8(d) && uint8(d) <= 15) {
            return bytes1(uint8(bytes1('a')) + d - 10);
        }
        revert();
    }

    /// @notice Converts bytes4 to readable string
    /// @dev the final string includes "0x". useful for selectors
    /// @param code bytes4 to convert
    /// @return s bytes4 as string including "0x" at the start
    function selectorToString(bytes4 code) internal pure returns (string memory) {
        bytes memory result = new bytes(10);
        result[0] = bytes1('0');
        result[1] = bytes1('x');
        for (uint i = 0; i < 4; ++i) {
            result[2 * i + 2] = toHexDigit(uint8(code[i]) / 16);
            result[2 * i + 3] = toHexDigit(uint8(code[i]) % 16);
        }
        return string(result);
    }
}
          

lib/openzeppelin-contracts/contracts/utils/Address.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [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://consensys.net/diligence/blog/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.8.0/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);
        }
    }
}
          

lib/@lagunagames/cu-common/src/interfaces/IShadowcornStatsFacet.sol

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

interface IShadowcornStatsFacet {
    // //  Classes
    // uint256 constant FIRE = 1;
    // uint256 constant SLIME = 2;
    // uint256 constant VOLT = 3;
    // uint256 constant SOUL = 4;
    // uint256 constant NEBULA = 5;

    // //  Stats
    // uint256 constant MIGHT = 1;
    // uint256 constant WICKEDNESS = 2;
    // uint256 constant TENACITY = 3;
    // uint256 constant CUNNING = 4;
    // uint256 constant ARCANA = 5;

    // //  Rarities
    // uint256 constant COMMON = 1;
    // uint256 constant RARE = 2;
    // uint256 constant MYTHIC = 3;

    function getClass(uint256 tokenId) external view returns (uint256 class);

    function getClassRarityAndStat(
        uint256 tokenId,
        uint256 statId
    ) external view returns (uint256 class, uint256 rarity, uint256 stat);

    function getStats(
        uint256 tokenId
    ) external view returns (uint256 might, uint256 wickedness, uint256 tenacity, uint256 cunning, uint256 arcana);

    function getMight(uint256 tokenId) external view returns (uint256 might);

    function getWickedness(uint256 tokenId) external view returns (uint256 wickedness);

    function getTenacity(uint256 tokenId) external view returns (uint256 tenacity);

    function getCunning(uint256 tokenId) external view returns (uint256 cunning);

    function getArcana(uint256 tokenId) external view returns (uint256 arcana);

    function getRarity(uint256 tokenId) external view returns (uint256 rarity);
}
          

Compiler Settings

{"viaIR":false,"remappings":["ds-test/=lib/forge-std/lib/ds-test/src/","forge-std/=lib/forge-std/src/","@openzeppelin-contracts/=lib/openzeppelin-contracts/"],"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers"]}},"optimizer":{"runs":200,"enabled":true,"details":{"yulDetails":{"stackAllocation":true,"optimizerSteps":"dhfoDgvulfnTUtnIf"},"yul":true,"peephole":true,"inliner":true,"deduplicate":true,"cse":true}},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"libraries":{},"evmVersion":"paris"}
              

Contract ABI

[{"type":"event","name":"AddedToQueue","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"waveId","internalType":"uint256","indexed":false},{"type":"uint256","name":"quantity","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"AffixBucketCreated","inputs":[{"type":"uint256","name":"id","internalType":"uint256","indexed":true},{"type":"uint256[]","name":"affixIds","internalType":"uint256[]","indexed":false}],"anonymous":false},{"type":"event","name":"AffixCreated","inputs":[{"type":"uint256","name":"id","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"BatchSacrificeUnicorns","inputs":[{"type":"uint256[]","name":"tokenIds","internalType":"uint256[]","indexed":false},{"type":"address","name":"user","internalType":"address","indexed":false}],"anonymous":false},{"type":"event","name":"BeginRitualCreation","inputs":[{"type":"address","name":"playerWallet","internalType":"address","indexed":true},{"type":"uint256","name":"ritualTemplatePoolId","internalType":"uint256","indexed":true},{"type":"uint256","name":"vrfRequestId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"BegunNewWave","inputs":[{"type":"uint256","name":"newWaveId","internalType":"uint256","indexed":false},{"type":"uint256","name":"prevWaveTime","internalType":"uint256","indexed":false},{"type":"uint256","name":"newWaveTime","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ClaimedRewards","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"rewardUNIM","internalType":"uint256","indexed":false},{"type":"uint256","name":"rewardDarkMarks","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FarmableItemActivated","inputs":[{"type":"address","name":"admin","internalType":"address","indexed":true},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FarmableItemDeactivated","inputs":[{"type":"address","name":"admin","internalType":"address","indexed":true},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FarmableItemDeleted","inputs":[{"type":"address","name":"admin","internalType":"address","indexed":true},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FarmableItemModified","inputs":[{"type":"address","name":"admin","internalType":"address","indexed":true},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false},{"type":"uint256","name":"poolId","internalType":"uint256","indexed":false},{"type":"uint256","name":"hourlyRate","internalType":"uint256","indexed":false},{"type":"uint256","name":"cap","internalType":"uint256","indexed":false},{"type":"uint256","name":"class","internalType":"uint256","indexed":false},{"type":"uint256","name":"stat","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FarmableItemRegistered","inputs":[{"type":"address","name":"admin","internalType":"address","indexed":true},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false},{"type":"uint256","name":"poolId","internalType":"uint256","indexed":false},{"type":"uint256","name":"hourlyRate","internalType":"uint256","indexed":false},{"type":"uint256","name":"cap","internalType":"uint256","indexed":false},{"type":"string","name":"uri","internalType":"string","indexed":true}],"anonymous":false},{"type":"event","name":"FinishRitualCreation","inputs":[{"type":"uint256","name":"vrfRequestId","internalType":"uint256","indexed":true},{"type":"uint256","name":"ritualTemplateId","internalType":"uint256","indexed":false},{"type":"uint256","name":"ritualTokenId","internalType":"uint256","indexed":true},{"type":"uint256[]","name":"affixIdsApplied","internalType":"uint256[]","indexed":false},{"type":"address","name":"user","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"ForcedUnstakeExecuted","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"tokenId","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"GasReturnedToUser","inputs":[{"type":"uint256","name":"amountReturned","internalType":"uint256","indexed":false},{"type":"uint256","name":"txPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"gasSpent","internalType":"uint256","indexed":false},{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"bool","name":"success","internalType":"bool","indexed":true},{"type":"string","name":"transactionType","internalType":"string","indexed":true}],"anonymous":false},{"type":"event","name":"GasReturnerInsufficientBalance","inputs":[{"type":"uint256","name":"txPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"gasSpent","internalType":"uint256","indexed":false},{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"string","name":"transactionType","internalType":"string","indexed":true}],"anonymous":false},{"type":"event","name":"GasReturnerMaxGasReturnedPerTransactionChanged","inputs":[{"type":"uint256","name":"oldMaxGasReturnedPerTransaction","internalType":"uint256","indexed":false},{"type":"uint256","name":"newMaxGasReturnedPerTransaction","internalType":"uint256","indexed":false},{"type":"address","name":"admin","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"HatcheryAffixWarning","inputs":[{"type":"string","name":"warningText","internalType":"string","indexed":false}],"anonymous":false},{"type":"event","name":"HatcheryLevelUnlocked","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"oldLevel","internalType":"uint256","indexed":false},{"type":"uint256","name":"newLevel","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"RitualTemplateCreated","inputs":[{"type":"uint256","name":"id","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"RitualTemplatePoolCreated","inputs":[{"type":"uint256","name":"id","internalType":"uint256","indexed":true}],"anonymous":false},{"type":"event","name":"SetShadowcornFarmingData","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"tokenId","internalType":"uint256","indexed":true},{"type":"tuple","name":"farmingData","internalType":"struct LibStructs.FarmableItem","indexed":false,"components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}],"anonymous":false},{"type":"event","name":"ShadowcornHarvest","inputs":[{"type":"address","name":"player","internalType":"address","indexed":true},{"type":"uint256","name":"tokenId","internalType":"uint256","indexed":true},{"type":"uint256","name":"poolId","internalType":"uint256","indexed":true},{"type":"uint256","name":"stakingRewards","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ShadowcornStaked","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256","indexed":true},{"type":"address","name":"staker","internalType":"address","indexed":true},{"type":"bool","name":"staked","internalType":"bool","indexed":false},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false},{"type":"uint256","name":"stakeTimestamp","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ShadowcornUnstaked","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256","indexed":true},{"type":"address","name":"staker","internalType":"address","indexed":true},{"type":"bool","name":"staked","internalType":"bool","indexed":false},{"type":"uint256","name":"farmableItemId","internalType":"uint256","indexed":false},{"type":"uint256","name":"stakedTime","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"activateFarmableItem","inputs":[{"type":"uint256","name":"farmableItemId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addAffixesToBucket","inputs":[{"type":"uint256[]","name":"affixesIds","internalType":"uint256[]"},{"type":"uint256","name":"bucketId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addHatcheryLevelUnlockCost","inputs":[{"type":"uint256","name":"_hatcheryLevel","internalType":"uint256"},{"type":"uint256","name":"_transferType","internalType":"uint256"},{"type":"uint128","name":"_amount","internalType":"uint128"},{"type":"uint128","name":"_assetType","internalType":"uint128"},{"type":"address","name":"_asset","internalType":"address"},{"type":"uint256","name":"_poolId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"addRitualTemplateToPool","inputs":[{"type":"uint256","name":"ritualTemplateId","internalType":"uint256"},{"type":"uint256","name":"ritualPoolId","internalType":"uint256"},{"type":"uint256","name":"rngWeight","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"batchConsumeRitualCharges","inputs":[{"type":"uint256[]","name":"ritualIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"batchSacrificeUnicorns","inputs":[{"type":"uint256[]","name":"tokenIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveCount","internalType":"uint256"}],"name":"calculateCurrentWaveCount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"shadowcornFarmingData","internalType":"struct LibStructs.FarmableItem","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}],"name":"calculateFarmingBonus","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"},{"type":"uint256","name":"farmableItemId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"stakingReward","internalType":"uint256"}],"name":"calculateStakingRewards","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"canConsume","internalType":"bool"}],"name":"canConsumeRitual","inputs":[{"type":"uint256","name":"ritualId","internalType":"uint256"},{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"rewardUNIM","internalType":"uint256"},{"type":"uint256","name":"rewardDarkMarks","internalType":"uint256"}],"name":"claimRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"timeToReachCap","internalType":"uint256"}],"name":"computeTimeToReachCap","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"consumeRitualCharge","inputs":[{"type":"uint256","name":"ritualId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"id","internalType":"uint256"}],"name":"createAffix","inputs":[{"type":"tuple","name":"affix","internalType":"struct LibHatcheryRituals.Affix","components":[{"type":"uint8","name":"affixType","internalType":"enum LibHatcheryRituals.AffixType"},{"type":"bool","name":"isPositive","internalType":"bool"},{"type":"uint256","name":"charges","internalType":"uint256"},{"type":"tuple","name":"cost","internalType":"struct LibRitualComponents.RitualCost","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualCostTransferType"}]},{"type":"tuple","name":"product","internalType":"struct LibRitualComponents.RitualProduct","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualProductTransferType"}]},{"type":"tuple","name":"constraint","internalType":"struct LibConstraints.Constraint","components":[{"type":"uint128","name":"constraintType","internalType":"uint128"},{"type":"uint128","name":"operator","internalType":"uint128"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"uint256","name":"weight","internalType":"uint256"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"createAffixBucket","inputs":[{"type":"uint256[]","name":"affixIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"createRitual","inputs":[{"type":"uint256","name":"ritualTemplatePoolId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"id","internalType":"uint256"}],"name":"createRitualTemplate","inputs":[{"type":"tuple","name":"template","internalType":"struct LibHatcheryRituals.RitualTemplate","components":[{"type":"uint8","name":"rarity","internalType":"uint8"},{"type":"uint256","name":"charges","internalType":"uint256"},{"type":"bool","name":"soulbound","internalType":"bool"},{"type":"uint256[]","name":"affixBucketIds","internalType":"uint256[]"},{"type":"tuple[]","name":"consumptionConstraints","internalType":"struct LibConstraints.Constraint[]","components":[{"type":"uint128","name":"constraintType","internalType":"uint128"},{"type":"uint128","name":"operator","internalType":"uint128"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"tuple[]","name":"consumptionCosts","internalType":"struct LibRitualComponents.RitualCost[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualCostTransferType"}]},{"type":"tuple[]","name":"consumptionProducts","internalType":"struct LibRitualComponents.RitualProduct[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualProductTransferType"}]}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"id","internalType":"uint256"}],"name":"createRitualTemplatePool","inputs":[{"type":"tuple[]","name":"creationCosts","internalType":"struct LibRitualComponents.RitualCost[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualCostTransferType"}]},{"type":"tuple[]","name":"creationConstraints","internalType":"struct LibConstraints.Constraint[]","components":[{"type":"uint128","name":"constraintType","internalType":"uint128"},{"type":"uint128","name":"operator","internalType":"uint128"},{"type":"uint256","name":"value","internalType":"uint256"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"deactivateFarmableItem","inputs":[{"type":"uint256","name":"farmableItemId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"forceUnstake","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"fulfillCreateRitualRandomness","inputs":[{"type":"uint256","name":"nonce","internalType":"uint256"},{"type":"uint256[]","name":"rngList","internalType":"uint256[]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getActiveFarmableCount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"","internalType":"struct LibStructs.FarmableItem[]","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]},{"type":"uint256[]","name":"","internalType":"uint256[]"}],"name":"getActiveFarmables","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"claimableDarkMarks","internalType":"uint256"}],"name":"getClaimableDarkMarks","inputs":[{"type":"uint256","name":"waveId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"claimableUNIM","internalType":"uint256"}],"name":"getClaimableUNIM","inputs":[{"type":"uint256","name":"waveId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"contributionPercentage","internalType":"uint256"}],"name":"getContributionPercentage","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"constraints","internalType":"struct LibConstraints.Constraint[]","components":[{"type":"uint128","name":"constraintType","internalType":"uint128"},{"type":"uint128","name":"operator","internalType":"uint128"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"tuple[]","name":"costs","internalType":"struct LibRitualComponents.RitualCost[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualCostTransferType"}]}],"name":"getCreationConstraintsAndCosts","inputs":[{"type":"uint256","name":"ritualTemplatePoolId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"dailyUNIM","internalType":"uint256"},{"type":"uint256","name":"dailyDarkMarks","internalType":"uint256"}],"name":"getDailyRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"distributedUNIM","internalType":"uint256"},{"type":"uint256","name":"distributedDarkMarks","internalType":"uint256"}],"name":"getDistributedRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"farmableItem","internalType":"struct LibStructs.FarmableItem","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}],"name":"getFarmableItemByShadowcornId","inputs":[{"type":"uint256","name":"shadowcornId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getFarmableItemCount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"","internalType":"struct LibStructs.FarmableItem","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}],"name":"getFarmableItemData","inputs":[{"type":"uint256","name":"farmableItemId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"baseRate","internalType":"uint256"},{"type":"uint256","name":"hatcheryLevelBonus","internalType":"uint256"},{"type":"uint256","name":"classBonus","internalType":"uint256"},{"type":"uint256","name":"rarityBonus","internalType":"uint256"}],"name":"getFarmingBonusBreakdown","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"fireHusks","internalType":"uint256"},{"type":"uint256","name":"slimeHusks","internalType":"uint256"},{"type":"uint256","name":"voltHusks","internalType":"uint256"},{"type":"uint256","name":"soulHusks","internalType":"uint256"},{"type":"uint256","name":"nebulaHusks","internalType":"uint256"}],"name":"getFarmingHusksPerHour","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"","internalType":"uint256[]"}],"name":"getFarmingRateByFarmableItems","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"},{"type":"uint256[]","name":"farmableItemIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"waveIds","internalType":"uint256[]"},{"type":"uint256[]","name":"quantities","internalType":"uint256[]"}],"name":"getGlobalQueue","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveId","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"},{"type":"uint256","name":"waveUNIM","internalType":"uint256"},{"type":"uint256","name":"waveDarkMarks","internalType":"uint256"},{"type":"uint256","name":"unclaimedUnim","internalType":"uint256"},{"type":"uint256","name":"unclaimedDarkmarks","internalType":"uint256"}],"name":"getGlobalQueueAtIndex","inputs":[{"type":"uint256","name":"index","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveId","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"},{"type":"uint256","name":"waveUNIM","internalType":"uint256"},{"type":"uint256","name":"waveDarkMarks","internalType":"uint256"},{"type":"uint256","name":"unclaimedUnim","internalType":"uint256"},{"type":"uint256","name":"unclaimedDarkmarks","internalType":"uint256"}],"name":"getGlobalQueueFront","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"length","internalType":"uint256"}],"name":"getGlobalQueueLength","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveId","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"},{"type":"uint256","name":"waveUNIM","internalType":"uint256"},{"type":"uint256","name":"waveDarkMarks","internalType":"uint256"},{"type":"uint256","name":"unclaimedUnim","internalType":"uint256"},{"type":"uint256","name":"unclaimedDarkmarks","internalType":"uint256"}],"name":"getGlobalQueueTail","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"unclaimedUnim","internalType":"uint256[]"},{"type":"uint256[]","name":"unclaimedDarkmarks","internalType":"uint256[]"}],"name":"getGlobalQueueUnclaimedRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"waveUNIM","internalType":"uint256[]"},{"type":"uint256[]","name":"waveDarkMarks","internalType":"uint256[]"}],"name":"getGlobalQueueWaveRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getHatcheryLevel","inputs":[{"type":"address","name":"_user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getHatcheryLevelCap","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"getHatcheryLevelCumulativeBonus","inputs":[{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"cumulativeHuskLimit","internalType":"uint256"}],"name":"getHatcheryLevelHuskLimitCumulative","inputs":[{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"","internalType":"struct LevelUnlockCost[]","components":[{"type":"tuple","name":"component","internalType":"struct Component","components":[{"type":"address","name":"asset","internalType":"address"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint128","name":"amount","internalType":"uint128"},{"type":"uint128","name":"assetType","internalType":"uint128"}]},{"type":"uint8","name":"transferType","internalType":"enum LevelUnlockCostTokenTransferType"}]}],"name":"getHatcheryLevelUnlockCosts","inputs":[{"type":"uint256","name":"_hatcheryLevel","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[5]","name":"","internalType":"struct LevelFullInfo[5]","components":[{"type":"tuple[]","name":"unlockCosts","internalType":"struct LevelUnlockCost[]","components":[{"type":"tuple","name":"component","internalType":"struct Component","components":[{"type":"address","name":"asset","internalType":"address"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint128","name":"amount","internalType":"uint128"},{"type":"uint128","name":"assetType","internalType":"uint128"}]},{"type":"uint8","name":"transferType","internalType":"enum LevelUnlockCostTokenTransferType"}]},{"type":"uint256","name":"cumulativeBonus","internalType":"uint256"},{"type":"uint256","name":"cumulativeHuskLimit","internalType":"uint256"},{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"}]}],"name":"getHatcheryLevelsFullInfo","inputs":[{"type":"uint8","name":"page","internalType":"uint8"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"huskPoolIds","internalType":"uint256[]"}],"name":"getHuskPoolIds","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"maxBatchAmount","internalType":"uint256"}],"name":"getMaxBatchSacrificeUnicornsAmount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"maxRituals","internalType":"uint256"}],"name":"getMaxRitualsPerBatch","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"minionPoolIds","internalType":"uint256[]"}],"name":"getMinionPoolIds","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"waveIds","internalType":"uint256[]"},{"type":"uint256[]","name":"quantities","internalType":"uint256[]"}],"name":"getPlayerQueue","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveId","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"}],"name":"getPlayerQueueAtIndex","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"uint256","name":"index","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"timestamp","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"}],"name":"getPlayerQueueFront","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"length","internalType":"uint256"}],"name":"getPlayerQueueLength","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveId","internalType":"uint256"},{"type":"uint256","name":"quantity","internalType":"uint256"}],"name":"getPlayerQueueTail","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"rewardUNIM","internalType":"uint256"},{"type":"uint256","name":"rewardDarkMarks","internalType":"uint256"}],"name":"getPlayerRewards","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"ritualTemplate","internalType":"struct LibHatcheryRituals.RitualTemplate","components":[{"type":"uint8","name":"rarity","internalType":"uint8"},{"type":"uint256","name":"charges","internalType":"uint256"},{"type":"bool","name":"soulbound","internalType":"bool"},{"type":"uint256[]","name":"affixBucketIds","internalType":"uint256[]"},{"type":"tuple[]","name":"consumptionConstraints","internalType":"struct LibConstraints.Constraint[]","components":[{"type":"uint128","name":"constraintType","internalType":"uint128"},{"type":"uint128","name":"operator","internalType":"uint128"},{"type":"uint256","name":"value","internalType":"uint256"}]},{"type":"tuple[]","name":"consumptionCosts","internalType":"struct LibRitualComponents.RitualCost[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualCostTransferType"}]},{"type":"tuple[]","name":"consumptionProducts","internalType":"struct LibRitualComponents.RitualProduct[]","components":[{"type":"tuple","name":"component","internalType":"struct LibRitualComponents.RitualComponent","components":[{"type":"uint256","name":"amount","internalType":"uint256"},{"type":"uint128","name":"assetType","internalType":"uint128"},{"type":"uint128","name":"poolId","internalType":"uint128"},{"type":"address","name":"asset","internalType":"address"}]},{"type":"uint8","name":"transferType","internalType":"enum LibRitualComponents.RitualProductTransferType"}]}]}],"name":"getRitualTemplateById","inputs":[{"type":"uint256","name":"ritualTemplateId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"stakedShadowcorns","internalType":"uint256[]"},{"type":"bool","name":"moreEntriesExist","internalType":"bool"}],"name":"getStakedShadowcornsByUser","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"uint32","name":"_pageNumber","internalType":"uint32"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"husksCreated","internalType":"uint256"},{"type":"uint256","name":"timeToReachCap","internalType":"uint256"},{"type":"uint256","name":"capAmount","internalType":"uint256"},{"type":"uint256","name":"timeUntilNextHusk","internalType":"uint256"}],"name":"getStakingDetails","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple","name":"","internalType":"struct LibStructs.StakeData","components":[{"type":"address","name":"staker","internalType":"address"},{"type":"bool","name":"staked","internalType":"bool"},{"type":"uint256","name":"farmableItemId","internalType":"uint256"},{"type":"uint256","name":"stakeTimestamp","internalType":"uint256"}]}],"name":"getStakingInfoByShadowcornId","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"timeUntilNextHusk","internalType":"uint256"}],"name":"getTimeUntilNextHusk","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"totalHusks","internalType":"uint256"}],"name":"getTotalHusks","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"totalMinions","internalType":"uint256"}],"name":"getTotalMinions","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"unclaimedUnim","internalType":"uint256"},{"type":"uint256","name":"unclaimedDarkmarks","internalType":"uint256"}],"name":"getUnclaimedRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"poolId","internalType":"uint256"}],"name":"getUnicornSoulsPoolId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveCount","internalType":"uint256"}],"name":"getWaveCount","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveDarkMarks","internalType":"uint256"}],"name":"getWaveDarkMarks","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveDarkMarks","internalType":"uint256"}],"name":"getWaveDarkMarksByWaveId","inputs":[{"type":"uint256","name":"waveId","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveTime","internalType":"uint256"}],"name":"getWaveTime","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveUNIM","internalType":"uint256"}],"name":"getWaveUNIM","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"waveUNIM","internalType":"uint256"}],"name":"getWaveUNIMByWaveId","inputs":[{"type":"uint256","name":"waveId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"harvestAndRestake","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"initializeGlobalQueue","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"isInitialized","internalType":"bool"}],"name":"isGlobalQueueInitialized","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"isInitialized","internalType":"bool"}],"name":"isPlayerQueueInitialized","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256[]","name":"levels","internalType":"uint256[]"}],"name":"mig_getHatcheryLevelForAddress","inputs":[{"type":"address[]","name":"users","internalType":"address[]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"mig_getLastAffixBucketId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"mig_getLastAffixId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"mig_getLastPoolId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"mig_getLastTemplateId","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"mig_getMaxRitualsPerBatch","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"farmableItems","internalType":"struct LibStructs.FarmableItem[]","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}],"name":"mig_getShadowcornFarmingData","inputs":[{"type":"uint256[]","name":"shadowcornIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"tuple[]","name":"stakeData","internalType":"struct LibStructs.StakeData[]","components":[{"type":"address","name":"staker","internalType":"address"},{"type":"bool","name":"staked","internalType":"bool"},{"type":"uint256","name":"farmableItemId","internalType":"uint256"},{"type":"uint256","name":"stakeTimestamp","internalType":"uint256"}]}],"name":"mig_getStakingInfoByShadowcornIds","inputs":[{"type":"uint256[]","name":"shadowcornIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setFarmableItemCount","inputs":[{"type":"uint256","name":"count","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setHatcheryLevelCumulativeBonus","inputs":[{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"},{"type":"uint256","name":"cumulativeBonus","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setHatcheryLevelForAddress","inputs":[{"type":"address[]","name":"users","internalType":"address[]"},{"type":"uint256[]","name":"levels","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setLastAffixBucketId","inputs":[{"type":"uint256","name":"n","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setLastAffixId","inputs":[{"type":"uint256","name":"n","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setLastPoolId","inputs":[{"type":"uint256","name":"n","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setLastTemplateId","inputs":[{"type":"uint256","name":"n","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setMaxRitualsPerBatch","inputs":[{"type":"uint256","name":"n","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setShadowcornFarmingData","inputs":[{"type":"uint256[]","name":"shadowcornIds","internalType":"uint256[]"},{"type":"tuple[]","name":"farmableItems","internalType":"struct LibStructs.FarmableItem[]","components":[{"type":"bool","name":"active","internalType":"bool"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"string","name":"uri","internalType":"string"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setStakingInfoByShadowcornIds","inputs":[{"type":"uint256[]","name":"shadowcornIds","internalType":"uint256[]"},{"type":"tuple[]","name":"stakeData","internalType":"struct LibStructs.StakeData[]","components":[{"type":"address","name":"staker","internalType":"address"},{"type":"bool","name":"staked","internalType":"bool"},{"type":"uint256","name":"farmableItemId","internalType":"uint256"},{"type":"uint256","name":"stakeTimestamp","internalType":"uint256"}]}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"mig_setUserToStakedShadowcorns","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"uint256[]","name":"stakedTokenIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"modifyFarmableItem","inputs":[{"type":"uint256","name":"farmableItemId","internalType":"uint256"},{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"registerNewFarmableItem","inputs":[{"type":"uint256","name":"poolId","internalType":"uint256"},{"type":"uint256","name":"hourlyRate","internalType":"uint256"},{"type":"uint256","name":"cap","internalType":"uint256"},{"type":"uint256","name":"class","internalType":"uint256"},{"type":"uint256","name":"stat","internalType":"uint256"},{"type":"bool","name":"receivesHatcheryLevelBonus","internalType":"bool"},{"type":"bool","name":"receivesRarityBonus","internalType":"bool"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeAffixFromBucket","inputs":[{"type":"uint256","name":"affixId","internalType":"uint256"},{"type":"uint256","name":"bucketId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeHuskPoolId","inputs":[{"type":"uint256","name":"_huskPoolId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"removeMinionPoolId","inputs":[{"type":"uint256","name":"_minionPoolId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHatcheryLevelCap","inputs":[{"type":"uint256","name":"_hatcheryLevelCap","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHatcheryLevelCumulativeBonus","inputs":[{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"},{"type":"uint256","name":"cumulativeBonus","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHatcheryLevelHuskLimitCumulative","inputs":[{"type":"uint256","name":"hatcheryLevel","internalType":"uint256"},{"type":"uint256","name":"cumulativeHuskLimit","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHatcheryLevelUnlockCosts","inputs":[{"type":"uint256","name":"_hatcheryLevel","internalType":"uint256"},{"type":"uint256[]","name":"_transferTypes","internalType":"uint256[]"},{"type":"uint128[]","name":"_amounts","internalType":"uint128[]"},{"type":"uint128[]","name":"_assetTypes","internalType":"uint128[]"},{"type":"address[]","name":"_assets","internalType":"address[]"},{"type":"uint256[]","name":"_poolIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setHuskPoolIds","inputs":[{"type":"uint256[]","name":"_huskPoolIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setMaxBatchSacrificeUnicornsAmount","inputs":[{"type":"uint256","name":"maxBatchAmount","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setMaxRitualsPerBatch","inputs":[{"type":"uint256","name":"maxRitualsPerBatch","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setMinionPoolIds","inputs":[{"type":"uint256[]","name":"_minionPoolIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setUnicornSoulsPoolId","inputs":[{"type":"uint256","name":"poolId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setWaveDarkMarks","inputs":[{"type":"uint256","name":"_waveDarkMarks","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setWaveUNIM","inputs":[{"type":"uint256","name":"_waveUNIM","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"stakeShadowcorn","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"},{"type":"uint256","name":"farmableItemId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"unlockNextHatcheryLevel","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"unstakeShadowcorn","inputs":[{"type":"uint256","name":"tokenId","internalType":"uint256"}]}]
              

Contract Creation Code

0x608060405234801561001057600080fd5b5061247d806100206000396000f3fe608060405234801561001057600080fd5b50600436106107015760003560e01c806368ca82a41161039d578063b204d3be116101e9578063d4688cc91161011a578063e55b1803116100b8578063f85a86b611610087578063f85a86b6146107a4578063f92f8b3414610aef578063fa44d4cb14610afd578063ff4623e51461076557600080fd5b8063e55b180314610781578063eab88bf514610a68578063ebe654da14610781578063ede844981461078157600080fd5b8063db38858b116100f4578063db38858b14610755578063de90d48914610ac1578063e0780cf914610acf578063e2dc49ac1461074357600080fd5b8063d4688cc914610a93578063d5c2803114610755578063d7ce428b14610aa657600080fd5b8063c2d848ea11610187578063c734cd0611610161578063c734cd06146107ba578063cbd94efb14610765578063d11f77571461093d578063d3e9b15c14610a0157600080fd5b8063c2d848ea14610773578063c354ec4414610755578063c5f9f86b1461093d57600080fd5b8063b9bc64e1116101c3578063b9bc64e114610781578063bc23a3bc14610a48578063bec0854014610765578063c0a1d29114610a6857600080fd5b8063b204d3be14610755578063b3a722ef14610743578063b707c9af146107f557600080fd5b80638dff87f1116102ce57806397e567e91161026c578063a292c1e11161023b578063a292c1e114610836578063a40dccd414610781578063a4e0aeb314610a22578063a80a56bb1461093d57600080fd5b806397e567e9146109f357806398b3001114610a01578063999e16b614610a1b5780639f7454571461076557600080fd5b80639161867a116102a85780639161867a146109aa578063930f7d41146109ce578063942d4f981461093d57806396e3cd13146109e557600080fd5b80638dff87f1146107815780638ea0330b1461099c578063914601491461078157600080fd5b80637d3aaafe1161033b5780638685275511610315578063868527551461083657806387aaee391461077357806389148de21461075557806389c23fe31461098e57600080fd5b80637d3aaafe1461075557806381227c921461078157806382b7e9801461078157600080fd5b80637523f7dd116103775780637523f7dd1461093d578063795fe204146107555780637b8ae016146107555780637c716b911461094b57600080fd5b806368ca82a41461074357806369d502ca14610906578063747e9f611461092757600080fd5b80632d6f65321161055c57806349b2d4481161048d578063556fb0841161042b5780636427bc40116103fa5780636427bc40146108d857806366dc9bdd146108f8578063685270081461079257806368c02a991461078157600080fd5b8063556fb084146108915780635b2e8435146108b45780635b44cce91461078157806360f71809146108c257600080fd5b80635206245e116104675780635206245e146107a457806352a44107146108835780635365dbe31461078157806353f4ef311461075557600080fd5b806349b2d448146107815780634d6ce674146108755780634f2888721461078157600080fd5b806339018b38116104fa57806342698baf116104d457806342698baf14610781578063440c4f6b1461075557806344e5e0221461076557806348f8ad811461078157600080fd5b806339018b3814610781578063408ccff11461084857806340b640df1461076557600080fd5b80633315b516116105365780633315b516146108035780633643986814610836578063372500ab1461079257806338f804a11461078157600080fd5b80632d6f6532146107555780632fe458da146108235780633055ad5b1461075557600080fd5b8063152e86ef116106365780631da3e033116105d457806325b94e89116105ae57806325b94e89146107de57806327ec3b2c146107f557806328275b07146107925780632888752d1461080357600080fd5b80631da3e033146107815780631ecc68b2146107ca578063230a2a9c1461076557600080fd5b80631648f1d6116106105780631648f1d6146107a45780631752b705146107ba5780631cf2651b146107555780631d278c1a1461076557600080fd5b8063152e86ef1461073057806315c4f1ce14610755578063162302571461079257600080fd5b80630ee2bb31116106a357806312a74e6e1161067d57806312a74e6e14610755578063133aa8231461078157806314e0294a146107555780631517c5611461075557600080fd5b80630ee2bb3114610765578063114d80761461077357806311d1db661461078157600080fd5b806308baea3a116106df57806308baea3a14610743578063098a11f0146107435780630a2912c4146107555780630e23c51e1461075557600080fd5b8063012c4997146107065780630132b39314610730578063060811d614610732575b600080fd5b61071a610714366004610c4f565b50600090565b6040516107279190610c82565b60405180910390f35b005b606080604051610727929190610e79565b610730610751366004610eaf565b5050565b60005b6040516107279190610eec565b610758610714366004610efa565b610758610714366004610c4f565b61073061078f366004610efa565b50565b6000805b604051610727929190610f1b565b6107966107b2366004610c4f565b600080915091565b60605b6040516107279190610f36565b6107306107d8366004610fe2565b50505050565b6107966107ec366004611059565b50600091829150565b6107306107d836600461107b565b610816610811366004610efa565b610b15565b60405161072791906110e6565b6107306108313660046110f7565b505050565b6060805b604051610727929190611147565b610864610856366004610efa565b506000908190819081908190565b604051610727959493929190611158565b610758610714366004611410565b610730610831366004611432565b6108a761089f36600461148d565b606092915050565b60405161072791906114d4565b6107bd61089f36600461148d565b6107306108d0366004611670565b505050505050565b6108eb6108e6366004610efa565b610b22565b6040516107279190611a36565b6107306108d0366004611a47565b61091a610914366004610efa565b50606090565b6040516107279190611b5c565b610758610935366004611c57565b600092915050565b61073061078f366004611cbd565b610981610959366004610efa565b5060408051608081018252600080825260208201819052918101829052606081019190915290565b6040516107279190611d3c565b610730610751366004611d4a565b6107bd61089f366004611d85565b6109c06109b8366004610efa565b606080915091565b604051610727929190611e4c565b6107306109dc366004611e71565b50505050505050565b610730610831366004611f10565b61071a610935366004611f34565b600080808080805b60405161072796959493929190611f67565b600061071a565b610a3a610a30366004611fd8565b5060609160009150565b60405161072792919061200b565b610a09610a56366004610efa565b60008060008060008091939550919395565b610a83610a76366004610efa565b6000806000809193509193565b604051610727949392919061202b565b610816610aa1366004610eaf565b610b67565b610ab461089f36600461148d565b60405161072791906120ae565b61083a6109b8366004610c4f565b610ae2610add3660046120d3565b610b75565b60405161072791906121cd565b61075861071436600461235a565b610730610b0b366004612394565b5050505050505050565b610b1d610b7d565b919050565b610b1d6040518060e00160405280600060ff16815260200160008152602001600015158152602001606081526020016060815260200160608152602001606081525090565b610b6f610b7d565b92915050565b610b1d610bcf565b6040518061012001604052806000151581526020016000815260200160008152602001600081526020016060815260200160008152602001600081526020016000151581526020016000151581525090565b6040518060a001604052806005905b610c096040518060800160405280606081526020016000815260200160008152602001600081525090565b815260200190600190039081610bde5790505090565b60006001600160a01b038216610b6f565b610c3981610c1f565b811461078f57600080fd5b8035610b6f81610c30565b600060208284031215610c6457610c64600080fd5b6000610c708484610c44565b949350505050565b8015155b82525050565b60208101610b6f8284610c78565b80610c7c565b60005b83811015610cb1578181015183820152602001610c99565b50506000910152565b6000610cc4825190565b808452602084019350610cdb818560208601610c96565b601f01601f19169290920192915050565b8051600090610120840190610d018582610c78565b506020830151610d146020860182610c90565b506040830151610d276040860182610c90565b506060830151610d3a6060860182610c90565b5060808301518482036080860152610d528282610cba565b91505060a0830151610d6760a0860182610c90565b5060c0830151610d7a60c0860182610c90565b5060e0830151610d8d60e0860182610c78565b50610100830151610da2610100860182610c78565b509392505050565b6000610db68383610cec565b9392505050565b6000610dc7825190565b80845260208401935083602082028501610de18560200190565b60005b84811015610e155783830388528151610dfd8482610daa565b93505060208201602098909801979150600101610de4565b50909695505050505050565b610e2b8282610c90565b5060200190565b6000610e3c825190565b808452602093840193830160005b82811015610e6f578151610e5e8782610e21565b965050602082019150600101610e4a565b5093949350505050565b60408082528101610e8a8185610dbd565b90508181036020830152610c708184610e32565b80610c39565b8035610b6f81610e9e565b60008060408385031215610ec557610ec5600080fd5b6000610ed18585610ea4565b9250506020610ee285828601610ea4565b9150509250929050565b60208101610b6f8284610c90565b600060208284031215610f0f57610f0f600080fd5b6000610c708484610ea4565b60408101610f298285610c90565b610db66020830184610c90565b60208082528101610db68184610e32565b60008083601f840112610f5c57610f5c600080fd5b5081356001600160401b03811115610f7657610f76600080fd5b602083019150836020820283011115610f9157610f91600080fd5b9250929050565b60008083601f840112610fad57610fad600080fd5b5081356001600160401b03811115610fc757610fc7600080fd5b602083019150836080820283011115610f9157610f91600080fd5b60008060008060408587031215610ffb57610ffb600080fd5b84356001600160401b0381111561101457611014600080fd5b61102087828801610f47565b945094505060208501356001600160401b0381111561104157611041600080fd5b61104d87828801610f98565b95989497509550505050565b6000806040838503121561106f5761106f600080fd5b6000610ed18585610c44565b6000806000806040858703121561109457611094600080fd5b84356001600160401b038111156110ad576110ad600080fd5b6110b987828801610f47565b945094505060208501356001600160401b038111156110da576110da600080fd5b61104d87828801610f47565b60208082528101610db68184610cec565b60008060006060848603121561110f5761110f600080fd5b600061111b8686610ea4565b935050602061112c86828701610ea4565b925050604061113d86828701610ea4565b9150509250925092565b60408082528101610e8a8185610e32565b60a081016111668288610c90565b6111736020830187610c90565b6111806040830186610c90565b61118d6060830185610c90565b61119a6080830184610c90565b9695505050505050565b634e487b7160e01b600052604160045260246000fd5b601f19601f83011681018181106001600160401b03821117156111df576111df6111a4565b6040525050565b60006111f160405190565b9050610b1d82826111ba565b6006811061078f57600080fd5b8035610b6f816111fd565b801515610c39565b8035610b6f81611215565b6001600160801b038116610c39565b8035610b6f81611228565b60006080828403121561125757611257600080fd5b61126160806111e6565b9050600061126f8484610ea4565b90820152602061128184848301611237565b90820152604061129384848301611237565b9082015260606112a584848301610c44565b9082015292915050565b6003811061078f57600080fd5b8035610b6f816112af565b600060a082840312156112dc576112dc600080fd5b6112e660406111e6565b905060006112f48484611242565b908201526080611306848483016112bc565b60208301525092915050565b60006060828403121561132757611327600080fd5b61133160606111e6565b9050600061133f8484611237565b90820152602061135184848301611237565b9082015260406112a584848301610ea4565b6000610220828403121561137957611379600080fd5b61138360e06111e6565b90506000611391848461120a565b9082015260206113a38484830161121d565b9082015260406113b584848301610ea4565b9082015260606113c7848483016112c7565b908201526101006113da848483016112c7565b6080830152506101a06113ef84828501611312565b60a08301525061020061140484828501610ea4565b60c08301525092915050565b6000610220828403121561142657611426600080fd5b6000610c708484611363565b60008060006040848603121561144a5761144a600080fd5b60006114568686610ea4565b93505060208401356001600160401b0381111561147557611475600080fd5b61148186828701610f47565b92509250509250925092565b600080602083850312156114a3576114a3600080fd5b82356001600160401b038111156114bc576114bc600080fd5b6114c885828601610f47565b92509250509250929050565b60208082528101610db68184610dbd565b60006001600160401b038211156114fe576114fe6111a4565b5060209081020190565b600061151b611516846114e5565b6111e6565b8381529050602080820190840283018581111561153a5761153a600080fd5b835b8181101561155c5761154e8782610ea4565b83526020928301920161153c565b5050509392505050565b600082601f83011261157a5761157a600080fd5b8135610c70848260208601611508565b6000611598611516846114e5565b838152905060208082019084028301858111156115b7576115b7600080fd5b835b8181101561155c576115cb8782611237565b8352602092830192016115b9565b600082601f8301126115ed576115ed600080fd5b8135610c7084826020860161158a565b600061160b611516846114e5565b8381529050602080820190840283018581111561162a5761162a600080fd5b835b8181101561155c5761163e8782610c44565b83526020928301920161162c565b600082601f83011261166057611660600080fd5b8135610c708482602086016115fd565b60008060008060008060c0878903121561168c5761168c600080fd5b60006116988989610ea4565b96505060208701356001600160401b038111156116b7576116b7600080fd5b6116c389828a01611566565b95505060408701356001600160401b038111156116e2576116e2600080fd5b6116ee89828a016115d9565b94505060608701356001600160401b0381111561170d5761170d600080fd5b61171989828a016115d9565b93505060808701356001600160401b0381111561173857611738600080fd5b61174489828a0161164c565b92505060a08701356001600160401b0381111561176357611763600080fd5b61176f89828a01611566565b9150509295509295509295565b60ff8116610c7c565b600061178f825190565b808452602093840193830160005b82811015610e6f5781516117b18782610e21565b96505060208201915060010161179d565b6001600160801b038116610c7c565b80516117dd83826117c2565b5060208101516117f060208401826117c2565b5060408101516108316040840182610c90565b61180d82826117d1565b5060600190565b600061181e825190565b808452602093840193830160005b82811015610e6f5781516118408782611803565b96505060208201915060010161182c565b610c7c81610c1f565b80516118668382610c90565b50602081015161187960208401826117c2565b50604081015161188c60408401826117c2565b5060608101516108316060840182611851565b634e487b7160e01b600052602160045260246000fd5b6003811061078f5761078f61189f565b80610b1d816118b5565b6000610b6f826118c5565b610c7c816118cf565b80516118ef838261185a565b50602081015161083160808401826118da565b61190c82826118e3565b5060a00190565b600061191d825190565b808452602093840193830160005b82811015610e6f57815161193f8782611902565b96505060208201915060010161192b565b600061195a825190565b808452602093840193830160005b82811015610e6f57815161197c8782611902565b965050602082019150600101611968565b805160009060e08401906119a1858261177c565b5060208301516119b46020860182610c90565b5060408301516119c76040860182610c78565b50606083015184820360608601526119df8282611785565b915050608083015184820360808601526119f98282611814565b91505060a083015184820360a0860152611a138282611913565b91505060c083015184820360c0860152611a2d8282611950565b95945050505050565b60208082528101610db6818461198d565b60008060008060008060c08789031215611a6357611a63600080fd5b6000611a6f8989610ea4565b9650506020611a8089828a01610ea4565b9550506040611a9189828a01611237565b9450506060611aa289828a01611237565b9350506080611ab389828a01610c44565b92505060a061176f89828a01610ea4565b8051611ad08382611851565b506020810151611ae36020840182610c90565b506040810151611af660408401826117c2565b50606081015161083160608401826117c2565b80516118ef8382611ac4565b61190c8282611b09565b6000611b29825190565b808452602093840193830160005b82811015610e6f578151611b4b8782611b15565b965050602082019150600101611b37565b60208082528101610db68184611b1f565b6000611b7b611516846114e5565b83815290506020810160a08402830185811115611b9a57611b9a600080fd5b835b8181101561155c57611bae87826112c7565b835260209092019160a001611b9c565b600082601f830112611bd257611bd2600080fd5b8135610c70848260208601611b6d565b6000611bf0611516846114e5565b83815290506020810160608402830185811115611c0f57611c0f600080fd5b835b8181101561155c57611c238782611312565b8352602090920191606001611c11565b600082601f830112611c4757611c47600080fd5b8135610c70848260208601611be2565b60008060408385031215611c6d57611c6d600080fd5b82356001600160401b03811115611c8657611c86600080fd5b611c9285828601611bbe565b92505060208301356001600160401b03811115611cb157611cb1600080fd5b610ee285828601611c33565b600060208284031215611cd257611cd2600080fd5b81356001600160401b03811115611ceb57611ceb600080fd5b610c7084828501611566565b8051611d038382611851565b506020810151611d166020840182610c78565b506040810151611d296040840182610c90565b5060608101516108316060840182610c90565b60808101610b6f8284611cf7565b60008060408385031215611d6057611d60600080fd5b82356001600160401b03811115611d7957611d79600080fd5b610ed185828601611566565b60008060408385031215611d9b57611d9b600080fd5b6000611da78585610ea4565b92505060208301356001600160401b03811115611dc657611dc6600080fd5b610ee285828601611566565b6000611ddc825190565b808452602093840193830160005b82811015610e6f578151611dfe8782611803565b965050602082019150600101611dea565b6000611e19825190565b808452602093840193830160005b82811015610e6f578151611e3b8782611902565b965050602082019150600101611e27565b60408082528101611e5d8185611dd2565b90508181036020830152610c708184611e0f565b600080600080600080600060e0888a031215611e8f57611e8f600080fd5b6000611e9b8a8a610ea4565b9750506020611eac8a828b01610ea4565b9650506040611ebd8a828b01610ea4565b9550506060611ece8a828b01610ea4565b9450506080611edf8a828b01610ea4565b93505060a0611ef08a828b0161121d565b92505060c0611f018a828b0161121d565b91505092959891949750929550565b600080600060408486031215611f2857611f28600080fd5b60006114568686610c44565b60008060408385031215611f4a57611f4a600080fd5b6000611f568585610ea4565b9250506020610ee285828601610c44565b60c08101611f758289610c90565b611f826020830188610c90565b611f8f6040830187610c90565b611f9c6060830186610c90565b611fa96080830185610c90565b611fb660a0830184610c90565b979650505050505050565b63ffffffff8116610c39565b8035610b6f81611fc1565b60008060408385031215611fee57611fee600080fd5b6000611ffa8585610c44565b9250506020610ee285828601611fcd565b6040808252810161201c8185610e32565b9050610db66020830184610c78565b608081016120398287610c90565b6120466020830186610c90565b6120536040830185610c90565b611a2d6060830184610c90565b61206a8282611cf7565b5060800190565b600061207b825190565b808452602093840193830160005b82811015610e6f57815161209d8782612060565b965050602082019150600101612089565b60208082528101610db68184612071565b60ff8116610c39565b8035610b6f816120bf565b6000602082840312156120e8576120e8600080fd5b6000610c7084846120c8565b60006120fe825190565b808452602093840193830160005b82811015610e6f5781516121208782611b15565b96505060208201915060010161210c565b805160808084526000919084019061214982826120f4565b915050602083015161215e6020860182610c90565b5060408301516121716040860182610c90565b506060830151610da26060860182610c90565b6000610db68383612131565b600060058360a0810184845b84811015610e1557838303885281516121b58482612184565b9350506020820160209890980197915060010161219c565b60208082528101610db68184612190565b60006121ec611516846114e5565b83815290506020810160a0840283018581111561220b5761220b600080fd5b835b8181101561155c5761221f87826112c7565b835260209092019160a00161220d565b600082601f83011261224357612243600080fd5b8135610c708482602086016121de565b600060e0828403121561226857612268600080fd5b61227260e06111e6565b9050600061228084846120c8565b90820152602061229284848301610ea4565b9082015260406122a48484830161121d565b9082015260608201356001600160401b038111156122c4576122c4600080fd5b6122d084828501611566565b60608301525060808201356001600160401b038111156122f2576122f2600080fd5b6122fe84828501611c33565b60808301525060a08201356001600160401b0381111561232057612320600080fd5b61232c84828501611bbe565b60a08301525060c08201356001600160401b0381111561234e5761234e600080fd5b6114048482850161222f565b60006020828403121561236f5761236f600080fd5b81356001600160401b0381111561238857612388600080fd5b610c7084828501612253565b600080600080600080600080610100898b0312156123b4576123b4600080fd5b60006123c08b8b610ea4565b98505060206123d18b828c01610ea4565b97505060406123e28b828c01610ea4565b96505060606123f38b828c01610ea4565b95505060806124048b828c01610ea4565b94505060a06124158b828c01610ea4565b93505060c06124268b828c0161121d565b92505060e06124378b828c0161121d565b915050929598509295989093965056fea2646970667358221220c5a5fd91005be3632ad5231d468988af2dcc478b4e43dd01da8ea6879d50790364736f6c63430008130033

Deployed ByteCode

0x608060405234801561001057600080fd5b50600436106107015760003560e01c806368ca82a41161039d578063b204d3be116101e9578063d4688cc91161011a578063e55b1803116100b8578063f85a86b611610087578063f85a86b6146107a4578063f92f8b3414610aef578063fa44d4cb14610afd578063ff4623e51461076557600080fd5b8063e55b180314610781578063eab88bf514610a68578063ebe654da14610781578063ede844981461078157600080fd5b8063db38858b116100f4578063db38858b14610755578063de90d48914610ac1578063e0780cf914610acf578063e2dc49ac1461074357600080fd5b8063d4688cc914610a93578063d5c2803114610755578063d7ce428b14610aa657600080fd5b8063c2d848ea11610187578063c734cd0611610161578063c734cd06146107ba578063cbd94efb14610765578063d11f77571461093d578063d3e9b15c14610a0157600080fd5b8063c2d848ea14610773578063c354ec4414610755578063c5f9f86b1461093d57600080fd5b8063b9bc64e1116101c3578063b9bc64e114610781578063bc23a3bc14610a48578063bec0854014610765578063c0a1d29114610a6857600080fd5b8063b204d3be14610755578063b3a722ef14610743578063b707c9af146107f557600080fd5b80638dff87f1116102ce57806397e567e91161026c578063a292c1e11161023b578063a292c1e114610836578063a40dccd414610781578063a4e0aeb314610a22578063a80a56bb1461093d57600080fd5b806397e567e9146109f357806398b3001114610a01578063999e16b614610a1b5780639f7454571461076557600080fd5b80639161867a116102a85780639161867a146109aa578063930f7d41146109ce578063942d4f981461093d57806396e3cd13146109e557600080fd5b80638dff87f1146107815780638ea0330b1461099c578063914601491461078157600080fd5b80637d3aaafe1161033b5780638685275511610315578063868527551461083657806387aaee391461077357806389148de21461075557806389c23fe31461098e57600080fd5b80637d3aaafe1461075557806381227c921461078157806382b7e9801461078157600080fd5b80637523f7dd116103775780637523f7dd1461093d578063795fe204146107555780637b8ae016146107555780637c716b911461094b57600080fd5b806368ca82a41461074357806369d502ca14610906578063747e9f611461092757600080fd5b80632d6f65321161055c57806349b2d4481161048d578063556fb0841161042b5780636427bc40116103fa5780636427bc40146108d857806366dc9bdd146108f8578063685270081461079257806368c02a991461078157600080fd5b8063556fb084146108915780635b2e8435146108b45780635b44cce91461078157806360f71809146108c257600080fd5b80635206245e116104675780635206245e146107a457806352a44107146108835780635365dbe31461078157806353f4ef311461075557600080fd5b806349b2d448146107815780634d6ce674146108755780634f2888721461078157600080fd5b806339018b38116104fa57806342698baf116104d457806342698baf14610781578063440c4f6b1461075557806344e5e0221461076557806348f8ad811461078157600080fd5b806339018b3814610781578063408ccff11461084857806340b640df1461076557600080fd5b80633315b516116105365780633315b516146108035780633643986814610836578063372500ab1461079257806338f804a11461078157600080fd5b80632d6f6532146107555780632fe458da146108235780633055ad5b1461075557600080fd5b8063152e86ef116106365780631da3e033116105d457806325b94e89116105ae57806325b94e89146107de57806327ec3b2c146107f557806328275b07146107925780632888752d1461080357600080fd5b80631da3e033146107815780631ecc68b2146107ca578063230a2a9c1461076557600080fd5b80631648f1d6116106105780631648f1d6146107a45780631752b705146107ba5780631cf2651b146107555780631d278c1a1461076557600080fd5b8063152e86ef1461073057806315c4f1ce14610755578063162302571461079257600080fd5b80630ee2bb31116106a357806312a74e6e1161067d57806312a74e6e14610755578063133aa8231461078157806314e0294a146107555780631517c5611461075557600080fd5b80630ee2bb3114610765578063114d80761461077357806311d1db661461078157600080fd5b806308baea3a116106df57806308baea3a14610743578063098a11f0146107435780630a2912c4146107555780630e23c51e1461075557600080fd5b8063012c4997146107065780630132b39314610730578063060811d614610732575b600080fd5b61071a610714366004610c4f565b50600090565b6040516107279190610c82565b60405180910390f35b005b606080604051610727929190610e79565b610730610751366004610eaf565b5050565b60005b6040516107279190610eec565b610758610714366004610efa565b610758610714366004610c4f565b61073061078f366004610efa565b50565b6000805b604051610727929190610f1b565b6107966107b2366004610c4f565b600080915091565b60605b6040516107279190610f36565b6107306107d8366004610fe2565b50505050565b6107966107ec366004611059565b50600091829150565b6107306107d836600461107b565b610816610811366004610efa565b610b15565b60405161072791906110e6565b6107306108313660046110f7565b505050565b6060805b604051610727929190611147565b610864610856366004610efa565b506000908190819081908190565b604051610727959493929190611158565b610758610714366004611410565b610730610831366004611432565b6108a761089f36600461148d565b606092915050565b60405161072791906114d4565b6107bd61089f36600461148d565b6107306108d0366004611670565b505050505050565b6108eb6108e6366004610efa565b610b22565b6040516107279190611a36565b6107306108d0366004611a47565b61091a610914366004610efa565b50606090565b6040516107279190611b5c565b610758610935366004611c57565b600092915050565b61073061078f366004611cbd565b610981610959366004610efa565b5060408051608081018252600080825260208201819052918101829052606081019190915290565b6040516107279190611d3c565b610730610751366004611d4a565b6107bd61089f366004611d85565b6109c06109b8366004610efa565b606080915091565b604051610727929190611e4c565b6107306109dc366004611e71565b50505050505050565b610730610831366004611f10565b61071a610935366004611f34565b600080808080805b60405161072796959493929190611f67565b600061071a565b610a3a610a30366004611fd8565b5060609160009150565b60405161072792919061200b565b610a09610a56366004610efa565b60008060008060008091939550919395565b610a83610a76366004610efa565b6000806000809193509193565b604051610727949392919061202b565b610816610aa1366004610eaf565b610b67565b610ab461089f36600461148d565b60405161072791906120ae565b61083a6109b8366004610c4f565b610ae2610add3660046120d3565b610b75565b60405161072791906121cd565b61075861071436600461235a565b610730610b0b366004612394565b5050505050505050565b610b1d610b7d565b919050565b610b1d6040518060e00160405280600060ff16815260200160008152602001600015158152602001606081526020016060815260200160608152602001606081525090565b610b6f610b7d565b92915050565b610b1d610bcf565b6040518061012001604052806000151581526020016000815260200160008152602001600081526020016060815260200160008152602001600081526020016000151581526020016000151581525090565b6040518060a001604052806005905b610c096040518060800160405280606081526020016000815260200160008152602001600081525090565b815260200190600190039081610bde5790505090565b60006001600160a01b038216610b6f565b610c3981610c1f565b811461078f57600080fd5b8035610b6f81610c30565b600060208284031215610c6457610c64600080fd5b6000610c708484610c44565b949350505050565b8015155b82525050565b60208101610b6f8284610c78565b80610c7c565b60005b83811015610cb1578181015183820152602001610c99565b50506000910152565b6000610cc4825190565b808452602084019350610cdb818560208601610c96565b601f01601f19169290920192915050565b8051600090610120840190610d018582610c78565b506020830151610d146020860182610c90565b506040830151610d276040860182610c90565b506060830151610d3a6060860182610c90565b5060808301518482036080860152610d528282610cba565b91505060a0830151610d6760a0860182610c90565b5060c0830151610d7a60c0860182610c90565b5060e0830151610d8d60e0860182610c78565b50610100830151610da2610100860182610c78565b509392505050565b6000610db68383610cec565b9392505050565b6000610dc7825190565b80845260208401935083602082028501610de18560200190565b60005b84811015610e155783830388528151610dfd8482610daa565b93505060208201602098909801979150600101610de4565b50909695505050505050565b610e2b8282610c90565b5060200190565b6000610e3c825190565b808452602093840193830160005b82811015610e6f578151610e5e8782610e21565b965050602082019150600101610e4a565b5093949350505050565b60408082528101610e8a8185610dbd565b90508181036020830152610c708184610e32565b80610c39565b8035610b6f81610e9e565b60008060408385031215610ec557610ec5600080fd5b6000610ed18585610ea4565b9250506020610ee285828601610ea4565b9150509250929050565b60208101610b6f8284610c90565b600060208284031215610f0f57610f0f600080fd5b6000610c708484610ea4565b60408101610f298285610c90565b610db66020830184610c90565b60208082528101610db68184610e32565b60008083601f840112610f5c57610f5c600080fd5b5081356001600160401b03811115610f7657610f76600080fd5b602083019150836020820283011115610f9157610f91600080fd5b9250929050565b60008083601f840112610fad57610fad600080fd5b5081356001600160401b03811115610fc757610fc7600080fd5b602083019150836080820283011115610f9157610f91600080fd5b60008060008060408587031215610ffb57610ffb600080fd5b84356001600160401b0381111561101457611014600080fd5b61102087828801610f47565b945094505060208501356001600160401b0381111561104157611041600080fd5b61104d87828801610f98565b95989497509550505050565b6000806040838503121561106f5761106f600080fd5b6000610ed18585610c44565b6000806000806040858703121561109457611094600080fd5b84356001600160401b038111156110ad576110ad600080fd5b6110b987828801610f47565b945094505060208501356001600160401b038111156110da576110da600080fd5b61104d87828801610f47565b60208082528101610db68184610cec565b60008060006060848603121561110f5761110f600080fd5b600061111b8686610ea4565b935050602061112c86828701610ea4565b925050604061113d86828701610ea4565b9150509250925092565b60408082528101610e8a8185610e32565b60a081016111668288610c90565b6111736020830187610c90565b6111806040830186610c90565b61118d6060830185610c90565b61119a6080830184610c90565b9695505050505050565b634e487b7160e01b600052604160045260246000fd5b601f19601f83011681018181106001600160401b03821117156111df576111df6111a4565b6040525050565b60006111f160405190565b9050610b1d82826111ba565b6006811061078f57600080fd5b8035610b6f816111fd565b801515610c39565b8035610b6f81611215565b6001600160801b038116610c39565b8035610b6f81611228565b60006080828403121561125757611257600080fd5b61126160806111e6565b9050600061126f8484610ea4565b90820152602061128184848301611237565b90820152604061129384848301611237565b9082015260606112a584848301610c44565b9082015292915050565b6003811061078f57600080fd5b8035610b6f816112af565b600060a082840312156112dc576112dc600080fd5b6112e660406111e6565b905060006112f48484611242565b908201526080611306848483016112bc565b60208301525092915050565b60006060828403121561132757611327600080fd5b61133160606111e6565b9050600061133f8484611237565b90820152602061135184848301611237565b9082015260406112a584848301610ea4565b6000610220828403121561137957611379600080fd5b61138360e06111e6565b90506000611391848461120a565b9082015260206113a38484830161121d565b9082015260406113b584848301610ea4565b9082015260606113c7848483016112c7565b908201526101006113da848483016112c7565b6080830152506101a06113ef84828501611312565b60a08301525061020061140484828501610ea4565b60c08301525092915050565b6000610220828403121561142657611426600080fd5b6000610c708484611363565b60008060006040848603121561144a5761144a600080fd5b60006114568686610ea4565b93505060208401356001600160401b0381111561147557611475600080fd5b61148186828701610f47565b92509250509250925092565b600080602083850312156114a3576114a3600080fd5b82356001600160401b038111156114bc576114bc600080fd5b6114c885828601610f47565b92509250509250929050565b60208082528101610db68184610dbd565b60006001600160401b038211156114fe576114fe6111a4565b5060209081020190565b600061151b611516846114e5565b6111e6565b8381529050602080820190840283018581111561153a5761153a600080fd5b835b8181101561155c5761154e8782610ea4565b83526020928301920161153c565b5050509392505050565b600082601f83011261157a5761157a600080fd5b8135610c70848260208601611508565b6000611598611516846114e5565b838152905060208082019084028301858111156115b7576115b7600080fd5b835b8181101561155c576115cb8782611237565b8352602092830192016115b9565b600082601f8301126115ed576115ed600080fd5b8135610c7084826020860161158a565b600061160b611516846114e5565b8381529050602080820190840283018581111561162a5761162a600080fd5b835b8181101561155c5761163e8782610c44565b83526020928301920161162c565b600082601f83011261166057611660600080fd5b8135610c708482602086016115fd565b60008060008060008060c0878903121561168c5761168c600080fd5b60006116988989610ea4565b96505060208701356001600160401b038111156116b7576116b7600080fd5b6116c389828a01611566565b95505060408701356001600160401b038111156116e2576116e2600080fd5b6116ee89828a016115d9565b94505060608701356001600160401b0381111561170d5761170d600080fd5b61171989828a016115d9565b93505060808701356001600160401b0381111561173857611738600080fd5b61174489828a0161164c565b92505060a08701356001600160401b0381111561176357611763600080fd5b61176f89828a01611566565b9150509295509295509295565b60ff8116610c7c565b600061178f825190565b808452602093840193830160005b82811015610e6f5781516117b18782610e21565b96505060208201915060010161179d565b6001600160801b038116610c7c565b80516117dd83826117c2565b5060208101516117f060208401826117c2565b5060408101516108316040840182610c90565b61180d82826117d1565b5060600190565b600061181e825190565b808452602093840193830160005b82811015610e6f5781516118408782611803565b96505060208201915060010161182c565b610c7c81610c1f565b80516118668382610c90565b50602081015161187960208401826117c2565b50604081015161188c60408401826117c2565b5060608101516108316060840182611851565b634e487b7160e01b600052602160045260246000fd5b6003811061078f5761078f61189f565b80610b1d816118b5565b6000610b6f826118c5565b610c7c816118cf565b80516118ef838261185a565b50602081015161083160808401826118da565b61190c82826118e3565b5060a00190565b600061191d825190565b808452602093840193830160005b82811015610e6f57815161193f8782611902565b96505060208201915060010161192b565b600061195a825190565b808452602093840193830160005b82811015610e6f57815161197c8782611902565b965050602082019150600101611968565b805160009060e08401906119a1858261177c565b5060208301516119b46020860182610c90565b5060408301516119c76040860182610c78565b50606083015184820360608601526119df8282611785565b915050608083015184820360808601526119f98282611814565b91505060a083015184820360a0860152611a138282611913565b91505060c083015184820360c0860152611a2d8282611950565b95945050505050565b60208082528101610db6818461198d565b60008060008060008060c08789031215611a6357611a63600080fd5b6000611a6f8989610ea4565b9650506020611a8089828a01610ea4565b9550506040611a9189828a01611237565b9450506060611aa289828a01611237565b9350506080611ab389828a01610c44565b92505060a061176f89828a01610ea4565b8051611ad08382611851565b506020810151611ae36020840182610c90565b506040810151611af660408401826117c2565b50606081015161083160608401826117c2565b80516118ef8382611ac4565b61190c8282611b09565b6000611b29825190565b808452602093840193830160005b82811015610e6f578151611b4b8782611b15565b965050602082019150600101611b37565b60208082528101610db68184611b1f565b6000611b7b611516846114e5565b83815290506020810160a08402830185811115611b9a57611b9a600080fd5b835b8181101561155c57611bae87826112c7565b835260209092019160a001611b9c565b600082601f830112611bd257611bd2600080fd5b8135610c70848260208601611b6d565b6000611bf0611516846114e5565b83815290506020810160608402830185811115611c0f57611c0f600080fd5b835b8181101561155c57611c238782611312565b8352602090920191606001611c11565b600082601f830112611c4757611c47600080fd5b8135610c70848260208601611be2565b60008060408385031215611c6d57611c6d600080fd5b82356001600160401b03811115611c8657611c86600080fd5b611c9285828601611bbe565b92505060208301356001600160401b03811115611cb157611cb1600080fd5b610ee285828601611c33565b600060208284031215611cd257611cd2600080fd5b81356001600160401b03811115611ceb57611ceb600080fd5b610c7084828501611566565b8051611d038382611851565b506020810151611d166020840182610c78565b506040810151611d296040840182610c90565b5060608101516108316060840182610c90565b60808101610b6f8284611cf7565b60008060408385031215611d6057611d60600080fd5b82356001600160401b03811115611d7957611d79600080fd5b610ed185828601611566565b60008060408385031215611d9b57611d9b600080fd5b6000611da78585610ea4565b92505060208301356001600160401b03811115611dc657611dc6600080fd5b610ee285828601611566565b6000611ddc825190565b808452602093840193830160005b82811015610e6f578151611dfe8782611803565b965050602082019150600101611dea565b6000611e19825190565b808452602093840193830160005b82811015610e6f578151611e3b8782611902565b965050602082019150600101611e27565b60408082528101611e5d8185611dd2565b90508181036020830152610c708184611e0f565b600080600080600080600060e0888a031215611e8f57611e8f600080fd5b6000611e9b8a8a610ea4565b9750506020611eac8a828b01610ea4565b9650506040611ebd8a828b01610ea4565b9550506060611ece8a828b01610ea4565b9450506080611edf8a828b01610ea4565b93505060a0611ef08a828b0161121d565b92505060c0611f018a828b0161121d565b91505092959891949750929550565b600080600060408486031215611f2857611f28600080fd5b60006114568686610c44565b60008060408385031215611f4a57611f4a600080fd5b6000611f568585610ea4565b9250506020610ee285828601610c44565b60c08101611f758289610c90565b611f826020830188610c90565b611f8f6040830187610c90565b611f9c6060830186610c90565b611fa96080830185610c90565b611fb660a0830184610c90565b979650505050505050565b63ffffffff8116610c39565b8035610b6f81611fc1565b60008060408385031215611fee57611fee600080fd5b6000611ffa8585610c44565b9250506020610ee285828601611fcd565b6040808252810161201c8185610e32565b9050610db66020830184610c78565b608081016120398287610c90565b6120466020830186610c90565b6120536040830185610c90565b611a2d6060830184610c90565b61206a8282611cf7565b5060800190565b600061207b825190565b808452602093840193830160005b82811015610e6f57815161209d8782612060565b965050602082019150600101612089565b60208082528101610db68184612071565b60ff8116610c39565b8035610b6f816120bf565b6000602082840312156120e8576120e8600080fd5b6000610c7084846120c8565b60006120fe825190565b808452602093840193830160005b82811015610e6f5781516121208782611b15565b96505060208201915060010161210c565b805160808084526000919084019061214982826120f4565b915050602083015161215e6020860182610c90565b5060408301516121716040860182610c90565b506060830151610da26060860182610c90565b6000610db68383612131565b600060058360a0810184845b84811015610e1557838303885281516121b58482612184565b9350506020820160209890980197915060010161219c565b60208082528101610db68184612190565b60006121ec611516846114e5565b83815290506020810160a0840283018581111561220b5761220b600080fd5b835b8181101561155c5761221f87826112c7565b835260209092019160a00161220d565b600082601f83011261224357612243600080fd5b8135610c708482602086016121de565b600060e0828403121561226857612268600080fd5b61227260e06111e6565b9050600061228084846120c8565b90820152602061229284848301610ea4565b9082015260406122a48484830161121d565b9082015260608201356001600160401b038111156122c4576122c4600080fd5b6122d084828501611566565b60608301525060808201356001600160401b038111156122f2576122f2600080fd5b6122fe84828501611c33565b60808301525060a08201356001600160401b0381111561232057612320600080fd5b61232c84828501611bbe565b60a08301525060c08201356001600160401b0381111561234e5761234e600080fd5b6114048482850161222f565b60006020828403121561236f5761236f600080fd5b81356001600160401b0381111561238857612388600080fd5b610c7084828501612253565b600080600080600080600080610100898b0312156123b4576123b4600080fd5b60006123c08b8b610ea4565b98505060206123d18b828c01610ea4565b97505060406123e28b828c01610ea4565b96505060606123f38b828c01610ea4565b95505060806124048b828c01610ea4565b94505060a06124158b828c01610ea4565b93505060c06124268b828c0161121d565b92505060e06124378b828c0161121d565b915050929598509295989093965056fea2646970667358221220c5a5fd91005be3632ad5231d468988af2dcc478b4e43dd01da8ea6879d50790364736f6c63430008130033