// SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.0; import "./ERC5007.sol"; import "./IERC5007Composable.sol"; abstract contract ERC5007Composable is ERC5007, IERC5007Composable { mapping(uint256 => uint256) internal _rootIdMapping; /** * @dev See {IERC5007Composable-rootTokenId}. */ function rootTokenId(uint256 tokenId) public view override returns (uint256 rootId) { require(_exists(tokenId), "ERC5007: invalid tokenId"); rootId = _rootIdMapping[tokenId]; } /** * @dev See {IERC5007Composable-split}. */ function split( uint256 oldTokenId, uint256 newTokenId, address newTokenOwner, int64 newTokenStartTime ) public virtual override { require( _isApprovedOrOwner(_msgSender(), oldTokenId), "ERC5007: caller is not owner nor approved" ); int64 oldTokenStartTime = _timeNftMapping[oldTokenId].startTime; int64 oldTokenEndTime = _timeNftMapping[oldTokenId].endTime; require( oldTokenStartTime < newTokenStartTime && newTokenStartTime <= oldTokenEndTime, "ERC5007: invalid newTokenStartTime" ); _timeNftMapping[oldTokenId].endTime = newTokenStartTime - 1; int64 newTokenEndTime = oldTokenEndTime; _mintTimeNftWithRootId( newTokenOwner, newTokenId, _rootIdMapping[oldTokenId], newTokenStartTime, newTokenEndTime ); } /** * @dev See {IERC5007Composable-merge}. */ function merge( uint256 firstTokenId, uint256 secondTokenId, address newTokenOwner, uint256 newTokenId ) public virtual { require( _isApprovedOrOwner(_msgSender(), firstTokenId) && _isApprovedOrOwner(_msgSender(), secondTokenId), "ERC5007: caller is not owner nor approved" ); TimeNftInfo memory firstToken = _timeNftMapping[firstTokenId]; TimeNftInfo memory secondToken = _timeNftMapping[secondTokenId]; require( _rootIdMapping[firstTokenId] == _rootIdMapping[secondTokenId] && firstToken.startTime <= firstToken.endTime && (firstToken.endTime + 1) == secondToken.startTime && secondToken.startTime <= secondToken.endTime, "ERC5007: invalid input data" ); _mintTimeNftWithRootId( newTokenOwner, newTokenId, _rootIdMapping[firstTokenId], firstToken.startTime, secondToken.endTime ); _burn(firstTokenId); _burn(secondTokenId); } /** * @dev mint a new common time NFT * * Requirements: * * - `to_` cannot be the zero address. * - `tokenId_` must not exist. * - `rootId_` must exist. * - `endTime_` should be equal or greater than `startTime_` */ function _mintTimeNftWithRootId( address to_, uint256 tokenId_, uint256 rootId_, int64 startTime_, int64 endTime_ ) internal virtual { require(_exists(rootId_), "ERC5007: invalid rootId_"); super._mintTimeNft(to_, tokenId_, startTime_, endTime_); _rootIdMapping[tokenId_] = rootId_; } /** * @dev mint a new common time NFT * * Requirements: * * - `to_` cannot be the zero address. * - `tokenId_` must not exist. * - `endTime_` should be equal or greater than `startTime_` */ function _mintTimeNft( address to_, uint256 tokenId_, int64 startTime_, int64 endTime_ ) internal virtual override { super._mintTimeNft(to_, tokenId_, startTime_, endTime_); _rootIdMapping[tokenId_] = tokenId_; } /** * @dev Destroys `tokenId`. * * Requirements: * * - `tokenId` must exist. * */ function _burn(uint256 tokenId) internal virtual override { super._burn(tokenId); delete _rootIdMapping[tokenId]; } /** * @dev See {IERC165-supportsInterface}. */ function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { return interfaceId == type(IERC5007Composable).interfaceId || super.supportsInterface(interfaceId); } }