// SPDX-License-Identifier: UNLICENSED pragma solidity 0.8.18; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "../Utilities/MutableERC2771Context.sol"; import "../Interfaces/ISmartAssetBase.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../Interfaces/ICreditManager.sol"; import "../Interfaces/IRulesManager.sol"; /** * @title SmartAssetBase. * @author Arianee - Dynamic NFTs for real-world use cases and consumer engagement (www.arianee.org). */ abstract contract SmartAssetBase is Ownable, MutableERC2771Context, Pausable, AccessControl, ERC721, ERC721Enumerable, ISmartAssetBase { /** * @notice PAUSER_ROLE: those who can pause the contract in case of emergency. */ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); /** * @notice MINTER_ROLE: those who can mint or hydrate tokens. */ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); /** * @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 Providers of a token * @param creatorProvider is the address that will be rewarded for creating the token * @param creatorProvider is the address that will be rewarded for claiming the token */ struct TokenProviders { address creatorProvider; address walletProvider; } /** * @notice Mapping from token id to its footprint. See {TokenFootprint}. */ mapping(uint256 => TokenFootprint) internal idToFootprint; /** * @notice Mapping from token id to its imprint. */ mapping(uint256 => bytes32) internal idToImprint; /** * @notice Mapping from token id to a boolean that indicates whether the first transfer has been done or not. */ mapping(uint256 => bool) internal idToFirstTransfer; /** * @notice Mapping from token id to its view key. */ mapping(uint256 => address) internal idToViewKey; /** * @notice Mapping from token id to its transfer key. */ mapping(uint256 => address) internal idToTransferKey; /** * @notice Mapping from token id to its providers. */ mapping(uint256 => TokenProviders) internal tokenProviders; /** * @notice This emits when a token is hydrated. */ event TokenHydrated(uint256 indexed tokenId, bytes32 indexed imprint, address viewKey, address transferKey, uint256 creationDate); /** * @notice This emits when a token view access is added. */ event TokenViewAccessAdded(uint256 indexed tokenId, address key); /** * @notice This emits when a token view access is removed. */ event TokenViewAccessRemoved(uint256 indexed tokenId); /** * @notice This emits when a token transfer access is added. */ event TokenTransferAccessAdded(uint256 indexed tokenId, address key); /** * @notice This emits when a token transfer access is removed. */ event TokenTransferAccessRemoved(uint256 indexed tokenId); constructor( string memory name_, string memory symbol_, address rulesManagerAddress_, address creditManagerAddress_, address trustedForwarder_ ) MutableERC2771Context(trustedForwarder_) ERC721(name_, symbol_) { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(PAUSER_ROLE, msg.sender); rulesManager = IRulesManager(rulesManagerAddress_); creditManager = ICreditManager(creditManagerAddress_); } /** * @notice Check if `spender` is approved or owner of `tokenId`. */ modifier isApprovedOrOwner(address spender, uint256 tokenId) { require(_isApprovedOrOwner(spender, tokenId), "SmartAssetBase: The caller is not owner nor approved"); _; } /** * @notice Check if `_msgSender()` is the issuer of `tokenId`. */ modifier isIssuer(uint256 tokenId) { require(_msgSender() == idToFootprint[tokenId].issuer, "SmartAssetBase: The caller is not the issuer"); _; } /** * @notice Reserve a token with a specific ID for a given address. * @dev It is equivalent to mint a token with a specific ID for a given address. * @param to The address that will own the minted token. * @param tokenId The desired token ID. */ function reserveToken(address to, uint256 tokenId) public onlyRole(MINTER_ROLE) whenNotPaused { super._safeMint(to, tokenId); idToFirstTransfer[tokenId] = true; } /** * @notice Hydrate a specific pre-reserved token. * @param tokenHydratationParams The token hydratation parameters. See {TokenHydratationParams}. */ function hydrateToken(TokenHydratationParams memory tokenHydratationParams) public virtual onlyRole(MINTER_ROLE) whenNotPaused { uint256 tokenId = tokenHydratationParams.tokenId; if (!_exists(tokenId)) { reserveToken(_msgSender(), tokenId); } require(_isApprovedOrOwner(_msgSender(), tokenId), "SmartAssetBase: The caller is not owner nor approved"); TokenFootprint storage existingFootprint = idToFootprint[tokenId]; require(existingFootprint.creationTimestamp == 0, "SmartAssetBase: This token has already been hydrated"); uint256 creationTimestamp = block.timestamp; TokenFootprint memory newFootprint = TokenFootprint({creationTimestamp: creationTimestamp, issuer: _msgSender()}); idToFootprint[tokenId] = newFootprint; setTokenViewKey(tokenId, tokenHydratationParams.viewKey); if (tokenHydratationParams.transferKey != address(0)) { setTokenTransferKey(tokenId, tokenHydratationParams.transferKey); } idToImprint[tokenId] = tokenHydratationParams.imprint; tokenProviders[tokenId] = TokenProviders({ creatorProvider: tokenHydratationParams.creatorProvider, walletProvider: tokenHydratationParams.creatorProvider }); emit TokenHydrated(tokenId, tokenHydratationParams.imprint, tokenHydratationParams.viewKey, tokenHydratationParams.transferKey, creationTimestamp); } function getViewKey(uint256 tokenId) public view returns (address) { return idToViewKey[tokenId]; } function getTransferKey(uint256 tokenId) public view returns (address) { return idToTransferKey[tokenId]; } /** * @notice Transfer a given token to a new owner. This function is only callable by someone who give a valid signature according to the token transfer key. * @param tokenId The token ID. * @param signature An envelope of `keccak256(abi.encode(tokenId, newOwner))` signed by the private key matching the token transfer key. * @param newOwner The new owner of the token. * @param keepTransferKey A flag to indicate if the transfer key should be kept or not. */ function requestToken( uint256 tokenId, bytes calldata signature, address newOwner, bool keepTransferKey, address walletProvider ) public virtual whenNotPaused { bytes32 message = keccak256(abi.encode(tokenId, newOwner)); bytes32 messageHash = ECDSA.toEthSignedMessageHash(message); require(ECDSA.recover(messageHash, signature) == idToTransferKey[tokenId], "SmartAssetBase: The signature does not match current token transfer key"); if (!keepTransferKey) { _setTokenTransferKey(tokenId, address(0)); } super._approve(_msgSender(), tokenId); address owner = super.ownerOf(tokenId); tokenProviders[tokenId].walletProvider = walletProvider; super.safeTransferFrom(owner, newOwner, tokenId); } /** * @notice Set the view key for a given token. * @param tokenId The token ID. * @param key The view key. */ function setTokenViewKey(uint256 tokenId, address key) public virtual isApprovedOrOwner(_msgSender(), tokenId) whenNotPaused { _setTokenViewKey(tokenId, key); } /** * @notice Set the view key for a given token. * @param tokenId The token ID. * @param key The view key. */ function setTokenTransferKey(uint256 tokenId, address key) public virtual isApprovedOrOwner(_msgSender(), tokenId) whenNotPaused { _setTokenTransferKey(tokenId, key); } /** * @notice Pauses all transfers. */ function pause() public onlyRole(PAUSER_ROLE) { _pause(); } /** * @notice Unpauses all transfers. */ function unpause() public onlyRole(PAUSER_ROLE) { _unpause(); } /** * @notice Check if a token is requestable. * @dev Throws if the token is not minted. * @param tokenId The token ID. * @return A boolean indicating if the token is requestable. */ function isRequestable(uint256 tokenId) public view returns (bool) { _requireMinted(tokenId); return idToTransferKey[tokenId] != address(0); } /** * @notice Try to retrieve the TokenFootprint of a given token. * @dev Throws if the token is not minted. * @param tokenId The token ID. * @return The TokenFootprint of the token if any. */ function footprintOf(uint256 tokenId) public view returns (TokenFootprint memory) { _requireMinted(tokenId); return idToFootprint[tokenId]; } /** * @notice Try to retrieve the issuer of a given token. * @dev Throws if the token is not minted. * @param tokenId The token ID. * @return issuer of the token if any. */ function issuerOf(uint256 tokenId) public view returns (address issuer) { _requireMinted(tokenId); return idToFootprint[tokenId].issuer; } /** * @notice Try to retrieve the imprint of a given token. * @dev Throws if the token is not minted. * @param tokenId The token ID. * @return The imprint of the token if any. */ function imprintOf(uint256 tokenId) public view returns (bytes32) { _requireMinted(tokenId); return idToImprint[tokenId]; } /** * @notice Flag indicating if this contract is shared by multiple issuers or not. * @dev This is mostly used by off-chain sync services. * @return False unless explicitly overridden. */ function isShared() public pure virtual returns (bool) { return false; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable, AccessControl) returns (bool) { return interfaceId == type(ISmartAssetBase).interfaceId || super.supportsInterface(interfaceId); } /** * @notice Internal function to set the view key for a given token. * As opposed to {setTokenViewKey}, this imposes no restrictions on _msgSender(). */ function _setTokenViewKey(uint256 tokenId, address key) internal { _requireMinted(tokenId); idToViewKey[tokenId] = key; if (key != address(0)) { emit TokenViewAccessAdded(tokenId, key); } else { emit TokenViewAccessRemoved(tokenId); } } /** * @notice Internal function to set the transfer key for a given token. * As opposed to {setTokenTransferKey}, this imposes no restrictions on _msgSender(). */ function _setTokenTransferKey(uint256 tokenId, address key) internal { _requireMinted(tokenId); idToTransferKey[tokenId] = key; if (key != address(0)) { emit TokenTransferAccessAdded(tokenId, key); } else { emit TokenTransferAccessRemoved(tokenId); } } /** * @dev See {ERC721-_transfer} */ function _transfer(address from, address to, uint256 tokenId) internal virtual override { super._transfer(from, to, tokenId); if (_isFirstTokenTransfer(tokenId)) { idToFirstTransfer[tokenId] = false; _afterFirstTokenTransfer(tokenId); } } /** * @dev Hook that is called before first token transfer. */ function _afterFirstTokenTransfer(uint256 tokenId) internal virtual { creditManager.consumeCredit( idToFootprint[tokenId].issuer, tokenProviders[tokenId].creatorProvider, tokenProviders[tokenId].walletProvider, super.ownerOf(tokenId) ); } /** * @dev See {ERC721-_beforeTokenTransfer}. */ function _beforeTokenTransfer( address from, address to, uint256 tokenId, uint256 batchSize ) internal virtual override(ERC721, ERC721Enumerable) whenNotPaused { super._beforeTokenTransfer(from, to, tokenId, batchSize); } /** * @notice Authorize the issuer to send messages to the new owner * @dev See {ERC721-_afterTokenTransfer}. */ function _afterTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal virtual override(ERC721) { if (to != address(0)) { address issuer = idToFootprint[tokenId].issuer; if (to != issuer && !rulesManager.isAllowedMsgSender(address(this), tokenId, issuer)) { address[] memory addresses = new address[](1); addresses[0] = issuer; rulesManager.addMsgPerTokenAllowlist(address(this), tokenId, addresses); } } super._afterTokenTransfer(from, to, tokenId, batchSize); } /** * @notice Check if a token has already been transferred. * @param tokenId The token ID. */ function _isFirstTokenTransfer(uint256 tokenId) internal view returns (bool) { return idToFirstTransfer[tokenId] == true; } /** * @dev See {MutableERC2771Context-_msgSender}. */ function _msgSender() internal view virtual override(MutableERC2771Context, Context) returns (address) { return MutableERC2771Context._msgSender(); } /** * @dev See {MutableERC2771Context-_msgData}. */ function _msgData() internal view virtual override(MutableERC2771Context, Context) returns (bytes calldata) { return MutableERC2771Context._msgData(); } }