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