/* Crafted with love by Fueled on Bacon https://fueledonbacon.com */ //SPDX-License-Identifier: MIT pragma solidity ^0.8.15; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/access/AccessControl.sol'; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import '../ERC721A.sol'; import '../interfaces/IERC721EW.sol'; import '../ERC721AMarketplace.sol'; import "../VenueRegistar.sol"; // @description this contract will use whitelist option for each ticket type, where a whitelisted user will get the ticket for free contract ERC721EW is IERC721EW, ERC721AMarketplace, Ownable, AccessControl { bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE'); bytes32 public constant CREATOR_ROLE = keccak256('CREATOR_ROLE'); address public venue; string internal _baseUri; uint256 public entriesCount; //starts from 1 uint256 public eventDate; //epoch mapping(uint256 => EntryType) internal _entryByPos; //in the form of userId => (entryPos => amountOfTickets) mapping(uint256 => mapping(uint256 => uint256)) internal _userTicketsBought; VenueRegistar internal _venueRegistar; constructor( string memory name, string memory symbol, address venue_, bytes memory data ) ERC721A(name, symbol) { venue = venue_; ( string memory baseUri, address venueRegistar, address paymentToken_, address accessControl, address royaltyReceiver, address secondarySalesRoyaltyReceiver, uint96 royaltyFeeNumerator, uint96 secondarySalesRoyaltyFeeDenominator, bytes memory extraData ) = abi.decode(data, (string, address, address, address, address, address, uint96, uint96, bytes)); _baseUri = baseUri; paymentToken = paymentToken_; _setupRole(DEFAULT_ADMIN_ROLE, accessControl); _setupRole(MINTER_ROLE, accessControl); _setupRole(CREATOR_ROLE, accessControl); _venueRegistar = VenueRegistar(payable(venueRegistar)); transferOwnership(_venueRegistar.getOwner()); _secondarySalesRoyaltyReceiver = secondarySalesRoyaltyReceiver; _secondarySalesRoyaltyFeeDenominator = secondarySalesRoyaltyFeeDenominator; _setDefaultRoyalty(royaltyReceiver, royaltyFeeNumerator); eventDate = abi.decode(extraData, (uint256)); if (eventDate <= block.timestamp) revert ERC721WrongEventDate(); } /// @inheritdoc IERC721AMarketplace function versionERC721() external pure virtual override returns (string memory) { return '1.0.0-beta.0+fob.rsv.iERC721EW'; } /// @inheritdoc IERC721Wide function setBaseURI(string memory baseUri) external virtual override onlyRole(DEFAULT_ADMIN_ROLE) { _baseUri = baseUri; } /// @dev override base uri. It will be combined with token ID /// @inheritdoc ERC721A function _baseURI() internal view override returns (string memory) { return _baseUri; } /// @inheritdoc IERC721Wide function setEventDate(uint256 _eventDate) external virtual override onlyRole(DEFAULT_ADMIN_ROLE) { if (_eventDate <= block.timestamp) revert ERC721WrongEventDate(); eventDate = _eventDate; } /// @inheritdoc IERC721Wide function editEntryName(uint256 entryPos, string memory name) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); EntryType storage entry = _entryByPos[entryPos]; entry.name = name; } /// @inheritdoc IERC721Wide function editEntryPrice(uint256 entryPos, uint256 price) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); // TODO: not for production // if (price == 0) revert ERC721EWrongPrice(); EntryType storage entry = _entryByPos[entryPos]; entry.price = price; } /// @inheritdoc IERC721Wide function editEntryMaxSupply(uint256 entryPos, uint256 maxSupply) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); EntryType storage entry = _entryByPos[entryPos]; if (maxSupply < entry.sold) revert ERC721WrongMaxSupply(); entry.maxSupply = maxSupply; } /// @inheritdoc IERC721Wide function editEntryMaxBuy(uint256 entryPos, uint256 maxBuy) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); EntryType storage entry = _entryByPos[entryPos]; entry.maxBuy = maxBuy; } /// @inheritdoc IERC721Wide function editEntrySaleEnd(uint256 entryPos, uint256 saleEnd) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); if (saleEnd < block.timestamp) revert ERC721WrongSaleEnd(); EntryType storage entry = _entryByPos[entryPos]; entry.saleEnd = saleEnd; } /// @inheritdoc IERC721EW function editMerkleRoot(uint256 entryPos, bytes32 newMerkleRoot) external virtual override onlyRole(CREATOR_ROLE) { if (entryPos > entriesCount) revert ERC721WrongEntryPos(); EntryType storage entry = _entryByPos[entryPos]; entry.merkleRoot = newMerkleRoot; } /// @inheritdoc IERC721EW function buyTickets( address receiver, uint256 userId, uint256 entryPos, uint256 amount, uint256[] memory sellingPrices, bytes32[] calldata merkleProof ) external virtual override { if (amount != sellingPrices.length) revert ERC721WrongBatchLengths(); EntryType memory entryType = _entryByPos[entryPos]; if (entryType.saleEnd < block.timestamp) revert ERC721SaleEnded(); uint256 soldTickets = entryType.sold; uint256 maxSupply = entryType.maxSupply; if (soldTickets + amount > maxSupply) revert ERC721ReachedMaxSupply(); uint256 maxBuy = entryType.maxBuy; if (_userTicketsBought[userId][entryPos] + amount > maxBuy) revert ERC721TooManyTickets(); uint256 price = entryType.price; entryType.sold += amount; _entryByPos[entryPos] = entryType; _saveTickets(_currentIndex, amount, entryPos, sellingPrices); _userTicketsBought[userId][entryPos] += amount; bytes32 merkleRoot = entryType.merkleRoot; address tokenCreator = _venueRegistar.tokenCreator(); //For merkle verification if(merkleRoot != "") { address wallet = _msgSender(); if(_msgSender() == tokenCreator) wallet = receiver; if(!_verifyWhitelist(merkleRoot, merkleProof, wallet)) revert ERC721EWUnableToVerifyMerkleProof(); } if (price > 0 && _msgSender() != tokenCreator) { uint256 totalPrice = amount * price; (address receiverRoyalty, uint256 amountRoyalty) = _getDefaultRoyaltyBatch(totalPrice); IERC20(paymentToken).transferFrom(_msgSender(), venue, totalPrice - amountRoyalty); IERC20(paymentToken).transferFrom(_msgSender(), receiverRoyalty, amountRoyalty); } _safeMint(receiver, amount); emit MintTickets(receiver, userId, entryPos, amount); } /// @inheritdoc IERC721EW function createEntry( string memory name, uint256 price, uint256 maxSupply, uint256 maxBuy, uint256 saleEnd, bytes32 merkleRoot ) external virtual override onlyRole(CREATOR_ROLE) { if (saleEnd < block.timestamp) revert ERC721WrongSaleEnd(); if (maxSupply == 0) revert ERC721WrongMaxSupply(); entriesCount += 1; uint256 pos = entriesCount; _entryByPos[pos] = EntryType(name, price, maxSupply, maxBuy, 0, saleEnd, merkleRoot); emit NewEntry(pos, name, price, maxSupply, saleEnd); } /// @inheritdoc IERC721EW function getTicketInfo(uint256 tokenId) external view virtual override returns (EntryType memory) { if (!_exists(tokenId)) revert ERC721NonExistentToken(); uint256 entryPos = _ticketByEntryPos[tokenId]; return _entryByPos[entryPos]; } /// @inheritdoc IERC721EW function getEntry(uint256 pos) external view virtual override returns (EntryType memory) { return _entryByPos[pos]; } function _verifyWhitelist(bytes32 merkleRoot, bytes32[] calldata merkleProof, address addr) private pure returns(bool) { return (MerkleProof.verify(merkleProof, merkleRoot, keccak256(abi.encodePacked(addr))) == true); } function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721AMarketplace, AccessControl) returns (bool) { return interfaceId == type(IERC721EW).interfaceId || interfaceId == type(IERC721Wide).interfaceId || interfaceId == type(IERC721Receiver).interfaceId || super.supportsInterface(interfaceId); } }