diff --git a/EIPS/eip-6147.md b/EIPS/eip-6147.md index 1c790503c96523..f45e5cbd099529 100644 --- a/EIPS/eip-6147.md +++ b/EIPS/eip-6147.md @@ -4,7 +4,7 @@ title: Guard of NFT/SBT, an Extension of EIP-721 description: A new management role of NFT/SBT is defined, which realizes the separation of transfer right and holding right of NFT/SBT. author: 5660-eth (@5660-eth), Wizard Wang discussions-to: https://ethereum-magicians.org/t/guard-of-nft-sbt-an-extension-of-eip-721/12052 -status: Draft +status: Review type: Standards Track category: ERC created: 2022-12-07 @@ -49,7 +49,6 @@ When a token has a guard, if the token burns, the guard MUST be deleted. If issuing or minting SBTs, the guard MAY be uniformly set to the designated address to facilitate management. ### Contract Interface - ```solidity interface IERC6147 { @@ -57,9 +56,9 @@ If issuing or minting SBTs, the guard MAY be uniformly set to the designated add /// Logged when the guard of an NFT is changed /// @notice Emitted when the `guard` is changed /// The zero address for guard indicates that there is no guard address - event UpdateGuardLog(uint256 indexed tokenId,address indexed newGuard,address oldGuard); + event UpdateGuardLog(uint256 indexed tokenId, address indexed newGuard, address oldGuard); - /// @notice Owner, authorised operators and approved address of the NFT can set guard of the NFT and guard can modifiy guard of the NFT + /// @notice Owner, authorised operators and approved address of the NFT can set guard of the NFT and guard can modifiy guard of the NFT /// If the NFT has a guard role, the owner, authorised operators and approved address of the NFT cannot modify guard /// @dev The newGuard can not be zero address /// Throws if `tokenId` is not valid NFT @@ -67,7 +66,6 @@ If issuing or minting SBTs, the guard MAY be uniformly set to the designated add /// @param newGuard The new guard address of the NFT function changeGuard(uint256 tokenId, address newGuard) external; - /// @notice Remove the guard of the NFT /// Only guard can remove its own guard role /// @dev The guard address is set to 0 address @@ -81,7 +79,7 @@ If issuing or minting SBTs, the guard MAY be uniformly set to the designated add /// @param from The address of the previous owner of the NFT /// @param to The address of NFT recipient /// @param tokenId The NFT to get transferred for - function transferAndRemove(address from,address to,uint256 tokenId) external; + function transferAndRemove(address from, address to, uint256 tokenId) external; /// @notice Get the guard address of the NFT /// @dev The zero address indicates that there is no guard @@ -90,7 +88,6 @@ If issuing or minting SBTs, the guard MAY be uniformly set to the designated add /// @return The guard address for the NFT function guardOf(uint256 tokenId) external view returns (address); } - ``` The `changeGuard(uint256 tokenId, address newGuard)` function MAY be implemented as `public` or `external`. @@ -135,8 +132,7 @@ The alternative names are `guardian` and `guard`, both of which basically match This standard can be fully EIP-721 compatible by adding an extension function set. -If the NFT issued based on the above standard does not set a `guard` , then it is no different from the current NFT issued based on the EIP-721 standard. - +If an NFT issued based on the above standard does not set a `guard` , then it is no different in the existing functions from the current NFT issued based on the EIP-721 standard. ## Reference Implementation @@ -151,54 +147,33 @@ abstract contract ERC6147 is ERC721, IERC6147 { mapping(uint256 => address) internal token_guard_map; - /// @notice Update the guard of the NFT - /// @dev Delete function: set guard to 0 address,update function: set guard to new address - /// Throws if `tokenId` is not valid NFT - /// @param tokenId The NFT to update the guard address for - /// @param newGuard The newGuard address - /// @param allowNull Allow 0 address - function updateGuard(uint256 tokenId,address newGuard,bool allowNull) internal { - address guard = guardOf(tokenId); - if (!allowNull) { - require(newGuard != address(0), "ERC6147: new guard can not be null"); - } - if (guard != address(0)) { - require(guard == _msgSender(), "ERC6147: only guard can change it self"); - } else { - require(_isApprovedOrOwner(_msgSender(), tokenId),""ERC6147: caller is not owner nor approved"); - } - - if (guard != address(0) || newGuard != address(0)) { - token_guard_map[tokenId] = newGuard; - emit UpdateGuardLog(tokenId, newGuard, guard); - } - } - - /// @notice Owner sets guard or guard modifies guard + /// @notice Owner, authorised operators and approved address of the NFT can set guard of the NFT and guard can modifiy guard of the NFT + /// If the NFT has a guard role, the owner, authorised operators and approved address of the NFT cannot modify guard /// @dev The newGuard can not be zero address /// Throws if `tokenId` is not valid NFT /// @param tokenId The NFT to get the guard address for /// @param newGuard The new guard address of the NFT function changeGuard(uint256 tokenId, address newGuard) public virtual{ - updateGuard(tokenId, newGuard, false); + _updateGuard(tokenId, newGuard, false); } /// @notice Remove the guard of the NFT + /// Only guard can remove its own guard role /// @dev The guard address is set to 0 address - /// Only guard can remove its own guard role /// Throws if `tokenId` is not valid NFT /// @param tokenId The NFT to remove the guard address for function removeGuard(uint256 tokenId) public virtual { - updateGuard(tokenId, address(0), true); + _updateGuard(tokenId, address(0), true); } /// @notice Transfer the NFT and remove its guard role - /// Throws if `tokenId` is not valid NFT - /// @param from The address of the previous owner of the NFT - /// @param to The address of NFT recipient - /// @param tokenId The NFT to get transferred for - function transferAndRemove(address from,address to,uint256 tokenId) public virtual { - transferFrom(from,to,tokenId); + /// @dev The NFT is transferred to `to` and the guard address is set to 0 address + /// Throws if `tokenId` is not valid NFT + /// @param from The address of the previous owner of the NFT + /// @param to The address of NFT recipient + /// @param tokenId The NFT to get transferred for + function transferAndRemove(address from, address to, uint256 tokenId) public virtual { + safeTransferFrom(from, to, tokenId); removeGuard(tokenId); } @@ -210,37 +185,52 @@ abstract contract ERC6147 is ERC721, IERC6147 { function guardOf(uint256 tokenId) public view virtual returns (address) { return token_guard_map[tokenId]; } + + /// @notice Update the guard of the NFT + /// @dev Delete function: set guard to 0 address; and update function: set guard to new address + /// Throws if `tokenId` is not valid NFT + /// @param tokenId The NFT to update the guard address for + /// @param newGuard The newGuard address + /// @param allowNull Allow 0 address + function _updateGuard(uint256 tokenId, address newGuard, bool allowNull) internal { + address guard = guardOf(tokenId); + if (!allowNull) { + require(newGuard != address(0), "ERC6147: new guard can not be null"); + } + if (guard != address(0)) { + require(guard == _msgSender(), "ERC6147: only guard can change it self"); + } else { + require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC6147: caller is not owner nor approved"); + } + + if (guard != address(0) || newGuard != address(0)) { + token_guard_map[tokenId] = newGuard; + emit UpdateGuardLog(tokenId, newGuard, guard); + } + } /// @notice Check the guard address /// @dev The zero address indicates there is no guard /// Throws if `tokenId` is not valid NFT /// @param tokenId The NFT to check the guard address for /// @return The guard address - function checkGuard(uint256 tokenId) internal view returns (address) { + function _checkGuard(uint256 tokenId) internal view returns (address) { address guard = guardOf(tokenId); address sender = _msgSender(); if (guard != address(0)) { - require(guard == sender, "sender is not guard of token"); + require(guard == sender, "ERC6147: sender is not guard of the token"); return guard; }else{ return address(0); } } - - ///@dev When burning, delete `token_guard_map[tokenId]` - function _burn(uint256 tokenId) internal virtual override { - address guard=guardOf(tokenId); - super._burn(tokenId); - delete token_guard_map[tokenId]; - emit UpdateGuardLog(tokenId, address(0), guard); - } /// @dev Before transferring the NFT, need to check the gurard address - function transferFrom(address from,address to,uint256 tokenId) public virtual override { + function transferFrom(address from, address to, uint256 tokenId) public virtual override { address guard; address new_from = from; if (from != address(0)) { - guard = checkGuard(tokenId); + guard = _checkGuard(tokenId); new_from = ownerOf(tokenId); } if (guard == address(0)) { @@ -253,11 +243,11 @@ abstract contract ERC6147 is ERC721, IERC6147 { } /// @dev Before safe transferring the NFT, need to check the gurard address - function safeTransferFrom(address from,address to,uint256 tokenId,bytes memory _data) public virtual override { + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { address guard; address new_from = from; if (from != address(0)) { - guard = checkGuard(tokenId); + guard = _checkGuard(tokenId); new_from = ownerOf(tokenId); } if (guard == address(0)) { @@ -269,20 +259,27 @@ abstract contract ERC6147 is ERC721, IERC6147 { _safeTransfer(from, to, tokenId, _data); } + /// @dev When burning, delete `token_guard_map[tokenId]` + /// This is an internal function that does not check if the sender is authorized to operate on the token. + function _burn(uint256 tokenId) internal virtual override { + address guard=guardOf(tokenId); + super._burn(tokenId); + delete token_guard_map[tokenId]; + emit UpdateGuardLog(tokenId, address(0), guard); + } + /// @dev See {IERC165-supportsInterface}. function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC6147).interfaceId || super.supportsInterface(interfaceId); } } - ``` - ## Security Considerations When an NFT has a `guard`, even if an address is authorized as an operator through `approve` or `setApprovalForAll`, the operator still has no right to transfer the NFT. -For NFT trading platforms that trade through `setApprovalForAll` + holder's signature, when an NFT has a `guard`, it cannot be traded. It is recommended to prevent such pending orders by checking the interface beforehand. +When an NFT has a `guard`, the `owner` cannot sell the NFT. Some trading platforms list NFTs through `setApprovalForAll` and owners' signature. It is recommended to prevent listing these NFTs by checking `guardOf`. ## Copyright