Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update EIP-6147: Move to Review #6452

Merged
merged 1 commit into from
Feb 7, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 56 additions & 59 deletions EIPS/eip-6147.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,25 +49,23 @@ 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 {

/// 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
/// @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) 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
Expand All @@ -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
Expand All @@ -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`.
Expand Down Expand Up @@ -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

Expand All @@ -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);
}

Expand All @@ -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)) {
Expand All @@ -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)) {
Expand All @@ -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

Expand Down