diff --git a/EIPS/eip-4512.md b/EIPS/eip-4512.md new file mode 100644 index 0000000000000..a65ddfda60dcb --- /dev/null +++ b/EIPS/eip-4512.md @@ -0,0 +1,284 @@ +--- +eip: 4512 +title: Non-Fungible Token with usage rights +description: Separation of Non-Fungible Token ownership and usage rights , Truly in line with real life. +status: Draft +type: Standards Track +author: KKimos (@KKimos), KKimos +discussions-to: https://ethereum-magicians.org/t/unsecured-leasing-nft-promotes-the-development-of-metaverses-sharing-economy/7643 +category: ERC +created: 2021-12-01 +requires: 165, 721 +--- + +## Abstract + +This standard adds a new right of Non-Fungible Token that the right to use. Through this standard, you can achieve : + +- Separation of the right to use and ownership of Non-Fungible Token +- Non-secured lease Non-Fungible Token +- You can continue to use it after you mortgage the Non-Fungible Token +- Metaverse sharing economy + +It is precisely because of the separation of ownership and use right that the utilization rate of assets can be greater. You must distinguish between the rights of the user and the owner. + +## Motivation + +Uber, Airbnb, and WeWork promote the development of the sharing economy. In the blockchain , the ERC721-based leasing system has encountered great bottlenecks. For example, excess assets need to be mortgaged for leasing, The increase in NFT prices will lead to defaults ,You cannot continue to use it during NFT in mortgages. As a result, a new NFT standard is proposed, which is fully compatible with ERC-721. Separate the right to use and own the NFT. + +Different from [EIP-2615](./eip-2615.md), we only added a right to use, so that we can get all the functions of EIP-2615, and reduce many unnecessary operations in EIP-2615. The explanation is as follows: + +- The owner should not use some of the ownership rights when mortgage NFT, such as transfer, AXS-like breeding or weapon upgrade, because it is likely to change the status of the original NFT, so when the user mortgages the NFT, the ownership must be mortgaged in the contract middle. +- In the case of mortgage or transaction, the owner has the right to continue to use it before returning the ransom or selling it + +## Specification + +This standard adds the user role, and users can transfer their own usage rights. + +### ERC-4512 Interface + +```solidity +// SPDX-License-Identifier: MIT + + +pragma solidity ^0.8.0; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol"; + +/** + * @title a interface of ERC-4512 + * @author KKimos + * @dev Required interface of an ERC-4512 compliant contract. + */ +interface IERC4512 is IERC721{ + + /** + * @dev Emitted when `tokenId` tokenUser is transferred from `from` to `to`. + */ + event TransferUser(address from,address to,uint256 tokenId); + + /** + * @dev Emitted when `user` enables `approved` to manage the `tokenId` tokenUser. + */ + event ApprovalUser(address indexed user, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Returns the number of usable token in ``user``'s account. + */ + function balanceOfUser(address user) external view returns (uint256 balance); + + /** + * @dev Returns the user of tokenId token + * @param tokenId the NFT token's Id + * @return the address of token user + */ + function userOf(uint256 tokenId) external view returns (address user); + + /** + * @notice Safely transfers `tokenId` tokenUser from `from` to `to` + * @dev If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * @param from the address token user transfer from + * @param to the address token user transfer to + * @param tokenId the NFT token's Id + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @notice Safely transfers `tokenId` tokenUser from `from` to `to` + * @dev If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * @param from the address token user transfer from + * @param to the address token user transfer to + * @param tokenId the NFT token's Id + * @param data caller want to add extra information + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @notice Safely transfers `tokenId` token and tokenUser from `from` to `to` + * @dev If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * @param from the address token and its user transfer from + * @param to the address token and its user transfer to + * @param tokenId the NFT token's Id + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @notice Safely transfers `tokenId` token and tokenUser from `from` to `to` + * @dev If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * @param from the address token and its user transfer from + * @param to the address token and its user transfer to + * @param tokenId the NFT token's Id + * @param data caller want to add extra information + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @notice Gives permission to `to` to transfer `tokenId` tokenUser to another account. + * @dev Only a single account can be approved at a time, so approving the zero address clears previous approvals.The caller must be tokenUser or be an approvedUser operator.The approval is cleared when the tokenUser is transferred. + * @param to the approved address + * @param tokenId the NFT token's Id + */ + function approveUser(address to, uint256 tokenId) external; + + /** + * @dev Returns the approvedUser for `tokenId` token. + * @param tokenId the NFT token's Id + * @return the approved address + */ + function getApprovedUser(uint256 tokenId) external view returns (address operator); + + +} +``` + +### ERC-4512 Receiver + +```solidity +function onERCXReceived(address operator, address from, uint256 itemId, uint256 layer, bytes memory data) public returns(bytes4); +``` + +### ERC-4512 Extensions + +Extensions here are provided to help developers build with this standard. + +#### 1. ERC-4512 Enumerable + +```solidity +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/* + *@title a interface for ERC-4512Enumerable. + *@dev enumerable NFT include user and owner. + *@author KKimos + */ +interface IERC4512Enumerable is IERC4512 { + + /** + * @dev enumerate the user token list. Use along with {ERCX.balanceOfUser} to enumerate all of ``user``'s tokens. + * @param user the address of token user. + * @param index the index of user token list. + * @return a tokenId used by `user` at a given `index` of its token list. + */ + function tokenOfUserByIndex(address user, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev get the total amount of tokens stored by the contract. + * @return the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev enumerate the owner token list. + * @param owner the address of token owner. + * @param index the index of owner token list. + * return a token ID owned by `owner` at a given `index` of its token list. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev get a tokenId through index of contract token list. + * @param index the index of contract token list. + * @return a tokenId at a given `index` of all the tokens stored by the contract. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +} +``` + +#### 2. ERC-721 + +Fully compatible with ERC-721 + +```solidity +interface ERC721{ + event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); + event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); + event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); + function balanceOf(address _owner) external view returns (uint256); + function ownerOf(uint256 _tokenId) external view returns (address); + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; + function approve(address _approved, uint256 _tokenId) external payable; + function setApprovalForAll(address _operator, bool _approved) external; + function getApproved(uint256 _tokenId) external view returns (address); + function isApprovedForAll(address _owner, address _operator) external view returns (bool); +} +interface ERC165 { + function supportsInterface(bytes4 interfaceID) external view returns (bool); +} +``` + + + +## Rationale + +#### 1 . Mortgage + +When mortgage NFT, you only need to mortgage the right to use into the mortgage contract, leaving the right to use.The advantage of this is that you can still use your own NFT before delivery. + +#### 2 . Rent + +Lease does not require any collateral. First, you mortgage the NFT ownership into the contract, and the tenantry leases the right to use. + +This has the following benefits : + +- No need to mortgage assets +- Don't worry about the risk of default + +## Backward compatibility + +This protocol is fully backward compatible with ERC721.Refer to above Specification. + + + +## Test Cases + +When running the tests, you need to create a test network with Ganache-CLI: + +``` +ganache-cli -a 15 --gasLimit=0x1fffffffffffff -e 1000000000 +``` + +And then run the tests using Truffle: + +``` +truffle test -e development +``` + +Powered by Truffle and Openzeppelin test helper. + +## Reference Implementation + +[Github Reposotory](../assets/eip-4512). + + + +## Security Considerations + +When using the modified agreement, you must distinguish between the right of use and ownership. In theory, the right of use cannot change the NFT. + + + +## Copyright + +Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/). diff --git a/assets/eip-4512/LICENSE b/assets/eip-4512/LICENSE new file mode 100644 index 0000000000000..da938ea03bfbd Binary files /dev/null and b/assets/eip-4512/LICENSE differ diff --git a/assets/eip-4512/README.md b/assets/eip-4512/README.md new file mode 100644 index 0000000000000..2b58c52517a0a --- /dev/null +++ b/assets/eip-4512/README.md @@ -0,0 +1,2 @@ +# ERCX +Unsecured Leasing NFT Promotes the Development of Metaverse's Sharing Economy diff --git a/assets/eip-4512/contracts/ERCX.sol b/assets/eip-4512/contracts/ERCX.sol new file mode 100644 index 0000000000000..440790112a98b --- /dev/null +++ b/assets/eip-4512/contracts/ERCX.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: MIT + + +pragma solidity ^0.8.0; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol"; +import "./interface/IERCX.sol"; + +/// @title cryptosharing +/// @author Kimos +/// @notice cryptosharing + +contract ERCX is IERCX , ERC721 { + + // Mapping from tokenId to user address + mapping(uint256 => address) private _users; + + // Mapping user address to token usable count + mapping(address => uint256) private _balancesOfUser; + + // Mapping from tokenId to approved address + mapping(uint256 => address) private _tokenUserApprovals; + + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) ERC721(name_ , symbol_){ + + } + + /* + *@dev see{IERCX-approveUser} + */ + function approveUser(address to, uint256 tokenId) public virtual override{ + address user = ERCX.userOf(tokenId); + require(to != user, "ERCX: approval to current user"); + + require( + _isApprovedOrUser(_msgSender(), tokenId), + "ERCX: approve caller is not user" + ); + + _approveUser(to, tokenId); + } + + /** + * @dev Approve `to` to operate on `tokenId` tokenUser. + * + * Emits a {ApprovalUser} event. + */ + function _approveUser(address to, uint256 tokenId) internal virtual { + _tokenUserApprovals[tokenId] = to; + emit ApprovalUser(ERC721.ownerOf(tokenId), to, tokenId); + } + + /* + *@dev see{IERCX-getApprovedUser} + */ + function getApprovedUser(uint256 tokenId) public view virtual override returns (address) { + require(_exists(tokenId), "ERCX: approved query for nonexistent token"); + + return _tokenUserApprovals[tokenId]; + } + + /* + *@dev see{IERCX-balanceOfUser} + */ + function balanceOfUser(address user) public view virtual override returns (uint256) { + require(user != address(0), "ERCX: balance query for the zero address"); + return _balancesOfUser[user]; + } + + /* + *@dev see{IERCX-userOf} + */ + function userOf(uint256 tokenId) public view virtual override returns (address) { + address user = _users[tokenId]; + require(user != address(0), "ERCX: user query for nonexistent token"); + return user; + } + + /* + *@dev see{IERCX-safeTranserUserFrom} + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferUserFrom(from, to, tokenId, ""); + } + + /* + *@dev see{IERCX-safeTransferUserFrom} + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrUser(_msgSender(), tokenId) || _isApprovedOrOwner(_msgSender(),tokenId), "ERCX: transfer caller is not user or owner nor approved"); + _safeTransferUser(from, to, tokenId, _data); + } + + /* + *@dev see{IERCX-safeTransfferAllFrom} + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId + ) public virtual override { + safeTransferAllFrom(from, to, tokenId, ""); + } + + /* + *@dev see{IERCX-safeTransferAllFrom} + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) public virtual override { + require(_isApprovedOrOwner(_msgSender(),tokenId),"ERCX: transfer caller is not owner nor approved"); + safeTransferUserFrom(from, to, tokenId, ""); + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev Returns whether `spender` is allowed to manage `tokenId` tokenUser. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function _isApprovedOrUser(address spender, uint256 tokenId) internal view virtual returns (bool) { + require(ERC721._exists(tokenId), "ERCX: operator query for nonexistent token"); + address user = ERCX.userOf(tokenId); + return (spender == user || spender == getApprovedUser(tokenId) ); + } + + /** + * @dev Safely transfers `tokenId` tokenUser from `from` to `to` + * + * `_data` is additional data, it has no specified format and it is sent in call to `to`. + * + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be used or owned by 'from' + * + * Emits a {TransferUser} event. + */ + function _safeTransferUser( + address from, + address to, + uint256 tokenId, + bytes memory _data + ) internal virtual { + _transferUser(from, to, tokenId); + } + + /** + * @dev Transfers `tokenId` tokenUser 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 exist and be used or owned by 'from' + * + * Emits a {TransferUser} event. + */ + function _transferUser( + address from, + address to, + uint256 tokenId + ) internal virtual { + require(ERCX.userOf(tokenId) == from || ERC721.ownerOf(tokenId) == from, "ERCX: transfer of token that is not use"); + require(to != address(0), "ERCX: transfer to the zero address"); + address user = userOf(tokenId); + _beforeTokenTransferUser(user, to, tokenId); + + // Clear approvals from the previous owner + _approveUser(address(0), tokenId); + + _balancesOfUser[user] -= 1; + _balancesOfUser[to] += 1; + _users[tokenId] = to; + + emit TransferUser(from, to, tokenId); + } + + /** + * @dev Mints `tokenId` and transfers it to `to`. + * + * + * Requirements: + * + * - `tokenId` must not exist. + * - `to` cannot be the zero address. + * + * Emits a {Transfer} and a {TransferUser} event. + */ + function _safeMint(address to , uint256 tokenId) internal virtual override{ + require(to != address(0), "ERCX: mint to the zero address"); + + _beforeTokenTransferUser(address(0), to, tokenId); + + _balancesOfUser[to] += 1; + _users[tokenId] = to; + super._safeMint(to,tokenId); + emit TransferUser(address(0), to, tokenId); + } + + /** + * @dev Destroys `tokenId` token and its use right. + * The approval is cleared when the token is burned. + * + * Requirements: + * + * - `tokenId` must exist. + * + * Emits a {TransferUser} and a {Transfer} event. + */ + function _burn(uint256 tokenId) internal virtual override{ + super._burn(); + + address user = userOf(tokenId); + + _beforeTokenTransferUser(user, address(0), tokenId); + // Clear approvals + _approveUser(address(0), tokenId); + + _balancesOfUser[user] -= 1; + delete _users[tokenId]; + + emit TransferUser(user, address(0), tokenId); + } + + /** + * @dev Hook that is called before any tokenUser transfer. This includes minting + * and burning. + * + * Calling conditions: + * + * - When `from` and `to` are both non-zero, ``from``'s `tokenId` token use right will be + * transferred to `to`. + * + */ + function _beforeTokenTransferUser( + address from, + address to, + uint256 tokenId + ) internal virtual {} + +} diff --git a/assets/eip-4512/contracts/extensions/ERCXEnumerable.sol b/assets/eip-4512/contracts/extensions/ERCXEnumerable.sol new file mode 100644 index 0000000000000..69193a1027f25 --- /dev/null +++ b/assets/eip-4512/contracts/extensions/ERCXEnumerable.sol @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../ERCX.sol"; +import "../interface/IERCXEnumerable.sol"; + +abstract contract ERCXEnumerable is ERCX ,IERCXEnumerable{ + // Mapping from user to list of useable token IDs + mapping(address => mapping(uint256 => uint256)) private _userTokens; + + // Mapping from tokenId to index of the user tokens list + mapping(uint256 => uint256) private _userTokensIndex; + + // Mapping from owner to list of owned token IDs + mapping(address => mapping(uint256 => uint256)) private _ownedTokens; + + // Mapping from token ID to index of the owner tokens list + mapping(uint256 => uint256) private _ownedTokensIndex; + + // Array with all token ids, used for enumeration + uint256[] private _allTokens; + + // Mapping from token id to position in the allTokens array + mapping(uint256 => uint256) private _allTokensIndex; + + /** + * @dev See {IERCXEnumerable-tokenOfOwnerByIndex}. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) { + require(index < super.balanceOf(owner), "ERCXEnumerable: owner index out of bounds"); + return _ownedTokens[owner][index]; + } + + /** + * @dev See {IERCXEnumerable-tokenOfOwnerByIndex}. + */ + function tokenOfUserByIndex(address user, uint256 index) public view virtual override returns (uint256) { + require(index < super.balanceOfUser(user), "ERCXEnumerable: user index out of bounds"); + return _userTokens[user][index]; + } + + /** + * @dev See {IERCXEnumerable-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _allTokens.length; + } + + /** + * @dev See {IERCXEnumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual override returns (uint256) { + require(index < ERCXEnumerable.totalSupply(), "ERCXEnumerable: global index out of bounds"); + return _allTokens[index]; + } + + /** + * @dev Private function to add a token to this extension's ownership-tracking data structures. + * @param to address representing the new owner of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private { + uint256 length = super.balanceOf(to); + _ownedTokens[to][length] = tokenId; + _ownedTokensIndex[tokenId] = length; + } + + /** + * @dev Private function to add a token to this extension's usership-tracking data structures. + * @param to address representing the new user of the given token ID + * @param tokenId uint256 ID of the token to be added to the tokens list of the given address + */ + function _addTokenToUserEnumeration(address to, uint256 tokenId) private { + uint256 length = super.balanceOfUser(to); + _userTokens[to][length] = tokenId; + _userTokensIndex[tokenId] = length; + } + + /** + * @dev Private function to add a token to this extension's token tracking data structures. + * @param tokenId uint256 ID of the token to be added to the tokens list + */ + function _addTokenToAllTokensEnumeration(uint256 tokenId) private { + _allTokensIndex[tokenId] = _allTokens.length; + _allTokens.push(tokenId); + } + + /** + * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that + * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _ownedTokens array. + * @param from address representing the previous owner of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = super.balanceOf(from) - 1; + uint256 tokenIndex = _ownedTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _ownedTokens[from][lastTokenIndex]; + + _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + delete _ownedTokensIndex[tokenId]; + delete _ownedTokens[from][lastTokenIndex]; + } + + /** + * @dev Private function to remove a token from this extension's usership-tracking data structures. Note that + * while the token is not assigned a new user, the `_userTokensIndex` mapping is _not_ updated: this allows for + * gas optimizations e.g. when performing a transfer operation (avoiding double writes). + * This has O(1) time complexity, but alters the order of the _userTokens array. + * @param from address representing the previous user of the given token ID + * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address + */ + function _removeTokenFromUserEnumeration(address from, uint256 tokenId) private { + // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = super.balanceOfUser(from) - 1; + uint256 tokenIndex = _userTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary + if (tokenIndex != lastTokenIndex) { + uint256 lastTokenId = _userTokens[from][lastTokenIndex]; + + _userTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _userTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + } + + // This also deletes the contents at the last position of the array + delete _userTokensIndex[tokenId]; + delete _userTokens[from][lastTokenIndex]; + } + + /** + * @dev Private function to remove a token from this extension's token tracking data structures. + * This has O(1) time complexity, but alters the order of the _allTokens array. + * @param tokenId uint256 ID of the token to be removed from the tokens list + */ + function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private { + // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and + // then delete the last slot (swap and pop). + + uint256 lastTokenIndex = _allTokens.length - 1; + uint256 tokenIndex = _allTokensIndex[tokenId]; + + // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so + // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding + // an 'if' statement (like in _removeTokenFromOwnerEnumeration) + uint256 lastTokenId = _allTokens[lastTokenIndex]; + + _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token + _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index + + // This also deletes the contents at the last position of the array + delete _allTokensIndex[tokenId]; + _allTokens.pop(); + } + + /** + * @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` cannot be the zero address. + * - `to` cannot be the zero address. + * + * 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 override { + super._beforeTokenTransfer(from, to, tokenId); + + if (from == address(0)) { + _addTokenToAllTokensEnumeration(tokenId); + } else if (from != to) { + _removeTokenFromOwnerEnumeration(from, tokenId); + } + if (to == address(0)) { + _removeTokenFromAllTokensEnumeration(tokenId); + } else if (to != from) { + _addTokenToOwnerEnumeration(to, tokenId); + } + } + + /** + * @dev Hook that is called before tokenUser transfer. + */ + function _beforeTokenTransferUser( + address from, + address to, + uint256 tokenId + ) internal virtual override { + + super._beforeTokenTransferUser(from, to, tokenId); + if(from != address(0)){ + _removeTokenFromUserEnumeration(from, tokenId); + } + if(to != address(0)){ + _addTokenToUserEnumeration(to, tokenId); + } + } +} diff --git a/assets/eip-4512/contracts/interface/IERCX.sol b/assets/eip-4512/contracts/interface/IERCX.sol new file mode 100644 index 0000000000000..5c2e8ca30cce9 --- /dev/null +++ b/assets/eip-4512/contracts/interface/IERCX.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + + +pragma solidity ^0.8.0; + +import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol"; + +/** + * @dev Required interface of an ERCX compliant contract. + */ +interface IERCX is IERC721{ + + /** + * @dev Emitted when `tokenId` tokenUser is transferred from `from` to `to`. + */ + event TransferUser(address from,address to,uint256 tokenId); + + /** + * @dev Emitted when `user` enables `approved` to manage the `tokenId` tokenUser. + */ + event ApprovalUser(address indexed user, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Returns the number of usable token in ``user``'s account. + */ + function balanceOfUser(address user) external view returns (uint256 balance); + + /** + * @dev Returns the user of tokenId token + * Requirements: + * + * - `tokenId` must exist. + */ + function userOf(uint256 tokenId) external view returns (address user); + + /** + * @dev Safely transfers `tokenId` tokenUser from `from` to `to` + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be used or owned by 'from' + * - If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * + * Emits a {TransferUser} event. + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Safely transfers `tokenId` tokenUser from `from` to `to` + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be used or owned by 'from' + * - If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll} or {approveUser}. + * + * Emits a {TransferUser} event. + */ + function safeTransferUserFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @dev Safely transfers `tokenId` token and tokenUser from `from` to `to` + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by 'from' + * - If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} and a {TransferUser} event. + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId + ) external; + + /** + * @dev Safely transfers `tokenId` token and tokenUser from `from` to `to` + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `tokenId` token must exist and be owned by 'from' + * - If the caller is not `from`, it must be approved to move this tokenUser by {approve} or {setApprovalForAll}. + * + * Emits a {Transfer} and a {TransferUser} event. + */ + function safeTransferAllFrom( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external; + + /** + * @dev Gives permission to `to` to transfer `tokenId` tokenUser to another account. + * The approval is cleared when the tokenUser is transferred. + * + * Only a single account can be approved at a time, so approving the zero address clears previous approvals. + * + * Requirements: + * + * - The caller must be tokenUser or be an approvedUser operator. + * - `tokenId` must exist. + * + * Emits an {ApprovalUser} event. + */ + function approveUser(address to, uint256 tokenId) external; + + /** + * @dev Returns the approvedUser for `tokenId` token. + * + * Requirements: + * + * - `tokenId` must exist. + */ + function getApprovedUser(uint256 tokenId) external view returns (address operator); + + +} diff --git a/assets/eip-4512/contracts/interface/IERCXEnumerable.sol b/assets/eip-4512/contracts/interface/IERCXEnumerable.sol new file mode 100644 index 0000000000000..313ee55964f6b --- /dev/null +++ b/assets/eip-4512/contracts/interface/IERCXEnumerable.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IERCX.sol"; + + +interface IERCXEnumerable is IERCX { + + /** + * @dev Returns a tokenId used by `user` at a given `index` of its token list. + * Use along with {balanceOfUser} to enumerate all of ``user``'s tokens. + */ + function tokenOfUserByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns the total amount of tokens stored by the contract. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns a token ID owned by `owner` at a given `index` of its token list. + * Use along with {balanceOf} to enumerate all of ``owner``'s tokens. + */ + function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId); + + /** + * @dev Returns a tokenId at a given `index` of all the tokens stored by the contract. + * Use along with {totalSupply} to enumerate all tokens. + */ + function tokenByIndex(uint256 index) external view returns (uint256); +}