ETH Price: $2,429.81 (-3.50%)

Transaction Decoder

Block:
15814161 at Oct-23-2022 11:42:23 PM +UTC
Transaction Fee:
0.000505066860781182 ETH $1.23
Gas Used:
45,129 Gas / 11.191625358 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x34e1d1d1...383660a49
0.270857738660966884 Eth
Nonce: 15
0.270352671800185702 Eth
Nonce: 16
0.000505066860781182
0.051053845333179647 Eth0.051144103333179647 Eth0.000090258

Execution Trace

OddworxStaking.stakeNfts( nftContract=0xa0fa51F54dbab68C068a2E2c62AA72Fe334D9b09, nftIds=[650] )
  • FoodzPartyV2.transferFrom( from=0x34e1d1d11e73f8fde08bf167c16dE52383660a49, to=0x428b6a13277116C62D751bebbC6f47011A0Cdc11, id=650 )
    File 1 of 2: OddworxStaking
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity ^0.8.4;
    import {IOddworx} from './IOddworx.sol';
    import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
    import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
    error NotAdmin();
    error InvalidInput();
    error NotOwnerOfToken();
    struct nftDataStruct { // Stored in 32 bytes / 256 bits
        address ownerAddress; // 20 bytes 
        bool staked; // 1 byte
        uint64 timestamp; // 8 bytes
        bool legacyStaking; // 1 byte
    }
    /// @title Oddworx Staking
    /// @author Mytchall
    /// @notice Special Staking contract for ODDX
    contract OddworxStaking is Pausable {
        mapping(address => bool) public admin;
        mapping(IERC721 => bool) public nftInterfaces;
        mapping(IERC721 => mapping(uint256 => nftDataStruct)) public nftData;
        IOddworx public oddworxContract;
        bool public nftHoldRewardsActive = true;
        uint256 public STAKING_REWARD = 20 * 10 ** 18;
        uint256 public HOLDING_REWARD = 10 * 10 ** 18;
        address public oddworxContractAddress;
        constructor(address oddworxAddress) {
            oddworxContractAddress = oddworxAddress;
            oddworxContract = IOddworx(oddworxAddress);
            admin[msg.sender] = true;
        }
        /// @notice emitted when an item is purchased
        /// @param user address of the user that purchased an item
        /// @param itemSKU the SKU of the item purchased
        /// @param price the amount paid for the item
        event ItemPurchased(address indexed user, uint256 itemSKU, uint256 price);
        /// @notice emitted when a user stakes a token
        /// @param user address of the user that staked the NFT
        /// @param nftContract which NFT set was used
        /// @param nftId the id of the NFT staked
        event StakedNft(address indexed user, address indexed nftContract, uint256 indexed nftId);
        /// @notice emitted when a user unstakes a token
        /// @param user address of the user that unstaked the NFT
        /// @param nftContract which NFT set was used
        /// @param nftId the id of the NFT unstaked
        /// @param to address where NFT was unstaked to
        event UnstakedNft(address indexed user, address indexed nftContract, uint256 indexed nftId, address to);
        /// @notice emitted when a user claim NFT rewards
        /// @param user address of the user that claimed ODDX
        /// @param nftContract which NFT set was used
        /// @param nftId the id of the NFT that generated the rewards
        /// @param amount the amount of ODDX claimed
        event UserClaimedRewards(address indexed user, address indexed nftContract, uint256 indexed nftId, uint256 amount);
        modifier onlyAdmin() {
            if (admin[msg.sender] != true) revert NotAdmin();
            _;
        }
        /*///////////////////////////////////////////////////////////////
                                 General Functions
        //////////////////////////////////////////////////////////////*/
        function pause() external onlyAdmin { _pause(); }
        function unpause() external onlyAdmin { _unpause(); }
        function toggleAdmin(address address_) external onlyAdmin {
            admin[address_] = !admin[address_];
        }
        function mint(address to, uint256 amount) internal {
            oddworxContract.mint(to, amount);
        }
        function burn(address from, uint256 amount) internal {
            oddworxContract.burn(from, amount);
        }
        function setOddworxAddress(address address_) external onlyAdmin {
            oddworxContractAddress = address_;
            oddworxContract = IOddworx(address_);
        }
        function toggleNftInterface(IERC721 address_) external onlyAdmin {
            nftInterfaces[address_] = !nftInterfaces[address_];
        }
        /*///////////////////////////////////////////////////////////////
                                 Shop features
        //////////////////////////////////////////////////////////////*/
        /// @notice Buy item in shop by burning Oddx, if NFT ids are supplied, it will claim rewards on them first.
        /// @param itemSKU A unique ID used to identify shop products.
        /// @param amount Amount of Oddx to pay.
        /// @param nftContract which NFT contract to use
        /// @param nftIds Which NFT ids to use
        function buyItem(uint itemSKU, uint amount, IERC721 nftContract, uint[] calldata nftIds, address user) public whenNotPaused {
            address realUser = (admin[msg.sender]==true) ? user : msg.sender;
            if (nftIds.length>0) claimRewards(nftContract, nftIds, realUser);
            oddworxContract.burn(realUser, amount);
            emit ItemPurchased(realUser, itemSKU, amount);
        }
        /*///////////////////////////////////////////////////////////////
                                    Staking
        //////////////////////////////////////////////////////////////*/
        /// @notice Get an array of data for a NFT
        /// @param nftContract which NFT contract to use
        /// @param id Which NFT to use
        function getNftData(address nftContract, uint256 id) external view returns (address, bool, uint64, bool) {
            nftDataStruct memory nft = nftData[IERC721(nftContract)][id];
            return (nft.ownerAddress, nft.staked, nft.timestamp, nft.legacyStaking);
        }
        /// @notice Updates either Staked or Holding reward amount
        /// @param newAmount new amount to use, supply number in wei.
        /// @param changeStaking true to change Staking, false to change Hold rewards
        function changeRewardAmount(uint256 newAmount, bool changeStaking) external onlyAdmin {
            (changeStaking == true) ? STAKING_REWARD = newAmount : HOLDING_REWARD = newAmount;
        }
        /// @notice Manually update staking info (contract launch date - 3 weeks)
        /// @param nftContract which NFT contract to use
        /// @param nftIds NFT's to update
        /// @param newTimestamp new timestamp
        function setUserNftData(IERC721 nftContract, uint256[] calldata nftIds, address newOwner, bool isStaked, uint256 newTimestamp, bool usingLegacyStaking) external onlyAdmin {
            for (uint256 i; i<nftIds.length; i++) {
                nftData[nftContract][nftIds[i]] = nftDataStruct(newOwner, isStaked, uint64(newTimestamp), usingLegacyStaking);
            }
        }
        /// @notice Stake NFT and claim any Hold rewards owing if not legacyStaked, otherwise claim Staked rewards and update
        /// @param nftContract NFT contract to use
        /// @param nftIds List of NFTs to stake
        function stakeNfts(IERC721 nftContract, uint256[] calldata nftIds) external whenNotPaused {
            if (!nftInterfaces[nftContract]) revert InvalidInput();
            uint256 totalRewards = 0;
            nftDataStruct memory nft;
            for (uint256 i; i<nftIds.length; i++) {
                uint256 nftId = nftIds[i];
                nft = nftData[nftContract][nftId];
                if (nft.legacyStaking == false) {
                    totalRewards += _executeRewards(nftContract, nftId, HOLDING_REWARD, HOLDING_REWARD * 3);
                } else {
                    totalRewards += _executeRewards(nftContract, nftId, STAKING_REWARD, 0);
                    confirmLegacyStaking(nftContract, nftId);
                }
                nftData[nftContract][nftId] = nftDataStruct(msg.sender, true, uint64(block.timestamp), false);
                _transferNft(nftContract, msg.sender, address(this), nftId);
                emit StakedNft(msg.sender, address(nftContract), nftId);
            }
            if (totalRewards > 0) mint(msg.sender, totalRewards);
        }
        /// @notice Unstake NFT and claim Stake rewards owing, resetting Hold reward time
        /// @param nftContract NFT contract to use
        /// @param nftIds List of NFTs to stake
        function unstakeNfts(IERC721 nftContract, uint256[] calldata nftIds) external whenNotPaused {
            nftDataStruct memory nft;
            uint256 totalRewards;
            for (uint256 i; i<nftIds.length; i++) {
                uint256 nftId = nftIds[i];
                nft = nftData[nftContract][nftId];
                if (nft.staked == false) revert InvalidInput();
                if (nft.ownerAddress != msg.sender) revert NotOwnerOfToken();
                totalRewards += _executeRewards(nftContract, nftId, STAKING_REWARD, 0);
                nftData[nftContract][nftId] = nftDataStruct(msg.sender, false, uint64(block.timestamp), false);
                _transferNft(nftContract, address(this), nft.ownerAddress, nftId);
                emit UnstakedNft(msg.sender, address(nftContract), nftId, msg.sender);
            }
            if (totalRewards > 0) mint(msg.sender, totalRewards);
        }    
        /// @notice Returns amount of rewards to mint 
        /// @dev Emits event assuming mint will happen
        /// @param nftContract NFT contract to use
        /// @param nftId NFT to calculate rewards for
        /// @param rewardAmount Weekly reward amount
        /// @param initialReward Default reward amount
        function _executeRewards(IERC721 nftContract, uint256 nftId, uint256 rewardAmount, uint256 initialReward) internal returns (uint256) {
            uint256 rewards = _rewardsForTimestamp(
                nftData[nftContract][nftId].timestamp,
                rewardAmount,
                initialReward
            );
            emit UserClaimedRewards(msg.sender, address(nftContract), nftId, rewards);
            return rewards;
        }
        /// @notice Emergency Unstake NFT
        /// @param nftContract NFT contract to use
        /// @param nftIds List of NFTs to stake
        /// @param to Where to send NFT
        function unstakeNftEmergency(IERC721 nftContract, uint256[] calldata nftIds, address user, address to) external onlyAdmin {
            for (uint256 i; i<nftIds.length; i++) {
                address realUser = (admin[msg.sender]==true) ? user : msg.sender;
                nftData[nftContract][nftIds[i]] = nftDataStruct(to, false, uint64(block.timestamp), false);
                _transferNft(nftContract, address(this), to, nftIds[i]);
                emit UnstakedNft(realUser, address(nftContract), nftIds[i], to);
            }
        }
        /// @notice Claim either Hold or Claim rewards for each Nft
        /// @param nftContract Which NFT set is being used
        /// @param nftIds NFT id's to claim for
        function claimRewards(IERC721 nftContract, uint256[] calldata nftIds, address user) public whenNotPaused {
            if (!nftInterfaces[nftContract] || msg.sender == address(0)) revert InvalidInput();
            uint256 totalRewards;
            nftDataStruct memory nft;
            address realUser = (admin[msg.sender]==true) ? user : msg.sender;
        
            for (uint256 i; i<nftIds.length; i++) {
                uint256 nftId = nftIds[i];
                nft = nftData[nftContract][nftId];
                if (nft.staked == false) {
                    if (nftContract.ownerOf(nftId) != realUser) revert NotOwnerOfToken();
                    totalRewards += _executeRewards(nftContract, nftId, HOLDING_REWARD, HOLDING_REWARD * 3);
                } else {
                    if (nft.ownerAddress != realUser) revert NotOwnerOfToken();
                    totalRewards += _executeRewards(nftContract, nftId, STAKING_REWARD, 0);
                    if (nft.legacyStaking == true) confirmLegacyStaking(nftContract, nftId);
                }
                
                nftData[nftContract][nftId].timestamp = uint64(block.timestamp);
            }
            if (totalRewards > 0) mint(realUser, totalRewards);
        }
        /// @notice Calculate Hold or Staked rewards based on timestamp
        /// @param timestamp Timestamp to use
        /// @param rewardValue How much to reward per week
        /// @param initialReward Initial reward if first time claiming
        function _rewardsForTimestamp(uint256 timestamp, uint256 rewardValue, uint256 initialReward) internal view returns (uint256) {
            return (timestamp > 0)
                ? rewardValue * ((block.timestamp - timestamp) / 1 weeks)
                : initialReward;
        }
        /// @notice Actually transfer NFT
        /// @dev Internal only, checks are done before this
        /// @param nftContract NFT contract to use
        /// @param from Where to transfer NFT from
        /// @param to Where to send NFT
        function _transferNft(IERC721 nftContract, address from, address to, uint256 nftId) internal {
            nftContract.transferFrom(from, to, nftId);
        }
        /// @notice Checks if NFT uses legacyStaking and if it's still valid, otherwise update struct to show not staked
        /// @param nftContract Which NFT contract to use
        /// @param nftId Which NFT to check
        function confirmLegacyStaking(IERC721 nftContract, uint256 nftId) internal {
            if (nftContract.ownerOf(nftId) != oddworxContractAddress ) {
                nftData[nftContract][nftId].legacyStaking = false;
                nftData[nftContract][nftId].staked = false; 
            }
        }
    }
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity ^0.8.4;
    interface IOddworx {
        function burn(address _from, uint256 amount) external;
        function mint(address to, uint256 amount) external;
        function balanceOf(address account) external view returns (uint256);
    }// SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (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`, 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 be 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: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
         *
         * 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 Returns the account approved for `tokenId` token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function getApproved(uint256 tokenId) external view returns (address operator);
        /**
         * @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 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);
        /**
         * @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;
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which allows children to implement an emergency stop
     * mechanism that can be triggered by an authorized account.
     *
     * This module is used through inheritance. It will make available the
     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
     * the functions of your contract. Note that they will not be pausable by
     * simply including this module, only once the modifiers are put in place.
     */
    abstract contract Pausable is Context {
        /**
         * @dev Emitted when the pause is triggered by `account`.
         */
        event Paused(address account);
        /**
         * @dev Emitted when the pause is lifted by `account`.
         */
        event Unpaused(address account);
        bool private _paused;
        /**
         * @dev Initializes the contract in unpaused state.
         */
        constructor() {
            _paused = false;
        }
        /**
         * @dev Returns true if the contract is paused, and false otherwise.
         */
        function paused() public view virtual returns (bool) {
            return _paused;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is not paused.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        modifier whenNotPaused() {
            require(!paused(), "Pausable: paused");
            _;
        }
        /**
         * @dev Modifier to make a function callable only when the contract is paused.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        modifier whenPaused() {
            require(paused(), "Pausable: not paused");
            _;
        }
        /**
         * @dev Triggers stopped state.
         *
         * Requirements:
         *
         * - The contract must not be paused.
         */
        function _pause() internal virtual whenNotPaused {
            _paused = true;
            emit Paused(_msgSender());
        }
        /**
         * @dev Returns to normal state.
         *
         * Requirements:
         *
         * - The contract must be paused.
         */
        function _unpause() internal virtual whenPaused {
            _paused = false;
            emit Unpaused(_msgSender());
        }
    }
    // 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);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    

    File 2 of 2: FoodzPartyV2
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
            _;
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions anymore. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby removing any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // 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;
        }
    }
    // 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);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/MerkleProof.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev These functions deal with verification of Merkle Trees proofs.
     *
     * The proofs can be generated using the JavaScript library
     * https://github.com/miguelmota/merkletreejs[merkletreejs].
     * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
     *
     * See `test/utils/cryptography/MerkleProof.test.js` for some examples.
     */
    library MerkleProof {
        /**
         * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
         * defined by `root`. For this, a `proof` must be provided, containing
         * sibling hashes on the branch from the leaf to the root of the tree. Each
         * pair of leaves and each pair of pre-images are assumed to be sorted.
         */
        function verify(
            bytes32[] memory proof,
            bytes32 root,
            bytes32 leaf
        ) internal pure returns (bool) {
            return processProof(proof, leaf) == root;
        }
        /**
         * @dev Returns the rebuilt hash obtained by traversing a Merklee tree up
         * from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
         * hash matches the root of the tree. When processing the proof, the pairs
         * of leafs & pre-images are assumed to be sorted.
         *
         * _Available since v4.4._
         */
        function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
            bytes32 computedHash = leaf;
            for (uint256 i = 0; i < proof.length; i++) {
                bytes32 proofElement = proof[i];
                if (computedHash <= proofElement) {
                    // Hash(current computed hash + current element of the proof)
                    computedHash = _efficientHash(computedHash, proofElement);
                } else {
                    // Hash(current element of the proof + current computed hash)
                    computedHash = _efficientHash(proofElement, computedHash);
                }
            }
            return computedHash;
        }
        function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
            assembly {
                mstore(0x00, a)
                mstore(0x20, b)
                value := keccak256(0x00, 0x40)
            }
        }
    }
    // 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)
    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 BALANCE/OWNER STORAGE
        //////////////////////////////////////////////////////////////*/
        mapping(uint256 => address) internal _ownerOf;
        mapping(address => uint256) internal _balanceOf;
        function ownerOf(uint256 id) public view virtual returns (address owner) {
            require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
        }
        function balanceOf(address owner) public view virtual returns (uint256) {
            require(owner != address(0), "ZERO_ADDRESS");
            return _balanceOf[owner];
        }
        /*//////////////////////////////////////////////////////////////
                             ERC721 APPROVAL STORAGE
        //////////////////////////////////////////////////////////////*/
        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 || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
                "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 calldata 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 view 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 {
                _balanceOf[to]++;
            }
            _ownerOf[id] = to;
            emit Transfer(address(0), to, id);
        }
        function _burn(uint256 id) internal virtual {
            address owner = _ownerOf[id];
            require(owner != address(0), "NOT_MINTED");
            // Ownership check above ensures no underflow.
            unchecked {
                _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)
    abstract contract ERC721TokenReceiver {
        function onERC721Received(
            address,
            address,
            uint256,
            bytes calldata
        ) external virtual returns (bytes4) {
            return ERC721TokenReceiver.onERC721Received.selector;
        }
    }
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity >=0.8.4;
    import {ERC721} from "@solmate/tokens/ERC721.sol";
    import {Ownable} from "@openzeppelin/access/Ownable.sol";
    import {MerkleProof} from "@openzeppelin/utils/cryptography/MerkleProof.sol";
    import {Strings} from "@openzeppelin/utils/Strings.sol";
    interface IGoldenPass {
        function burn(address from, uint256 amount) external;
    }
    interface IFoodzPartyLegacy {
        function transferFrom(
            address from,
            address to,
            uint256 id
        ) external;
    }
    interface IOddworxStaking {
        function buyItem(
            uint256 itemSKU,
            uint256 amount,
            address nftContract,
            uint256[] calldata nftIds,
            address user
        ) external;
    }
    contract FoodzPartyV2 is ERC721, Ownable {
        using Strings for uint256;
        /// @dev 0xb36c1284
        error MaxSupply();
        /// @dev 0xa99edc71
        error MigrationOff();
        /// @dev 0xb9968551
        error PassSaleOff();
        /// @dev 0x3afc8ce9
        error SaleOff();
        /// @dev 0xb52aa4c0
        error QueryForNonExistentToken();
        /// @dev 0xe6c4247b
        error InvalidAddress();
        /// @dev 0x2c5211c6
        error InvalidAmount();
        /// @dev 0xab143c06
        error Reentrancy();
        // Immutable
        uint256 internal constant MIGRATION_START_INDEX = 0;
        uint256 internal constant MIGRATION_END_INDEX = 1160;
        uint256 internal constant MIGRATION_EXTRAS_START_INDEX = 1161;
        uint256 internal constant MIGRATION_EXTRAS_END_INDEX = 2321;
        uint256 internal constant REGULAR_START_INDEX = 2322;
        uint256 internal constant REGULAR_END_INDEX = 2975;
        uint256 internal constant GOLDEN_PASS_START_INDEX = 2976;
        uint256 internal constant GOLDEN_PASS_END_INDEX = 3475;
        uint256 internal constant HANDMADE_START_INDEX = 3476;
        uint256 internal constant HANDMADE_END_INDEX = 3499;
        /// @notice address of the oddx staking contract
        IOddworxStaking internal immutable staking;
        /// @notice address of the golden pass contract
        IGoldenPass internal immutable goldenPass;
        /// @notice address of the legacy foodz party contract
        IFoodzPartyLegacy internal immutable foodzLegacy;
        /// @notice address of the genzee contract
        address internal immutable genzee;
        // Mutable
        /// @notice amount of regular mints
        /// @dev starts at 1 cuz constructor mints #0
        uint256 public migrationSupply = 1;
        uint256 public migrationExtrasSupply;
        uint256 public regularSupply;
        uint256 public goldenPassSupply;
        uint256 public handmadeSupply;
        string public baseURI;
        uint256 public mintPriceOddx = 200 ether;
        /// @notice if users can migrate their tokens from the legacy contract
        /// @dev 1 = not active; 2 = active;
        uint256 private _isMigrationActive = 1;
        /// @notice if users can redeem their golden passes
        /// @dev 1 = not active; 2 = active;
        uint256 private _isPassSaleActive = 1;
        /// @notice if users can redeem their golden passes
        /// @dev 1 = not active; 2 = active;
        uint256 private _isSaleActive = 1;
        /// @dev reentrancy lock
        uint256 private _locked = 1;
        // Constructor
        constructor(
            IOddworxStaking staking_,
            IGoldenPass goldenPass_,
            IFoodzPartyLegacy foodzLegacy_,
            address genzee_,
            string memory baseURI_
        ) ERC721("Foodz Party", "FP") {
            staking = staking_;
            goldenPass = goldenPass_;
            foodzLegacy = foodzLegacy_;
            genzee = genzee_;
            baseURI = baseURI_;
            _safeMint(0x067423C244442ca0Eb6d6fd6B747c2BD21414107, 0);
        }
        // Owner Only
        function setBaseURI(string calldata newBaseURI) external onlyOwner {
            baseURI = newBaseURI;
        }
        function setIsPassSaleActive(bool newIsPassSaleActive) external onlyOwner {
            _isPassSaleActive = newIsPassSaleActive ? 2 : 1;
        }
        function setIsSaleActive(bool newIsSaleActive) external onlyOwner {
            _isSaleActive = newIsSaleActive ? 2 : 1;
        }
        function setIsMigrationActive(bool newIsMigrationActive)
            external
            onlyOwner
        {
            _isMigrationActive = newIsMigrationActive ? 2 : 1;
        }
        function setMintPriceOddx(uint256 newMintPriceOddx) external onlyOwner {
            mintPriceOddx = newMintPriceOddx;
        }
        function handmadeMint(address to) external onlyOwner {
            unchecked {
                uint256 tokenId = HANDMADE_START_INDEX + handmadeSupply;
                if (tokenId > HANDMADE_END_INDEX) revert MaxSupply();
                // slither-disable-next-line events-maths
                ++handmadeSupply;
                _safeMint(to, tokenId);
            }
        }
        // User
        /// @notice Migrate a token from legacy Foodz contract to this contract.
        ///         It "burns" the token on the other contract so it requires the tokens to be approved first.
        function migrate(uint256[] calldata ids) external {
            if (_isMigrationActive != 2) revert MigrationOff();
            if (msg.sender == address(0)) revert InvalidAddress();
            if (_locked == 2) revert Reentrancy();
            _locked = 2;
            uint256 length = ids.length;
            uint256 i = 0;
            unchecked {
                migrationSupply += length;
            }
            for (i = 0; i < length; ) {
                foodzLegacy.transferFrom(
                    msg.sender,
                    address(0x000000000000000000000000000000000000dEaD),
                    ids[i]
                );
                unchecked {
                    ++i;
                }
            }
            unchecked {
                uint256 extraMingStartIndex = MIGRATION_EXTRAS_START_INDEX +
                    migrationExtrasSupply;
                migrationExtrasSupply += length;
                for (i = 0; i < length; i++) {
                    _safeMint(msg.sender, ids[i]);
                    _safeMint(msg.sender, extraMingStartIndex + i);
                }
            }
            _locked = 1;
        }
        function mint(uint256 amount, uint256[] calldata nftIds) external {
            if (amount == 0) revert InvalidAmount();
            if (_isSaleActive != 2) revert SaleOff();
            uint256 startIndex;
            unchecked {
                startIndex = REGULAR_START_INDEX + regularSupply;
                if (startIndex + amount - 1 > REGULAR_END_INDEX) revert MaxSupply();
            }
            staking.buyItem(
                0x0105,
                amount * mintPriceOddx,
                genzee,
                nftIds,
                msg.sender
            );
            unchecked {
                // slither-disable-next-line events-maths
                regularSupply += amount;
                for (uint256 i = 0; i < amount; i++) {
                    _safeMint(msg.sender, startIndex + i);
                }
            }
        }
        function passmint(uint256 amount) external {
            if (_isPassSaleActive != 2) revert PassSaleOff();
            uint256 startIndex;
            unchecked {
                startIndex = GOLDEN_PASS_START_INDEX + goldenPassSupply;
                if (startIndex + amount - 1 > GOLDEN_PASS_END_INDEX)
                    revert MaxSupply();
            }
            goldenPass.burn(msg.sender, amount);
            unchecked {
                // slither-disable-next-line events-maths
                goldenPassSupply += amount;
                for (uint256 i = 0; i < amount; i++) {
                    _safeMint(msg.sender, startIndex + i);
                }
            }
        }
        // View
        function currentSupply() external view returns (uint256) {
            unchecked {
                return
                    migrationSupply +
                    migrationExtrasSupply +
                    regularSupply +
                    goldenPassSupply +
                    handmadeSupply;
            }
        }
        function isMigrationActive() external view returns (bool) {
            return _isMigrationActive == 2 ? true : false;
        }
        function isPassSaleActive() external view returns (bool) {
            return _isPassSaleActive == 2 ? true : false;
        }
        function isSaleActive() external view returns (bool) {
            return _isSaleActive == 2 ? true : false;
        }
        // Overrides
        function tokenURI(uint256 id) public view override returns (string memory) {
            if (_ownerOf[id] == address(0)) revert QueryForNonExistentToken();
            return string(abi.encodePacked(baseURI, id.toString()));
        }
    }