From 7134471b8657bbbee55afb17633935723d3efcd2 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 30 Jan 2025 13:50:39 +0100 Subject: [PATCH] feat: getnftinfo will return its metadata and creation time (#164) Signed-off-by: Mariusz Jasuwienas --- contracts/HtsSystemContract.sol | 37 ++++++++++++++++++++++------- contracts/HtsSystemContractJson.sol | 11 +++++++++ contracts/MirrorNode.sol | 8 +++++++ test/HTS.t.sol | 7 ++++++ 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/contracts/HtsSystemContract.sol b/contracts/HtsSystemContract.sol index 4a897b8b..8208cad9 100644 --- a/contracts/HtsSystemContract.sol +++ b/contracts/HtsSystemContract.sol @@ -414,16 +414,15 @@ contract HtsSystemContract is IHederaTokenService { returns (int64, NonFungibleTokenInfo memory) { (int64 responseCode, TokenInfo memory tokenInfo) = getTokenInfo(token); require(responseCode == HederaResponseCodes.SUCCESS, "getNonFungibleTokenInfo: failed to get token data"); - NonFungibleTokenInfo memory nonFungibleTokenInfo; + (, NonFungibleTokenInfo memory nonFungibleTokenInfo) = IHederaTokenService(token).getNonFungibleTokenInfo( + token, + serialNumber + ); nonFungibleTokenInfo.tokenInfo = tokenInfo; nonFungibleTokenInfo.serialNumber = serialNumber; - nonFungibleTokenInfo.spenderId = IERC721(token).getApproved(uint256(uint64(serialNumber))); - nonFungibleTokenInfo.ownerId = IERC721(token).ownerOf(uint256(uint64(serialNumber))); - - // ToDo: - // nonFungibleTokenInfo.metadata = bytes(IERC721(token).tokenURI(uint256(uint64(serialNumber)))); - // nonFungibleTokenInfo.creationTime = int64(0); - + uint256 serial = uint256(uint64(serialNumber)); + nonFungibleTokenInfo.spenderId = IERC721(token).getApproved(serial); + nonFungibleTokenInfo.ownerId = IERC721(token).ownerOf(serial); return (responseCode, nonFungibleTokenInfo); } @@ -593,6 +592,15 @@ contract HtsSystemContract is IHederaTokenService { _setApprovalForAll(from, to, approved); return abi.encode(true); } + if (selector == this.getNonFungibleTokenInfo.selector) { + require(msg.data.length >= 92, "getNonFungibleTokenInfo: Not enough calldata"); + uint256 serialId = uint256(bytes32(msg.data[60:92])); + NonFungibleTokenInfo memory info; + (int64 creationTime, bytes memory metadata) = __nftInfo(serialId); + info.creationTime = creationTime; + info.metadata = metadata; + return abi.encode(HederaResponseCodes.SUCCESS, info); + } if (selector == this._update.selector) { require(msg.data.length >= 124, "update: Not enough calldata"); address from = address(bytes20(msg.data[40:60])); @@ -788,6 +796,12 @@ contract HtsSystemContract is IHederaTokenService { return bytes32(abi.encodePacked(selector, pad, serialId)); } + function __nftInfoSlot(uint32 serialId) internal virtual returns (bytes32) { + bytes4 selector = IHederaTokenService.getNonFungibleTokenInfo.selector; + uint192 pad = 0x0; + return bytes32(abi.encodePacked(selector, pad, serialId)); + } + function _ownerOfSlot(uint32 serialId) internal virtual returns (bytes32) { bytes4 selector = IERC721.ownerOf.selector; uint192 pad = 0x0; @@ -825,6 +839,13 @@ contract HtsSystemContract is IHederaTokenService { uri = _uri; } + function __nftInfo(uint256 serialId) private returns (int64, bytes memory) { + bytes32 slot = __nftInfoSlot(uint32(serialId)); + bytes storage _nftInfo; + assembly { _nftInfo.slot := slot } + return abi.decode(_nftInfo, (int64, bytes)); + } + function __ownerOf(uint256 serialId) private returns (address owner) { bytes32 slot = _ownerOfSlot(uint32(serialId)); assembly { owner := sload(slot) } diff --git a/contracts/HtsSystemContractJson.sol b/contracts/HtsSystemContractJson.sol index 2b6b59e0..e1e6c74b 100644 --- a/contracts/HtsSystemContractJson.sol +++ b/contracts/HtsSystemContractJson.sol @@ -463,6 +463,17 @@ contract HtsSystemContractJson is HtsSystemContract { return slot; } + function __nftInfoSlot(uint32 serialId) internal override virtual returns (bytes32) { + bytes32 slot = super.__nftInfoSlot(serialId); + if (_shouldFetch(slot)) { + string memory metadata = mirrorNode().getNftMetadata(address(this), serialId); + string memory createdTimestamp = mirrorNode().getNftCreatedTimestamp(address(this), serialId); + int64 creationTime = int64(vm.parseInt(vm.split(createdTimestamp, ".")[0])); + storeBytes(address(this), uint256(slot), abi.encode(creationTime, bytes(metadata))); + } + return slot; + } + function _ownerOfSlot(uint32 serialId) internal override virtual returns (bytes32) { bytes32 slot = super._ownerOfSlot(serialId); if (_shouldFetch(slot)) { diff --git a/contracts/MirrorNode.sol b/contracts/MirrorNode.sol index 8aef3422..f4616077 100644 --- a/contracts/MirrorNode.sol +++ b/contracts/MirrorNode.sol @@ -40,6 +40,14 @@ abstract contract MirrorNode { return ""; } + function getNftCreatedTimestamp(address token, uint32 serial) external returns (string memory) { + string memory json = this.fetchNonFungibleToken(token, serial); + if (vm.keyExistsJson(json, ".created_timestamp")) { + return vm.parseJsonString(json, ".created_timestamp"); + } + return ""; + } + function getNftOwner(address token, uint32 serial) external returns (address) { string memory json = this.fetchNonFungibleToken(token, serial); if (vm.keyExistsJson(json, ".account_id")) { diff --git a/test/HTS.t.sol b/test/HTS.t.sol index 12597d5c..bade938a 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -628,6 +628,13 @@ contract HTSTest is Test, TestSetup { assertEq(nonFungibleTokenInfo.tokenInfo.fractionalFees.length, 0); assertEq(nonFungibleTokenInfo.tokenInfo.royaltyFees.length, 0); assertEq(nonFungibleTokenInfo.tokenInfo.ledgerId, testMode == TestMode.FFI ? "0x01" : "0x00"); + + // Additional information, not a part of the IERC721 interface. + assertEq( + string(nonFungibleTokenInfo.metadata), + "aHR0cHM6Ly92ZXJ5LWxvbmctc3RyaW5nLXdoaWNoLWV4Y2VlZHMtMzEtYnl0ZXMtYW5kLXJlcXVpcmVzLW11bHRpcGxlLXN0b3JhZ2Utc2xvdHMuY29tLzE=" + ); + assertEq(nonFungibleTokenInfo.creationTime, 1734948254); } function test_HTS_transferToken() external {