Transaction Hash:
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 | ||
---|---|---|---|---|---|
0x34e1d1d1...383660a49 |
0.270857738660966884 Eth
Nonce: 15
|
0.270352671800185702 Eth
Nonce: 16
| 0.000505066860781182 | ||
0xBf5d2760...fc561f089
Miner
| 0.051053845333179647 Eth | 0.051144103333179647 Eth | 0.000090258 |
Execution Trace
OddworxStaking.stakeNfts( nftContract=0xa0fa51F54dbab68C068a2E2c62AA72Fe334D9b09, nftIds=[650] )
-
FoodzPartyV2.transferFrom( from=0x34e1d1d11e73f8fde08bf167c16dE52383660a49, to=0x428b6a13277116C62D751bebbC6f47011A0Cdc11, id=650 )
stakeNfts[OddworxStaking (ln:121)]
InvalidInput[OddworxStaking (ln:122)]
_executeRewards[OddworxStaking (ln:129)]
_rewardsForTimestamp[OddworxStaking (ln:165)]
UserClaimedRewards[OddworxStaking (ln:170)]
_executeRewards[OddworxStaking (ln:131)]
_rewardsForTimestamp[OddworxStaking (ln:165)]
UserClaimedRewards[OddworxStaking (ln:170)]
confirmLegacyStaking[OddworxStaking (ln:132)]
ownerOf[OddworxStaking (ln:231)]
nftDataStruct[OddworxStaking (ln:134)]
_transferNft[OddworxStaking (ln:135)]
transferFrom[OddworxStaking (ln:225)]
StakedNft[OddworxStaking (ln:136)]
File 1 of 2: OddworxStaking
File 2 of 2: FoodzPartyV2
// 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())); } }