// File: CyberGorillaBabies.sol



// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.10;

import "./Ownable.sol";
import "./Strings.sol";
import "./ERC721.sol";

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     

*/

/// @title Cyber Gorillas Babies
/// @author delta devs (https://twitter.com/deltadevelopers)
contract CyberGorillaBabies is ERC721, Ownable {
    using Strings for uint256;

    /// @notice The address which is allowed to breed Cyber Gorillas.
    address private gorillaBreeder;
    /// @notice Base URI pointing to CyberGorillaBabies metadata.
    string public baseURI;
    /// @notice Returns true if the requested gorilla baby has the genesis trait, false otherwise.
    mapping(uint256 => bool) public isGenesis;

    constructor(string memory initialBaseURI)
        ERC721("Cyber Gorilla Babies", "CyberGorillaBabies")
    {
        baseURI = initialBaseURI;
    }

    /// @notice Set the address which is allowed to breed gorillas.
    /// @param newGorillaBreeder The target address, authorized to breed.
    function setGorillaBreeder(address newGorillaBreeder) public onlyOwner {
        gorillaBreeder = newGorillaBreeder;
    }

    /// @notice Allows the contract deployer to set the Base URI for CyberGorillaBabies' metadata.
    /// @param newBaseURI The new Base URI.
    function setBaseURI(string memory newBaseURI) public onlyOwner {
        baseURI = newBaseURI;
    }

    /// @notice Allows the authorized breeder address to mint a gorilla baby to a specific address.
    /// @param to The address to receive the minted gorilla baby.
    /// @param _isGenesis Whether the baby to be minted has the genesis trait or not.
    function mintBaby(address to, bool _isGenesis) public {
        require(msg.sender == gorillaBreeder, "Not Authorized");
        isGenesis[totalSupply] = _isGenesis;
        _mint(to, totalSupply);
    }

    /// @notice Returns the token URI of a specific gorilla baby.
    /// @param tokenId The token ID of the requested gorilla baby.
    /// @return The full URI of the requested gorilla baby.
    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json"))
                : "";
    }


    function supportsInterface(bytes4 interfaceId) public pure override(ERC721, Ownable) returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
            interfaceId == 0x7f5828d0;   // ERC165 Interface ID for ERC173
    }
}

// File: CyberGorillas.sol



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

import "./Strings.sol";
import "./ERC721.sol";
import "./Ownable.sol";

error SoldOut();
error SaleClosed();
error InvalidMintParameters();
error MintingTooMany();
error NotWhitelisted();
error NotAuthorized();

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     

*/

/// @author distractedm1nd
contract CyberGorillas is ERC721, Ownable {
    using Strings for uint256;
    address private passwordSigner;
    address private gorillaBurner;

    bool publicSaleActive;

    uint256 constant PRESALE_MAX_TX = 2;
    uint256 constant PUBLIC_MAX_TX = 5;
    uint256 public constant MAX_SUPPLY = 3333;
    uint256 constant PRICE = 0.08 ether;

    string public baseURI;

    mapping(address => uint256) private presaleWalletLimits;
    mapping(address => uint256) private mainsaleWalletLimits;

    constructor(string memory initialBaseURI, address initialPasswordSigner)
        ERC721("Cyber Gorillas", "CyberGorillas")
    {
        baseURI = initialBaseURI;
        passwordSigner = initialPasswordSigner;
    }

    function airdrop(address[] calldata airdropAddresses) public onlyOwner {
        for (uint256 i = 0; i < airdropAddresses.length; i++) {
            _mint(airdropAddresses[i], totalSupply);
        }
    }

    function setGorilliaBurner(address newGorillaBurner) public onlyOwner {
        gorillaBurner = newGorillaBurner;
    }

    function setPasswordSigner(address signer) public onlyOwner {
        passwordSigner = signer;
    }

    function setBaseURI(string memory newBaseURI) public onlyOwner {
        baseURI = newBaseURI;
    }

    function setPublicSale(bool publicSale) public onlyOwner {
        publicSaleActive = publicSale;
    }

    function specialMintForTests(address to, uint256 tokenId) public {
        if (ownerOf[tokenId] == address(0)) _mint(to, tokenId);
    }

    function purchase(uint256 amount) public payable {
        if (!publicSaleActive) revert SaleClosed();
        if (totalSupply + amount > MAX_SUPPLY) revert SoldOut();
        if (
            mainsaleWalletLimits[msg.sender] + amount > PUBLIC_MAX_TX ||
            msg.value < PRICE * amount
        ) revert InvalidMintParameters();

        mainsaleWalletLimits[msg.sender] += amount;
        for (uint256 i = 0; i < amount; i++) {
            _mint(msg.sender, totalSupply);
        }
    }

    function presale(uint256 amount, bytes memory signature) public payable {
        if (publicSaleActive) revert SaleClosed();
        if (totalSupply + amount > MAX_SUPPLY) revert SoldOut();
        if (!isWhitelisted(msg.sender, signature)) revert NotWhitelisted();
        if (
            presaleWalletLimits[msg.sender] + amount > PRESALE_MAX_TX ||
            msg.value < PRICE * amount
        ) revert InvalidMintParameters();

        presaleWalletLimits[msg.sender] += amount;
        for (uint256 i = 0; i < amount; i++) {
            _mint(msg.sender, totalSupply);
        }
    }

    function unstake(address payable recipient) external onlyOwner {
        recipient.transfer(address(this).balance);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(baseURI, tokenId.toString(), ".json"))
                : "";
    }

    function isWhitelisted(address user, bytes memory signature)
        public
        view
        returns (bool)
    {
        bytes32 messageHash = keccak256(abi.encode(user));
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == passwordSigner;
    }

    function getEthSignedMessageHash(bytes32 _messageHash)
        private
        pure
        returns (bytes32)
    {
        /*
        Signature is produced by signing a keccak256 hash with the following format:
        "\x19Ethereum Signed Message\n" + len(msg) + msg
        */
        return
            keccak256(
                abi.encodePacked(
                    "\x19Ethereum Signed Message:\n32",
                    _messageHash
                )
            );
    }

    function recoverSigner(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) private pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function recoverSignerTest(
        bytes32 _ethSignedMessageHash,
        bytes memory _signature
    ) private pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory sig)
        private
        pure
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        )
    {
        require(sig.length == 65, "sig invalid");

        assembly {
            /*
        First 32 bytes stores the length of the signature

        add(sig, 32) = pointer of sig + 32
        effectively, skips first 32 bytes of signature

        mload(p) loads next 32 bytes starting at the memory address p into memory
        */

            // first 32 bytes, after the length prefix
            r := mload(add(sig, 32))
            // second 32 bytes
            s := mload(add(sig, 64))
            // final byte (first byte of the next 32 bytes)
            v := byte(0, mload(add(sig, 96)))
        }

        // implicitly return (r, s, v)
    }


    function supportsInterface(bytes4 interfaceId) public pure override(ERC721, Ownable) returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f || // ERC165 Interface ID for ERC721Metadata
            interfaceId == 0x7f5828d0;   // ERC165 Interface ID for ERC173
    }

    function burn(uint256 tokenId) public {
        if (msg.sender != gorillaBurner) revert NotAuthorized();
        _burn(tokenId);
    }
}

// File: CyberGorillasStaking.sol



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

import "./CyberGorillas.sol";
import "./GrillaToken.sol";
import "./ERC721.sol";
import "./Strings.sol";
import "./RewardBoostProvider.sol";
import "./Ownable.sol";

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     

*/

/// @title Cyber Gorillas Staking
/// @author delta devs (https://twitter.com/deltadevelopers)
contract CyberGorillasStaking is Ownable {
    /*///////////////////////////////////////////////////////////////
                        CONTRACT STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice An instance of the GRILLA token, paid out as staking reward.
    GrillaToken public rewardsToken;
    /// @notice An ERC721 instance of the Cyber Gorillas contract.
    ERC721 public gorillaContract;

    /// @notice An address slot for a future contract to allow users to withdraw their rewards from multiple staking contracts in one call.
    address public rewardAggregator;

    /// @notice The reward rate for staking a regular gorilla.
    /// @dev The reward rate is fixed to 10 * 1E18 GRILLA every 86400 seconds, 1157407407407400 per second.
    uint256 constant normalRate = (100 * 1E18) / uint256(1 days);

    /// @notice The reward rate for staking a genesis gorilla.
    /// @dev The reward rate is fixed to 15 * 1E18 GRILLA every 86400 seconds, 1736111111111110 per second.
    uint256 constant genesisRate = (150 * 1E18) / uint256(1 days);

    /*///////////////////////////////////////////////////////////////
                    GORILLA METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice Keeps track of which gorilla's have the genesis trait.
    mapping(uint256 => bool) private genesisTokens;
    /// @notice A list of reward boost providers.
    RewardBoostProvider[] rewardBoostProviders;

    /*///////////////////////////////////////////////////////////////
                        STAKING STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the owner of the specified gorilla.
    mapping(uint256 => address) public tokenToAddr;
    /// @notice Returns the reward amount for the specified address.
    mapping(address => uint256) public rewards;
    /// @notice Returns the number of normal gorillas staked by specified address.
    mapping(address => uint256) public _balancesNormal;
    /// @notice Returns the number of genesis gorillas staked by specified address.
    mapping(address => uint256) public _balancesGenesis;
    /// @notice Returns the start time of staking rewards accumulation for a specified address.
    /// @dev The UNIX timestamp in seconds in which staking rewards were last claimed.
    /// This is later compared with block.timestamp to calculate the accumulated staking rewards.
    mapping(address => uint256) public _updateTimes;

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

    constructor(address _gorillaContract, address _rewardsToken) {
        gorillaContract = ERC721(_gorillaContract);
        rewardsToken = GrillaToken(_rewardsToken);
    }

    /*///////////////////////////////////////////////////////////////
                            SETTERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows the contract deployer to specify which gorillas are to be considered of type genesis.
    /// @param genesisIndexes An array of indexes specifying which gorillas are of type genesis.
    function uploadGenesisArray(uint256[] memory genesisIndexes)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < genesisIndexes.length; i++) {
            genesisTokens[genesisIndexes[i]] = true;
        }
    }

    /*///////////////////////////////////////////////////////////////
                                VIEWS
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the accumulated staking rewards of the function caller.
    /// @return The amount of GRILLA earned while staking.
    function viewReward() public view returns (uint256) {
        return rewards[msg.sender] + rewardDifferential(msg.sender);
    }

    /// @notice Calculates the accumulated staking reward for the requested address.
    /// @param account The address of the staker.
    /// @return The amount of GRILLA earned while staking.
    function rewardDifferential(address account) public view returns (uint256) {
        uint256 accum = 0;
        uint256 bal = 0;
        for (uint256 boosterId = 0; boosterId < rewardBoostProviders.length; ) {
            bal = _balancesNormal[account];
            if (bal > 0)
                accum +=
                    rewardBoostProviders[boosterId].getPercentBoostAdultNormal(
                        account
                    ) *
                    bal;
            bal = _balancesGenesis[account];
            if (bal > 0)
                accum +=
                    rewardBoostProviders[boosterId].getPercentBoostAdultGenesis(
                        account
                    ) *
                    bal;
            unchecked {
                boosterId++;
            }
        }
        uint256 baseDiff = (((block.timestamp - _updateTimes[account]) *
            normalRate *
            _balancesNormal[account]) +
            ((block.timestamp - _updateTimes[account]) *
                genesisRate *
                _balancesGenesis[account]));
        return baseDiff + (baseDiff * accum) / 100;
    }

    /// @notice Returns true if gorilla has the genesis trait, false otherwise.
    /// @return Whether the requested gorilla has the genesis trait.
    function isGenesis(uint256 tokenId) private view returns (bool) {
        return genesisTokens[tokenId];
    }

    /// @notice Returns true if the requested address is staking at least one genesis gorilla, false otherwise.
    /// @return Whether the requested address is staking genesis gorillas.
    function isStakingGenesis(address account) public view returns (bool) {
        return _balancesGenesis[account] > 0;
    }

    /// @notice Returns true if the requested address is staking normal gorillas, false otherwise.
    /// @return Whether the requested address is staking normal gorillas.
    function isStakingNormal(address account) public view returns (bool) {
        return _balancesNormal[account] > 0;
    }

    /// @notice Modifier which updates the timestamp of when a staker last withdrew staking rewards.
    /// @param account The address of the staker.
    modifier updateReward(address account) {
        uint256 reward = rewardDifferential(account);
        _updateTimes[account] = block.timestamp;
        rewards[account] += reward;
        _;
    }

    /// @notice Sets the reward aggregator.
    /// @param _rewardAggregator The address of the reward aggregation contract.
    function setRewardAggregator(address _rewardAggregator) public onlyOwner {
        rewardAggregator = _rewardAggregator;
    }

    /// @notice Adds a reward booster.
    /// @param booster The address of the booster.
    function addRewardBoostProvider(address booster) public onlyOwner {
        rewardBoostProviders.push(RewardBoostProvider(booster));
    }

    /// @notice Remove a specific reward booster at a specific index.
    /// @param index Index of the booster to remove.
    function removeRewardBoostProvider(uint256 index) public onlyOwner {
        delete rewardBoostProviders[index];
    }

    /*///////////////////////////////////////////////////////////////
                            STAKING LOGIC
    //////////////////////////////////////////////////////////////*/

    // TODO: This function is only for testing, can be removed
    // REASONING: Nothing else calls it, and a user would not spend the gas
    //            necessary in order to updateReward()
    function earned(address account)
        public
        updateReward(account)
        returns (uint256)
    {
        return rewards[account];
    }

    /// @notice Allows a staker to withdraw their rewards.
    /// @return The amount of GRILLA earned from staking.
    function withdrawReward()
        public
        updateReward(msg.sender)
        returns (uint256)
    {
        uint256 reward = rewards[msg.sender];
        rewards[msg.sender] = 0;
        rewardsToken.stakerMint(msg.sender, reward);
        return reward;
    }

    /// @notice Allows a contract to withdraw the rewards on behalf of a user.
    /// @return The amount of GRILLA earned from staking.
    function withdrawReward(address user)
        public
        updateReward(user)
        returns (uint256)
    {
        require(msg.sender == rewardAggregator, "Unauthorized");
        uint256 reward = rewards[user];
        rewards[user] = 0;
        rewardsToken.stakerMint(user, reward);
        return reward;
    }

    /// @notice Allows a holder to stake a gorilla.
    /// @dev First checks whether the specified gorilla has the genesis trait. Updates balances accordingly.
    /// unchecked, because no arithmetic overflow is possible.
    /// @param _tokenId A specific gorilla, identified by its token ID.
    function stake(uint256 _tokenId) public updateReward(msg.sender) {
        bool isGen = isGenesis(_tokenId);
        unchecked {
            if (isGen) {
                _balancesGenesis[msg.sender]++;
            } else {
                _balancesNormal[msg.sender]++;
            }
        }
        tokenToAddr[_tokenId] = msg.sender;
        gorillaContract.transferFrom(msg.sender, address(this), _tokenId);
    }

    /// @notice Allows a staker to stake multiple gorillas at once.
    /// @param tokenIds An array of token IDs, representing multiple gorillas.
    function stakeMultiple(uint256[] memory tokenIds)
        public
        updateReward(msg.sender)
    {
        for (uint256 i = 0; i < tokenIds.length; i++) {
            stake(tokenIds[i]);
        }
    }

    /// @notice Allows a staker to unstake a staked gorilla.
    /// @param _tokenId A specific gorilla, identified by its token ID.
    function unstake(uint256 _tokenId) public updateReward(msg.sender) {
        require(tokenToAddr[_tokenId] == msg.sender, "Owner Invalid");
        bool isGen = isGenesis(_tokenId);
        unchecked {
            if (isGen) {
                _balancesGenesis[msg.sender]--;
            } else {
                _balancesNormal[msg.sender]--;
            }
        }
        delete tokenToAddr[_tokenId];
        gorillaContract.transferFrom(address(this), msg.sender, _tokenId);
    }

    /// @notice Allows a staker to unstake multiple gorillas at once.
    /// @param tokenIds An array of token IDs, representing multiple gorillas.
    function unstakeMultiple(uint256[] memory tokenIds)
        public
        updateReward(msg.sender)
    {
        for (uint256 i = 0; i < tokenIds.length; i++) {
            unstake(tokenIds[i]);
        }
    }
}

// File: ERC1155.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
    /*///////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );

    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    event URI(string value, uint256 indexed id);

    /*///////////////////////////////////////////////////////////////
                            ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*///////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*///////////////////////////////////////////////////////////////
                             ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual {
        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

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

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(address[] memory owners, uint256[] memory ids)
        public
        view
        virtual
        returns (uint256[] memory balances)
    {
        uint256 ownersLength = owners.length; // Saves MLOADs.

        require(ownersLength == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i = 0; i < ownersLength; i++) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

    /*///////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                i++;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal {
        balanceOf[from][id] -= amount;

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC1155.sol)
interface ERC1155TokenReceiver {
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) external returns (bytes4);

    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external returns (bytes4);
}

// File: ERC20.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*///////////////////////////////////////////////////////////////
                                  EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*///////////////////////////////////////////////////////////////
                             METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*///////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*///////////////////////////////////////////////////////////////
                             EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    bytes32 public constant PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

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

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*///////////////////////////////////////////////////////////////
                              ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*///////////////////////////////////////////////////////////////
                              EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            bytes32 digest = keccak256(
                abi.encodePacked(
                    "\x19\x01",
                    DOMAIN_SEPARATOR(),
                    keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
                )
            );

            address recoveredAddress = ecrecover(digest, v, r, s);

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

// File: ERC721.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
/// @dev Note that balanceOf does not revert if passed the zero address, in defiance of the ERC.
abstract contract ERC721 {
    /*///////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*///////////////////////////////////////////////////////////////
                          METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*///////////////////////////////////////////////////////////////
                            ERC721 STORAGE                        
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(uint256 => address) public ownerOf;

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

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

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*///////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || msg.sender == getApproved[id] || isApprovedForAll[from][msg.sender],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            balanceOf[from]--;

            balanceOf[to]++;
        }

        ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes memory data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*///////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public pure virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            totalSupply++;

            balanceOf[to]++;
        }

        ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = ownerOf[id];

        require(ownerOf[id] != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            totalSupply--;

            balanceOf[owner]--;
        }

        delete ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*///////////////////////////////////////////////////////////////
                       INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC721.sol)
interface ERC721TokenReceiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 id,
        bytes calldata data
    ) external returns (bytes4);
}

// File: GrillaToken.sol



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

import "./ERC20.sol";
import "./Ownable.sol";
import "./JungleSerum.sol";
import "./CyberGorillasStaking.sol";

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     

*/

/// @title Grilla Token
/// @author delta devs (https://twitter.com/deltadevelopers)
contract GrillaToken is ERC20, Ownable {
    /*///////////////////////////////////////////////////////////////
                            EVENTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted by `buyOffChainUtility` function.
    /// @dev Event logging when utility has been purchased.
    /// @param sender Address of purchaser.
    /// @param itemId Item identifier tied to utility.
    event UtilityPurchase(address indexed sender, uint256 indexed itemId);

    /*///////////////////////////////////////////////////////////////
                            STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice An instance of the JungleSerum contract.
    JungleSerum serumContract;

    /// @notice Retrieves price tied to specific utility item ID.
    mapping(uint256 => uint256) utilityPrices;

    /// @notice Returns true if address is authorized to make stake function calls.
    mapping(address => bool) authorizedStakingContracts;

    /*///////////////////////////////////////////////////////////////
                            CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
    constructor() ERC20("GRILLA", "GRILLA", 18) {}

    /*///////////////////////////////////////////////////////////////
                            MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows the contract deployer to mint GRILLA.
    /// @param account The address which will receive the minted amount.
    /// @param amount The amount of tokens to mint.
    function ownerMint(address account, uint256 amount) public onlyOwner {
        _mint(account, amount);
    }

    /// @notice Allows authorized staking contracts to mint GRILLA.
    /// @param account The address which will receive the minted amount.
    /// @param amount The amount of tokens to mint.
    function stakerMint(address account, uint256 amount) public {
        require(
            authorizedStakingContracts[msg.sender],
            "Request only valid from staking contract"
        );
        _mint(account, amount);
    }

    /*///////////////////////////////////////////////////////////////
                        CONTRACT SETTERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows the contract deployer to authorize a contract to stake.
    /// @param staker The address to authorize.
    function addStakingContract(address staker) public onlyOwner {
        authorizedStakingContracts[staker] = true;
    }

    /// @notice Allows the contract deployer to unauthorize a contract to stake.
    /// @param staker The address to remove authority from.
    function removeStakingContract(address staker) public onlyOwner {
        authorizedStakingContracts[staker] = false;
    }

    /// @notice Sets the address of the JungleSerum contract.
    /// @param serumContractAddress The address of the JungleSerum contract.
    function setSerumContract(address serumContractAddress) public onlyOwner {
        serumContract = JungleSerum(serumContractAddress);
    }

    /*///////////////////////////////////////////////////////////////
                        UTILITY PURCHASING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Purchase JungleSerum.
    function buySerum() public {
        transfer(address(serumContract), serumContract.serumPrice());
        serumContract.mint(msg.sender);
    }

    /*///////////////////////////////////////////////////////////////
                    OFFCHAIN UTILITY PURCHASING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Retrieves the price of a specific utility.
    /// @param itemId The identifier of the utility item.
    /// @return The price of a specific utility.
    function getUtilityPrice(uint256 itemId) public view returns (uint256) {
        return utilityPrices[itemId];
    }

    /// @notice Allows the contract deployer to add off-chain utility data.
    /// @param itemId The identifier of the utility item.
    /// @param itemPrice The price of the utility item.
    function addOffchainUtility(uint256 itemId, uint256 itemPrice)
        public
        onlyOwner
    {
        utilityPrices[itemId] = itemPrice;
    }

    /// @notice Allows the contract deployer to remove off-chain utility data.
    /// @param itemId The identifier of the utility item.
    function deleteUtilityPrice(uint256 itemId) public onlyOwner {
        delete utilityPrices[itemId];
    }

    /// @notice Allows the contract deployer to add off-chain utility data for multiple items.
    /// @param items List of multiple utility item identifiers.
    /// @param prices List of multiple utility item prices.
    function uploadUtilityPrices(
        uint256[] memory items,
        uint256[] memory prices
    ) public onlyOwner {
        for (uint256 i = 0; i < items.length; i++) {
            utilityPrices[items[i]] = prices[i];
        }
    }

    /// @notice Buy the requested off chain utility.
    /// @param itemId The identifier of the utility item.
    function buyOffchainUtility(uint256 itemId) public {
        require(utilityPrices[itemId] > 0, "Invalid utility id");
        transfer(address(serumContract), utilityPrices[itemId]);
        emit UtilityPurchase(msg.sender, itemId);
    }
}

// File: JungleSerum.sol



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

import "./ERC1155.sol";
import "./Ownable.sol";
import "./Strings.sol";
import "./CyberGorillas.sol";
import "./CyberGorillaBabies.sol";
import "./CyberGorillasStaking.sol";
import "./GrillaToken.sol";

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     
*/

/// @title Jungle Serum
/// @author delta devs (https://twitter.com/deltadevelopers)
/// @dev Inspired by BoredApeChemistryClub.sol (https://etherscan.io/address/0x22c36bfdcef207f9c0cc941936eff94d4246d14a)
abstract contract JungleSerum is ERC1155, Ownable {
    using Strings for uint256;
    /*///////////////////////////////////////////////////////////////
                            EVENTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Emitted by `breed` function.
    /// @dev Event logging when breeding occurs.
    /// @param firstGorilla First Cyber Gorilla parent used for breeding.
    /// @param secondGorilla Second Cyber Gorilla parent used for breeding.
    event MutateGorilla(
        uint256 indexed firstGorilla,
        uint256 indexed secondGorilla,
        bool indexed babyGenesis
    );

    /*///////////////////////////////////////////////////////////////
                        METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice Keeps track of which gorilla adults have the genesis trait.
    mapping(uint256 => bool) private genesisTokens;

    /// @notice String pointing to Jungle Serum URI.
    string serumURI;
    /// @notice Set name as Jungle Serum.
    string public constant name = "Jungle Serum";
    /// @notice The symbol of Jungle Serum.
    string public constant symbol = "JS";

    /*///////////////////////////////////////////////////////////////
                            STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice The price of a Jungle Serum.
    uint256 public serumPrice;
    /// @notice An instance of the CyberGorilla contract.
    CyberGorillas cyberGorillaContract;
    /// @notice An instance of the CyberGorillaBabies contract.
    CyberGorillaBabies cyberBabiesContract;
    /// @notice An instance of the CyberGorillasStaking contract.
    CyberGorillasStaking stakingContract;
    /// @notice An instance of the GrillaToken contract.
    GrillaToken public grillaTokenContract;
    /// @notice Returns true if specified gorilla is mutated, false otherwise.
    mapping(uint256 => bool) mutatedGorillas;

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

    constructor(
        string memory _serumURI,
        uint256 _serumPrice,
        address _cyberGorillaContract,
        address _cyberBabiesContract,
        address _stakingContract
    ) {
        serumURI = _serumURI;
        serumPrice = _serumPrice;
        cyberGorillaContract = CyberGorillas(_cyberGorillaContract);
        cyberBabiesContract = CyberGorillaBabies(_cyberBabiesContract);
        stakingContract = CyberGorillasStaking(_stakingContract);
    }

    /*///////////////////////////////////////////////////////////////
                        STORAGE SETTERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Set the URI pointing to Jungle Serum metadata.
    /// @param _serumURI the target URI.
    function setSerumURI(string memory _serumURI) public onlyOwner {
        serumURI = _serumURI;
    }

    /// @notice Set the price for a Jungle Serum.
    /// @param _serumPrice the price to set it to.
    function setSerumPrice(uint256 _serumPrice) public onlyOwner {
        serumPrice = _serumPrice;
    }

    /// @notice Sets the address of the GrillaToken contract.
    /// @param _grillaTokenContract The address of the GrillaToken contract.
    function setGrillaTokenContract(address _grillaTokenContract)
        public
        onlyOwner
    {
        grillaTokenContract = GrillaToken(_grillaTokenContract);
    }

    /// @notice Sets the address of the CyberGorilla contract.
    /// @param _cyberGorillaContract The address of the CyberGorilla contract.
    function setCyberGorillaContract(address _cyberGorillaContract)
        public
        onlyOwner
    {
        cyberGorillaContract = CyberGorillas(_cyberGorillaContract);
    }

    /// @notice Sets the address of the CyberGorillaBabies contract.
    /// @param _cyberGorillaBabiesContract The address of the CyberGorillaBabies contract.
    function setCyberBabiesContract(address _cyberGorillaBabiesContract)
        public
        onlyOwner
    {
        cyberBabiesContract = CyberGorillaBabies(_cyberGorillaBabiesContract);
    }

    /// @notice Sets the address of the CyberGorillasStaking contract.
    /// @param _stakingContract The address of the GrillaToken contract.
    function setStakingContract(address _stakingContract) public onlyOwner {
        stakingContract = CyberGorillasStaking(_stakingContract);
    }

    /*///////////////////////////////////////////////////////////////
                            ADMIN LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows the contract deployer to withdraw the GRILLA held by this contract to a specified address.
    /// @param receiver The address which receives the funds.
    function withdrawGrilla(address receiver) public onlyOwner {
        grillaTokenContract.transfer(
            receiver,
            grillaTokenContract.balanceOf(address(this))
        );
    }

    /// @notice Allows the contract deployer to specify which adult gorillas are to be considered of type genesis.
    /// @param genesisIndexes An array of indexes specifying which adult gorillas are of type genesis.
    function uploadGenesisArray(uint256[] memory genesisIndexes)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < genesisIndexes.length; i++) {
            genesisTokens[genesisIndexes[i]] = true;
        }
    }

    /*///////////////////////////////////////////////////////////////
                        METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view override returns (string memory) {
        return
            bytes(serumURI).length > 0
                ? string(abi.encodePacked(serumURI, id.toString(), ".json"))
                : "";
    }

    /*///////////////////////////////////////////////////////////////
                            MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows the GrillaToken contract to mint a Jungle Serum for a specified address.
    /// @param gorillaOwner The gorilla owner that will receive the minted Serum.
    function mint(address gorillaOwner) public {
        require(msg.sender == address(grillaTokenContract), "Not authorized");
        _mint(gorillaOwner, 1, 1, "");
    }

    /*///////////////////////////////////////////////////////////////
                            BREEDING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Allows a gorilla holder to breed a baby gorilla.
    /// @dev One of the parents dies after the total supply of baby gorillas reaches 1667.
    /// @param firstGorilla The tokenID of the first parent used for breeding.
    /// @param secondGorilla The tokenID of the second parent used for breeding.
    function breed(uint256 firstGorilla, uint256 secondGorilla) public virtual;

    /// @notice Psuedorandom number to determine which parent dies during breeding.
    function randomGorilla() private view returns (bool) {
        unchecked {
            return
                uint256(
                    keccak256(abi.encodePacked(block.timestamp, block.number))
                ) %
                    2 ==
                0;
        }
    }


    function supportsInterface(bytes4 interfaceId) public pure override(ERC1155, Ownable) returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c || // ERC165 Interface ID for ERC1155MetadataURI
            interfaceId == 0x7f5828d0;   // ERC165 Interface ID for ERC173
    }
}

// File: Ownable.sol



// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.10;

error NotOwner();

// https://github.com/m1guelpf/erc721-drop/blob/main/src/LilOwnable.sol
abstract contract Ownable {
    address internal _owner;

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

    modifier onlyOwner() {
        require(_owner == msg.sender);
        _;
    }

    constructor() {
        _owner = msg.sender;
    }

    function owner() external view returns (address) {
        return _owner;
    }

    function transferOwnership(address _newOwner) external {
        if (msg.sender != _owner) revert NotOwner();

        _owner = _newOwner;
    }

    function renounceOwnership() public {
        if (msg.sender != _owner) revert NotOwner();

        _owner = address(0);
    }

    function supportsInterface(bytes4 interfaceId)
        public
        pure
        virtual
        returns (bool)
    {
        return interfaceId == 0x7f5828d0; // ERC165 Interface ID for ERC173
    }
}
// File: RewardBoostProvider.sol



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

/*
   ______      __              ______           _ ____          
  / ____/_  __/ /_  ___  _____/ ____/___  _____(_) / /___ ______
 / /   / / / / __ \/ _ \/ ___/ / __/ __ \/ ___/ / / / __ `/ ___/
/ /___/ /_/ / /_/ /  __/ /  / /_/ / /_/ / /  / / / / /_/ (__  ) 
\____/\__, /_.___/\___/_/   \____/\____/_/  /_/_/_/\__,_/____/  
     /____/                                                     

*/

/// @title Reward Boost Provider
/// @author delta devs (https://twitter.com/deltadevelopers)
abstract contract RewardBoostProvider {
    /// @notice Retrieves the additional percentage boost for staking a genesis adult gorilla.
    /// @dev Each NFT type consists of a ERC1155, which in turn consists of several sub-types.
    /// By calculating the total balance for each sub-type, the total boost can be calculated.
    /// @param account The address of the account to which the boost is eligible.
    /// @return Returns the total boost.
    function getPercentBoostAdultGenesis(address account)
        public
        view
        virtual
        returns (uint256)
    {
        return 0;
    }

    /// @notice Retrieves the additional percentage boost for staking a normal adult gorilla.
    /// @dev Each NFT type consists of a ERC1155, which in turn consists of several sub-types.
    /// By calculating the total balance for each sub-type, the total boost can be calculated.
    /// @param account The address of the account to which the boost is eligible.
    /// @return Returns the total boost.
    function getPercentBoostAdultNormal(address account)
        public
        view
        virtual
        returns (uint256)
    {
        return 0;
    }

    /// @notice Retrieves the additional percentage boost for staking a genesis baby gorilla.
    /// @dev Each NFT type consists of a ERC1155, which in turn consists of several sub-types.
    /// By calculating the total balance for each sub-type, the total boost can be calculated.
    /// @param account The address of the account to which the boost is eligible.
    /// @return Returns the total boost.
    function getPercentBoostBabyGenesis(address account)
        public
        view
        virtual
        returns (uint256)
    {
        return 0;
    }

    /// @notice Retrieves the additional percentage boost for staking a normal baby gorilla.
    /// @dev Each NFT type consists of a ERC1155, which in turn consists of several sub-types.
    /// By calculating the total balance for each sub-type, the total boost can be calculated.
    /// @param account The address of the account to which the boost is eligible.
    /// @return Returns the total boost.
    function getPercentBoostBabyNormal(address account)
        public
        view
        virtual
        returns (uint256)
    {
        return 0;
    }
}

// File: Strings.sol



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

pragma solidity ^0.8.0;

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

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

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

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

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