xrpld
Loading...
Searching...
No Matches
NFTInvariant.cpp
1#include <xrpl/tx/invariants/NFTInvariant.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/beast/utility/Journal.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/ledger/ReadView.h>
8#include <xrpl/ledger/helpers/NFTokenHelpers.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/LedgerFormats.h>
11#include <xrpl/protocol/Protocol.h>
12#include <xrpl/protocol/SField.h>
13#include <xrpl/protocol/STArray.h>
14#include <xrpl/protocol/STLedgerEntry.h>
15#include <xrpl/protocol/STTx.h>
16#include <xrpl/protocol/TER.h>
17#include <xrpl/protocol/TxFormats.h>
18#include <xrpl/protocol/XRPAmount.h>
19#include <xrpl/protocol/nftPageMask.h>
20#include <xrpl/tx/invariants/InvariantCheckPrivilege.h>
21
22#include <cstddef>
23#include <optional>
24
25namespace xrpl {
26
27void
29{
30 static constexpr uint256 const& kPageBits = nft::kPageMask;
31 static constexpr uint256 kAccountBits = ~kPageBits;
32
33 if ((before && before->getType() != ltNFTOKEN_PAGE) ||
34 (after && after->getType() != ltNFTOKEN_PAGE))
35 return;
36
37 auto check = [this, isDelete](SLE::const_ref sle) {
38 uint256 const account = sle->key() & kAccountBits;
39 uint256 const hiLimit = sle->key() & kPageBits;
40 std::optional<uint256> const prev = (*sle)[~sfPreviousPageMin];
41
42 // Make sure that any page links...
43 // 1. Are properly associated with the owning account and
44 // 2. The page is correctly ordered between links.
45 if (prev)
46 {
47 if (account != (*prev & kAccountBits))
48 badLink_ = true;
49
50 if (hiLimit <= (*prev & kPageBits))
51 badLink_ = true;
52 }
53
54 if (auto const next = (*sle)[~sfNextPageMin])
55 {
56 if (account != (*next & kAccountBits))
57 badLink_ = true;
58
59 if (hiLimit >= (*next & kPageBits))
60 badLink_ = true;
61 }
62
63 {
64 auto const& nftokens = sle->getFieldArray(sfNFTokens);
65
66 // An NFTokenPage should never contain too many tokens or be empty.
67 if (std::size_t const nftokenCount = nftokens.size();
68 (!isDelete && nftokenCount == 0) || nftokenCount > kDirMaxTokensPerPage)
69 invalidSize_ = true;
70
71 // If prev is valid, use it to establish a lower bound for
72 // page entries. If prev is not valid the lower bound is zero.
73 uint256 const loLimit = prev ? *prev & kPageBits : uint256(beast::kZero);
74
75 // Also verify that all NFTokenIDs in the page are sorted.
76 uint256 loCmp = loLimit;
77 for (auto const& obj : nftokens)
78 {
79 uint256 const tokenID = obj[sfNFTokenID];
80 if (!nft::compareTokens(loCmp, tokenID))
81 badSort_ = true;
82 loCmp = tokenID;
83
84 // None of the NFTs on this page should belong on lower or
85 // higher pages.
86 if (uint256 const tokenPageBits = tokenID & kPageBits;
87 tokenPageBits < loLimit || tokenPageBits >= hiLimit)
88 badEntry_ = true;
89
90 if (auto uri = obj[~sfURI]; uri && uri->empty())
91 badURI_ = true;
92 }
93 }
94 };
95
96 if (before)
97 {
98 check(before);
99
100 // While an account's NFToken directory contains any NFTokens, the last
101 // NFTokenPage (with 96 bits of 1 in the low part of the index) should
102 // never be deleted.
103 if (isDelete && (before->key() & nft::kPageMask) == nft::kPageMask &&
104 before->isFieldPresent(sfPreviousPageMin))
105 {
106 deletedFinalPage_ = true;
107 }
108 }
109
110 if (after)
111 check(after);
112
113 if (!isDelete && before && after)
114 {
115 // If the NFTokenPage
116 // 1. Has a NextMinPage field in before, but loses it in after, and
117 // 2. This is not the last page in the directory
118 // Then we have identified a corruption in the links between the
119 // NFToken pages in the NFToken directory.
120 if ((before->key() & nft::kPageMask) != nft::kPageMask &&
121 before->isFieldPresent(sfNextPageMin) && !after->isFieldPresent(sfNextPageMin))
122 {
123 deletedLink_ = true;
124 }
125 }
126}
127
128bool
130 STTx const& tx,
131 TER const result,
132 XRPAmount const,
133 ReadView const& view,
134 beast::Journal const& j) const
135{
136 if (badLink_)
137 {
138 JLOG(j.fatal()) << "Invariant failed: NFT page is improperly linked.";
139 return false;
140 }
141
142 if (badEntry_)
143 {
144 JLOG(j.fatal()) << "Invariant failed: NFT found in incorrect page.";
145 return false;
146 }
147
148 if (badSort_)
149 {
150 JLOG(j.fatal()) << "Invariant failed: NFTs on page are not sorted.";
151 return false;
152 }
153
154 if (badURI_)
155 {
156 JLOG(j.fatal()) << "Invariant failed: NFT contains empty URI.";
157 return false;
158 }
159
160 if (invalidSize_)
161 {
162 JLOG(j.fatal()) << "Invariant failed: NFT page has invalid size.";
163 return false;
164 }
165
166 if (view.rules().enabled(fixNFTokenPageLinks))
167 {
169 {
170 JLOG(j.fatal()) << "Invariant failed: Last NFT page deleted with "
171 "non-empty directory.";
172 return false;
173 }
174 if (deletedLink_)
175 {
176 JLOG(j.fatal()) << "Invariant failed: Lost NextMinPage link.";
177 return false;
178 }
179 }
180
181 return true;
182}
183
184//------------------------------------------------------------------------------
185void
187{
188 if (before && before->getType() == ltACCOUNT_ROOT)
189 {
190 beforeMintedTotal_ += (*before)[~sfMintedNFTokens].value_or(0);
191 beforeBurnedTotal_ += (*before)[~sfBurnedNFTokens].value_or(0);
192 }
193
194 if (after && after->getType() == ltACCOUNT_ROOT)
195 {
196 afterMintedTotal_ += (*after)[~sfMintedNFTokens].value_or(0);
197 afterBurnedTotal_ += (*after)[~sfBurnedNFTokens].value_or(0);
198 }
199}
200
201bool
203 STTx const& tx,
204 TER const result,
205 XRPAmount const,
206 ReadView const& view,
207 beast::Journal const& j) const
208{
210 {
212 {
213 JLOG(j.fatal()) << "Invariant failed: the number of minted tokens "
214 "changed without a mint transaction!";
215 return false;
216 }
217
219 {
220 JLOG(j.fatal()) << "Invariant failed: the number of burned tokens "
221 "changed without a burn transaction!";
222 return false;
223 }
224
225 return true;
226 }
227
228 if (tx.getTxnType() == ttNFTOKEN_MINT)
229 {
231 {
232 JLOG(j.fatal()) << "Invariant failed: successful minting didn't increase "
233 "the number of minted tokens.";
234 return false;
235 }
236
238 {
239 JLOG(j.fatal()) << "Invariant failed: failed minting changed the "
240 "number of minted tokens.";
241 return false;
242 }
243
245 {
246 JLOG(j.fatal()) << "Invariant failed: minting changed the number of "
247 "burned tokens.";
248 return false;
249 }
250 }
251
252 if (tx.getTxnType() == ttNFTOKEN_BURN)
253 {
254 if (isTesSuccess(result))
255 {
257 {
258 JLOG(j.fatal()) << "Invariant failed: successful burning didn't increase "
259 "the number of burned tokens.";
260 return false;
261 }
262 }
263
265 {
266 JLOG(j.fatal()) << "Invariant failed: failed burning changed the "
267 "number of burned tokens.";
268 return false;
269 }
270
272 {
273 JLOG(j.fatal()) << "Invariant failed: burning changed the number of "
274 "minted tokens.";
275 return false;
276 }
277 }
278
279 return true;
280}
281
282} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
std::uint32_t afterMintedTotal_
std::uint32_t beforeBurnedTotal_
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
std::uint32_t beforeMintedTotal_
std::uint32_t afterBurnedTotal_
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
std::shared_ptr< STLedgerEntry const > const & const_ref
TxType getTxnType() const
Definition STTx.h:188
void visitEntry(bool, SLE::const_ref, SLE::const_ref)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &) const
constexpr uint256 kPageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
bool compareTokens(uint256 const &a, uint256 const &b)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool hasPrivilege(STTx const &tx, Privilege priv)
bool after(NetClock::time_point now, std::uint32_t mark)
Has the specified time passed?
Definition View.cpp:554
constexpr std::size_t kDirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition Protocol.h:50
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
BaseUInt< 256 > uint256
Definition base_uint.h:562
uint256 key
Definition Keylet.h:20