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