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

EIP5008: ERC-721 Nonce Extension #5008

Merged
merged 8 commits into from May 1, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
147 changes: 147 additions & 0 deletions EIPS/eip-5008.md
@@ -0,0 +1,147 @@
---
eip: 5008
title: ERC-721 Nonce Extension
description: Add a `nonce` function to ERC-721.
author: Anders (@0xanders), Lance (@LanceSnow), Shrug <shrug@emojidao.org>
discussions-to: https://ethereum-magicians.org/t/eip5008-erc-721-nonce-and-metadata-update-extension/8925
status: Draft
type: Standards Track
category: ERC
created: 2022-04-10
requires: 165, 721
---

## Abstract

This standard is an extension of [ERC-721](./eip-721.md). It proposes adding a `nonce` function to ERC-721 tokens.

## Motivation

Some orders of NFT marketplaces have been attacked and the NFTs sold at a lower price than the current market floor price. This can happen when users transfer an NFT to another wallet and, later, back to the original wallet. This reactivates the order, which may list the token at a much lower price than the owner would have intended.

This EIP proposes adding a `nonce` property to ERC-721 tokens, and the `nonce` will be changed when a token is transferred. If a `nonce` is added to an order, the order can be checked to avoid attacks.

## Specification

The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

```solidity
interface IERC5008 is IERC165 {
/// @notice Get the nonce of an NFT
/// Throws if `tokenId` is not a valid NFT
/// @param tokenId The id of the NFT
/// @return The nonce of the NFT
function nonce(uint256 tokenId) external view returns(uint256);
}
```
The `nonce(uint256 tokenId)` function MUST be implemented as `view`.

The `supportsInterface` method MUST return `true` when called with `0xce03fdab`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably want to specify the EIP-165 identifier, and that implementers of this EIP must handle supportsInterface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

The supportsInterface method MUST return true when called with 0xce03fdab.

## Rationale

At first `transferCount` was considered as function name, but there may some case to change the `nonce` besides transfer, such as important properties changed, then we changed `transferCount` to `nonce`.


## Backwards Compatibility

This standard is compatible with ERC-721 standards.

## Test Cases

### Test Contract

```solidity
pragma solidity 0.8.10;
import "./ERC5008.sol";

contract ERC5008Demo is ERC5008{
mapping(uint256 => uint256) private _tokenData;

constructor(string memory name_, string memory symbol_)ERC5008(name_, symbol_){
}

/// @notice mint a new NFT
/// @param to The owner of the new token
/// @param tokenId The id of the new token
function mint(address to, uint256 tokenId) public {
_mint(to, id);
}

}

```
### Test Code

run in terminal: `npm hardhat test`

```TypeScript
import { expect } from "chai";
import { ethers } from "hardhat";

describe("Test ERC5008 ", function () {

let [alice, bob] = await ethers.getSigners();

const ERC5008Demo = await ethers.getContractFactory("ERC5008Demo");

let contract = await ERC5008Demo.deploy();

let tokenId = 1;
await contract.mint(alice.address, tokenId);

expect(await contract.nonce(tokenId)).equals(1);

await contract.transferFrom(alice.address, bob.address, tokenId);

expect(await contract.nonce(tokenId)).equals(2);

});
```

## Reference Implementation

```solidity
// SPDX-License-Identifier: CC0
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "./IERC5008.sol";

contract ERC5008 is ERC721, IERC5008 {
mapping(uint256 => uint256) private _tokenNonce;

constructor(string memory name_, string memory symbol_)ERC721(name_, symbol_){
}

/// @notice Get the nonce of an NFT
/// Throws if `tokenId` is not a valid NFT
/// @param tokenId The NFT to get the nonce for
/// @return The nonce of this NFT
function nonce(uint256 tokenId) public virtual override view returns(uint256) {
require(_exists(tokenId), "Error: query for nonexistent token");

return _tokenNonce[tokenId];
}

function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override{
super._beforeTokenTransfer(from, to, tokenId);
_tokenNonce[tokenId]++;
}

/// @dev See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
return interfaceId == type(IERC5008).interfaceId || super.supportsInterface(interfaceId);
}
}
```

## Security Considerations
No security issues found.

## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).