/* Crafted with love by Fueled on Bacon https://fueledonbacon.com */ //SPDX-License-Identifier: MIT pragma solidity ^0.8.15; import '@openzeppelin/contracts/access/AccessControl.sol'; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; import '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; import './interfaces/IVenueRegistar.sol'; import './interfaces/IVenueSBT.sol'; import './ERC721A.sol'; import './EventReviewPaymaster.sol'; contract VenueSBT is IVenueSBT, ERC721A, IERC721Receiver, Ownable, AccessControl { using EnumerableSet for EnumerableSet.AddressSet; bytes32 public constant MINTER_ROLE = keccak256('MINTER_ROLE'); string private _baseUri; address internal _venueRegistar; address private _royaltyReceiver; address private _royaltyReceiverSecondarySales; uint96 private _royaltyFeeNumerator; //from 0 to 10000 where 10000 is 100% uint96 private _royaltyFeeNumeratorSecondarySales; //from 0 to 10000 where 10000 is 100% mapping(uint256 => address) public eventByToken; mapping(address => uint256) public tokenByEvent; mapping(address => uint256) public typeByEvent; EnumerableSet.AddressSet private _events; constructor( string memory name, string memory symbol, string memory baseUri, address registar, address owner, address royaltyReceiver, uint96 royaltyFeeNumerator, uint96 royaltyFeeNumeratorSecondarySales ) ERC721A(name, symbol) { _baseUri = baseUri; _royaltyReceiver = royaltyReceiver; _royaltyFeeNumerator = royaltyFeeNumerator; _royaltyFeeNumeratorSecondarySales = royaltyFeeNumeratorSecondarySales; _venueRegistar = registar; _setupRole(DEFAULT_ADMIN_ROLE, owner); _setupRole(MINTER_ROLE, owner); _transferOwnership(owner); } /// @inheritdoc IVenueSBT function versionVenueSBT() external pure virtual override returns (string memory) { return '1.0.0-beta.0+fob.rsv.iVenueSBT'; } /// @inheritdoc IVenueSBT function setBaseURI(string memory baseUri) external virtual override onlyOwner { _baseUri = baseUri; } /// @inheritdoc IVenueSBT function setRoyaltyReceiverSecondarySales(address royaltyReceiverSecondarySales) public virtual override { if (_royaltyReceiverSecondarySales != address(0)) revert VenueSecondarySalesAlreadySet(); _royaltyReceiverSecondarySales = royaltyReceiverSecondarySales; } /// @inheritdoc IVenueSBT function setRoyaltyInfo(address royaltyReceiver, uint96 royaltyFeeNumerator) external virtual override { if (_msgSender() != Ownable(_venueRegistar).owner()) revert VenueSenderIsNotRegistarOwner(); if (royaltyReceiver == address(0)) revert VenueInvalidRoyaltyReceiver(); if (royaltyFeeNumerator > 10000) revert VenueInvalidRoyaltyFeeNumerator(); _royaltyReceiver = royaltyReceiver; _royaltyFeeNumerator = royaltyFeeNumerator; } /// @inheritdoc IVenueSBT function setRoyaltyInfoSecondarySales(address royaltyReceiver, uint96 royaltyFeeNumerator) external virtual override { if (_msgSender() != Ownable(_venueRegistar).owner()) revert VenueSenderIsNotRegistarOwner(); if (royaltyReceiver == address(0)) revert VenueInvalidRoyaltyReceiver(); if (royaltyFeeNumerator > 10000) revert VenueInvalidRoyaltyFeeNumerator(); _royaltyReceiverSecondarySales = royaltyReceiver; _royaltyFeeNumeratorSecondarySales = royaltyFeeNumerator; } /// @inheritdoc ERC721A function _baseURI() internal view override returns (string memory) { return _baseUri; } /// @inheritdoc IVenueSBT /// @dev if theres a tokenReviewPaymaster contract present, we check if the venue has enough funds function mint( uint256 typeOf, string memory name, string memory symbol, string memory baseUri, address paymentToken, bytes memory extraData ) external virtual payable override onlyRole(MINTER_ROLE) { IVenueRegistar registar = IVenueRegistar(_venueRegistar); (,address paymaster, uint256 minBalance) = registar.getTokenReviewParams(); if(paymaster != address(0)) { EventReviewPaymaster eventReviewPaymaster = EventReviewPaymaster(payable(paymaster)); uint256 paymasterBalance = eventReviewPaymaster.getBalance(); if(paymasterBalance + msg.value < minBalance) revert VenuePaymasterBalanceTooLow(); eventReviewPaymaster.fundVenue{value: msg.value}(address(this)); } if (!registar.isPaymentTokenWhitelisted(paymentToken)) revert VenueWrongPaymentToken(); address forwarder = registar.getForwarder(); bytes memory mainData = abi.encode( baseUri, _venueRegistar, paymentToken, owner(), _royaltyReceiver, _royaltyReceiverSecondarySales, _royaltyFeeNumerator, _royaltyFeeNumeratorSecondarySales, extraData ); (bool success, bytes memory data) = forwarder.call( abi.encodeWithSignature('deploy(uint256,string,string,bytes)', typeOf, name, symbol, mainData) ); if (!success) revert VenueFailedDeployingEvent(); address _event = abi.decode(data, (address)); uint256 nextTokenId = _currentIndex; _safeMint(address(this), 1); eventByToken[nextTokenId] = _event; tokenByEvent[_event] = nextTokenId; typeByEvent[_event] = typeOf; _events.add(_event); emit NewEvent(_event, nextTokenId, block.number); } /// @inheritdoc IVenueSBT function burn(uint256 tokenId) external virtual override onlyRole(MINTER_ROLE) { if (!_exists(tokenId)) revert VenueUnexistentToken(); address _event = eventByToken[tokenId]; if (IERC721Enumerable(_event).totalSupply() > 0) revert VenueUnableToBurn(); uint256 eventType = typeByEvent[_event]; address forwarder = IVenueRegistar(_venueRegistar).getForwarder(); (bool success, ) = forwarder.call(abi.encodeWithSignature('removeEvent(address,uint256)', _event, eventType)); if (!success) revert VenueFailedForwardingCall(); delete eventByToken[tokenId]; delete tokenByEvent[_event]; delete typeByEvent[_event]; _events.remove(_event); _burn(tokenId); emit BurnEvent(_event, tokenId); } /// @inheritdoc IVenueSBT function withdrawFromPaymaster(uint256 amount) external virtual override onlyOwner { IVenueRegistar registar = IVenueRegistar(_venueRegistar); (,address paymaster, ) = registar.getTokenReviewParams(); if(paymaster != address(0)) { EventReviewPaymaster eventReviewPaymaster = EventReviewPaymaster(payable(paymaster)); if(eventReviewPaymaster.getBalance() < amount) revert VenuePaymasterNotEnoughFunds(); eventReviewPaymaster.withdraw(payable(_msgSender()), amount); } } /// @inheritdoc IVenueSBT function withdrawToken(address token) external virtual override onlyOwner { IERC20 tokenContract = IERC20(token); uint256 balance = tokenContract.balanceOf(address(this)); tokenContract.transfer(_msgSender(), balance); } /// @inheritdoc IVenueSBT function withdrawETH() external virtual override onlyOwner { uint256 total = address(this).balance; (bool sent, ) = _msgSender().call{value: total}(''); if (!sent) revert VenueFailedToTransferETH(); } function getPaymasterBalance(address paymaster) public view returns (uint256) { EventReviewPaymaster eventReviewPaymaster = EventReviewPaymaster(payable(paymaster)); return eventReviewPaymaster.getBalance(); } // @inheritdoc IVenueSBT function isEvent(address _event) external view virtual override returns (bool) { return _events.contains(_event); } /// @inheritdoc IVenueSBT function getEvents() external view virtual override returns (address[] memory) { return _events.values(); } /// @inheritdoc IVenueSBT function getVenueRegistar() external view virtual override returns (address) { return _venueRegistar; } /// @inheritdoc ERC721A /// @notice non transferable function _beforeTokenTransfers( address from, address to, uint256, /*startTokenId*/ uint256 /*quantity*/ ) internal virtual override { if (from != address(0) && to != address(0)) revert VenueSBTNonTransferable(); } /// @inheritdoc IERC721Receiver function onERC721Received( address, /** operator */ address, /** from */ uint256, /** tokenId */ bytes calldata /** data */ ) external pure override returns (bytes4) { return bytes4(keccak256('onERC721Received(address,address,uint256,bytes)')); } function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721A, AccessControl) returns (bool) { return interfaceId == type(IVenueSBT).interfaceId || interfaceId == type(IERC721Receiver).interfaceId || super.supportsInterface(interfaceId); } receive() external payable {} }