rippled
Loading...
Searching...
No Matches
NFTokenID.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2023 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpl/basics/base_uint.h>
21#include <xrpl/json/json_value.h>
22#include <xrpl/protocol/LedgerFormats.h>
23#include <xrpl/protocol/NFTokenID.h>
24#include <xrpl/protocol/SField.h>
25#include <xrpl/protocol/STArray.h>
26#include <xrpl/protocol/STObject.h>
27#include <xrpl/protocol/STTx.h>
28#include <xrpl/protocol/TER.h>
29#include <xrpl/protocol/TxFormats.h>
30#include <xrpl/protocol/TxMeta.h>
31#include <xrpl/protocol/jss.h>
32
33#include <algorithm>
34#include <iterator>
35#include <memory>
36#include <optional>
37#include <vector>
38
39namespace ripple {
40
41bool
43 std::shared_ptr<STTx const> const& serializedTx,
44 TxMeta const& transactionMeta)
45{
46 if (!serializedTx)
47 return false;
48
49 TxType const tt = serializedTx->getTxnType();
50 if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER &&
51 tt != ttNFTOKEN_CANCEL_OFFER)
52 return false;
53
54 // if the transaction failed nothing could have been delivered.
55 if (transactionMeta.getResultTER() != tesSUCCESS)
56 return false;
57
58 return true;
59}
60
62getNFTokenIDFromPage(TxMeta const& transactionMeta)
63{
64 // The metadata does not make it obvious which NFT was added. To figure
65 // that out we gather up all of the previous NFT IDs and all of the final
66 // NFT IDs and compare them to find what changed.
68 std::vector<uint256> finalIDs;
69
70 for (STObject const& node : transactionMeta.getNodes())
71 {
72 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE)
73 continue;
74
75 SField const& fName = node.getFName();
76 if (fName == sfCreatedNode)
77 {
78 STArray const& toAddPrevNFTs = node.peekAtField(sfNewFields)
80 .getFieldArray(sfNFTokens);
82 toAddPrevNFTs.begin(),
83 toAddPrevNFTs.end(),
84 std::back_inserter(finalIDs),
85 [](STObject const& nft) {
86 return nft.getFieldH256(sfNFTokenID);
87 });
88 }
89 else if (fName == sfModifiedNode)
90 {
91 // When a mint results in splitting an existing page,
92 // it results in a created page and a modified node. Sometimes,
93 // the created node needs to be linked to a third page, resulting
94 // in modifying that third page's PreviousPageMin or NextPageMin
95 // field changing, but no NFTs within that page changing. In this
96 // case, there will be no previous NFTs and we need to skip.
97 // However, there will always be NFTs listed in the final fields,
98 // as rippled outputs all fields in final fields even if they were
99 // not changed.
100 STObject const& previousFields =
101 node.peekAtField(sfPreviousFields).downcast<STObject>();
102 if (!previousFields.isFieldPresent(sfNFTokens))
103 continue;
104
105 STArray const& toAddPrevNFTs =
106 previousFields.getFieldArray(sfNFTokens);
108 toAddPrevNFTs.begin(),
109 toAddPrevNFTs.end(),
110 std::back_inserter(prevIDs),
111 [](STObject const& nft) {
112 return nft.getFieldH256(sfNFTokenID);
113 });
114
115 STArray const& toAddFinalNFTs = node.peekAtField(sfFinalFields)
117 .getFieldArray(sfNFTokens);
119 toAddFinalNFTs.begin(),
120 toAddFinalNFTs.end(),
121 std::back_inserter(finalIDs),
122 [](STObject const& nft) {
123 return nft.getFieldH256(sfNFTokenID);
124 });
125 }
126 }
127
128 // We expect NFTs to be added one at a time. So finalIDs should be one
129 // longer than prevIDs. If that's not the case something is messed up.
130 if (finalIDs.size() != prevIDs.size() + 1)
131 return std::nullopt;
132
133 // Find the first NFT ID that doesn't match. We're looking for an
134 // added NFT, so the one we want will be the mismatch in finalIDs.
135 auto const diff = std::mismatch(
136 finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
137
138 // There should always be a difference so the returned finalIDs
139 // iterator should never be end(). But better safe than sorry.
140 if (diff.first == finalIDs.end())
141 return std::nullopt;
142
143 return *diff.first;
144}
145
148{
149 std::vector<uint256> tokenIDResult;
150 for (STObject const& node : transactionMeta.getNodes())
151 {
152 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER ||
153 node.getFName() != sfDeletedNode)
154 continue;
155
156 auto const& toAddNFT = node.peekAtField(sfFinalFields)
157 .downcast<STObject>()
158 .getFieldH256(sfNFTokenID);
159 tokenIDResult.push_back(toAddNFT);
160 }
161
162 // Deduplicate the NFT IDs because multiple offers could affect the same NFT
163 // and hence we would get duplicate NFT IDs
164 sort(tokenIDResult.begin(), tokenIDResult.end());
165 tokenIDResult.erase(
166 unique(tokenIDResult.begin(), tokenIDResult.end()),
167 tokenIDResult.end());
168 return tokenIDResult;
169}
170
171void
173 Json::Value& response,
174 std::shared_ptr<STTx const> const& transaction,
175 TxMeta const& transactionMeta)
176{
177 if (!canHaveNFTokenID(transaction, transactionMeta))
178 return;
179
180 // We extract the NFTokenID from metadata by comparing affected nodes
181 if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
182 {
183 std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
184 if (result.has_value())
185 response[jss::nftoken_id] = to_string(result.value());
186 }
187 else if (type == ttNFTOKEN_ACCEPT_OFFER)
188 {
189 std::vector<uint256> result =
190 getNFTokenIDFromDeletedOffer(transactionMeta);
191
192 if (result.size() > 0)
193 response[jss::nftoken_id] = to_string(result.front());
194 }
195 else if (type == ttNFTOKEN_CANCEL_OFFER)
196 {
197 std::vector<uint256> result =
198 getNFTokenIDFromDeletedOffer(transactionMeta);
199
200 response[jss::nftoken_ids] = Json::Value(Json::arrayValue);
201 for (auto const& nftID : result)
202 response[jss::nftoken_ids].append(to_string(nftID));
203 }
204}
205
206} // namespace ripple
T back_inserter(T... args)
T begin(T... args)
Represents a JSON value.
Definition json_value.h:149
Identifies fields.
Definition SField.h:146
iterator end()
Definition STArray.h:230
iterator begin()
Definition STArray.h:224
D & downcast()
Definition STBase.h:213
STArray const & getFieldArray(SField const &field) const
Definition STObject.cpp:702
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:484
STBase const & peekAtField(SField const &field) const
Definition STObject.cpp:429
STArray & getNodes()
Definition TxMeta.h:89
TER getResultTER() const
Definition TxMeta.h:57
T end(T... args)
T erase(T... args)
T front(T... args)
T is_same_v
T mismatch(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:44
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition NFTokenID.cpp:62
TxType
Transaction type identifiers.
Definition TxFormats.h:57
bool canHaveNFTokenID(std::shared_ptr< STTx const > const &serializedTx, TxMeta const &transactionMeta)
Add a nftoken_ids field to the meta output parameter.
Definition NFTokenID.cpp:42
@ tesSUCCESS
Definition TER.h:245
std::vector< uint256 > getNFTokenIDFromDeletedOffer(TxMeta const &transactionMeta)
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
void insertNFTokenID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
T has_value(T... args)
T push_back(T... args)
T size(T... args)
T transform(T... args)
T value(T... args)