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