// SPDX-License-Identifier: MIT // ERC721A Contracts v4.3.0 // Creator: Chiru Labs pragma solidity ^0.8.4; import './IERC4907AUpgradeable.sol'; import '../ERC721AUpgradeable.sol'; import {ERC4907AStorage} from './ERC4907AStorage.sol'; import '../ERC721A__Initializable.sol'; /** * @title ERC4907A * * @dev [ERC4907](https://eips.ethereum.org/EIPS/eip-4907) compliant * extension of ERC721A, which allows owners and authorized addresses * to add a time-limited role with restricted permissions to ERC721 tokens. */ abstract contract ERC4907AUpgradeable is ERC721A__Initializable, ERC721AUpgradeable, IERC4907AUpgradeable { using ERC4907AStorage for ERC4907AStorage.Layout; function __ERC4907A_init() internal onlyInitializingERC721A { __ERC4907A_init_unchained(); } function __ERC4907A_init_unchained() internal onlyInitializingERC721A {} // The bit position of `expires` in packed user info. uint256 private constant _BITPOS_EXPIRES = 160; /** * @dev Sets the `user` and `expires` for `tokenId`. * The zero address indicates there is no user. * * Requirements: * * - The caller must own `tokenId` or be an approved operator. */ function setUser( uint256 tokenId, address user, uint64 expires ) public virtual override { // Require the caller to be either the token owner or an approved operator. address owner = ownerOf(tokenId); if (_msgSenderERC721A() != owner) if (!isApprovedForAll(owner, _msgSenderERC721A())) if (getApproved(tokenId) != _msgSenderERC721A()) _revert(SetUserCallerNotOwnerNorApproved.selector); ERC4907AStorage.layout()._packedUserInfo[tokenId] = (uint256(expires) << _BITPOS_EXPIRES) | uint256(uint160(user)); emit UpdateUser(tokenId, user, expires); } /** * @dev Returns the user address for `tokenId`. * The zero address indicates that there is no user or if the user is expired. */ function userOf(uint256 tokenId) public view virtual override returns (address) { uint256 packed = ERC4907AStorage.layout()._packedUserInfo[tokenId]; assembly { // Branchless `packed *= (block.timestamp <= expires ? 1 : 0)`. // If the `block.timestamp == expires`, the `lt` clause will be true // if there is a non-zero user address in the lower 160 bits of `packed`. packed := mul( packed, // `block.timestamp <= expires ? 1 : 0`. lt(shl(_BITPOS_EXPIRES, timestamp()), packed) ) } return address(uint160(packed)); } /** * @dev Returns the user's expires of `tokenId`. */ function userExpires(uint256 tokenId) public view virtual override returns (uint256) { return ERC4907AStorage.layout()._packedUserInfo[tokenId] >> _BITPOS_EXPIRES; } /** * @dev Override of {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721AUpgradeable, IERC721AUpgradeable) returns (bool) { // The interface ID for ERC4907 is `0xad092b5c`, // as defined in [ERC4907](https://eips.ethereum.org/EIPS/eip-4907). return super.supportsInterface(interfaceId) || interfaceId == 0xad092b5c; } /** * @dev Returns the user address for `tokenId`, ignoring the expiry status. */ function _explicitUserOf(uint256 tokenId) internal view virtual returns (address) { return address(uint160(ERC4907AStorage.layout()._packedUserInfo[tokenId])); } }