// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.18; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "../Utilities/MutableERC2771ContextUpgradeable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../Interfaces/IRulesManager.sol"; import "../Interfaces/ICreditManager.sol"; import "../Interfaces/ISmartAssetBase.sol"; /** * @title EventHub. * @author Arianee - Dynamic NFTs for real-world use cases and consumer engagement (www.arianee.org). * @notice The EventHub contract is part of the Arianee protocol. Its permits to create events for specific tokens according to `RulesManager` conditions. * @dev See: https://docs.arianee.org */ contract EventHub is OwnableUpgradeable, MutableERC2771ContextUpgradeable, UUPSUpgradeable { /** * @notice Rules manager contract used for event authorization. * Rules are managed by globally by token contract owner and on a per-token basis by token owners. */ IRulesManager public rulesManager; /** * @notice Credit manager contract used for credit consumption. */ ICreditManager public creditManager; /** * @notice Event data. * @param tokenId ID of the token. * @param uri URI of the event. * @param imprint imprint of the event. * @param destroyLimitTimestamp * @param creator address of the creator provider. * @param walletProvider address of the wallet provider. * @param issuer address of the event issuer. */ struct Event { uint256 tokenId; string uri; bytes32 imprint; uint256 destroyLimitTimestamp; address issuer; bool accepted; uint256 batchIndex; } /** * @notice Mapping from token address to event ID <> event data. */ mapping(address => mapping(uint256 => Event)) public events; /** * @notice Mapping from token address to token ID <> event IDs. */ mapping(address => mapping(uint256 => uint256[])) public tokenIdToEvents; /** * @notice Mapping from token address to event ID <> event index. */ mapping(address => mapping(uint256 => uint256)) public eventIdToEventsIndex; /** * @notice This emits when an event is created. */ event EventCreated( address indexed tokenAddress, uint256 indexed tokenId, uint256 indexed eventId, bytes32 imprint, string uri, address issuer ); /** * @notice This emits when an event is accepted. */ event EventAccepted(address indexed tokenAddress, uint256 indexed tokenId, uint256 indexed eventId); /** * @notice This emits when an event is refused. */ event EventRefused(address indexed tokenAddress, uint256 indexed tokenId, uint256 indexed eventId); /** * @notice This emits when an event is destroyed. */ event EventDestroyed(address indexed tokenAddress, uint256 indexed tokenId, uint256 indexed eventId); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Initializer of the contract, used instead of constructor to let the proxy pattern works. * @param rulesManagerAddress_ address of the rules manager contract. * @param creditManagerAddress_ address of the credit manager contract. */ function initialize(address rulesManagerAddress_, address creditManagerAddress_, address trustedForwarder_) public initializer { __Ownable_init_unchained(); __MutableERC2771ContextUpgradeable_init_unchained(trustedForwarder_); __UUPSUpgradeable_init(); rulesManager = IRulesManager(rulesManagerAddress_); creditManager = ICreditManager(creditManagerAddress_); } /** * @notice Check if the `sender` is allowed to emit an event for the target token contract. * @dev See {IRulesManager-isAllowedEventEmitter}. */ modifier isAllowedEventEmitter(address tokenAddress, address sender) { require(rulesManager.isAllowedEventEmitter(tokenAddress, sender), "EventHub: Not allowed to emit event for this token"); _; } /** * @notice Check if the sender is the issuer of the target token. * @dev This modifier must be used ONLY if the target token is a SmartAsset. * @param tokenAddress address of the target token contract. * @param tokenId id of the target token. */ modifier onlyIssuer(address tokenAddress, uint256 tokenId) { require( ISmartAssetBase(tokenAddress).issuerOf(tokenId) == _msgSender(), "EventHub: Not the issuer of the token" ); _; } /** * @notice Check if `operator` is the owner of the token or is approved to transfer it. * @param tokenAddress address of the target token contract. * @param eventId id of the target event. * @param operator address to check. */ modifier canOperate( address tokenAddress, uint256 eventId, address operator ) { uint256 tokenId = events[tokenAddress][eventId].tokenId; require(_isApprovedOrOwner(tokenAddress, operator, tokenId), "EventHub: Not approved nor owner of the token"); _; } /** * @notice Check if `_msgSender()` is the owner of the token contract. * @param tokenAddress address of the target token contract. */ modifier onlyContractOwner(address tokenAddress) { require(Ownable(tokenAddress).owner() == _msgSender(), "EventHub: Not the owner of the token contract"); _; } /** * @notice Create a new event for a target token. * @param tokenAddress address of the target token contract. * @param eventId desired ID of the event. * @param tokenId ID of the target token. * @param imprint imprint of the event. * @param uri URI of the event. * @param creatorProvider address of the creator provider. */ function createEvent( address tokenAddress, uint256 eventId, uint256 tokenId, bytes32 imprint, string memory uri, address creatorProvider ) external isAllowedEventEmitter(tokenAddress, _msgSender()) { _createEvent(tokenAddress, eventId, tokenId, imprint, uri, creatorProvider); } /** * @notice Create a new event for a target token and accept it. * WARNING: This function can be used ONLY if the target token is a SmartAsset and called by the token issuer. * If the target token is a "regular" ERC721, use `createEvent` and `acceptEvent` instead. * @param tokenAddress address of the target token contract. * @param eventId desired ID of the event. * @param tokenId ID of the target token. * @param imprint imprint of the event. * @param uri URI of the event. * @param creatorProvider address of the creator provider. */ function createAndAcceptEvent( address tokenAddress, uint256 eventId, uint256 tokenId, bytes32 imprint, string memory uri, address creatorProvider ) external onlyIssuer(tokenAddress, tokenId) isAllowedEventEmitter(tokenAddress, _msgSender()) { _createEvent(tokenAddress, eventId, tokenId, imprint, uri, creatorProvider); _acceptEvent(tokenAddress, eventId, creatorProvider); } /** * @notice Accept an event. * @param tokenAddress address of the target token contract. * @param eventId ID of the target event. * @param walletProvider address of the wallet provider. */ function acceptEvent( address tokenAddress, uint256 eventId, address walletProvider ) external canOperate(tokenAddress, eventId, _msgSender()) { _acceptEvent(tokenAddress, eventId, walletProvider); } /** * @notice Refuse an event. * @param tokenAddress address of the target token contract. * @param eventId ID of the target event. */ function refuseEvent( address tokenAddress, uint256 eventId ) external canOperate(tokenAddress, eventId, _msgSender()) { require(events[tokenAddress][eventId].accepted == false, "EventHub: This event was already accepted"); uint256 tokenId = events[tokenAddress][eventId].tokenId; _destroyEvent(tokenAddress, eventId); emit EventRefused(tokenAddress, eventId, tokenId); } /** * @notice Destroy an event. * WARNING: This function can be used ONLY by the issuer of the event BEFORE the `destroyLimitTimestamp`. * @param tokenAddress address of the target token contract. * @param eventId ID of the target event. */ function destroyEvent( address tokenAddress, uint256 eventId ) external { require(events[tokenAddress][eventId].issuer == _msgSender(), "EventHub: Not the issuer of the event"); require(block.timestamp < events[tokenAddress][eventId].destroyLimitTimestamp, "EventHub: Destroy window expired"); require(events[tokenAddress][eventId].accepted == false, "EventHub: This event was already accepted"); uint256 tokenId = events[tokenAddress][eventId].tokenId; _destroyEvent(tokenAddress, eventId); emit EventDestroyed(tokenAddress, eventId, tokenId); } /** * @notice Try to retrieve an Event from `tokenAddress` and `eventId`. * @param tokenAddress address of the target token contract. * @param eventId ID of the target event. * WARNING: This was renamed from `getEvent` to `getEventData` to avoid conflicts with TypeChain code generation. */ function getEventData(address tokenAddress, uint256 eventId) external view returns (Event memory) { return events[tokenAddress][eventId]; } /** * @dev Create a new event for a target token. */ function _createEvent( address tokenAddress, uint256 eventId, uint256 tokenId, bytes32 imprint, string memory uri, address creatorProvider ) internal { require(events[tokenAddress][eventId].imprint == "", "EventHub: This eventId already exist"); uint256 _batchIndex = creditManager.consumeCredit(_msgSender(), creatorProvider); events[tokenAddress][eventId] = Event({ tokenId: tokenId, uri: uri, imprint: imprint, destroyLimitTimestamp: block.timestamp + 31536000, issuer: _msgSender(), accepted: false, batchIndex: _batchIndex }); tokenIdToEvents[tokenAddress][tokenId].push(eventId); eventIdToEventsIndex[tokenAddress][eventId] = tokenIdToEvents[tokenAddress][tokenId].length - 1; emit EventCreated(tokenAddress, tokenId, eventId, imprint, uri, _msgSender()); } /** * @dev Accept an event. */ function _acceptEvent(address tokenAddress, uint256 eventId, address walletProvider) internal { require(events[tokenAddress][eventId].accepted == false, "EventHub: This event was already accepted"); uint256 tokenId = events[tokenAddress][eventId].tokenId; address nftOwner = IERC721(tokenAddress).ownerOf(tokenId); events[tokenAddress][eventId].accepted = true; creditManager.rewardClaim( events[tokenAddress][eventId].batchIndex, walletProvider, nftOwner ); emit EventAccepted(tokenAddress, eventId, tokenId); } /** * @notice Destroy an event. */ function _destroyEvent( address tokenAddress, uint256 eventId ) internal { uint256 tokenId = events[tokenAddress][eventId].tokenId; uint256 eventIndex = eventIdToEventsIndex[tokenAddress][eventId]; uint256 lastEventIndex = tokenIdToEvents[tokenAddress][tokenId].length - 1; if (lastEventIndex != eventIndex) { uint256 lastEvent = tokenIdToEvents[tokenAddress][tokenId][lastEventIndex]; tokenIdToEvents[tokenAddress][tokenId][eventIndex] = lastEvent; eventIdToEventsIndex[tokenAddress][lastEvent] = eventIndex; } tokenIdToEvents[tokenAddress][tokenId].pop(); delete eventIdToEventsIndex[tokenAddress][eventId]; delete events[tokenAddress][eventId]; } /** * @dev See {UUPSUpgradeable-_authorizeUpgrade}. */ function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} /** * @dev Check if `operator` is the owner or an approved operator of `tokenId`. */ function _isApprovedOrOwner(address tokenAddress, address operator, uint256 tokenId) internal view returns (bool) { IERC721 token = IERC721(tokenAddress); address tokenOwner = token.ownerOf(tokenId); return tokenOwner == operator || token.getApproved(tokenId) == operator || token.isApprovedForAll(tokenOwner, operator); } /** * @dev See {MutableERC2771ContextUpgradeable-_msgSender}. */ function _msgSender() internal view override(MutableERC2771ContextUpgradeable, ContextUpgradeable) returns (address sender) { return MutableERC2771ContextUpgradeable._msgSender(); } /** * @dev See {MutableERC2771ContextUpgradeable-_msgData}. */ function _msgData() internal view override(MutableERC2771ContextUpgradeable, ContextUpgradeable) returns (bytes calldata ret) { return MutableERC2771ContextUpgradeable._msgData(); } }