When I was testing ERC-4907 contract, I found out something. Let’s say an NFT with token id:1 was minted on user(1) and rented to user(2) for 3 days. Now during these 3 days, whenever user(1) transfers that NFT to let’s assume user(3), user(2) which should have access to rented NFT for 3 days, now no longer have access to that NFT. userOf(1) sets to address(0). This can be overcome by adding modifiers and overriding transfer and safeTransfer functions of ERC-721.
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "./IERC4907.sol";
contract ERC4907 is ERC721, IERC4907 {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
struct UserInfo
{
address user; // address of user role
uint64 expires; // unix timestamp, user expires
}
mapping (uint256 => UserInfo) internal _users;
modifier notRented(uint256 tokenId){
require(userOf(tokenId)==address(0),"can not transfer rented NFT");
_;
}
constructor(string memory name_, string memory symbol_)
ERC721(name_,symbol_)
{
}
function safeMint(address to) public {
_tokenIdCounter.increment();
uint256 tokenId = _tokenIdCounter.current();
_safeMint(to, tokenId);
}
/// @notice set the user and expires of a NFT
/// @dev The zero address indicates there is no user
/// Throws if `tokenId` is not valid NFT
/// @param user The new user of the NFT
/// @param expires UNIX timestamp, The new user could use the NFT before expires
function setUser(uint256 tokenId, address user, uint64 expires) public virtual{
require(_isApprovedOrOwner(msg.sender, tokenId),"ERC721: transfer caller is not owner nor approved");
UserInfo storage info = _users[tokenId];
info.user = user;
info.expires = expires;
emit UpdateUser(tokenId,user,expires);
}
function getTimestamp()public view returns(uint256){
return block.timestamp +300;
}
/// @notice Get the user1664725514 address of an NFT
/// @dev The zero address indicates that there is no user or the user is expired
/// @param tokenId The NFT to get the user address for
/// @return The user address for this NFT
function userOf(uint256 tokenId)public view virtual returns(address){
if( uint256(_users[tokenId].expires) >= block.timestamp){
return _users[tokenId].user;
}
else{
return address(0);
}
}
/// @notice Get the user expires of an NFT
/// @dev The zero value indicates that there is no user
/// @param tokenId The NFT to get the user expires for
/// @return The user expires for this NFT
function userExpires(uint256 tokenId) public view virtual returns(uint256){
return _users[tokenId].expires;
}
/// @dev See {IERC165-supportsInterface}.
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC4907).interfaceId || super.supportsInterface(interfaceId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual override{
super._beforeTokenTransfer(from, to, tokenId);
if (from != to && _users[tokenId].user != address(0)) {
delete _users[tokenId];
emit UpdateUser(tokenId, address(0), 0);
}
}
function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override notRented(tokenId){
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_transfer(from, to, tokenId);
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) public virtual override notRented(tokenId){
safeTransferFrom(from, to, tokenId, "");
}
/**
* @dev See {IERC721-safeTransferFrom}.
*/
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
) public virtual override notRented(tokenId){
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
_safeTransfer(from, to, tokenId, data);
}
}