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/protocol/NFTokenID.h>
21#include <xrpl/protocol/jss.h>
22
23namespace ripple {
24
25bool
27 std::shared_ptr<STTx const> const& serializedTx,
28 TxMeta const& transactionMeta)
29{
30 if (!serializedTx)
31 return false;
32
33 TxType const tt = serializedTx->getTxnType();
34 if (tt != ttNFTOKEN_MINT && tt != ttNFTOKEN_ACCEPT_OFFER &&
35 tt != ttNFTOKEN_CANCEL_OFFER)
36 return false;
37
38 // if the transaction failed nothing could have been delivered.
39 if (transactionMeta.getResultTER() != tesSUCCESS)
40 return false;
41
42 return true;
43}
44
46getNFTokenIDFromPage(TxMeta const& transactionMeta)
47{
48 // The metadata does not make it obvious which NFT was added. To figure
49 // that out we gather up all of the previous NFT IDs and all of the final
50 // NFT IDs and compare them to find what changed.
52 std::vector<uint256> finalIDs;
53
54 for (STObject const& node : transactionMeta.getNodes())
55 {
56 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_PAGE)
57 continue;
58
59 SField const& fName = node.getFName();
60 if (fName == sfCreatedNode)
61 {
62 STArray const& toAddPrevNFTs = node.peekAtField(sfNewFields)
64 .getFieldArray(sfNFTokens);
66 toAddPrevNFTs.begin(),
67 toAddPrevNFTs.end(),
68 std::back_inserter(finalIDs),
69 [](STObject const& nft) {
70 return nft.getFieldH256(sfNFTokenID);
71 });
72 }
73 else if (fName == sfModifiedNode)
74 {
75 // When a mint results in splitting an existing page,
76 // it results in a created page and a modified node. Sometimes,
77 // the created node needs to be linked to a third page, resulting
78 // in modifying that third page's PreviousPageMin or NextPageMin
79 // field changing, but no NFTs within that page changing. In this
80 // case, there will be no previous NFTs and we need to skip.
81 // However, there will always be NFTs listed in the final fields,
82 // as rippled outputs all fields in final fields even if they were
83 // not changed.
84 STObject const& previousFields =
85 node.peekAtField(sfPreviousFields).downcast<STObject>();
86 if (!previousFields.isFieldPresent(sfNFTokens))
87 continue;
88
89 STArray const& toAddPrevNFTs =
90 previousFields.getFieldArray(sfNFTokens);
92 toAddPrevNFTs.begin(),
93 toAddPrevNFTs.end(),
94 std::back_inserter(prevIDs),
95 [](STObject const& nft) {
96 return nft.getFieldH256(sfNFTokenID);
97 });
98
99 STArray const& toAddFinalNFTs = node.peekAtField(sfFinalFields)
101 .getFieldArray(sfNFTokens);
103 toAddFinalNFTs.begin(),
104 toAddFinalNFTs.end(),
105 std::back_inserter(finalIDs),
106 [](STObject const& nft) {
107 return nft.getFieldH256(sfNFTokenID);
108 });
109 }
110 }
111
112 // We expect NFTs to be added one at a time. So finalIDs should be one
113 // longer than prevIDs. If that's not the case something is messed up.
114 if (finalIDs.size() != prevIDs.size() + 1)
115 return std::nullopt;
116
117 // Find the first NFT ID that doesn't match. We're looking for an
118 // added NFT, so the one we want will be the mismatch in finalIDs.
119 auto const diff = std::mismatch(
120 finalIDs.begin(), finalIDs.end(), prevIDs.begin(), prevIDs.end());
121
122 // There should always be a difference so the returned finalIDs
123 // iterator should never be end(). But better safe than sorry.
124 if (diff.first == finalIDs.end())
125 return std::nullopt;
126
127 return *diff.first;
128}
129
132{
133 std::vector<uint256> tokenIDResult;
134 for (STObject const& node : transactionMeta.getNodes())
135 {
136 if (node.getFieldU16(sfLedgerEntryType) != ltNFTOKEN_OFFER ||
137 node.getFName() != sfDeletedNode)
138 continue;
139
140 auto const& toAddNFT = node.peekAtField(sfFinalFields)
141 .downcast<STObject>()
142 .getFieldH256(sfNFTokenID);
143 tokenIDResult.push_back(toAddNFT);
144 }
145
146 // Deduplicate the NFT IDs because multiple offers could affect the same NFT
147 // and hence we would get duplicate NFT IDs
148 sort(tokenIDResult.begin(), tokenIDResult.end());
149 tokenIDResult.erase(
150 unique(tokenIDResult.begin(), tokenIDResult.end()),
151 tokenIDResult.end());
152 return tokenIDResult;
153}
154
155void
157 Json::Value& response,
158 std::shared_ptr<STTx const> const& transaction,
159 TxMeta const& transactionMeta)
160{
161 if (!canHaveNFTokenID(transaction, transactionMeta))
162 return;
163
164 // We extract the NFTokenID from metadata by comparing affected nodes
165 if (auto const type = transaction->getTxnType(); type == ttNFTOKEN_MINT)
166 {
167 std::optional<uint256> result = getNFTokenIDFromPage(transactionMeta);
168 if (result.has_value())
169 response[jss::nftoken_id] = to_string(result.value());
170 }
171 else if (type == ttNFTOKEN_ACCEPT_OFFER)
172 {
173 std::vector<uint256> result =
174 getNFTokenIDFromDeletedOffer(transactionMeta);
175
176 if (result.size() > 0)
177 response[jss::nftoken_id] = to_string(result.front());
178 }
179 else if (type == ttNFTOKEN_CANCEL_OFFER)
180 {
181 std::vector<uint256> result =
182 getNFTokenIDFromDeletedOffer(transactionMeta);
183
184 response[jss::nftoken_ids] = Json::Value(Json::arrayValue);
185 for (auto const& nftID : result)
186 response[jss::nftoken_ids].append(to_string(nftID));
187 }
188}
189
190} // namespace ripple
T back_inserter(T... args)
T begin(T... args)
Represents a JSON value.
Definition: json_value.h:147
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:891
Identifies fields.
Definition: SField.h:144
iterator end()
Definition: STArray.h:230
iterator begin()
Definition: STArray.h:224
D & downcast()
Definition: STBase.h:202
const STArray & getFieldArray(SField const &field) const
Definition: STObject.cpp:656
const STBase & peekAtField(SField const &field) const
Definition: STObject.cpp:399
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
STArray & getNodes()
Definition: TxMeta.h:100
TER getResultTER() const
Definition: TxMeta.h:68
T end(T... args)
T erase(T... args)
T front(T... args)
T mismatch(T... args)
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
std::optional< uint256 > getNFTokenIDFromPage(TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:46
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:26
@ tesSUCCESS
Definition: TER.h:242
std::vector< uint256 > getNFTokenIDFromDeletedOffer(TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:131
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
void insertNFTokenID(Json::Value &response, std::shared_ptr< STTx const > const &transaction, TxMeta const &transactionMeta)
Definition: NFTokenID.cpp:156
T has_value(T... args)
T push_back(T... args)
T size(T... args)
T transform(T... args)
T value(T... args)