// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC721/ERC721.sol) pragma solidity ^0.8.15; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Context.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import "./interfaces/IRMRKMultiResource.sol"; import "./library/RMRKLib.sol"; error ERC721AddressZeroIsNotaValidOwner(); error ERC721ApprovalToCurrentOwner(); error ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); error ERC721ApprovedQueryForNonexistentToken(); error ERC721ApproveToCaller(); error ERC721InvalidTokenId(); error ERC721MintToTheZeroAddress(); error ERC721NotApprovedOrOwner(); error ERC721TokenAlreadyMinted(); error ERC721TransferFromIncorrectOwner(); error ERC721TransferToNonReceiverImplementer(); error ERC721TransferToTheZeroAddress(); error RMRKBadPriorityListLength(); error RMRKIndexOutOfRange(); error RMRKMaxPendingResourcesReached(); error RMRKNoResourceMatchingId(); error RMRKResourceAlreadyExists(); error RMRKWriteToZero(); error RMRKNotApprovedForResourcesOrOwner(); error RMRKApprovalForResourcesToCurrentOwner(); error RMRKApproveForResourcesCallerIsNotOwnerNorApprovedForAll(); error RMRKApproveForResourcesToCaller(); /** * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, based on https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol with some modifications for RMRK standards, including: Use of custom errors, having _balances and _tokenApprovals internal instead of private, call to ownerOf not fixed to ERC721. */ contract RMRKMultiResource is Context, IERC165, IERC721, IERC721Metadata, IRMRKMultiResource { using Address for address; using Strings for uint256; using RMRKLib for uint64[]; using RMRKLib for uint128[]; using RMRKLib for uint256; // Token name string private _name; // Token symbol string private _symbol; // Mapping from token ID to owner address mapping(uint256 => address) private _owners; // Mapping owner address to token count mapping(address => uint256) internal _balances; // Mapping from token ID to approved address mapping(uint256 => address) internal _tokenApprovals; // Mapping from owner to operator approvals mapping(address => mapping(address => bool)) private _operatorApprovals; // ------------------- RESOURCES -------------- //mapping of uint64 Ids to resource object mapping(uint64 => string) internal _resources; //mapping of tokenId to new resource, to resource to be replaced mapping(uint256 => mapping(uint64 => uint64)) internal _resourceOverwrites; //mapping of tokenId to all resources mapping(uint256 => uint64[]) internal _activeResources; //mapping of tokenId to an array of resource priorities mapping(uint256 => uint16[]) internal _activeResourcePriorities; //Double mapping of tokenId to active resources mapping(uint256 => mapping(uint64 => bool)) internal _tokenResources; //mapping of tokenId to all resources by priority mapping(uint256 => uint64[]) internal _pendingResources; //List of all resources uint64[] internal _allResources; // Mapping from token ID to approved address for resources mapping(uint256 => address) internal _tokenApprovalsForResources; // Mapping from owner to operator approvals for resources mapping(address => mapping(address => bool)) internal _operatorApprovalsForResources; // -------------------------- ERC721 MODIFIERS ---------------------------- function _onlyApprovedOrOwner(uint256 tokenId) private view { if(!_isApprovedOrOwner(_msgSender(), tokenId)) revert ERC721NotApprovedOrOwner(); } modifier onlyApprovedOrOwner(uint256 tokenId) { _onlyApprovedOrOwner(tokenId); _; } // ----------------------- MODIFIERS FOR RESOURCES ------------------------ function _isApprovedForResourcesOrOwner(address user, uint256 tokenId) internal view virtual returns (bool) { address owner = ownerOf(tokenId); return (user == owner || isApprovedForAllForResources(owner, user) || getApprovedForResources(tokenId) == user); } function _onlyApprovedForResourcesOrOwner(uint256 tokenId) private view { if(!_isApprovedForResourcesOrOwner(_msgSender(), tokenId)) revert RMRKNotApprovedForResourcesOrOwner(); } modifier onlyApprovedForResourcesOrOwner(uint256 tokenId) { _onlyApprovedForResourcesOrOwner(tokenId); _; } // ----------------------------- CONSTRUCTOR ------------------------------ /** * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. */ constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } // ------------------------------- ERC721 --------------------------------- /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || interfaceId == type(IRMRKMultiResource).interfaceId; } /** * @dev See {IERC721-balanceOf}. */ function balanceOf(address owner) public view virtual returns (uint256) { if(owner == address(0)) revert ERC721AddressZeroIsNotaValidOwner(); return _balances[owner]; } /** * @dev See {IERC721-ownerOf}. */ function ownerOf(uint256 tokenId) public view virtual returns (address) { address owner = _owners[tokenId]; if(owner == address(0) ) revert ERC721InvalidTokenId(); return owner; } /** * @dev See {IERC721Metadata-name}. */ function name() public view virtual returns (string memory) { return _name; } /** * @dev See {IERC721Metadata-symbol}. */ function symbol() public view virtual returns (string memory) { return _symbol; } /** * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each * token will be the concatenation of the `baseURI` and the `tokenId`. Empty * by default, can be overridden in child contracts. */ function _baseURI() internal view virtual returns (string memory) { return ""; } /** * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual { address owner = ownerOf(tokenId); if(to == owner) revert ERC721ApprovalToCurrentOwner(); if(_msgSender() != owner && !isApprovedForAll(owner, _msgSender())) revert ERC721ApproveCallerIsNotOwnerNorApprovedForAll(); _approve(to, tokenId); } /** * @dev See {IERC721-getApproved}. */ function getApproved(uint256 tokenId) public view virtual returns (address) { _requireMinted(tokenId); return _tokenApprovals[tokenId]; } /** * @dev See {IERC721-setApprovalForAll}. */ function setApprovalForAll(address operator, bool approved) public virtual { _setApprovalForAll(_msgSender(), operator, approved); } /** * @dev See {IERC721-isApprovedForAll}. */ function isApprovedForAll(address owner, address operator) public view virtual returns (bool) { return _operatorApprovals[owner][operator]; } /** * @dev See {IERC721-transferFrom}. */ function transferFrom( address from, address to, uint256 tokenId ) public virtual onlyApprovedOrOwner(tokenId) { _transfer(from, to, tokenId); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 tokenId ) public virtual { safeTransferFrom(from, to, tokenId, ""); } /** * @dev See {IERC721-safeTransferFrom}. */ function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public virtual onlyApprovedOrOwner(tokenId) { _safeTransfer(from, to, tokenId, data); } /** * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked. * * `data` is additional data, it has no specified format and it is sent in call to `to`. * * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. * implement alternative mechanisms to perform token transfer, such as signature-based. * * Requirements: * * - `from` cannot be the zero address. * - `to` cannot be the zero address. * - `tokenId` token must exist and be owned by `from`. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeTransfer( address from, address to, uint256 tokenId, bytes memory data ) internal virtual { _transfer(from, to, tokenId); if(!_checkOnERC721Received(from, to, tokenId, data)) revert ERC721TransferToNonReceiverImplementer(); } /** * @dev Returns whether `tokenId` exists. * * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. * * Tokens start existing when they are minted (`_mint`), * and stop existing when they are burned (`_burn`). */ function _exists(uint256 tokenId) internal view virtual returns (bool) { return _owners[tokenId] != address(0); } /** * @dev Returns whether `spender` is allowed to manage `tokenId`. * * Requirements: * * - `tokenId` must exist. */ function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { address owner = ownerOf(tokenId); return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender); } /** * @dev Safely mints `tokenId` and transfers it to `to`. * * Requirements: * * - `tokenId` must not exist. * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. * * Emits a {Transfer} event. */ function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } /** * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is * forwarded in {IERC721Receiver-onERC721Received} to contract recipients. */ function _safeMint( address to, uint256 tokenId, bytes memory data ) internal virtual { _mint(to, tokenId); if(!_checkOnERC721Received(address(0), to, tokenId, data)) revert ERC721TransferToNonReceiverImplementer(); } /** * @dev Mints `tokenId` and transfers it to `to`. * * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible * * Requirements: * * - `tokenId` must not exist. * - `to` cannot be the zero address. * * Emits a {Transfer} event. */ function _mint(address to, uint256 tokenId) internal virtual { if(to == address(0)) revert ERC721MintToTheZeroAddress(); if(_exists(tokenId)) revert ERC721TokenAlreadyMinted(); _beforeTokenTransfer(address(0), to, tokenId); _balances[to] += 1; _owners[tokenId] = to; emit Transfer(address(0), to, tokenId); _afterTokenTransfer(address(0), to, tokenId); } /** * @dev Destroys `tokenId`. * The approval is cleared when the token is burned. * * Requirements: * * - `tokenId` must exist. * * Emits a {Transfer} event. */ function _burn(uint256 tokenId) internal virtual { address owner = ownerOf(tokenId); _beforeTokenTransfer(owner, address(0), tokenId); // Clear approvals _approve(address(0), tokenId); _approveForResources(address(0), tokenId); _balances[owner] -= 1; delete _owners[tokenId]; emit Transfer(owner, address(0), tokenId); _afterTokenTransfer(owner, address(0), tokenId); } /** * @dev Transfers `tokenId` from `from` to `to`. * As opposed to {transferFrom}, this imposes no restrictions on msg.sender. * * Requirements: * * - `to` cannot be the zero address. * - `tokenId` token must be owned by `from`. * * Emits a {Transfer} event. */ function _transfer( address from, address to, uint256 tokenId ) internal virtual { if(ownerOf(tokenId) != from) revert ERC721TransferFromIncorrectOwner(); if(to == address(0)) revert ERC721TransferToTheZeroAddress(); _beforeTokenTransfer(from, to, tokenId); // Clear approvals from the previous owner delete _tokenApprovals[tokenId]; delete _tokenApprovalsForResources[tokenId]; _balances[from] -= 1; _balances[to] += 1; _owners[tokenId] = to; emit Transfer(from, to, tokenId); _afterTokenTransfer(from, to, tokenId); } /** * @dev Approve `to` to operate on `tokenId` * * Emits an {Approval} event. */ function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; emit Approval(ownerOf(tokenId), to, tokenId); } /** * @dev Approve `operator` to operate on all of `owner` tokens * * Emits an {ApprovalForAll} event. */ function _setApprovalForAll( address owner, address operator, bool approved ) internal virtual { if(owner == operator) revert ERC721ApproveToCaller(); _operatorApprovals[owner][operator] = approved; emit ApprovalForAll(owner, operator, approved); } /** * @dev Reverts if the `tokenId` has not been minted yet. */ function _requireMinted(uint256 tokenId) internal view virtual { if(!_exists(tokenId)) revert ERC721InvalidTokenId(); } /** * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. * The call is not executed if the target address is not a contract. * * @param from address representing the previous owner of the given token ID * @param to target address that will receive the tokens * @param tokenId uint256 ID of the token to be transferred * @param data bytes optional data to send along with the call * @return bool whether the call correctly returned the expected magic value */ function _checkOnERC721Received( address from, address to, uint256 tokenId, bytes memory data ) internal returns (bool) { if (to.isContract()) { try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert ERC721TransferToNonReceiverImplementer(); } else { /// @solidity memory-safe-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } } /** * @dev Hook that is called before any token transfer. This includes minting * and burning. * * Calling conditions: * * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be * transferred to `to`. * - When `from` is zero, `tokenId` will be minted for `to`. * - When `to` is zero, ``from``'s `tokenId` will be burned. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _beforeTokenTransfer( address from, address to, uint256 tokenId ) internal virtual {} /** * @dev Hook that is called after any transfer of tokens. This includes * minting and burning. * * Calling conditions: * * - when `from` and `to` are both non-zero. * - `from` and `to` are never both zero. * * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. */ function _afterTokenTransfer( address from, address to, uint256 tokenId ) internal virtual {} // ------------------------------- RESOURCES ------------------------------ // --------------------------- GETTING RESOURCES -------------------------- function getResource( uint64 resourceId ) public view virtual returns (Resource memory) { string memory resourceData = _resources[resourceId]; if(bytes(resourceData).length == 0) revert RMRKNoResourceMatchingId(); Resource memory resource = Resource({ id: resourceId, metadataURI: resourceData }); return resource; } function getAllResources() public view virtual returns (uint64[] memory) { return _allResources; } function getResObjectByIndex( uint256 tokenId, uint256 index ) external view virtual returns(Resource memory) { uint64 resourceId = getActiveResources(tokenId)[index]; return getResource(resourceId); } function getPendingResObjectByIndex( uint256 tokenId, uint256 index ) external view virtual returns(Resource memory) { uint64 resourceId = getPendingResources(tokenId)[index]; return getResource(resourceId); } function getFullResources( uint256 tokenId ) external view virtual returns (Resource[] memory) { uint64[] memory resourceIds = _activeResources[tokenId]; return _getResourcesById(resourceIds); } function getFullPendingResources( uint256 tokenId ) external view virtual returns (Resource[] memory) { uint64[] memory resourceIds = _pendingResources[tokenId]; return _getResourcesById(resourceIds); } function _getResourcesById( uint64[] memory resourceIds ) internal view virtual returns (Resource[] memory) { uint256 len = resourceIds.length; Resource[] memory resources = new Resource[](len); for (uint i; i= _pendingResources[tokenId].length) revert RMRKIndexOutOfRange(); uint64 resourceId = _pendingResources[tokenId][index]; _pendingResources[tokenId].removeItemByIndex(index); uint64 overwrite = _resourceOverwrites[tokenId][resourceId]; if (overwrite != uint64(0)) { // We could check here that the resource to overwrite actually exists but it is probably harmless. _activeResources[tokenId].removeItemByValue(overwrite); emit ResourceOverwritten(tokenId, overwrite, resourceId); delete(_resourceOverwrites[tokenId][resourceId]); } _activeResources[tokenId].push(resourceId); //Push 0 value of uint16 to array, e.g., uninitialized _activeResourcePriorities[tokenId].push(uint16(0)); emit ResourceAccepted(tokenId, resourceId); } function _rejectResource(uint256 tokenId, uint256 index) internal { if(index >= _pendingResources[tokenId].length) revert RMRKIndexOutOfRange(); uint64 resourceId = _pendingResources[tokenId][index]; _pendingResources[tokenId].removeItemByIndex(index); _tokenResources[tokenId][resourceId] = false; delete(_resourceOverwrites[tokenId][resourceId]); emit ResourceRejected(tokenId, resourceId); } function _rejectAllResources(uint256 tokenId) internal { uint256 len = _pendingResources[tokenId].length; for (uint i; i 0) revert RMRKResourceAlreadyExists(); _resources[id] = metadataURI; _allResources.push(id); emit ResourceSet(id); } // This is expected to be implemented with custom guard: function _addResourceToToken( uint256 tokenId, uint64 resourceId, uint64 overwrites ) internal { if(_tokenResources[tokenId][resourceId]) revert RMRKResourceAlreadyExists(); if(bytes(_resources[resourceId]).length == 0) revert RMRKNoResourceMatchingId(); if(_pendingResources[tokenId].length >= 128) revert RMRKMaxPendingResourcesReached(); _tokenResources[tokenId][resourceId] = true; _pendingResources[tokenId].push(resourceId); if (overwrites != uint64(0)) { _resourceOverwrites[tokenId][resourceId] = overwrites; emit ResourceOverwriteProposed(tokenId, resourceId, overwrites); } emit ResourceAddedToToken(tokenId, resourceId); } // ----------------------------- TOKEN URI -------------------------------- /** * @dev See {IERC721Metadata-tokenURI}. Overwritten for MR */ function tokenURI( uint256 tokenId ) public view virtual override(IERC721Metadata, IRMRKMultiResource) returns (string memory) { return _tokenURIAtIndex(tokenId, 0); } function tokenURIAtIndex( uint256 tokenId, uint256 index ) public view virtual returns (string memory) { return _tokenURIAtIndex(tokenId, index); } function _tokenURIAtIndex( uint256 tokenId, uint256 index ) internal virtual view returns (string memory) { _requireMinted(tokenId); // TODO: Discuss is this is the best default path. // We could return empty string so it returns something if a token has no resources, but it might hide erros if (!(index < _activeResources[tokenId].length)) revert RMRKIndexOutOfRange(); uint64 activeResId = _activeResources[tokenId][index]; Resource memory _activeRes = getResource(activeResId); string memory uri = string( abi.encodePacked( _baseURI(), _activeRes.metadataURI) ); return uri; } // ----------------------- APPROVALS FOR RESOURCES ------------------------ function approveForResources(address to, uint256 tokenId) external virtual { address owner = ownerOf(tokenId); if(to == owner) revert RMRKApprovalForResourcesToCurrentOwner(); if(_msgSender() != owner && !isApprovedForAllForResources(owner, _msgSender())) revert RMRKApproveForResourcesCallerIsNotOwnerNorApprovedForAll(); _approveForResources(to, tokenId); } function getApprovedForResources(uint256 tokenId) public virtual view returns (address) { _requireMinted(tokenId); return _tokenApprovalsForResources[tokenId]; } function setApprovalForAllForResources(address operator, bool approved) external virtual { address owner = _msgSender(); if(owner == operator) revert RMRKApproveForResourcesToCaller(); _operatorApprovalsForResources[owner][operator] = approved; emit ApprovalForAllForResources(owner, operator, approved); } function isApprovedForAllForResources(address owner, address operator) public virtual view returns (bool) { return _operatorApprovalsForResources[owner][operator]; } function _approveForResources(address to, uint256 tokenId) internal virtual { _tokenApprovalsForResources[tokenId] = to; emit ApprovalForResources(ownerOf(tokenId), to, tokenId); } }