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