// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol"; import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; import {Diamond} from "@solarity/solidity-lib/diamond/Diamond.sol"; import {DiamondERC721} from "../ERC721/DiamondERC721.sol"; import {DiamondERC721Storage} from "../ERC721/DiamondERC721Storage.sol"; import {INftF, NftFMetadata} from "../interfaces/INftF.sol"; import {IKYCCompliance} from "../interfaces/IKYCCompliance.sol"; import {IRegulatoryCompliance} from "../interfaces/IRegulatoryCompliance.sol"; import {ISLCCore} from "../interfaces/ISLCCore.sol"; import {AgentAccessControl} from "./AgentAccessControl.sol"; import {NftFStorage} from "./storages/NftFStorage.sol"; import {RegulatoryComplianceStorage} from "./storages/RegulatoryComplianceStorage.sol"; import {KYCComplianceStorage} from "./storages/KYCComplianceStorage.sol"; abstract contract ANftF is INftF, NftFStorage, Diamond, DiamondERC721, AgentAccessControl { using MerkleProof for bytes32[]; bytes4 public constant SAFE_TRANSFER_FROM_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256)")); bytes4 public constant SAFE_TRANSFER_FROM_FULL_SELECTOR = bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")); bytes4 public constant TRANSFER_FROM_SELECTOR = this.transferFrom.selector; bytes4 public constant MINT_SELECTOR = this.mint.selector; bytes4 public constant BURN_SELECTOR = this.burn.selector; bytes4 public constant FORCED_TRANSFER_SELECTOR = this.forcedTransfer.selector; bytes4 public constant RECOVERY_SELECTOR = this.recovery.selector; function __ANftF_init( bool isTransferable_, address slc_, address regulatoryCompliance_, address kycCompliance_, bytes memory initRegulatory_, bytes memory initKYC_ ) internal virtual onlyInitializing(TOKEN_F_STORAGE_SLOT) { _switchTransferability(isTransferable_); _setSlc(slc_); _setRoleAdmin(AGENT_ROLE, AGENT_ROLE); bytes4[] memory rComplianceSelectors_ = new bytes4[](6); rComplianceSelectors_[0] = IRegulatoryCompliance.addRegulatoryModules.selector; rComplianceSelectors_[1] = IRegulatoryCompliance.removeRegulatoryModules.selector; rComplianceSelectors_[2] = IRegulatoryCompliance.transferred.selector; rComplianceSelectors_[3] = IRegulatoryCompliance.canTransfer.selector; rComplianceSelectors_[4] = RegulatoryComplianceStorage.getRegulatoryModules.selector; rComplianceSelectors_[5] = RegulatoryComplianceStorage.getRegulatoryModulesCount.selector; bytes4[] memory kycComplianceSelectors_ = new bytes4[](5); kycComplianceSelectors_[0] = IKYCCompliance.addKYCModules.selector; kycComplianceSelectors_[1] = IKYCCompliance.removeKYCModules.selector; kycComplianceSelectors_[2] = IKYCCompliance.isKYCed.selector; kycComplianceSelectors_[3] = KYCComplianceStorage.getKYCModules.selector; kycComplianceSelectors_[4] = KYCComplianceStorage.getKYCModulesCount.selector; Facet[] memory facets_ = new Facet[](2); facets_[0] = Facet(regulatoryCompliance_, FacetAction.Add, rComplianceSelectors_); facets_[1] = Facet(kycCompliance_, FacetAction.Add, kycComplianceSelectors_); _diamondCut(facets_, address(0), ""); _diamondCut(new Facet[](0), regulatoryCompliance_, initRegulatory_); _diamondCut(new Facet[](0), kycCompliance_, initKYC_); } /// @inheritdoc IERC721 function transferFrom( address from_, address to_, uint256 tokenId_ ) public virtual override(DiamondERC721, IERC721) { _canTransfer(from_, to_, tokenId_, msg.sender); _isKYCed(from_, to_, tokenId_, msg.sender); super.transferFrom(from_, to_, tokenId_); _transferred(from_, to_, tokenId_, msg.sender); } /// @inheritdoc IERC721 function safeTransferFrom( address from_, address to_, uint256 tokenId_ ) public virtual override(DiamondERC721, IERC721) { _canTransfer(from_, to_, tokenId_, address(0)); _isKYCed(from_, to_, tokenId_, address(0)); super.safeTransferFrom(from_, to_, tokenId_); _transferred(from_, to_, tokenId_, msg.sender); } /// @inheritdoc IERC721 function safeTransferFrom( address from_, address to_, uint256 tokenId_, bytes memory data_ ) public virtual override(DiamondERC721, IERC721) { _canTransfer(from_, to_, tokenId_, address(0), data_); _isKYCed(from_, to_, tokenId_, address(0), data_); super.safeTransferFrom(from_, to_, tokenId_, data_); _transferred(from_, to_, tokenId_, msg.sender); } /// @inheritdoc INftF function mint( address account_, bytes32 commitment_, uint256 tokenId_, string memory tokenURI_ ) public virtual override onlyRole(AGENT_ROLE) { _canTransfer(address(0), account_, tokenId_, msg.sender); _isKYCed(address(0), account_, tokenId_, msg.sender); super._mint(account_, tokenId_); _switchMetadataActivity(tokenId_, true); _updateMetadata(tokenId_, commitment_); _updateTokenURI(tokenId_, tokenURI_); _transferred(address(0), account_, tokenId_, msg.sender); } /// @inheritdoc INftF function burn( address account_, uint256 tokenId_ ) public virtual override onlyRole(AGENT_ROLE) { _canTransfer(account_, address(0), tokenId_, msg.sender); _isKYCed(account_, address(0), tokenId_, msg.sender); super._burn(tokenId_); _switchMetadataActivity(tokenId_, false); _transferred(account_, address(0), tokenId_, msg.sender); } /// @inheritdoc INftF function forcedTransfer( address from_, address to_, uint256 tokenId_ ) public virtual override onlyRole(AGENT_ROLE) { _canTransfer(from_, to_, tokenId_, msg.sender); _isKYCed(from_, to_, tokenId_, msg.sender); super._transfer(from_, to_, tokenId_); _transferred(from_, to_, tokenId_, msg.sender); } /// @inheritdoc INftF function recovery( address oldAccount_, address newAccount_ ) public virtual override onlyRole(AGENT_ROLE) { uint256[] memory tokens = _allTokens(oldAccount_); uint256 tokenAmount_ = tokens.length; for (uint256 i = tokenAmount_; i > 0; i--) { uint256 tokenId_ = tokens[i - 1]; _canTransfer(oldAccount_, newAccount_, tokenId_, msg.sender); _isKYCed(oldAccount_, newAccount_, tokenId_, msg.sender); super._transfer(oldAccount_, newAccount_, tokenId_); _transferred(oldAccount_, newAccount_, tokenId_, msg.sender); } } /// @inheritdoc INftF function switchTransferability(bool isTransferable) public override onlyRole(AGENT_ROLE) { require( _getNftFStorage().isTransferable != isTransferable, "NftF: transfer is already set" ); _switchTransferability(isTransferable); } /// @inheritdoc INftF function switchMetadataActivity( uint256 tokenId_, bool isActive_ ) public override onlyRole(AGENT_ROLE) { _requireMinted(tokenId_); _switchMetadataActivity(tokenId_, isActive_); } /// @inheritdoc INftF function updateMetadata( uint256 tokenId_, bytes32 commitment_, string memory tokenURI_ ) public override onlyRole(AGENT_ROLE) { _updateTokenURI(tokenId_, tokenURI_); _updateMetadata(tokenId_, commitment_); } /// @inheritdoc INftF function verifyMetadataCommitment( uint256 tokenId_, bytes32[] memory proof_, bytes32 leaf_ ) public view override returns (bool) { return _verifyMetadataCommitment(tokenId_, proof_, leaf_); } /// @inheritdoc IERC721Metadata function tokenURI( uint256 tokenId_ ) public view virtual override(DiamondERC721Storage, IERC721Metadata) returns (string memory) { return _exists(tokenId_) ? _getNftFStorage().tokenMetadata[tokenId_].tokenURI : ""; } function setSlc(address slcAddress_) public onlyRole(AGENT_ROLE) { require(slcAddress_ != address(0), "NftF: slcAddress is the zero address"); renounceRole(AGENT_ROLE, msg.sender); _setSlc(slcAddress_); _grantRole(AGENT_ROLE, slcAddress_); } function _switchTransferability(bool isTransferable) internal { _getNftFStorage().isTransferable = isTransferable; } function _switchMetadataActivity(uint256 tokenId_, bool isActive_) internal { NftFMetadata storage _tokenMetadata = _getNftFStorage().tokenMetadata[tokenId_]; require( _tokenMetadata.isActive != isActive_, "NftF: metadata is already in a correct state" ); _tokenMetadata.isActive = isActive_; if (isActive_) { emit MetadataActivated(tokenId_); } else { emit MetadataDeactivated(tokenId_); } } function _updateMetadata(uint256 tokenId_, bytes32 commitment_) internal { NftFMetadata storage _tokenMetadata = _getNftFStorage().tokenMetadata[tokenId_]; _tokenMetadata.commitment = commitment_; emit MetadataUpdated( tokenId_, _tokenMetadata.commitment, _tokenMetadata.tokenURI, _tokenMetadata.isActive ); } function _updateTokenURI(uint256 tokenId_, string memory tokenURI_) internal { _getNftFStorage().tokenMetadata[tokenId_].tokenURI = tokenURI_; } function _verifyMetadataCommitment( uint256 tokenId_, bytes32[] memory proof_, bytes32 leaf_ ) internal view returns (bool) { return proof_.verify(_getNftFStorage().tokenMetadata[tokenId_].commitment, leaf_); } function _setSlc(address slcAddress_) internal { _getNftFStorage().slcAddress = slcAddress_; } function _transferred( address from_, address to_, uint256 tokenId_, address operator_ ) internal virtual { _transferred(from_, to_, tokenId_, operator_, ""); } function _transferred( address from_, address to_, uint256 tokenId_, address operator_, bytes memory data_ ) internal virtual { try IRegulatoryCompliance(address(this)).transferred( Context(bytes4(bytes(msg.data[:4])), from_, to_, tokenId_, operator_, data_) ) {} catch { revert("NftF: transferred reverted"); } } function _canTransfer( address from_, address to_, uint256 tokenId_, address operator_ ) internal view virtual { _canTransfer(from_, to_, tokenId_, operator_, ""); } function _canTransfer( address from_, address to_, uint256 tokenId_, address operator_, bytes memory data_ ) internal view virtual { require(_getNftFStorage().isTransferable, "NftF: Not transferable"); try IRegulatoryCompliance(address(this)).canTransfer( Context(bytes4(bytes(msg.data[:4])), from_, to_, tokenId_, operator_, data_) ) returns (bool canTransfer_) { require(canTransfer_, "NftF: cannot transfer"); } catch { revert("NftF: canTransfer reverted"); } } function _isKYCed( address from_, address to_, uint256 tokenId_, address operator_ ) internal view virtual { _isKYCed(from_, to_, tokenId_, operator_, ""); } function _isKYCed( address from_, address to_, uint256 tokenId_, address operator_, bytes memory data_ ) internal view virtual { try IKYCCompliance(address(this)).isKYCed( Context(bytes4(bytes(msg.data[:4])), from_, to_, tokenId_, operator_, data_) ) returns (bool isKYCed_) { require(isKYCed_, "NftF: not KYCed"); } catch { revert("NftF: isKYCed reverted"); } } }