// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.18; import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "../Utilities/MutableERC2771ContextUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "../Interfaces/ISmartAssetBase.sol"; /** * @title RulesManager. * @author Arianee - Dynamic NFTs for real-world use cases and consumer engagement (www.arianee.org). * @notice The RulesManager contract is part of the Arianee protocol. Its helps token contract owners and token owners to manage communication rules of their tokens. * @dev See: https://docs.arianee.org */ contract RulesManager is MutableERC2771ContextUpgradeable, AccessControlUpgradeable, UUPSUpgradeable { /** * @notice MSG_PER_TOKEN_WHITELIST_MANAGER: those who can add or remove addresses from the per token message allowlist */ bytes32 public constant MSG_PER_TOKEN_WHITELIST_MANAGER = keccak256("MSG_PER_TOKEN_WHITELIST_MANAGER"); /** * @notice Communication rules for a specific token contract. * @param msgEnabled Whether or not the token contract allows messages. * @param msgPerTokenAllowlist The per token ID and token owner message allowlist. * @param msgPerTokenBlacklist The per token ID and token owner message blacklist. * @param eventEnabled Whether or not the token contract allows events. * @param eventAllowlistActive Active if at least one address is in the event global allowlist, inactive otherwise * (meaning that every address is allowed to emit events for this token contract). */ struct TokenRules { bool msgEnabled; mapping(uint256 => mapping(address => mapping(address => bool))) msgPerTokenAllowlist; mapping(uint256 => mapping(address => mapping(address => bool))) msgPerTokenBlacklist; bool eventEnabled; bool eventAllowlistActive; } /** * @notice Mapping from token contract address to token communication rules. */ mapping(address => TokenRules) public tokenToTokenRules; /** * @notice Mapping from token contract address to globally allowed addresses to send messages. */ mapping(address => mapping(address => bool)) public msgGlobalAllowlist; /** * @notice Mapping from token contract address to globally allowed addresses to emit events. */ mapping(address => mapping(address => bool)) public eventGlobalAllowlist; /** * @notice The length of the global allowlist for events. */ uint256 eventGlobalAllowlistLength; /** * @notice This emits when the communication features of a token contract are updated. * @param tokenAddress The address of the token contract. * @param msgEnabled Whether or not the token contract allows messages. * @param eventEnabled Whether or not the token contract allows events. */ event TokenFeaturesUpdated(address indexed tokenAddress, bool msgEnabled, bool eventEnabled); /** * @notice This emits when the global allowlist for events is updated. * @param tokenAddress The address of the token contract. * @param sender The address of the caller. * @param addresses The addresses that are added or removed from the global allowlist. * @param allowed A flag indicating whether the addresses are added or removed from the global allowlist. */ event TokenEventGlobalAllowlistUpdated(address indexed tokenAddress, address sender, address[] addresses, bool allowed); /** * @notice This emits when the global allowlist for messages is updated. * @param tokenAddress The address of the token contract. * @param sender The address of the caller. * @param addresses The addresses that are added or removed from the global allowlist. * @param allowed A flag indicating whether the addresses are added or removed from the global allowlist. */ event TokenMsgGlobalAllowlistUpdated(address indexed tokenAddress, address sender, address[] addresses, bool allowed); /** * @notice This emits when the per token allowlist for messages is updated. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param sender The address of the caller. * @param owner The address of the token owner (at the time of the update). * @param addresses The addresses that are added or removed from the per token allowlist. * @param allowed A flag indicating whether the addresses are added or removed from the per token allowlist. */ event TokenMsgPerTokenAllowlistUpdated( address indexed tokenAddress, uint256 indexed tokenId, address sender, address indexed owner, address[] addresses, bool allowed ); /** * This emits when the per token blacklist for messages is updated. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param sender The address of the caller. * @param owner The address of the token owner (at the time of the update). * @param addresses The addresses that are added or removed from the per token blacklist. * @param allowed A flag indicating whether the addresses are added or removed from the per token blacklist. */ event TokenMsgPerTokenBlacklistUpdated( address indexed tokenAddress, uint256 indexed tokenId, address sender, address indexed owner, address[] addresses, bool allowed ); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @notice Initializer instead of constructor, called via UpgradableProxy. * See: https://docs.openzeppelin.com/contracts/4.x/api/proxy. */ function initialize(address trustedForwarder_) public initializer { __MutableERC2771ContextUpgradeable_init_unchained(trustedForwarder_); __AccessControl_init_unchained(); __UUPSUpgradeable_init(); _setupRole(DEFAULT_ADMIN_ROLE, _msgSender()); } /** * @notice Check if the caller is the owner of the target token contract. * @param tokenAddress The address of the token contract. */ modifier onlyTokenContractOwner(address tokenAddress) { require(_isTokenContractOwner(tokenAddress, _msgSender()), "RulesManager: The caller is not the owner of the target token contract"); _; } /** * @notice Check if the caller is the owner of the target token. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. */ modifier onlyTokenOwner(address tokenAddress, uint256 tokenId) { require(_isTokenOwner(tokenAddress, tokenId, _msgSender()), "RulesManager: The caller is not the owner of the target token"); _; } /** * @notice Check if the caller is the owner of the target token or has a specific role. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param role The role to check. */ modifier onlyTokenOwnerOrTokenContractOrRole( address tokenAddress, uint256 tokenId, bytes32 role ) { require( _isTokenOwner(tokenAddress, tokenId, _msgSender()) || _msgSender() == tokenAddress || hasRole(role, _msgSender()), "RulesManager: The caller is not the owner of the target token nor is the token contract nor has a sufficient role" ); _; } /** * @notice Enable or disable the communication features of a token contract. * @param tokenAddress The address of the token contract. * @param msgEnabled Whether or not the token contract allows messages. * @param eventEnabled Whether or not the token contract allows events. */ function toggleFeatures(address tokenAddress, bool msgEnabled, bool eventEnabled) public onlyTokenContractOwner(tokenAddress) { tokenToTokenRules[tokenAddress].msgEnabled = msgEnabled; tokenToTokenRules[tokenAddress].eventEnabled = eventEnabled; emit TokenFeaturesUpdated(tokenAddress, msgEnabled, eventEnabled); } /** * @notice Add an array of addresses to the global message allowlist of the target token contract. * NOTE: This function can only be called by the owner of the target token contract. * @param tokenAddress The address of the token contract. * @param addresses The addresses to add. */ function addMsgGlobalAllowlist(address tokenAddress, address[] memory addresses) public onlyTokenContractOwner(tokenAddress) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); for (uint256 i = 0; i < addresses.length; i++) { msgGlobalAllowlist[tokenAddress][addresses[i]] = true; } emit TokenMsgGlobalAllowlistUpdated(tokenAddress, _msgSender(), addresses, true); } /** * @notice Remove an array of addresses from the global message allowlist of the target token contract. * NOTE: This function can only be called by the owner of the target token contract. * @param tokenAddress The address of the token contract. * @param addresses The addresses to remove. */ function removeMsgGlobalAllowlist(address tokenAddress, address[] memory addresses) public onlyTokenContractOwner(tokenAddress) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); for (uint256 i = 0; i < addresses.length; i++) { msgGlobalAllowlist[tokenAddress][addresses[i]] = false; } emit TokenMsgGlobalAllowlistUpdated(tokenAddress, _msgSender(), addresses, false); } /** * @notice Add an array of addresses to the per token ID and owner message allowlist of the target token contract. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param addresses The addresses to add. */ function addMsgPerTokenAllowlist( address tokenAddress, uint256 tokenId, address[] memory addresses ) public onlyTokenOwnerOrTokenContractOrRole(tokenAddress, tokenId, MSG_PER_TOKEN_WHITELIST_MANAGER) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); address tokenOwner = _tokenOwner(tokenAddress, tokenId); for (uint256 i = 0; i < addresses.length; i++) { tokenToTokenRules[tokenAddress].msgPerTokenAllowlist[tokenId][tokenOwner][addresses[i]] = true; } emit TokenMsgPerTokenAllowlistUpdated(tokenAddress, tokenId, _msgSender(), tokenOwner, addresses, true); } /** * @notice Remove an array of addresses from the per token ID and owner message allowlist of the target token contract. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param addresses The addresses to remove. */ function removeMsgPerTokenAllowlist( address tokenAddress, uint256 tokenId, address[] memory addresses ) public onlyTokenOwnerOrTokenContractOrRole(tokenAddress, tokenId, MSG_PER_TOKEN_WHITELIST_MANAGER) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); address tokenOwner = _tokenOwner(tokenAddress, tokenId); for (uint256 i = 0; i < addresses.length; i++) { tokenToTokenRules[tokenAddress].msgPerTokenAllowlist[tokenId][tokenOwner][addresses[i]] = false; } emit TokenMsgPerTokenAllowlistUpdated(tokenAddress, tokenId, _msgSender(), tokenOwner, addresses, false); } /** * @notice Add an array of addresses from the per token ID and owner message blacklist of the target token contract. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param addresses The addresses to add. */ function addMsgPerTokenBlacklist(address tokenAddress, uint256 tokenId, address[] memory addresses) public onlyTokenOwner(tokenAddress, tokenId) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); address tokenOwner = _tokenOwner(tokenAddress, tokenId); for (uint256 i = 0; i < addresses.length; i++) { tokenToTokenRules[tokenAddress].msgPerTokenBlacklist[tokenId][tokenOwner][addresses[i]] = true; } emit TokenMsgPerTokenBlacklistUpdated(tokenAddress, tokenId, _msgSender(), tokenOwner, addresses, true); } /** * @notice Remove an array of addresses from the per token ID and owner message blacklist of the target token contract. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param addresses The addresses to remove. */ function removeMsgPerTokenBlacklist(address tokenAddress, uint256 tokenId, address[] memory addresses) public onlyTokenOwner(tokenAddress, tokenId) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); address tokenOwner = _tokenOwner(tokenAddress, tokenId); for (uint256 i = 0; i < addresses.length; i++) { tokenToTokenRules[tokenAddress].msgPerTokenBlacklist[tokenId][tokenOwner][addresses[i]] = false; } emit TokenMsgPerTokenBlacklistUpdated(tokenAddress, tokenId, _msgSender(), tokenOwner, addresses, false); } /** * @notice Add an array of addresses to the global event allowlist of the target token contract. * @param tokenAddress The address of the token contract. * @param addresses The addresses to add. */ function addEventGlobalAllowlist(address tokenAddress, address[] memory addresses) public onlyTokenContractOwner(tokenAddress) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); for (uint256 i = 0; i < addresses.length; i++) { if (eventGlobalAllowlist[tokenAddress][addresses[i]]) { continue; } eventGlobalAllowlist[tokenAddress][addresses[i]] = true; eventGlobalAllowlistLength++; } emit TokenEventGlobalAllowlistUpdated(tokenAddress, _msgSender(), addresses, true); } /** * @notice Remove an array of addresses from the global event allowlist of the target token contract. * @param tokenAddress The address of the token contract. * @param addresses The addresses to remove. */ function removeEventGlobalAllowlist(address tokenAddress, address[] memory addresses) public onlyTokenContractOwner(tokenAddress) { require(addresses.length > 0, "RulesManager: The addresses parameter must not be empty"); for (uint256 i = 0; i < addresses.length; i++) { if (!eventGlobalAllowlist[tokenAddress][addresses[i]]) { continue; } eventGlobalAllowlist[tokenAddress][addresses[i]] = false; eventGlobalAllowlistLength--; } emit TokenEventGlobalAllowlistUpdated(tokenAddress, _msgSender(), addresses, false); } /** * @notice Check if the `sender` is allowed to send a message to the target token contract and token ID. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @param sender The address of the sender to check. * @return A boolean indicating whether the sender is allowed to send a message. */ function isAllowedMsgSender(address tokenAddress, uint256 tokenId, address sender) external view returns (bool) { TokenRules storage tokenRules = tokenToTokenRules[tokenAddress]; return tokenToTokenRules[tokenAddress].msgEnabled && // Message feature is enabled AND !tokenRules.msgPerTokenBlacklist[tokenId][_tokenOwner(tokenAddress, tokenId)][sender] && // Sender is not in the per-token message blacklist for the current token owner AND (_isTokenContractOwner(tokenAddress, sender) || // Sender is the token contract owner OR msgGlobalAllowlist[tokenAddress][sender] || // Sender is in the global message allowlist OR tokenToTokenRules[tokenAddress].msgPerTokenAllowlist[tokenId][_tokenOwner(tokenAddress, tokenId)][sender]); // Sender is in the per-token message allowlist for the current token owner } /** * @notice Check if the `sender` is allowed to emit an event for the target token contract. * @param tokenAddress The address of the token contract. * @param sender The address of the sender to check. * @return A boolean indicating whether the sender is allowed to emit an event. */ function isAllowedEventEmitter(address tokenAddress, address sender) public view returns (bool) { return tokenToTokenRules[tokenAddress].eventEnabled && // Event feature is enabled AND (_isTokenContractOwner(tokenAddress, sender) || // Sender is the token contract owner OR eventGlobalAllowlistLength == 0 || // Event global allowlist is empty OR eventGlobalAllowlist[tokenAddress][sender]); // Sender is in the global event allowlist } /** * @dev Check if the `_msgSender()` is the owner of the target token contract. * @param tokenAddress The address of the token contract. */ function _isTokenContractOwner(address tokenAddress, address sender) internal view returns (bool) { return sender == Ownable(tokenAddress).owner(); } /** * @dev Check if the `_msgSender()` is the owner of the target ISmartAssetBase or IERC721 token. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. */ function _isTokenOwner(address tokenAddress, uint256 tokenId, address sender) internal view returns (bool) { return sender == _tokenOwner(tokenAddress, tokenId); } /** * @dev Try to retrieve the owner of the target ISmartAssetBase or IERC721 token. * @param tokenAddress The address of the token contract. * @param tokenId The ID of the token. * @return The owner of the token if the `ownerOf`call has succeeded. */ function _tokenOwner(address tokenAddress, uint256 tokenId) internal view returns (address) { try IERC721(tokenAddress).ownerOf(tokenId) returns (address owner) { return owner; } catch { revert("RulesManager: Target token contract does not implement ERC721.ownerOf(uint256 tokenId)"); } } /** * @dev See {UUPSUpgradeable-_authorizeUpgrade}. */ function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} /** * @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(); } }