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

EIP5007: TimeNFT, ERC-721 Time Extension #5007

Merged
merged 26 commits into from May 28, 2022
Merged
Show file tree
Hide file tree
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
73 changes: 73 additions & 0 deletions EIPS/eip-5007.md
@@ -0,0 +1,73 @@
---
eip: 5007
title: TimeNFT, ERC-721 Time Extension
description: Add start time and end time to ERC-721 tokens.
author: Anders (@0xanders), Lance (@LanceSnow), Shrug <shrug@emojidao.org>
discussions-to: https://ethereum-magicians.org/t/eip5007-erc721-time-extension/8924
status: Draft
type: Standards Track
category: ERC
created: 2022-04-13
requires: 165, 721
---

## Abstract

This standard is an extension of [ERC-721](./eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management.

## Motivation

Some NFTs have a defined usage period and cannot be used outside of that period. With traditional NFTs that do not include time information, if you want to mark a token as invalid or enable it at a specific time, you need to actively submit a transaction—a process both cumbersome and expensive.

Some existing NFTs contain time functions, but their interfaces are not consistent, so it is difficult to develop third-party platforms for them.

By introducing these functions (`startTime`, `endTime`), it is possible to enable and disable NFT automatically on chain.

## 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 IERC5007 /* is IERC721 */ {
/// @notice Get the start time of the NFT
/// @dev Throws if `tokenId` is not valid NFT
/// @param tokenId The tokenId of the NFT
/// @return The start time of the NFT
function startTime(uint256 tokenId) external view returns (uint64);
Comment on lines +32 to +36
Copy link
Contributor

Choose a reason for hiding this comment

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

If block.timestamp == IERC5007.startTime(0x...), is the token valid or invalid?

Copy link
Contributor

Choose a reason for hiding this comment

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

If block.timestamp == IERC5007.startTime(0x...), is the token valid or invalid?

valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright, note that in the EIP!


/// @notice Get the end time of the NFT
/// @dev Throws if `tokenId` is not valid NFT
/// @param tokenId The tokenId of the NFT
/// @return The end time of the NFT
function endTime(uint256 tokenId) external view returns (uint64);
Copy link
Contributor

Choose a reason for hiding this comment

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

If block.timestamp == IERC5007.endTime(0x...), is the token valid or invalid?

Copy link
Contributor

Choose a reason for hiding this comment

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

If block.timestamp == IERC5007.endTime(0x...), is the token valid or invalid?

valid.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also note this too. I know it's in the comment for isValidNow, but it could be more visible.


}
```

This comment was marked as resolved.

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

## Rationale

### Time Data Type

The max value of `uint64` is 18446744073709551615, timestamp 18446744073709551615 is about year 584942419325. `uint256` is too big for some software such as MySQL, Elastic Search, and `uint64` is natively supported on mainstream programming languages.
Copy link

Choose a reason for hiding this comment

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

Should really show a more detailed analysis of gas-savings with a real use case if you are going to take a non-uint256 opinionated view. This seems like the main property of the current EIP that needs further discussion.


## Backwards Compatibility

As mentioned in the specifications section, this standard can be fully ERC-721 compatible by adding an extension function set.

## Test Cases

Test cases are included in [test.js](../assets/eip-5007/test/test.js).

See [README.md](../assets/eip-5007/README.md) for how to run the test cases.

## Reference Implementation
See [ERC5007.sol](../assets/eip-5007/contracts/ERC5007.sol).

## Security Considerations

No security issues found.

## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).
2 changes: 2 additions & 0 deletions assets/eip-5007/.gitignore
@@ -0,0 +1,2 @@
node_modules
package-lock.json
15 changes: 15 additions & 0 deletions assets/eip-5007/README.md
@@ -0,0 +1,15 @@
# EIP-5007
This standard is an extension of [ERC-721](../../EIPS/eip-721.md). It proposes some additional functions (`startTime`, `endTime`) to help with on-chain time management.

## Tools
* [Truffle](https://truffleframework.com/) - a development framework for Ethereum

## Install
```
npm install
```

## Test
```
truffle test
```
63 changes: 63 additions & 0 deletions assets/eip-5007/contracts/ERC5007.sol
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: CC0
pragma solidity ^0.8.0;

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

contract ERC5007 is ERC721, IERC5007 {

struct TimeNftInfo {
uint64 startTime;
uint64 endTime;
}

mapping(uint256 /* tokenId */ => TimeNftInfo) internal _timeNftMapping;

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

/// @notice Get the start time of the token
/// @dev Throws if `tokenId` is not valid token
/// @param tokenId The tokenId of the token
/// @return The start time of the token
function startTime(uint256 tokenId) public view virtual override returns (uint64) {
require(_exists(tokenId),"invalid tokenId");
return _timeNftMapping[tokenId].startTime;
}

/// @notice Get the end time of the token
/// @dev Throws if `tokenId` is not valid token
/// @param tokenId The tokenId of the token
/// @return The end time of the token
function endTime(uint256 tokenId) public view virtual override returns (uint64) {
require(_exists(tokenId),"invalid tokenId");
return _timeNftMapping[tokenId].endTime;
}


/// @notice mint a new time NFT
/// @param to_ The owner of the new token
/// @param id_ The id of the new token
/// @param startTime_ The start time of the new token
/// @param endTime_ The end time of the new token
function _mintTimeNft(address to_, uint256 id_, uint64 startTime_, uint64 endTime_) internal virtual {
_mint(to_, id_);

TimeNftInfo storage info = _timeNftMapping[id_];
info.startTime = startTime_;
info.endTime = endTime_;
}


/// @notice burn a time NFT
/// @param tokenId The id of the token
function _burn(uint256 tokenId) internal virtual override{
super._burn(tokenId);
delete _timeNftMapping[tokenId];
}

/// @dev See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC5007).interfaceId || super.supportsInterface(interfaceId);
}
}
23 changes: 23 additions & 0 deletions assets/eip-5007/contracts/ERC5007Demo.sol
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: CC0
pragma solidity ^0.8.0;

import "./ERC5007.sol";

contract ERC5007Demo is ERC5007{

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

/// @notice mint a new original time NFT
/// @param to_ The owner of the new token
/// @param id_ The id of the new token
/// @param startTime_ The start time of the new token
/// @param endTime_ The end time of the new token
function mint(address to_, uint256 id_, uint64 startTime_, uint64 endTime_) public {
_mintTimeNft(to_, id_, startTime_, endTime_);
}

function getInterfaceId() public pure returns (bytes4) {
return type(IERC5007).interfaceId ;
}
}
17 changes: 17 additions & 0 deletions assets/eip-5007/contracts/IERC5007.sol
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: CC0

pragma solidity ^0.8.0;

interface IERC5007 /* is IERC721 */ {
/// @notice Get the start time of the NFT
/// @dev Throws if `tokenId` is not valid NFT
/// @param tokenId The tokenId of the NFT
/// @return The start time of the NFT
function startTime(uint256 tokenId) external view returns (uint64);

/// @notice Get the end time of the NFT
/// @dev Throws if `tokenId` is not valid NFT
/// @param tokenId The tokenId of the NFT
/// @return The end time of the NFT
function endTime(uint256 tokenId) external view returns (uint64);
}
6 changes: 6 additions & 0 deletions assets/eip-5007/migrations/1_initial_migration.js
@@ -0,0 +1,6 @@
const ERC5007Demo = artifacts.require("ERC5007Demo");

module.exports = function (deployer) {
deployer.deploy(ERC5007Demo,'ERC5007Demo','ERC5007Demo');
};

20 changes: 20 additions & 0 deletions assets/eip-5007/package.json
@@ -0,0 +1,20 @@
{
"name": "ERC5007",
"version": "1.0.0",
"description": "",
"main": "truffle-config.js",
"directories": {
"test": "test"
},
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@openzeppelin/contracts": "^4.3.3",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.1.0",
"bignumber.js": "^9.0.1",
"chai": "^4.3.6"
}
}
33 changes: 33 additions & 0 deletions assets/eip-5007/test/test.js
@@ -0,0 +1,33 @@
const { assert } = require("chai");

const { BigNumber } = require("bignumber.js")

const ERC5007Demo = artifacts.require("ERC5007Demo");

contract("test ERC5007", async accounts => {

it("test TimeNFT", async () => {
const Alice = accounts[0];

const instance = await ERC5007Demo.deployed("ERC5007Demo", "ERC5007Demo");
const demo = instance;

let now = Math.floor(new Date().getTime()/1000);
let inputStartTime1 = new BigNumber(now - 10000);
let inputEndTime1 = new BigNumber(now + 10000);
let id1 = 1;

await demo.mint(Alice, id1, inputStartTime1.toFixed(0), inputEndTime1.toFixed(0));


let outputStartTime1 = await demo.startTime(id1);
let outputEndTime1 = await demo.endTime(id1);
assert.equal(inputStartTime1.comparedTo(outputStartTime1) == 0 && inputEndTime1.comparedTo(outputEndTime1) == 0, true, "wrong data");


console.log("InterfaceId:", await demo.getInterfaceId())
let isSupport = await demo.supportsInterface('0x7a0cdf92');
assert.equal(isSupport, true , "supportsInterface error");

});
});
117 changes: 117 additions & 0 deletions assets/eip-5007/truffle-config.js
@@ -0,0 +1,117 @@
/**
* Use this file to configure your truffle project. It's seeded with some
* common settings for different networks and features like migrations,
* compilation and testing. Uncomment the ones you need or modify
* them to suit your project as necessary.
*
* More information about configuration can be found at:
*
* trufflesuite.com/docs/advanced/configuration
*
* To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
* to sign your transactions before they're sent to a remote public node. Infura accounts
* are available for free at: infura.io/register.
*
* You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
* public/private key pairs. If you're publishing your code to GitHub make sure you load this
* phrase from a file you've .gitignored so it doesn't accidentally become public.
*
*/

// const HDWalletProvider = require('@truffle/hdwallet-provider');
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
/**
* Networks define how you connect to your ethereum client and let you set the
* defaults web3 uses to send transactions. If you don't specify one truffle
* will spin up a development blockchain for you on port 9545 when you
* run `develop` or `test`. You can ask a truffle command to use a specific
* network from the command line, e.g
*
* $ truffle test --network <network-name>
*/

networks: {
// Useful for testing. The `development` name is special - truffle uses it by default
// if it's defined here and no other network is specified at the command line.
// You should run a client (like ganache-cli, geth or parity) in a separate terminal
// tab if you use this network and you must also set the `host`, `port` and `network_id`
// options below to some value.
//
// development: {
// host: "127.0.0.1", // Localhost (default: none)
// port: 8545, // Standard Ethereum port (default: none)
// network_id: "*", // Any network (default: none)
// },
// Another network with more advanced options...
// advanced: {
// port: 8777, // Custom port
// network_id: 1342, // Custom network
// gas: 8500000, // Gas sent with each transaction (default: ~6700000)
// gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei)
// from: <address>, // Account to send txs from (default: accounts[0])
// websocket: true // Enable EventEmitter interface for web3 (default: false)
// },
// Useful for deploying to a public network.
// NB: It's important to wrap the provider as a function.
// ropsten: {
// provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
// network_id: 3, // Ropsten's id
// gas: 5500000, // Ropsten has a lower block limit than mainnet
// confirmations: 2, // # of confs to wait between deployments. (default: 0)
// timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
// skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
// },
// Useful for private networks
// private: {
// provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
// network_id: 2111, // This network is yours, in the cloud.
// production: true // Treats this network as if it was a public net. (default: false)
// }
},

// Set default mocha options here, use special reporters etc.
mocha: {
// timeout: 100000
},

// Configure your compilers
compilers: {
solc: {
version: "0.8.10", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200
}
// ,
// evmVersion: "byzantium"
// }
}
},

// Truffle DB is currently disabled by default; to enable it, change enabled:
// false to enabled: true. The default storage location can also be
// overridden by specifying the adapter settings, as shown in the commented code below.
//
// NOTE: It is not possible to migrate your contracts to truffle DB and you should
// make a backup of your artifacts to a safe location before enabling this feature.
//
// After you backed up your artifacts you can utilize db by running migrate as follows:
// $ truffle migrate --reset --compile-all
//
// db: {
// enabled: false,
// host: "127.0.0.1",
// adapter: {
// name: "sqlite",
// settings: {
// directory: ".db"
// }
// }
}
};