rippled
Loading...
Searching...
No Matches
FixNFTokenPageLinks_test.cpp
1#include <test/jtx.h>
2
3#include <xrpld/app/tx/detail/ApplyContext.h>
4#include <xrpld/app/tx/detail/NFTokenUtils.h>
5
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/jss.h>
8
9namespace xrpl {
10
12{
13 // Helper function that returns the number of nfts owned by an account.
14 static std::uint32_t
16 {
17 Json::Value params;
18 params[jss::account] = acct.human();
19 params[jss::type] = "state";
20 Json::Value nfts = env.rpc("json", "account_nfts", to_string(params));
21 return nfts[jss::result][jss::account_nfts].size();
22 };
23
24 // A helper function that generates 96 nfts packed into three pages
25 // of 32 each. Returns a sorted vector of the NFTokenIDs packed into
26 // the pages.
29 {
30 using namespace test::jtx;
31
33 nfts.reserve(96);
34
35 // We want to create fully packed NFT pages. This is a little
36 // tricky since the system currently in place is inclined to
37 // assign consecutive tokens to only 16 entries per page.
38 //
39 // By manipulating the internal form of the taxon we can force
40 // creation of NFT pages that are completely full. This lambda
41 // tells us the taxon value we should pass in in order for the
42 // internal representation to match the passed in value.
43 auto internalTaxon = [this, &env](Account const& acct, std::uint32_t taxon) -> std::uint32_t {
44 std::uint32_t tokenSeq = [this, &env, &acct]() {
45 auto const le = env.le(acct);
46 if (BEAST_EXPECT(le))
47 return le->at(~sfMintedNFTokens).value_or(0u);
48 return 0u;
49 }();
50
51 // We must add FirstNFTokenSequence.
52 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
53
54 return toUInt32(nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon)));
55 };
56
57 for (std::uint32_t i = 0; i < 96; ++i)
58 {
59 // In order to fill the pages we use the taxon to break them
60 // into groups of 16 entries. By having the internal
61 // representation of the taxon go...
62 // 0, 3, 2, 5, 4, 7...
63 // in sets of 16 NFTs we can get each page to be fully
64 // populated.
65 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
66 uint32_t const extTaxon = internalTaxon(owner, intTaxon);
67 nfts.push_back(token::getNextID(env, owner, extTaxon, tfTransferable));
68 env(token::mint(owner, extTaxon), txflags(tfTransferable));
69 env.close();
70 }
71
72 // Sort the NFTs so they are listed in storage order, not
73 // creation order.
74 std::sort(nfts.begin(), nfts.end());
75
76 // Verify that the owner does indeed have exactly three pages
77 // of NFTs with 32 entries in each page.
78 {
79 Json::Value params;
80 params[jss::account] = owner.human();
81 auto resp = env.rpc("json", "account_objects", to_string(params));
82
83 Json::Value const& acctObjs = resp[jss::result][jss::account_objects];
84
85 int pageCount = 0;
86 for (Json::UInt i = 0; i < acctObjs.size(); ++i)
87 {
88 if (BEAST_EXPECT(
89 acctObjs[i].isMember(sfNFTokens.jsonName) && acctObjs[i][sfNFTokens.jsonName].isArray()))
90 {
91 BEAST_EXPECT(acctObjs[i][sfNFTokens.jsonName].size() == 32);
92 ++pageCount;
93 }
94 }
95 // If this check fails then the internal NFT directory logic
96 // has changed.
97 BEAST_EXPECT(pageCount == 3);
98 }
99 return nfts;
100 };
101
102 void
104 {
105 testcase("LedgerStateFix error cases");
106
107 using namespace test::jtx;
108
109 Account const alice("alice");
110
111 {
112 // Verify that the LedgerStateFix transaction is disabled
113 // without the fixNFTokenPageLinks amendment.
114 Env env{*this, testable_amendments() - fixNFTokenPageLinks};
115 env.fund(XRP(1000), alice);
116
117 auto const linkFixFee = drops(env.current()->fees().increment);
118 env(ledgerStateFix::nftPageLinks(alice, alice), fee(linkFixFee), ter(temDISABLED));
119 }
120
121 Env env{*this, testable_amendments()};
122 env.fund(XRP(1000), alice);
123 std::uint32_t const ticketSeq = env.seq(alice);
124 env(ticket::create(alice, 1));
125
126 // Preflight
127
128 {
129 // Fail preflight1. Can't combine AccountTxnID and ticket.
130 Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice);
131 tx[sfAccountTxnID.jsonName] =
132 "00000000000000000000000000000000"
133 "00000000000000000000000000000000";
134 env(tx, ticket::use(ticketSeq), ter(temINVALID));
135 }
136 // Fee too low.
137 env(ledgerStateFix::nftPageLinks(alice, alice), ter(telINSUF_FEE_P));
138
139 // Invalid flags.
140 auto const linkFixFee = drops(env.current()->fees().increment);
141 env(ledgerStateFix::nftPageLinks(alice, alice), fee(linkFixFee), txflags(tfPassive), ter(temINVALID_FLAG));
142
143 {
144 // ledgerStateFix::nftPageLinks requires an Owner field.
145 Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice);
146 tx.removeMember(sfOwner.jsonName);
147 env(tx, fee(linkFixFee), ter(temINVALID));
148 }
149 {
150 // Invalid LedgerFixType codes.
151 Json::Value tx = ledgerStateFix::nftPageLinks(alice, alice);
152 tx[sfLedgerFixType.jsonName] = 0;
153 env(tx, fee(linkFixFee), ter(tefINVALID_LEDGER_FIX_TYPE));
154
155 tx[sfLedgerFixType.jsonName] = 200;
156 env(tx, fee(linkFixFee), ter(tefINVALID_LEDGER_FIX_TYPE));
157 }
158
159 // Preclaim
160 Account const carol("carol");
161 env.memoize(carol);
162 env(ledgerStateFix::nftPageLinks(alice, carol), fee(linkFixFee), ter(tecOBJECT_NOT_FOUND));
163 }
164
165 void
167 {
168 testcase("NFTokenPageLinkFix error cases");
169
170 using namespace test::jtx;
171
172 Account const alice("alice");
173
174 Env env{*this, testable_amendments()};
175 env.fund(XRP(1000), alice);
176
177 // These cases all return the same TER code, but they exercise
178 // different cases where there is nothing to fix in an owner's
179 // NFToken pages. So they increase test coverage.
180
181 // Owner has no pages to fix.
182 auto const linkFixFee = drops(env.current()->fees().increment);
183 env(ledgerStateFix::nftPageLinks(alice, alice), fee(linkFixFee), ter(tecFAILED_PROCESSING));
184
185 // Alice has only one page.
186 env(token::mint(alice), txflags(tfTransferable));
187 env.close();
188
189 env(ledgerStateFix::nftPageLinks(alice, alice), fee(linkFixFee), ter(tecFAILED_PROCESSING));
190
191 // Alice has at least three pages.
192 for (std::uint32_t i = 0; i < 64; ++i)
193 {
194 env(token::mint(alice), txflags(tfTransferable));
195 env.close();
196 }
197
198 env(ledgerStateFix::nftPageLinks(alice, alice), fee(linkFixFee), ter(tecFAILED_PROCESSING));
199 }
200
201 void
203 {
204 // Steps:
205 // 1. Before the fixNFTokenPageLinks amendment is enabled, build the
206 // three kinds of damaged NFToken directories we know about:
207 // A. One where there is only one page, but without the final index.
208 // B. One with multiple pages and a missing final page.
209 // C. One with links missing in the middle of the chain.
210 // 2. Enable the fixNFTokenPageLinks amendment.
211 // 3. Invoke the LedgerStateFix transactor and repair the directories.
212 testcase("Fix links");
213
214 using namespace test::jtx;
215
216 Account const alice("alice");
217 Account const bob("bob");
218 Account const carol("carol");
219 Account const daria("daria");
220
221 Env env{*this, testable_amendments() - fixNFTokenPageLinks};
222 env.fund(XRP(1000), alice, bob, carol, daria);
223
224 //**********************************************************************
225 // Step 1A: Create damaged NFToken directories:
226 // o One where there is only one page, but without the final index.
227 //**********************************************************************
228
229 // alice generates three packed pages.
230 std::vector<uint256> aliceNFTs = genPackedTokens(env, alice);
231 BEAST_EXPECT(nftCount(env, alice) == 96);
232 BEAST_EXPECT(ownerCount(env, alice) == 3);
233
234 // Get the index of the middle page.
235 uint256 const aliceMiddleNFTokenPageIndex = [&env, &alice]() {
236 auto lastNFTokenPage = env.le(keylet::nftpage_max(alice));
237 return lastNFTokenPage->at(sfPreviousPageMin);
238 }();
239
240 // alice burns all the tokens in the first and last pages.
241 for (int i = 0; i < 32; ++i)
242 {
243 env(token::burn(alice, {aliceNFTs[i]}));
244 env.close();
245 }
246 aliceNFTs.erase(aliceNFTs.begin(), aliceNFTs.begin() + 32);
247 for (int i = 0; i < 32; ++i)
248 {
249 env(token::burn(alice, {aliceNFTs.back()}));
250 aliceNFTs.pop_back();
251 env.close();
252 }
253 BEAST_EXPECT(ownerCount(env, alice) == 1);
254 BEAST_EXPECT(nftCount(env, alice) == 32);
255
256 // Removing the last token from the last page deletes the last
257 // page. This is a bug. The contents of the next-to-last page
258 // should have been moved into the last page.
259 BEAST_EXPECT(!env.le(keylet::nftpage_max(alice)));
260
261 // alice's "middle" page is still present, but has no links.
262 {
263 auto aliceMiddleNFTokenPage =
264 env.le(keylet::nftpage(keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex));
265 if (!BEAST_EXPECT(aliceMiddleNFTokenPage))
266 return;
267
268 BEAST_EXPECT(!aliceMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
269 BEAST_EXPECT(!aliceMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
270 }
271
272 //**********************************************************************
273 // Step 1B: Create damaged NFToken directories:
274 // o One with multiple pages and a missing final page.
275 //**********************************************************************
276
277 // bob generates three packed pages.
278 std::vector<uint256> bobNFTs = genPackedTokens(env, bob);
279 BEAST_EXPECT(nftCount(env, bob) == 96);
280 BEAST_EXPECT(ownerCount(env, bob) == 3);
281
282 // Get the index of the middle page.
283 uint256 const bobMiddleNFTokenPageIndex = [&env, &bob]() {
284 auto lastNFTokenPage = env.le(keylet::nftpage_max(bob));
285 return lastNFTokenPage->at(sfPreviousPageMin);
286 }();
287
288 // bob burns all the tokens in the very last page.
289 for (int i = 0; i < 32; ++i)
290 {
291 env(token::burn(bob, {bobNFTs.back()}));
292 bobNFTs.pop_back();
293 env.close();
294 }
295 BEAST_EXPECT(nftCount(env, bob) == 64);
296 BEAST_EXPECT(ownerCount(env, bob) == 2);
297
298 // Removing the last token from the last page deletes the last
299 // page. This is a bug. The contents of the next-to-last page
300 // should have been moved into the last page.
301 BEAST_EXPECT(!env.le(keylet::nftpage_max(bob)));
302
303 // bob's "middle" page is still present, but has lost the
304 // NextPageMin field.
305 {
306 auto bobMiddleNFTokenPage = env.le(keylet::nftpage(keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex));
307 if (!BEAST_EXPECT(bobMiddleNFTokenPage))
308 return;
309
310 BEAST_EXPECT(bobMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
311 BEAST_EXPECT(!bobMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
312 }
313
314 //**********************************************************************
315 // Step 1C: Create damaged NFToken directories:
316 // o One with links missing in the middle of the chain.
317 //**********************************************************************
318
319 // carol generates three packed pages.
320 std::vector<uint256> carolNFTs = genPackedTokens(env, carol);
321 BEAST_EXPECT(nftCount(env, carol) == 96);
322 BEAST_EXPECT(ownerCount(env, carol) == 3);
323
324 // Get the index of the middle page.
325 uint256 const carolMiddleNFTokenPageIndex = [&env, &carol]() {
326 auto lastNFTokenPage = env.le(keylet::nftpage_max(carol));
327 return lastNFTokenPage->at(sfPreviousPageMin);
328 }();
329
330 // carol sells all of the tokens in the very last page to daria.
331 std::vector<uint256> dariaNFTs;
332 dariaNFTs.reserve(32);
333 for (int i = 0; i < 32; ++i)
334 {
335 uint256 const offerIndex = keylet::nftoffer(carol, env.seq(carol)).key;
336 env(token::createOffer(carol, carolNFTs.back(), XRP(0)), txflags(tfSellNFToken));
337 env.close();
338
339 env(token::acceptSellOffer(daria, offerIndex));
340 env.close();
341
342 dariaNFTs.push_back(carolNFTs.back());
343 carolNFTs.pop_back();
344 }
345 BEAST_EXPECT(nftCount(env, carol) == 64);
346 BEAST_EXPECT(ownerCount(env, carol) == 2);
347
348 // Removing the last token from the last page deletes the last
349 // page. This is a bug. The contents of the next-to-last page
350 // should have been moved into the last page.
351 BEAST_EXPECT(!env.le(keylet::nftpage_max(carol)));
352
353 // carol's "middle" page is still present, but has lost the
354 // NextPageMin field.
355 auto carolMiddleNFTokenPage = env.le(keylet::nftpage(keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex));
356 if (!BEAST_EXPECT(carolMiddleNFTokenPage))
357 return;
358
359 BEAST_EXPECT(carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
360 BEAST_EXPECT(!carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
361
362 // At this point carol's NFT directory has the same problem that
363 // bob's has: the last page is missing. Now we make things more
364 // complicated by putting the last page back. carol buys their NFTs
365 // back from daria.
366 for (uint256 const& nft : dariaNFTs)
367 {
368 uint256 const offerIndex = keylet::nftoffer(carol, env.seq(carol)).key;
369 env(token::createOffer(carol, nft, drops(1)), token::owner(daria));
370 env.close();
371
372 env(token::acceptBuyOffer(daria, offerIndex));
373 env.close();
374
375 carolNFTs.push_back(nft);
376 }
377
378 // Note that carol actually owns 96 NFTs, but only 64 are reported
379 // because the links are damaged.
380 BEAST_EXPECT(nftCount(env, carol) == 64);
381 BEAST_EXPECT(ownerCount(env, carol) == 3);
382
383 // carol's "middle" page is present and still has no NextPageMin field.
384 {
385 auto carolMiddleNFTokenPage =
386 env.le(keylet::nftpage(keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex));
387 if (!BEAST_EXPECT(carolMiddleNFTokenPage))
388 return;
389
390 BEAST_EXPECT(carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
391 BEAST_EXPECT(!carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
392 }
393 // carol has a "last" page again, but it has no PreviousPageMin field.
394 {
395 auto carolLastNFTokenPage = env.le(keylet::nftpage_max(carol));
396
397 BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin));
398 BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin));
399 }
400
401 //**********************************************************************
402 // Step 2: Enable the fixNFTokenPageLinks amendment.
403 //**********************************************************************
404 // Verify that the LedgerStateFix transaction is not enabled.
405 auto const linkFixFee = drops(env.current()->fees().increment);
406 env(ledgerStateFix::nftPageLinks(daria, alice), fee(linkFixFee), ter(temDISABLED));
407
408 // Wait 15 ledgers so the LedgerStateFix transaction is no longer
409 // retried.
410 for (int i = 0; i < 15; ++i)
411 env.close();
412
413 env.enableFeature(fixNFTokenPageLinks);
414 env.close();
415
416 //**********************************************************************
417 // Step 3A: Repair the one-page directory (alice's)
418 //**********************************************************************
419
420 // Verify that alice's NFToken directory is still damaged.
421
422 // alice's last page should still be missing.
423 BEAST_EXPECT(!env.le(keylet::nftpage_max(alice)));
424
425 // alice's "middle" page is still present and has no links.
426 {
427 auto aliceMiddleNFTokenPage =
428 env.le(keylet::nftpage(keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex));
429 if (!BEAST_EXPECT(aliceMiddleNFTokenPage))
430 return;
431
432 BEAST_EXPECT(!aliceMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
433 BEAST_EXPECT(!aliceMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
434 }
435
436 // The server "remembers" daria's failed nftPageLinks transaction
437 // signature. So we need to advance daria's sequence number before
438 // daria can submit a similar transaction.
439 env(noop(daria));
440
441 // daria fixes the links in alice's NFToken directory.
442 env(ledgerStateFix::nftPageLinks(daria, alice), fee(linkFixFee));
443 env.close();
444
445 // alice's last page should now be present and include no links.
446 {
447 auto aliceLastNFTokenPage = env.le(keylet::nftpage_max(alice));
448 if (!BEAST_EXPECT(aliceLastNFTokenPage))
449 return;
450
451 BEAST_EXPECT(!aliceLastNFTokenPage->isFieldPresent(sfPreviousPageMin));
452 BEAST_EXPECT(!aliceLastNFTokenPage->isFieldPresent(sfNextPageMin));
453 }
454
455 // alice's middle page should be gone.
456 BEAST_EXPECT(!env.le(keylet::nftpage(keylet::nftpage_min(alice), aliceMiddleNFTokenPageIndex)));
457
458 BEAST_EXPECT(nftCount(env, alice) == 32);
459 BEAST_EXPECT(ownerCount(env, alice) == 1);
460
461 //**********************************************************************
462 // Step 3B: Repair the two-page directory (bob's)
463 //**********************************************************************
464
465 // Verify that bob's NFToken directory is still damaged.
466
467 // bob's last page should still be missing.
468 BEAST_EXPECT(!env.le(keylet::nftpage_max(bob)));
469
470 // bob's "middle" page is still present and missing NextPageMin.
471 {
472 auto bobMiddleNFTokenPage = env.le(keylet::nftpage(keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex));
473 if (!BEAST_EXPECT(bobMiddleNFTokenPage))
474 return;
475
476 BEAST_EXPECT(bobMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
477 BEAST_EXPECT(!bobMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
478 }
479
480 // daria fixes the links in bob's NFToken directory.
481 env(ledgerStateFix::nftPageLinks(daria, bob), fee(linkFixFee));
482 env.close();
483
484 // bob's last page should now be present and include a previous
485 // link but no next link.
486 {
487 auto const lastPageKeylet = keylet::nftpage_max(bob);
488 auto const bobLastNFTokenPage = env.le(lastPageKeylet);
489 if (!BEAST_EXPECT(bobLastNFTokenPage))
490 return;
491
492 BEAST_EXPECT(bobLastNFTokenPage->isFieldPresent(sfPreviousPageMin));
493 BEAST_EXPECT(bobLastNFTokenPage->at(sfPreviousPageMin) != bobMiddleNFTokenPageIndex);
494 BEAST_EXPECT(!bobLastNFTokenPage->isFieldPresent(sfNextPageMin));
495
496 auto const bobNewFirstNFTokenPage =
497 env.le(keylet::nftpage(keylet::nftpage_min(bob), bobLastNFTokenPage->at(sfPreviousPageMin)));
498 if (!BEAST_EXPECT(bobNewFirstNFTokenPage))
499 return;
500
501 BEAST_EXPECT(
502 bobNewFirstNFTokenPage->isFieldPresent(sfNextPageMin) &&
503 bobNewFirstNFTokenPage->at(sfNextPageMin) == lastPageKeylet.key);
504 BEAST_EXPECT(!bobNewFirstNFTokenPage->isFieldPresent(sfPreviousPageMin));
505 }
506
507 // bob's middle page should be gone.
508 BEAST_EXPECT(!env.le(keylet::nftpage(keylet::nftpage_min(bob), bobMiddleNFTokenPageIndex)));
509
510 BEAST_EXPECT(nftCount(env, bob) == 64);
511 BEAST_EXPECT(ownerCount(env, bob) == 2);
512
513 //**********************************************************************
514 // Step 3C: Repair the three-page directory (carol's)
515 //**********************************************************************
516
517 // Verify that carol's NFToken directory is still damaged.
518
519 // carol's "middle" page is present and has no NextPageMin field.
520 {
521 auto carolMiddleNFTokenPage =
522 env.le(keylet::nftpage(keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex));
523 if (!BEAST_EXPECT(carolMiddleNFTokenPage))
524 return;
525
526 BEAST_EXPECT(carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
527 BEAST_EXPECT(!carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin));
528 }
529 // carol has a "last" page, but it has no PreviousPageMin field.
530 {
531 auto carolLastNFTokenPage = env.le(keylet::nftpage_max(carol));
532
533 BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin));
534 BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin));
535 }
536
537 // carol fixes the links in their own NFToken directory.
538 env(ledgerStateFix::nftPageLinks(carol, carol), fee(linkFixFee));
539 env.close();
540
541 {
542 // carol's "middle" page is present and now has a NextPageMin field.
543 auto const lastPageKeylet = keylet::nftpage_max(carol);
544 auto carolMiddleNFTokenPage =
545 env.le(keylet::nftpage(keylet::nftpage_min(carol), carolMiddleNFTokenPageIndex));
546 if (!BEAST_EXPECT(carolMiddleNFTokenPage))
547 return;
548
549 BEAST_EXPECT(carolMiddleNFTokenPage->isFieldPresent(sfPreviousPageMin));
550 BEAST_EXPECT(
551 carolMiddleNFTokenPage->isFieldPresent(sfNextPageMin) &&
552 carolMiddleNFTokenPage->at(sfNextPageMin) == lastPageKeylet.key);
553
554 // carol has a "last" page that includes a PreviousPageMin field.
555 auto carolLastNFTokenPage = env.le(lastPageKeylet);
556 if (!BEAST_EXPECT(carolLastNFTokenPage))
557 return;
558
559 BEAST_EXPECT(
560 carolLastNFTokenPage->isFieldPresent(sfPreviousPageMin) &&
561 carolLastNFTokenPage->at(sfPreviousPageMin) == carolMiddleNFTokenPageIndex);
562 BEAST_EXPECT(!carolLastNFTokenPage->isFieldPresent(sfNextPageMin));
563
564 // carol also has a "first" page that includes a NextPageMin field.
565 auto carolFirstNFTokenPage =
566 env.le(keylet::nftpage(keylet::nftpage_min(carol), carolMiddleNFTokenPage->at(sfPreviousPageMin)));
567 if (!BEAST_EXPECT(carolFirstNFTokenPage))
568 return;
569
570 BEAST_EXPECT(
571 carolFirstNFTokenPage->isFieldPresent(sfNextPageMin) &&
572 carolFirstNFTokenPage->at(sfNextPageMin) == carolMiddleNFTokenPageIndex);
573 BEAST_EXPECT(!carolFirstNFTokenPage->isFieldPresent(sfPreviousPageMin));
574 }
575
576 // With the link repair, the server knows that carol has 96 NFTs.
577 BEAST_EXPECT(nftCount(env, carol) == 96);
578 BEAST_EXPECT(ownerCount(env, carol) == 3);
579 }
580
581public:
582 void
589};
590
591BEAST_DEFINE_TESTSUITE(FixNFTokenPageLinks, app, xrpl);
592
593} // namespace xrpl
T back(T... args)
T begin(T... args)
Represents a JSON value.
Definition json_value.h:130
bool isArray() const
UInt size() const
Number of values in array or object.
Value removeMember(char const *key)
Remove and return the named member.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
A transaction testing environment.
Definition Env.h:119
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:249
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:240
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:792
T end(T... args)
T erase(T... args)
unsigned int UInt
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:360
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:375
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition Indexes.cpp:368
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:352
Taxon toTaxon(std::uint32_t i)
Definition nft.h:22
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition nft.h:64
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:122
@ telINSUF_FEE_P
Definition TER.h:37
constexpr std::uint32_t tfPassive
Definition TxFlags.h:78
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
@ tefINVALID_LEDGER_FIX_TYPE
Definition TER.h:167
@ temINVALID
Definition TER.h:90
@ temINVALID_FLAG
Definition TER.h:91
@ temDISABLED
Definition TER.h:94
@ tecOBJECT_NOT_FOUND
Definition TER.h:307
@ tecFAILED_PROCESSING
Definition TER.h:267
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:210
T pop_back(T... args)
T push_back(T... args)
T reserve(T... args)
T sort(T... args)
uint256 key
Definition Keylet.h:20