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