rippled
NFTokenDir_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2022 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/tx/impl/details/NFTokenUtils.h>
21 #include <ripple/protocol/Feature.h>
22 #include <ripple/protocol/jss.h>
23 #include <ripple/protocol/nftPageMask.h>
24 #include <test/jtx.h>
25 
26 #include <initializer_list>
27 
28 namespace ripple {
29 
30 class NFTokenDir_test : public beast::unit_test::suite
31 {
32  // printNFTPages is a helper function that may be used for debugging.
33  //
34  // It uses the ledger RPC command to show the NFT pages in the ledger.
35  // This parameter controls how noisy the output is.
36  enum Volume : bool {
37  quiet = false,
38  noisy = true,
39  };
40 
41  void
43  {
44  Json::Value jvParams;
45  jvParams[jss::ledger_index] = "current";
46  jvParams[jss::binary] = false;
47  {
48  Json::Value jrr = env.rpc(
49  "json",
50  "ledger_data",
51  boost::lexical_cast<std::string>(jvParams));
52 
53  // Iterate the state and print all NFTokenPages.
54  if (!jrr.isMember(jss::result) ||
55  !jrr[jss::result].isMember(jss::state))
56  {
57  std::cout << "No ledger state found!" << std::endl;
58  return;
59  }
60  Json::Value& state = jrr[jss::result][jss::state];
61  if (!state.isArray())
62  {
63  std::cout << "Ledger state is not array!" << std::endl;
64  return;
65  }
66  for (Json::UInt i = 0; i < state.size(); ++i)
67  {
68  if (state[i].isMember(sfNFTokens.jsonName) &&
69  state[i][sfNFTokens.jsonName].isArray())
70  {
71  std::uint32_t tokenCount =
72  state[i][sfNFTokens.jsonName].size();
73  std::cout << tokenCount << " NFtokens in page "
74  << state[i][jss::index].asString() << std::endl;
75 
76  if (vol == noisy)
77  {
78  std::cout << state[i].toStyledString() << std::endl;
79  }
80  else
81  {
82  if (tokenCount > 0)
83  std::cout << "first: "
84  << state[i][sfNFTokens.jsonName][0u]
86  << std::endl;
87  if (tokenCount > 1)
88  std::cout
89  << "last: "
90  << state[i][sfNFTokens.jsonName][tokenCount - 1]
92  << std::endl;
93  }
94  }
95  }
96  }
97  }
98 
99  void
101  {
102  // It should be possible to store many consecutive NFTs.
103  testcase("Sequential NFTs");
104 
105  using namespace test::jtx;
106  Env env{*this, features};
107 
108  // A single minter tends not to mint numerically sequential NFTokens
109  // because the taxon cipher mixes things up. We can override the
110  // cipher, however, and mint many sequential NFTokens with no gaps
111  // between them.
112  //
113  // Here we'll simply mint 100 sequential NFTs. Then we'll create
114  // offers for them to verify that the ledger can find them.
115 
116  Account const issuer{"issuer"};
117  Account const buyer{"buyer"};
118  env.fund(XRP(10000), buyer, issuer);
119  env.close();
120 
121  // Mint 100 sequential NFTs. Tweak the taxon so zero is always stored.
122  // That's what makes them sequential.
123  constexpr std::size_t nftCount = 100;
124  std::vector<uint256> nftIDs;
125  nftIDs.reserve(nftCount);
126  for (int i = 0; i < nftCount; ++i)
127  {
128  std::uint32_t taxon =
129  toUInt32(nft::cipheredTaxon(i, nft::toTaxon(0)));
130  nftIDs.emplace_back(
131  token::getNextID(env, issuer, taxon, tfTransferable));
132  env(token::mint(issuer, taxon), txflags(tfTransferable));
133  env.close();
134  }
135 
136  // Create an offer for each of the NFTs. This verifies that the ledger
137  // can find all of the minted NFTs.
138  std::vector<uint256> offers;
139  for (uint256 const& nftID : nftIDs)
140  {
141  offers.emplace_back(keylet::nftoffer(issuer, env.seq(issuer)).key);
142  env(token::createOffer(issuer, nftID, XRP(0)),
143  txflags((tfSellNFToken)));
144  env.close();
145  }
146 
147  // Buyer accepts all of the offers in reverse order.
148  std::reverse(offers.begin(), offers.end());
149  for (uint256 const& offer : offers)
150  {
151  env(token::acceptSellOffer(buyer, offer));
152  env.close();
153  }
154  }
155 
156  void
158  {
159  // All NFT IDs with the same low 96 bits must stay on the same NFT page.
160  testcase("Lopsided splits");
161 
162  using namespace test::jtx;
163 
164  // When a single NFT page exceeds 32 entries, the code is inclined
165  // to split that page into two equal pieces. That's fine, but
166  // the code also needs to keep NFTs with identical low 96-bits on
167  // the same page.
168  //
169  // Here we synthesize cases where there are several NFTs with
170  // identical 96-low-bits in the middle of a page. When that page
171  // is split because it overflows, we need to see that the NFTs
172  // with identical 96-low-bits are all kept on the same page.
173 
174  // Lambda that exercises the lopsided splits.
175  auto exerciseLopsided =
176  [this,
178  Env env{*this, features};
179 
180  // Eventually all of the NFTokens will be owned by buyer.
181  Account const buyer{"buyer"};
182  env.fund(XRP(10000), buyer);
183  env.close();
184 
185  // Create accounts for all of the seeds and fund those accounts.
186  std::vector<Account> accounts;
187  accounts.reserve(seeds.size());
188  for (std::string_view const& seed : seeds)
189  {
190  Account const& account = accounts.emplace_back(
191  Account::base58Seed, std::string(seed));
192  env.fund(XRP(10000), account);
193  env.close();
194  }
195 
196  // All of the accounts create one NFT and and offer that NFT to
197  // buyer.
198  std::vector<uint256> nftIDs;
199  std::vector<uint256> offers;
200  offers.reserve(accounts.size());
201  for (Account const& account : accounts)
202  {
203  // Mint the NFT.
204  uint256 const& nftID = nftIDs.emplace_back(
205  token::getNextID(env, account, 0, tfTransferable));
206  env(token::mint(account, 0), txflags(tfTransferable));
207  env.close();
208 
209  // Create an offer to give the NFT to buyer for free.
210  offers.emplace_back(
211  keylet::nftoffer(account, env.seq(account)).key);
212  env(token::createOffer(account, nftID, XRP(0)),
213  token::destination(buyer),
214  txflags((tfSellNFToken)));
215  }
216  env.close();
217 
218  // buyer accepts all of the offers.
219  for (uint256 const& offer : offers)
220  {
221  env(token::acceptSellOffer(buyer, offer));
222  env.close();
223  }
224 
225  // This can be a good time to look at the NFT pages.
226  // printNFTPages(env, noisy);
227 
228  // Verify that all NFTs are owned by buyer and findable in the
229  // ledger by having buyer create sell offers for all of their
230  // NFTs. Attempting to sell an offer that the ledger can't find
231  // generates a non-tesSUCCESS error code.
232  for (uint256 const& nftID : nftIDs)
233  {
234  uint256 const offerID =
235  keylet::nftoffer(buyer, env.seq(buyer)).key;
236  env(token::createOffer(buyer, nftID, XRP(100)),
237  txflags(tfSellNFToken));
238  env.close();
239 
240  env(token::cancelOffer(buyer, {offerID}));
241  }
242 
243  // Verify that all the NFTs are owned by buyer.
244  Json::Value buyerNFTs = [&env, &buyer]() {
245  Json::Value params;
246  params[jss::account] = buyer.human();
247  params[jss::type] = "state";
248  return env.rpc("json", "account_nfts", to_string(params));
249  }();
250 
251  BEAST_EXPECT(
252  buyerNFTs[jss::result][jss::account_nfts].size() ==
253  nftIDs.size());
254  for (Json::Value const& ownedNFT :
255  buyerNFTs[jss::result][jss::account_nfts])
256  {
257  uint256 ownedID;
258  BEAST_EXPECT(ownedID.parseHex(
259  ownedNFT[sfNFTokenID.jsonName].asString()));
260  auto const foundIter =
261  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
262 
263  // Assuming we find the NFT, erase it so we know it's been
264  // found and can't be found again.
265  if (BEAST_EXPECT(foundIter != nftIDs.end()))
266  nftIDs.erase(foundIter);
267  }
268 
269  // All NFTs should now be accounted for, so nftIDs should be
270  // empty.
271  BEAST_EXPECT(nftIDs.empty());
272  };
273 
274  // These seeds cause a lopsided split where the new NFT is added
275  // to the upper page.
277  splitAndAddToHi{
278  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
279  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
280  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
281  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
282  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
283  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
284 
285  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
286  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
287  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
288  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
289  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
290  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
291 
292  // These eight need to be kept together by the implementation.
293  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
294  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
295  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
296  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
297  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
298  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
299  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
300  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
301 
302  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
303  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
304  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
305  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
306  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
307  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
308 
309  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
310  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
311  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
312  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
313  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
314  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
315 
316  // Adding this NFT splits the page. It is added to the upper
317  // page.
318  "sp6JS7f14BuwFY8Mw6ut1hFrqWoY5", // 32. 0x503b6ba9
319  };
320 
321  // These seeds cause a lopsided split where the new NFT is added
322  // to the lower page.
324  splitAndAddToLo{
325  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
326  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
327  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
328  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
329  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
330  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
331 
332  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
333  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
334  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
335  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
336  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
337  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
338 
339  // These eight need to be kept together by the implementation.
340  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
341  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
342  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
343  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
344  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
345  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
346  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
347  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
348 
349  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
350  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
351  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
352  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
353  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
354  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
355 
356  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
357  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
358  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
359  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
360  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
361  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
362 
363  // Adding this NFT splits the page. It is added to the lower
364  // page.
365  "sp6JS7f14BuwFY8Mw6xCigaMwC6Dp", // 32. 0x309b67ed
366  };
367 
368  // Run the test cases.
369  exerciseLopsided(splitAndAddToHi);
370  exerciseLopsided(splitAndAddToLo);
371  }
372 
373  void
375  {
376  // Exercise a fix for an off-by-one in the creation of an NFTokenPage
377  // index.
378  testcase("fixNFTokenDirV1");
379 
380  using namespace test::jtx;
381 
382  // When a single NFT page exceeds 32 entries, the code is inclined
383  // to split that page into two equal pieces. The new page is lower
384  // than the original. There was an off-by-one in the selection of
385  // the index for the new page. This test recreates the problem.
386 
387  // Lambda that exercises the split.
388  auto exerciseFixNFTokenDirV1 =
389  [this,
391  Env env{
392  *this,
393  envconfig(),
394  features,
395  nullptr,
397 
398  // Eventually all of the NFTokens will be owned by buyer.
399  Account const buyer{"buyer"};
400  env.fund(XRP(10000), buyer);
401  env.close();
402 
403  // Create accounts for all of the seeds and fund those accounts.
404  std::vector<Account> accounts;
405  accounts.reserve(seeds.size());
406  for (std::string_view const& seed : seeds)
407  {
408  Account const& account = accounts.emplace_back(
409  Account::base58Seed, std::string(seed));
410  env.fund(XRP(10000), account);
411  env.close();
412  }
413 
414  // All of the accounts create one NFT and and offer that NFT to
415  // buyer.
416  std::vector<uint256> nftIDs;
417  std::vector<uint256> offers;
418  offers.reserve(accounts.size());
419  for (Account const& account : accounts)
420  {
421  // Mint the NFT.
422  uint256 const& nftID = nftIDs.emplace_back(
423  token::getNextID(env, account, 0, tfTransferable));
424  env(token::mint(account, 0), txflags(tfTransferable));
425  env.close();
426 
427  // Create an offer to give the NFT to buyer for free.
428  offers.emplace_back(
429  keylet::nftoffer(account, env.seq(account)).key);
430  env(token::createOffer(account, nftID, XRP(0)),
431  token::destination(buyer),
432  txflags((tfSellNFToken)));
433  }
434  env.close();
435 
436  // buyer accepts all of the but the last. The last offer
437  // causes the page to split.
438  for (std::size_t i = 0; i < offers.size() - 1; ++i)
439  {
440  env(token::acceptSellOffer(buyer, offers[i]));
441  env.close();
442  }
443 
444  // Here is the last offer. Without the fix accepting this
445  // offer causes tecINVARIANT_FAILED. With the fix the offer
446  // accept succeeds.
447  if (!features[fixNFTokenDirV1])
448  {
449  env(token::acceptSellOffer(buyer, offers.back()),
450  ter(tecINVARIANT_FAILED));
451  env.close();
452  return;
453  }
454  env(token::acceptSellOffer(buyer, offers.back()));
455  env.close();
456 
457  // This can be a good time to look at the NFT pages.
458  // printNFTPages(env, noisy);
459 
460  // Verify that all NFTs are owned by buyer and findable in the
461  // ledger by having buyer create sell offers for all of their
462  // NFTs. Attempting to sell an offer that the ledger can't find
463  // generates a non-tesSUCCESS error code.
464  for (uint256 const& nftID : nftIDs)
465  {
466  uint256 const offerID =
467  keylet::nftoffer(buyer, env.seq(buyer)).key;
468  env(token::createOffer(buyer, nftID, XRP(100)),
469  txflags(tfSellNFToken));
470  env.close();
471 
472  env(token::cancelOffer(buyer, {offerID}));
473  }
474 
475  // Verify that all the NFTs are owned by buyer.
476  Json::Value buyerNFTs = [&env, &buyer]() {
477  Json::Value params;
478  params[jss::account] = buyer.human();
479  params[jss::type] = "state";
480  return env.rpc("json", "account_nfts", to_string(params));
481  }();
482 
483  BEAST_EXPECT(
484  buyerNFTs[jss::result][jss::account_nfts].size() ==
485  nftIDs.size());
486  for (Json::Value const& ownedNFT :
487  buyerNFTs[jss::result][jss::account_nfts])
488  {
489  uint256 ownedID;
490  BEAST_EXPECT(ownedID.parseHex(
491  ownedNFT[sfNFTokenID.jsonName].asString()));
492  auto const foundIter =
493  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
494 
495  // Assuming we find the NFT, erase it so we know it's been
496  // found and can't be found again.
497  if (BEAST_EXPECT(foundIter != nftIDs.end()))
498  nftIDs.erase(foundIter);
499  }
500 
501  // All NFTs should now be accounted for, so nftIDs should be
502  // empty.
503  BEAST_EXPECT(nftIDs.empty());
504  };
505 
506  // These seeds fill the last 17 entries of the initial page with
507  // equivalent NFTs. The split should keep these together.
508  static std::initializer_list<std::string_view const> const seventeenHi{
509  // These 16 need to be kept together by the implementation.
510  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
511  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
512  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
513  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
514  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
515  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
516  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
517  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
518  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
519  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
520  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
521  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
522  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
523  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
524  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
525  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
526 
527  // These 17 need to be kept together by the implementation.
528  "sp6JS7f14BuwFY8MwjJCwYr9zSfAv", // 16. 0xabb11898
529  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
530  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
531  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
532  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
533  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
534  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
535  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
536  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
537  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
538  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
539  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
540  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
541  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
542  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
543  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
544  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
545  };
546 
547  // These seeds fill the first entries of the initial page with
548  // equivalent NFTs. The split should keep these together.
549  static std::initializer_list<std::string_view const> const seventeenLo{
550  // These 17 need to be kept together by the implementation.
551  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
552  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
553  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
554  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
555  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
556  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
557  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
558  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
559  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
560  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
561  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
562  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
563  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
564  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
565  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
566  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
567  "sp6JS7f14BuwFY8Mwj6TYekeeyukh", // 16. 0x399187e9
568 
569  // These 16 need to be kept together by the implementation.
570  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
571  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
572  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
573  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
574  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
575  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
576  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
577  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
578  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
579  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
580  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
581  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
582  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
583  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
584  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
585  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
586  };
587 
588  // Run the test cases.
589  exerciseFixNFTokenDirV1(seventeenHi);
590  exerciseFixNFTokenDirV1(seventeenLo);
591  }
592 
593  void
595  {
596  // Exercise the case where 33 NFTs with identical sort
597  // characteristics are owned by the same account.
598  testcase("NFToken too many same");
599 
600  using namespace test::jtx;
601 
602  Env env{*this, features};
603 
604  // Eventually all of the NFTokens will be owned by buyer.
605  Account const buyer{"buyer"};
606  env.fund(XRP(10000), buyer);
607  env.close();
608 
609  // Here are 33 seeds that produce identical low 32-bits in their
610  // corresponding AccountIDs.
612  "sp6JS7f14BuwFY8Mw5FnqmbciPvH6", // 0. 0x9a8ebed3
613  "sp6JS7f14BuwFY8Mw5MBGbyMSsXLp", // 1. 0x9a8ebed3
614  "sp6JS7f14BuwFY8Mw5S4PnDyBdKKm", // 2. 0x9a8ebed3
615  "sp6JS7f14BuwFY8Mw6kcXpM2enE35", // 3. 0x9a8ebed3
616  "sp6JS7f14BuwFY8Mw6tuuSMMwyJ44", // 4. 0x9a8ebed3
617  "sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt", // 5. 0x9a8ebed3
618  "sp6JS7f14BuwFY8Mw8WwdgWkCHhEx", // 6. 0x9a8ebed3
619  "sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ", // 7. 0x9a8ebed3
620  "sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ", // 8. 0x9a8ebed3
621  "sp6JS7f14BuwFY8Mw8fdSwLCZWDFd", // 9. 0x9a8ebed3
622  "sp6JS7f14BuwFY8Mw8zuF6Fg65i1E", // 10. 0x9a8ebed3
623  "sp6JS7f14BuwFY8MwF2k7bihVfqes", // 11. 0x9a8ebed3
624  "sp6JS7f14BuwFY8MwF6X24WXGn557", // 12. 0x9a8ebed3
625  "sp6JS7f14BuwFY8MwFMpn7strjekg", // 13. 0x9a8ebed3
626  "sp6JS7f14BuwFY8MwFSdy9sYVrwJs", // 14. 0x9a8ebed3
627  "sp6JS7f14BuwFY8MwFdMcLy9UkrXn", // 15. 0x9a8ebed3
628  "sp6JS7f14BuwFY8MwFdbwFm1AAboa", // 16. 0x9a8ebed3
629  "sp6JS7f14BuwFY8MwFdr5AhKThVtU", // 17. 0x9a8ebed3
630  "sp6JS7f14BuwFY8MwjFc3Q9YatvAw", // 18. 0x9a8ebed3
631  "sp6JS7f14BuwFY8MwjRXcNs1ozEXn", // 19. 0x9a8ebed3
632  "sp6JS7f14BuwFY8MwkQGUKL7v1FBt", // 20. 0x9a8ebed3
633  "sp6JS7f14BuwFY8Mwkamsoxx1wECt", // 21. 0x9a8ebed3
634  "sp6JS7f14BuwFY8Mwm3hus1dG6U8y", // 22. 0x9a8ebed3
635  "sp6JS7f14BuwFY8Mwm589M8vMRpXF", // 23. 0x9a8ebed3
636  "sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3", // 24. 0x9a8ebed3
637  "sp6JS7f14BuwFY8MwmRfy8fer4QbL", // 25. 0x9a8ebed3
638  "sp6JS7f14BuwFY8MwmkkFx1HtgWRx", // 26. 0x9a8ebed3
639  "sp6JS7f14BuwFY8MwmwP9JFdKa4PS", // 27. 0x9a8ebed3
640  "sp6JS7f14BuwFY8MwoXWJLB3ciHfo", // 28. 0x9a8ebed3
641  "sp6JS7f14BuwFY8MwoYc1gTtT2mWL", // 29. 0x9a8ebed3
642  "sp6JS7f14BuwFY8MwogXtHH7FNVoo", // 30. 0x9a8ebed3
643  "sp6JS7f14BuwFY8MwoqYoA9P8gf3r", // 31. 0x9a8ebed3
644  "sp6JS7f14BuwFY8MwoujwMJofGnsA", // 32. 0x9a8ebed3
645  };
646 
647  // Create accounts for all of the seeds and fund those accounts.
648  std::vector<Account> accounts;
649  accounts.reserve(seeds.size());
650  for (std::string_view const& seed : seeds)
651  {
652  Account const& account =
653  accounts.emplace_back(Account::base58Seed, std::string(seed));
654  env.fund(XRP(10000), account);
655  env.close();
656  }
657 
658  // All of the accounts create one NFT and and offer that NFT to buyer.
659  std::vector<uint256> nftIDs;
660  std::vector<uint256> offers;
661  offers.reserve(accounts.size());
662  for (Account const& account : accounts)
663  {
664  // Mint the NFT.
665  uint256 const& nftID = nftIDs.emplace_back(
666  token::getNextID(env, account, 0, tfTransferable));
667  env(token::mint(account, 0), txflags(tfTransferable));
668  env.close();
669 
670  // Create an offer to give the NFT to buyer for free.
671  offers.emplace_back(
672  keylet::nftoffer(account, env.seq(account)).key);
673  env(token::createOffer(account, nftID, XRP(0)),
674  token::destination(buyer),
675  txflags((tfSellNFToken)));
676  }
677  env.close();
678 
679  // Verify that the low 96 bits of all generated NFTs is identical.
680  uint256 const expectLowBits = nftIDs.front() & nft::pageMask;
681  for (uint256 const& nftID : nftIDs)
682  {
683  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
684  }
685 
686  // Remove one NFT and offer from the vectors. This offer is the one
687  // that will overflow the page.
688  nftIDs.pop_back();
689  uint256 const offerForPageOverflow = offers.back();
690  offers.pop_back();
691 
692  // buyer accepts all of the offers but one.
693  for (uint256 const& offer : offers)
694  {
695  env(token::acceptSellOffer(buyer, offer));
696  env.close();
697  }
698 
699  // buyer accepts the last offer which causes a page overflow.
700  env(token::acceptSellOffer(buyer, offerForPageOverflow),
702 
703  // Verify that all expected NFTs are owned by buyer and findable in
704  // the ledger by having buyer create sell offers for all of their NFTs.
705  // Attempting to sell an offer that the ledger can't find generates
706  // a non-tesSUCCESS error code.
707  for (uint256 const& nftID : nftIDs)
708  {
709  uint256 const offerID = keylet::nftoffer(buyer, env.seq(buyer)).key;
710  env(token::createOffer(buyer, nftID, XRP(100)),
711  txflags(tfSellNFToken));
712  env.close();
713 
714  env(token::cancelOffer(buyer, {offerID}));
715  }
716 
717  // Verify that all the NFTs are owned by buyer.
718  Json::Value buyerNFTs = [&env, &buyer]() {
719  Json::Value params;
720  params[jss::account] = buyer.human();
721  params[jss::type] = "state";
722  return env.rpc("json", "account_nfts", to_string(params));
723  }();
724 
725  BEAST_EXPECT(
726  buyerNFTs[jss::result][jss::account_nfts].size() == nftIDs.size());
727  for (Json::Value const& ownedNFT :
728  buyerNFTs[jss::result][jss::account_nfts])
729  {
730  uint256 ownedID;
731  BEAST_EXPECT(
732  ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString()));
733  auto const foundIter =
734  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
735 
736  // Assuming we find the NFT, erase it so we know it's been found
737  // and can't be found again.
738  if (BEAST_EXPECT(foundIter != nftIDs.end()))
739  nftIDs.erase(foundIter);
740  }
741 
742  // All NFTs should now be accounted for, so nftIDs should be empty.
743  BEAST_EXPECT(nftIDs.empty());
744 
745  // Show that Without fixNFTokenDirV1 no more NFTs can be added to
746  // buyer. Also show that fixNFTokenDirV1 fixes the problem.
747  TER const expect = features[fixNFTokenDirV1]
748  ? static_cast<TER>(tesSUCCESS)
749  : static_cast<TER>(tecNO_SUITABLE_NFTOKEN_PAGE);
750  env(token::mint(buyer, 0), txflags(tfTransferable), ter(expect));
751  env.close();
752  }
753 
754  void
756  {
757  // We'll make a worst case scenario for NFT packing:
758  //
759  // 1. 33 accounts with identical low-32 bits mint 7 consecutive NFTs.
760  // 2. The taxon is manipulated to always be stored as zero.
761  // 3. A single account buys all 7x32 of the 33 NFTs.
762  //
763  // All of the NFTs should be acquired by the buyer.
764  //
765  // Lastly, none of the remaining NFTs should be acquirable by the
766  // buyer. They would cause page overflow.
767 
768  // This test collapses in a heap if fixNFTokenDirV1 is not enabled.
769  // If it is enabled just return so we skip the test.
770  if (!features[fixNFTokenDirV1])
771  return;
772 
773  testcase("NFToken consecutive packing");
774 
775  using namespace test::jtx;
776 
777  Env env{*this, features};
778 
779  // Eventually all of the NFTokens will be owned by buyer.
780  Account const buyer{"buyer"};
781  env.fund(XRP(10000), buyer);
782  env.close();
783 
784  // Here are 33 seeds that produce identical low 32-bits in their
785  // corresponding AccountIDs.
787  "sp6JS7f14BuwFY8Mw56vZeiBuhePx", // 0. 0x115d0525
788  "sp6JS7f14BuwFY8Mw5BodF9tGuTUe", // 1. 0x115d0525
789  "sp6JS7f14BuwFY8Mw5EnhC1cg84J7", // 2. 0x115d0525
790  "sp6JS7f14BuwFY8Mw5P913Cunr2BK", // 3. 0x115d0525
791  "sp6JS7f14BuwFY8Mw5Pru7eLo1XzT", // 4. 0x115d0525
792  "sp6JS7f14BuwFY8Mw61SLUC8UX2m8", // 5. 0x115d0525
793  "sp6JS7f14BuwFY8Mw6AsBF9TpeMpq", // 6. 0x115d0525
794  "sp6JS7f14BuwFY8Mw84XqrBZkU2vE", // 7. 0x115d0525
795  "sp6JS7f14BuwFY8Mw89oSU6dBk3KB", // 8. 0x115d0525
796  "sp6JS7f14BuwFY8Mw89qUKCyDmyzj", // 9. 0x115d0525
797  "sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm", // 10. 0x115d0525
798  "sp6JS7f14BuwFY8Mw8LtW3VqrqMks", // 11. 0x115d0525
799  "sp6JS7f14BuwFY8Mw8ZrAkJc2sHew", // 12. 0x115d0525
800  "sp6JS7f14BuwFY8Mw8jpkYSNrD3ah", // 13. 0x115d0525
801  "sp6JS7f14BuwFY8MwF2mshd786m3V", // 14. 0x115d0525
802  "sp6JS7f14BuwFY8MwFHfXq9x5NbPY", // 15. 0x115d0525
803  "sp6JS7f14BuwFY8MwFrjWq5LAB8NT", // 16. 0x115d0525
804  "sp6JS7f14BuwFY8Mwj4asgSh6hQZd", // 17. 0x115d0525
805  "sp6JS7f14BuwFY8Mwj7ipFfqBSRrE", // 18. 0x115d0525
806  "sp6JS7f14BuwFY8MwjHqtcvGav8uW", // 19. 0x115d0525
807  "sp6JS7f14BuwFY8MwjLp4sk5fmzki", // 20. 0x115d0525
808  "sp6JS7f14BuwFY8MwjioHuYb3Ytkx", // 21. 0x115d0525
809  "sp6JS7f14BuwFY8MwkRjHPXWi7fGN", // 22. 0x115d0525
810  "sp6JS7f14BuwFY8MwkdVdPV3LjNN1", // 23. 0x115d0525
811  "sp6JS7f14BuwFY8MwkxUtVY5AXZFk", // 24. 0x115d0525
812  "sp6JS7f14BuwFY8Mwm4jQzdfTbY9F", // 25. 0x115d0525
813  "sp6JS7f14BuwFY8MwmCucYAqNp4iF", // 26. 0x115d0525
814  "sp6JS7f14BuwFY8Mwo2bgdFtxBzpF", // 27. 0x115d0525
815  "sp6JS7f14BuwFY8MwoGwD7v4U6qBh", // 28. 0x115d0525
816  "sp6JS7f14BuwFY8MwoUczqFADMoXi", // 29. 0x115d0525
817  "sp6JS7f14BuwFY8MwoY1xZeGd3gAr", // 30. 0x115d0525
818  "sp6JS7f14BuwFY8MwomVCbfkv4kYZ", // 31. 0x115d0525
819  "sp6JS7f14BuwFY8MwoqbrPSr4z13F", // 32. 0x115d0525
820  };
821 
822  // Create accounts for all of the seeds and fund those accounts.
823  std::vector<Account> accounts;
824  accounts.reserve(seeds.size());
825  for (std::string_view const& seed : seeds)
826  {
827  Account const& account =
828  accounts.emplace_back(Account::base58Seed, std::string(seed));
829  env.fund(XRP(10000), account);
830  env.close();
831  }
832 
833  // All of the accounts create seven consecutive NFTs and and offer
834  // those NFTs to buyer.
835  std::array<std::vector<uint256>, 7> nftIDsByPage;
836  for (auto& vec : nftIDsByPage)
837  vec.reserve(accounts.size());
839  for (auto& vec : offers)
840  vec.reserve(accounts.size());
841  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
842  {
843  for (Account const& account : accounts)
844  {
845  // Mint the NFT. Tweak the taxon so zero is always stored.
846  std::uint32_t taxon =
847  toUInt32(nft::cipheredTaxon(i, nft::toTaxon(0)));
848 
849  uint256 const& nftID = nftIDsByPage[i].emplace_back(
850  token::getNextID(env, account, taxon, tfTransferable));
851  env(token::mint(account, taxon), txflags(tfTransferable));
852  env.close();
853 
854  // Create an offer to give the NFT to buyer for free.
855  offers[i].emplace_back(
856  keylet::nftoffer(account, env.seq(account)).key);
857  env(token::createOffer(account, nftID, XRP(0)),
858  token::destination(buyer),
859  txflags((tfSellNFToken)));
860  }
861  }
862  env.close();
863 
864  // Verify that the low 96 bits of all generated NFTs of the same
865  // sequence is identical.
866  for (auto const& vec : nftIDsByPage)
867  {
868  uint256 const expectLowBits = vec.front() & nft::pageMask;
869  for (uint256 const& nftID : vec)
870  {
871  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
872  }
873  }
874 
875  // Remove one NFT and offer from each of the vectors. These offers
876  // are the ones that will overflow the page.
877  std::vector<uint256> overflowNFTs;
878  overflowNFTs.reserve(nftIDsByPage.size());
879  std::vector<uint256> overflowOffers;
880  overflowOffers.reserve(nftIDsByPage.size());
881 
882  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
883  {
884  overflowNFTs.push_back(nftIDsByPage[i].back());
885  nftIDsByPage[i].pop_back();
886  BEAST_EXPECT(nftIDsByPage[i].size() == seeds.size() - 1);
887 
888  overflowOffers.push_back(offers[i].back());
889  offers[i].pop_back();
890  BEAST_EXPECT(offers[i].size() == seeds.size() - 1);
891  }
892 
893  // buyer accepts all of the offers that won't cause an overflow.
894  // Fill the center and outsides first to exercise different boundary
895  // cases.
896  for (int i : std::initializer_list<int>{3, 6, 0, 1, 2, 5, 4})
897  {
898  for (uint256 const& offer : offers[i])
899  {
900  env(token::acceptSellOffer(buyer, offer));
901  env.close();
902  }
903  }
904 
905  // buyer accepts the seven offers that would cause page overflows if
906  // the transaction succeeded.
907  for (uint256 const& offer : overflowOffers)
908  {
909  env(token::acceptSellOffer(buyer, offer),
911  env.close();
912  }
913 
914  // Verify that all expected NFTs are owned by buyer and findable in
915  // the ledger by having buyer create sell offers for all of their NFTs.
916  // Attempting to sell an offer that the ledger can't find generates
917  // a non-tesSUCCESS error code.
918  for (auto const& vec : nftIDsByPage)
919  {
920  for (uint256 const& nftID : vec)
921  {
922  env(token::createOffer(buyer, nftID, XRP(100)),
923  txflags(tfSellNFToken));
924  env.close();
925  }
926  }
927 
928  // See what the account_objects command does with "nft_offer".
929  {
930  Json::Value ownedNftOffers(Json::arrayValue);
931  std::string marker;
932  do
933  {
934  Json::Value buyerOffers = [&env, &buyer, &marker]() {
935  Json::Value params;
936  params[jss::account] = buyer.human();
937  params[jss::type] = jss::nft_offer;
938 
939  if (!marker.empty())
940  params[jss::marker] = marker;
941  return env.rpc(
942  "json", "account_objects", to_string(params));
943  }();
944 
945  marker.clear();
946  if (buyerOffers.isMember(jss::result))
947  {
948  Json::Value& result = buyerOffers[jss::result];
949 
950  if (result.isMember(jss::marker))
951  marker = result[jss::marker].asString();
952 
953  if (result.isMember(jss::account_objects))
954  {
955  Json::Value& someOffers = result[jss::account_objects];
956  for (std::size_t i = 0; i < someOffers.size(); ++i)
957  ownedNftOffers.append(someOffers[i]);
958  }
959  }
960  } while (!marker.empty());
961 
962  // Verify there are as many offers are there are NFTs.
963  {
964  std::size_t totalOwnedNFTs = 0;
965  for (auto const& vec : nftIDsByPage)
966  totalOwnedNFTs += vec.size();
967  BEAST_EXPECT(ownedNftOffers.size() == totalOwnedNFTs);
968  }
969 
970  // Cancel all the offers.
971  {
972  std::vector<uint256> cancelOffers;
973  cancelOffers.reserve(ownedNftOffers.size());
974 
975  for (auto const& offer : ownedNftOffers)
976  {
977  if (offer.isMember(jss::index))
978  {
979  uint256 offerIndex;
980  if (offerIndex.parseHex(offer[jss::index].asString()))
981  cancelOffers.push_back(offerIndex);
982  }
983  }
984  env(token::cancelOffer(buyer, cancelOffers));
985  env.close();
986  }
987 
988  // account_objects should no longer return any "nft_offer"s.
989  Json::Value remainingOffers = [&env, &buyer]() {
990  Json::Value params;
991  params[jss::account] = buyer.human();
992  params[jss::type] = jss::nft_offer;
993 
994  return env.rpc("json", "account_objects", to_string(params));
995  }();
996  BEAST_EXPECT(
997  remainingOffers.isMember(jss::result) &&
998  remainingOffers[jss::result].isMember(jss::account_objects) &&
999  remainingOffers[jss::result][jss::account_objects].size() == 0);
1000  }
1001 
1002  // Verify that the ledger reports all of the NFTs owned by buyer.
1003  // Use the account_nfts rpc call to get the values.
1004  Json::Value ownedNFTs(Json::arrayValue);
1005  std::string marker;
1006  do
1007  {
1008  Json::Value buyerNFTs = [&env, &buyer, &marker]() {
1009  Json::Value params;
1010  params[jss::account] = buyer.human();
1011  params[jss::type] = "state";
1012 
1013  if (!marker.empty())
1014  params[jss::marker] = marker;
1015  return env.rpc("json", "account_nfts", to_string(params));
1016  }();
1017 
1018  marker.clear();
1019  if (buyerNFTs.isMember(jss::result))
1020  {
1021  Json::Value& result = buyerNFTs[jss::result];
1022 
1023  if (result.isMember(jss::marker))
1024  marker = result[jss::marker].asString();
1025 
1026  if (result.isMember(jss::account_nfts))
1027  {
1028  Json::Value& someNFTs = result[jss::account_nfts];
1029  for (std::size_t i = 0; i < someNFTs.size(); ++i)
1030  ownedNFTs.append(someNFTs[i]);
1031  }
1032  }
1033  } while (!marker.empty());
1034 
1035  // Copy all of the nftIDs into a set to make validation easier.
1036  std::set<uint256> allNftIDs;
1037  for (auto& vec : nftIDsByPage)
1038  allNftIDs.insert(vec.begin(), vec.end());
1039 
1040  BEAST_EXPECT(ownedNFTs.size() == allNftIDs.size());
1041 
1042  for (Json::Value const& ownedNFT : ownedNFTs)
1043  {
1044  if (ownedNFT.isMember(sfNFTokenID.jsonName))
1045  {
1046  uint256 ownedID;
1047  BEAST_EXPECT(ownedID.parseHex(
1048  ownedNFT[sfNFTokenID.jsonName].asString()));
1049  auto const foundIter = allNftIDs.find(ownedID);
1050 
1051  // Assuming we find the NFT, erase it so we know it's been found
1052  // and can't be found again.
1053  if (BEAST_EXPECT(foundIter != allNftIDs.end()))
1054  allNftIDs.erase(foundIter);
1055  }
1056  }
1057 
1058  // All NFTs should now be accounted for, so allNftIDs should be empty.
1059  BEAST_EXPECT(allNftIDs.empty());
1060  }
1061 
1062  void
1064  {
1065  testConsecutiveNFTs(features);
1066  testLopsidedSplits(features);
1067  testFixNFTokenDirV1(features);
1068  testTooManyEquivalent(features);
1069  testConsecutivePacking(features);
1070  }
1071 
1072 public:
1073  void
1074  run() override
1075  {
1076  using namespace test::jtx;
1077  FeatureBitset const all{supported_amendments()};
1078  FeatureBitset const fixNFTDir{
1080 
1081  testWithFeats(all - fixNFTDir);
1082  testWithFeats(all);
1083  }
1084 };
1085 
1086 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDir, tx, ripple, 1);
1087 
1088 } // namespace ripple
1089 
1090 // Seed that produces an account with the low-32 bits == 0xFFFFFFFF in
1091 // case it is needed for future testing:
1092 //
1093 // sp6JS7f14BuwFY8MwFe95Vpi9Znjs
1094 //
1095 
1096 // Sets of related accounts.
1097 //
1098 // Identifying the seeds of accounts that generate account IDs with the
1099 // same low 32 bits takes a while. However several sets of accounts with
1100 // that relationship have been located. In case these sets of accounts are
1101 // needed for future testing scenarios they are recorded below.
1102 #if 0
1103 34 account seeds that produce account IDs with low 32-bits 0x399187e9:
1104  sp6JS7f14BuwFY8Mw5EYu5z86hKDL
1105  sp6JS7f14BuwFY8Mw5PUAMwc5ygd7
1106  sp6JS7f14BuwFY8Mw5R3xUBcLSeTs
1107  sp6JS7f14BuwFY8Mw5W6oS5sdC3oF
1108  sp6JS7f14BuwFY8Mw5pYc3D9iuLcw
1109  sp6JS7f14BuwFY8Mw5pfGVnhcdp3b
1110  sp6JS7f14BuwFY8Mw6jS6RdEqXqrN
1111  sp6JS7f14BuwFY8Mw6krt6AKbvRXW
1112  sp6JS7f14BuwFY8Mw6mnVBQq7cAN2
1113  sp6JS7f14BuwFY8Mw8ECJxPjmkufQ
1114  sp6JS7f14BuwFY8Mw8asgzcceGWYm
1115  sp6JS7f14BuwFY8MwF6J3FXnPCgL8
1116  sp6JS7f14BuwFY8MwFEud2w5czv5q
1117  sp6JS7f14BuwFY8MwFNxKVqJnx8P5
1118  sp6JS7f14BuwFY8MwFnTCXg3eRidL
1119  sp6JS7f14BuwFY8Mwj47hv1vrDge6
1120  sp6JS7f14BuwFY8Mwj6TYekeeyukh
1121  sp6JS7f14BuwFY8MwjFjsRDerz7jb
1122  sp6JS7f14BuwFY8Mwjrj9mHTLBrcX
1123  sp6JS7f14BuwFY8MwkKcJi3zMzAea
1124  sp6JS7f14BuwFY8MwkYTDdnYRm9z4
1125  sp6JS7f14BuwFY8Mwkq8ei4D8uPNd
1126  sp6JS7f14BuwFY8Mwm2pFruxbnJRd
1127  sp6JS7f14BuwFY8MwmJV2ZnAjpC2g
1128  sp6JS7f14BuwFY8MwmTFMPHQHfVYF
1129  sp6JS7f14BuwFY8MwmkG2jXEgqiud
1130  sp6JS7f14BuwFY8Mwms3xEh5tMDTw
1131  sp6JS7f14BuwFY8MwmtipW4D8giZ9
1132  sp6JS7f14BuwFY8MwoRQBZm4KUUeE
1133  sp6JS7f14BuwFY8MwoVey94QpXcrc
1134  sp6JS7f14BuwFY8MwoZiuUoUTo3VG
1135  sp6JS7f14BuwFY8MwonFFDLT4bHAZ
1136  sp6JS7f14BuwFY8MwooGphD4hefBQ
1137  sp6JS7f14BuwFY8MwoxDp3dmX6q5N
1138 
1139 34 account seeds that produce account IDs with low 32-bits 0x473f2c9a:
1140  sp6JS7f14BuwFY8Mw53ktgqmv5Bmz
1141  sp6JS7f14BuwFY8Mw5KPb2Kz7APFX
1142  sp6JS7f14BuwFY8Mw5Xx4A6HRTPEE
1143  sp6JS7f14BuwFY8Mw5y6qZFNAo358
1144  sp6JS7f14BuwFY8Mw6kdaBg1QrZfn
1145  sp6JS7f14BuwFY8Mw8QmTfLMAZ5K1
1146  sp6JS7f14BuwFY8Mw8cbRRVcCEELr
1147  sp6JS7f14BuwFY8Mw8gQvJebmxvDG
1148  sp6JS7f14BuwFY8Mw8qPQurwu3P7Y
1149  sp6JS7f14BuwFY8MwFS4PEVKmuPy5
1150  sp6JS7f14BuwFY8MwFUQM1rAsQ8tS
1151  sp6JS7f14BuwFY8MwjJBZCkuwsRnM
1152  sp6JS7f14BuwFY8MwjTdS8vZhX5E9
1153  sp6JS7f14BuwFY8MwjhSmWCbNhd25
1154  sp6JS7f14BuwFY8MwjwkpqwZsDBw9
1155  sp6JS7f14BuwFY8MwjyET4p6eqd5J
1156  sp6JS7f14BuwFY8MwkMNAe4JhnG7E
1157  sp6JS7f14BuwFY8MwkRRpnT93UWWS
1158  sp6JS7f14BuwFY8MwkY9CvB22RvUe
1159  sp6JS7f14BuwFY8Mwkhw9VxXqmTr7
1160  sp6JS7f14BuwFY8MwkmgaTat7eFa7
1161  sp6JS7f14BuwFY8Mwkq5SxGGv1oLH
1162  sp6JS7f14BuwFY8MwmCBM5p5bTg6y
1163  sp6JS7f14BuwFY8MwmmmXaVah64dB
1164  sp6JS7f14BuwFY8Mwo7R7Cn614v9V
1165  sp6JS7f14BuwFY8MwoCAG1na7GR2M
1166  sp6JS7f14BuwFY8MwoDuPvJS4gG7C
1167  sp6JS7f14BuwFY8MwoMMowSyPQLfy
1168  sp6JS7f14BuwFY8MwoRqDiwTNsTBm
1169  sp6JS7f14BuwFY8MwoWbBWtjpB7pg
1170  sp6JS7f14BuwFY8Mwoi1AEeELGecF
1171  sp6JS7f14BuwFY8MwopGP6Lo5byuj
1172  sp6JS7f14BuwFY8MwoufkXGHp2VW8
1173  sp6JS7f14BuwFY8MwowGeagFQY32k
1174 
1175 34 account seeds that produce account IDs with low 32-bits 0x4d59f0d1:
1176  sp6JS7f14BuwFY8Mw5CsNgH64zxK7
1177  sp6JS7f14BuwFY8Mw5Dg4wi2E344h
1178  sp6JS7f14BuwFY8Mw5ErV949Zh2PX
1179  sp6JS7f14BuwFY8Mw5p4nsQvEUE1s
1180  sp6JS7f14BuwFY8Mw8LGnkbaP68Gn
1181  sp6JS7f14BuwFY8Mw8aq6RCBc3iHo
1182  sp6JS7f14BuwFY8Mw8bkWaGoKYT6e
1183  sp6JS7f14BuwFY8Mw8qrCuXnzAXVj
1184  sp6JS7f14BuwFY8MwFDKcPAHPHJTm
1185  sp6JS7f14BuwFY8MwFUXJs4unfgNu
1186  sp6JS7f14BuwFY8MwFj9Yv5LjshD9
1187  sp6JS7f14BuwFY8Mwj3H73nmq5UaC
1188  sp6JS7f14BuwFY8MwjHSYShis1Yhk
1189  sp6JS7f14BuwFY8MwjpfE1HVo8UP1
1190  sp6JS7f14BuwFY8Mwk6JE1SXUuiNc
1191  sp6JS7f14BuwFY8MwkASgxEjEnFmU
1192  sp6JS7f14BuwFY8MwkGNY8kg7R6RK
1193  sp6JS7f14BuwFY8MwkHinNZ8SYBQu
1194  sp6JS7f14BuwFY8MwkXLCW1hbhGya
1195  sp6JS7f14BuwFY8MwkZ7mWrYK9YtU
1196  sp6JS7f14BuwFY8MwkdFSqNB5DbKL
1197  sp6JS7f14BuwFY8Mwm3jdBaCAx8H6
1198  sp6JS7f14BuwFY8Mwm3rk5hEwDRtY
1199  sp6JS7f14BuwFY8Mwm77a2ULuwxu4
1200  sp6JS7f14BuwFY8MwmJpY7braKLaN
1201  sp6JS7f14BuwFY8MwmKHQjG4XiZ6g
1202  sp6JS7f14BuwFY8Mwmmv8Y3wyUDzs
1203  sp6JS7f14BuwFY8MwmucFe1WgqtwG
1204  sp6JS7f14BuwFY8Mwo1EjdU1bznZR
1205  sp6JS7f14BuwFY8MwoJiqankkU5uR
1206  sp6JS7f14BuwFY8MwoLnvQ6zdqbKw
1207  sp6JS7f14BuwFY8MwoUGeJ319eu48
1208  sp6JS7f14BuwFY8MwoYf135tQjHP4
1209  sp6JS7f14BuwFY8MwogeF6M6SAyid
1210 
1211 34 account seeds that produce account IDs with low 32-bits 0xabb11898:
1212  sp6JS7f14BuwFY8Mw5DgiYaNVSb1G
1213  sp6JS7f14BuwFY8Mw5k6e94TMvuox
1214  sp6JS7f14BuwFY8Mw5tTSN7KzYxiT
1215  sp6JS7f14BuwFY8Mw61XV6m33utif
1216  sp6JS7f14BuwFY8Mw87jKfrjiENCb
1217  sp6JS7f14BuwFY8Mw8AFtxxFiRtJG
1218  sp6JS7f14BuwFY8Mw8cosAVExzbeE
1219  sp6JS7f14BuwFY8Mw8fmkQ63zE8WQ
1220  sp6JS7f14BuwFY8Mw8iYSsxNbDN6D
1221  sp6JS7f14BuwFY8Mw8wTZdGRJyyM1
1222  sp6JS7f14BuwFY8Mw8z7xEh3qBGr7
1223  sp6JS7f14BuwFY8MwFL5gpKQWZj7g
1224  sp6JS7f14BuwFY8MwFPeZchXQnRZ5
1225  sp6JS7f14BuwFY8MwFSPxWSJVoU29
1226  sp6JS7f14BuwFY8MwFYyVkqX8kvRm
1227  sp6JS7f14BuwFY8MwFcbVikUEwJvk
1228  sp6JS7f14BuwFY8MwjF7NcZk1NctK
1229  sp6JS7f14BuwFY8MwjJCwYr9zSfAv
1230  sp6JS7f14BuwFY8MwjYa5yLkgCLuT
1231  sp6JS7f14BuwFY8MwjenxuJ3TH2Bc
1232  sp6JS7f14BuwFY8MwjriN7Ui11NzB
1233  sp6JS7f14BuwFY8Mwk3AuoJNSEo34
1234  sp6JS7f14BuwFY8MwkT36hnRv8hTo
1235  sp6JS7f14BuwFY8MwkTQixEXfi1Cr
1236  sp6JS7f14BuwFY8MwkYJaZM1yTJBF
1237  sp6JS7f14BuwFY8Mwkc4k1uo85qp2
1238  sp6JS7f14BuwFY8Mwkf7cFhF1uuxx
1239  sp6JS7f14BuwFY8MwmCK2un99wb4e
1240  sp6JS7f14BuwFY8MwmETztNHYu2Bx
1241  sp6JS7f14BuwFY8MwmJws9UwRASfR
1242  sp6JS7f14BuwFY8MwoH5PQkGK8tEb
1243  sp6JS7f14BuwFY8MwoVXtP2yCzjJV
1244  sp6JS7f14BuwFY8MwobxRXA9vsTeX
1245  sp6JS7f14BuwFY8Mwos3pc5Gb3ihU
1246 
1247 34 account seeds that produce account IDs with low 32-bits 0xce627322:
1248  sp6JS7f14BuwFY8Mw5Ck6i83pGNh3
1249  sp6JS7f14BuwFY8Mw5FKuwTxjAdH1
1250  sp6JS7f14BuwFY8Mw5FVKkEn6TkLH
1251  sp6JS7f14BuwFY8Mw5NbQwLwHDd5v
1252  sp6JS7f14BuwFY8Mw5X1dbz3msZaZ
1253  sp6JS7f14BuwFY8Mw6qv6qaXNeP74
1254  sp6JS7f14BuwFY8Mw81SXagUeutCw
1255  sp6JS7f14BuwFY8Mw84Ph7Qa8kwwk
1256  sp6JS7f14BuwFY8Mw8Hp4gFyU3Qko
1257  sp6JS7f14BuwFY8Mw8Kt8bAKredSx
1258  sp6JS7f14BuwFY8Mw8XHK3VKRQ7v7
1259  sp6JS7f14BuwFY8Mw8eGyWxZGHY6v
1260  sp6JS7f14BuwFY8Mw8iU5CLyHVcD2
1261  sp6JS7f14BuwFY8Mw8u3Zr26Ar914
1262  sp6JS7f14BuwFY8MwF2Kcdxtjzjv8
1263  sp6JS7f14BuwFY8MwFLmPWb6rbxNg
1264  sp6JS7f14BuwFY8MwFUu8s7UVuxuJ
1265  sp6JS7f14BuwFY8MwFYBaatwHxAJ8
1266  sp6JS7f14BuwFY8Mwjg6hFkeHwoqG
1267  sp6JS7f14BuwFY8MwjjycJojy2ufk
1268  sp6JS7f14BuwFY8MwkEWoxcSKGPXv
1269  sp6JS7f14BuwFY8MwkMe7wLkEUsQT
1270  sp6JS7f14BuwFY8MwkvyKLaPUc4FS
1271  sp6JS7f14BuwFY8Mwm8doqXPKZmVQ
1272  sp6JS7f14BuwFY8Mwm9r3No8yQ8Tx
1273  sp6JS7f14BuwFY8Mwm9w6dks68W9B
1274  sp6JS7f14BuwFY8MwmMPrv9sCdbpS
1275  sp6JS7f14BuwFY8MwmPAvs3fcQNja
1276  sp6JS7f14BuwFY8MwmS5jasapfcnJ
1277  sp6JS7f14BuwFY8MwmU2L3qJEhnuA
1278  sp6JS7f14BuwFY8MwoAQYmiBnW7fM
1279  sp6JS7f14BuwFY8MwoBkkkXrPmkKF
1280  sp6JS7f14BuwFY8MwonfmxPo6tkvC
1281  sp6JS7f14BuwFY8MwouZFwhiNcYq6
1282 
1283 34 account seeds that produce account IDs with low 32-bits 0xe29643e8:
1284  sp6JS7f14BuwFY8Mw5EfAavcXAh2k
1285  sp6JS7f14BuwFY8Mw5LhFjLkFSCVF
1286  sp6JS7f14BuwFY8Mw5bRfEv5HgdBh
1287  sp6JS7f14BuwFY8Mw5d6sPcKzypKN
1288  sp6JS7f14BuwFY8Mw5rcqDtk1fACP
1289  sp6JS7f14BuwFY8Mw5xkxRq1Notzv
1290  sp6JS7f14BuwFY8Mw66fbkdw5WYmt
1291  sp6JS7f14BuwFY8Mw6diEG8sZ7Fx7
1292  sp6JS7f14BuwFY8Mw6v2r1QhG7xc1
1293  sp6JS7f14BuwFY8Mw6zP6DHCTx2Fd
1294  sp6JS7f14BuwFY8Mw8B3n39JKuFkk
1295  sp6JS7f14BuwFY8Mw8FmBvqYw7uqn
1296  sp6JS7f14BuwFY8Mw8KEaftb1eRwu
1297  sp6JS7f14BuwFY8Mw8WJ1qKkegj9N
1298  sp6JS7f14BuwFY8Mw8r8cAZEkq2BS
1299  sp6JS7f14BuwFY8MwFKPxxwF65gZh
1300  sp6JS7f14BuwFY8MwFKhaF8APcN5H
1301  sp6JS7f14BuwFY8MwFN2buJn4BgYC
1302  sp6JS7f14BuwFY8MwFUTe175MjP3x
1303  sp6JS7f14BuwFY8MwFZhmRDb53NNb
1304  sp6JS7f14BuwFY8MwFa2Azn5nU2WS
1305  sp6JS7f14BuwFY8MwjNNt91hwgkn7
1306  sp6JS7f14BuwFY8MwjdiYt6ChACe7
1307  sp6JS7f14BuwFY8Mwk5qFVQ48Mmr9
1308  sp6JS7f14BuwFY8MwkGvCj7pNf1zG
1309  sp6JS7f14BuwFY8MwkY9UcN2D2Fzs
1310  sp6JS7f14BuwFY8MwkpGvSk9G9RyT
1311  sp6JS7f14BuwFY8MwmGQ7nJf1eEzV
1312  sp6JS7f14BuwFY8MwmQLjGsYdyAmV
1313  sp6JS7f14BuwFY8MwmZ8usztKvikT
1314  sp6JS7f14BuwFY8MwobyMLC2hQdFR
1315  sp6JS7f14BuwFY8MwoiRtwUecZeJ5
1316  sp6JS7f14BuwFY8MwojHjKsUzj1KJ
1317  sp6JS7f14BuwFY8Mwop29anGAjidU
1318 
1319 33 account seeds that produce account IDs with low 32-bits 0x115d0525:
1320  sp6JS7f14BuwFY8Mw56vZeiBuhePx
1321  sp6JS7f14BuwFY8Mw5BodF9tGuTUe
1322  sp6JS7f14BuwFY8Mw5EnhC1cg84J7
1323  sp6JS7f14BuwFY8Mw5P913Cunr2BK
1324  sp6JS7f14BuwFY8Mw5Pru7eLo1XzT
1325  sp6JS7f14BuwFY8Mw61SLUC8UX2m8
1326  sp6JS7f14BuwFY8Mw6AsBF9TpeMpq
1327  sp6JS7f14BuwFY8Mw84XqrBZkU2vE
1328  sp6JS7f14BuwFY8Mw89oSU6dBk3KB
1329  sp6JS7f14BuwFY8Mw89qUKCyDmyzj
1330  sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm
1331  sp6JS7f14BuwFY8Mw8LtW3VqrqMks
1332  sp6JS7f14BuwFY8Mw8ZrAkJc2sHew
1333  sp6JS7f14BuwFY8Mw8jpkYSNrD3ah
1334  sp6JS7f14BuwFY8MwF2mshd786m3V
1335  sp6JS7f14BuwFY8MwFHfXq9x5NbPY
1336  sp6JS7f14BuwFY8MwFrjWq5LAB8NT
1337  sp6JS7f14BuwFY8Mwj4asgSh6hQZd
1338  sp6JS7f14BuwFY8Mwj7ipFfqBSRrE
1339  sp6JS7f14BuwFY8MwjHqtcvGav8uW
1340  sp6JS7f14BuwFY8MwjLp4sk5fmzki
1341  sp6JS7f14BuwFY8MwjioHuYb3Ytkx
1342  sp6JS7f14BuwFY8MwkRjHPXWi7fGN
1343  sp6JS7f14BuwFY8MwkdVdPV3LjNN1
1344  sp6JS7f14BuwFY8MwkxUtVY5AXZFk
1345  sp6JS7f14BuwFY8Mwm4jQzdfTbY9F
1346  sp6JS7f14BuwFY8MwmCucYAqNp4iF
1347  sp6JS7f14BuwFY8Mwo2bgdFtxBzpF
1348  sp6JS7f14BuwFY8MwoGwD7v4U6qBh
1349  sp6JS7f14BuwFY8MwoUczqFADMoXi
1350  sp6JS7f14BuwFY8MwoY1xZeGd3gAr
1351  sp6JS7f14BuwFY8MwomVCbfkv4kYZ
1352  sp6JS7f14BuwFY8MwoqbrPSr4z13F
1353 
1354 33 account seeds that produce account IDs with low 32-bits 0x304033aa:
1355  sp6JS7f14BuwFY8Mw5DaUP9agF5e1
1356  sp6JS7f14BuwFY8Mw5ohbtmPN4yGN
1357  sp6JS7f14BuwFY8Mw5rRsA5fcoTAQ
1358  sp6JS7f14BuwFY8Mw6zpYHMY3m6KT
1359  sp6JS7f14BuwFY8Mw86BzQq4sTnoW
1360  sp6JS7f14BuwFY8Mw8CCpnfvmGdV7
1361  sp6JS7f14BuwFY8Mw8DRjUDaBcFco
1362  sp6JS7f14BuwFY8Mw8cL7GPo3zZN7
1363  sp6JS7f14BuwFY8Mw8y6aeYVtH6qt
1364  sp6JS7f14BuwFY8MwFZR3PtVTCdUH
1365  sp6JS7f14BuwFY8MwFcdcdbgz7m3s
1366  sp6JS7f14BuwFY8MwjdnJDiUxEBRR
1367  sp6JS7f14BuwFY8MwjhxWgSntqrFe
1368  sp6JS7f14BuwFY8MwjrSHEhZ8CUM1
1369  sp6JS7f14BuwFY8MwjzkEeSTc9ZYf
1370  sp6JS7f14BuwFY8MwkBZSk9JhaeCB
1371  sp6JS7f14BuwFY8MwkGfwNY4i2iiU
1372  sp6JS7f14BuwFY8MwknjtZd2oU2Ff
1373  sp6JS7f14BuwFY8Mwkszsqd3ok9NE
1374  sp6JS7f14BuwFY8Mwm58A81MAMvgZ
1375  sp6JS7f14BuwFY8MwmiPTWysuDJCH
1376  sp6JS7f14BuwFY8MwmxhiNeLfD76r
1377  sp6JS7f14BuwFY8Mwo7SPdkwpGrFH
1378  sp6JS7f14BuwFY8MwoANq4F1Sj3qH
1379  sp6JS7f14BuwFY8MwoVjcHufAkd6L
1380  sp6JS7f14BuwFY8MwoVxHBXdaxzhm
1381  sp6JS7f14BuwFY8MwoZ2oTjBNfLpm
1382  sp6JS7f14BuwFY8Mwoc9swzyotFVD
1383  sp6JS7f14BuwFY8MwogMqVRwVEcQ9
1384  sp6JS7f14BuwFY8MwohMm7WxwnFqH
1385  sp6JS7f14BuwFY8MwopUcpZHuF8BH
1386  sp6JS7f14BuwFY8Mwor6rW6SS7tiB
1387  sp6JS7f14BuwFY8MwoxyaqYz4Ngsb
1388 
1389 33 account seeds that produce account IDs with low 32-bits 0x42d4e09c:
1390  sp6JS7f14BuwFY8Mw58NSZH9EaUxQ
1391  sp6JS7f14BuwFY8Mw5JByk1pgPpL7
1392  sp6JS7f14BuwFY8Mw5YrJJuXnkHVB
1393  sp6JS7f14BuwFY8Mw5kZe2ZzNSnKR
1394  sp6JS7f14BuwFY8Mw6eXHTsbwi1U7
1395  sp6JS7f14BuwFY8Mw6gqN7HHDDKSh
1396  sp6JS7f14BuwFY8Mw6zw8L1sSSR53
1397  sp6JS7f14BuwFY8Mw8E4WqSKKbksy
1398  sp6JS7f14BuwFY8MwF3V9gemqJtND
1399  sp6JS7f14BuwFY8Mwj4j46LHWZuY6
1400  sp6JS7f14BuwFY8MwjF5i8vh4Ezjy
1401  sp6JS7f14BuwFY8MwjJZpEKgMpUAt
1402  sp6JS7f14BuwFY8MwjWL7LfnzNUuh
1403  sp6JS7f14BuwFY8Mwk7Y1csGuqAhX
1404  sp6JS7f14BuwFY8MwkB1HVH17hN5W
1405  sp6JS7f14BuwFY8MwkBntH7BZZupu
1406  sp6JS7f14BuwFY8MwkEy4rMbNHG9P
1407  sp6JS7f14BuwFY8MwkKz4LYesZeiN
1408  sp6JS7f14BuwFY8MwkUrXyo9gMDPM
1409  sp6JS7f14BuwFY8MwkV2hySsxej1G
1410  sp6JS7f14BuwFY8MwkozhTVN12F9C
1411  sp6JS7f14BuwFY8MwkpkzGB3sFJw5
1412  sp6JS7f14BuwFY8Mwks3zDZLGrhdn
1413  sp6JS7f14BuwFY8MwktG1KCS7L2wW
1414  sp6JS7f14BuwFY8Mwm1jVFsafwcYx
1415  sp6JS7f14BuwFY8Mwm8hmrU6g5Wd6
1416  sp6JS7f14BuwFY8MwmFvstfRF7e2f
1417  sp6JS7f14BuwFY8MwmeRohi6m5fs8
1418  sp6JS7f14BuwFY8MwmmU96RHUaRZL
1419  sp6JS7f14BuwFY8MwoDFzteYqaUh4
1420  sp6JS7f14BuwFY8MwoPkTf5tDykPF
1421  sp6JS7f14BuwFY8MwoSbMaDtiMoDN
1422  sp6JS7f14BuwFY8MwoVL1vY1CysjR
1423 
1424 33 account seeds that produce account IDs with low 32-bits 0x9a8ebed3:
1425  sp6JS7f14BuwFY8Mw5FnqmbciPvH6
1426  sp6JS7f14BuwFY8Mw5MBGbyMSsXLp
1427  sp6JS7f14BuwFY8Mw5S4PnDyBdKKm
1428  sp6JS7f14BuwFY8Mw6kcXpM2enE35
1429  sp6JS7f14BuwFY8Mw6tuuSMMwyJ44
1430  sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt
1431  sp6JS7f14BuwFY8Mw8WwdgWkCHhEx
1432  sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ
1433  sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ
1434  sp6JS7f14BuwFY8Mw8fdSwLCZWDFd
1435  sp6JS7f14BuwFY8Mw8zuF6Fg65i1E
1436  sp6JS7f14BuwFY8MwF2k7bihVfqes
1437  sp6JS7f14BuwFY8MwF6X24WXGn557
1438  sp6JS7f14BuwFY8MwFMpn7strjekg
1439  sp6JS7f14BuwFY8MwFSdy9sYVrwJs
1440  sp6JS7f14BuwFY8MwFdMcLy9UkrXn
1441  sp6JS7f14BuwFY8MwFdbwFm1AAboa
1442  sp6JS7f14BuwFY8MwFdr5AhKThVtU
1443  sp6JS7f14BuwFY8MwjFc3Q9YatvAw
1444  sp6JS7f14BuwFY8MwjRXcNs1ozEXn
1445  sp6JS7f14BuwFY8MwkQGUKL7v1FBt
1446  sp6JS7f14BuwFY8Mwkamsoxx1wECt
1447  sp6JS7f14BuwFY8Mwm3hus1dG6U8y
1448  sp6JS7f14BuwFY8Mwm589M8vMRpXF
1449  sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3
1450  sp6JS7f14BuwFY8MwmRfy8fer4QbL
1451  sp6JS7f14BuwFY8MwmkkFx1HtgWRx
1452  sp6JS7f14BuwFY8MwmwP9JFdKa4PS
1453  sp6JS7f14BuwFY8MwoXWJLB3ciHfo
1454  sp6JS7f14BuwFY8MwoYc1gTtT2mWL
1455  sp6JS7f14BuwFY8MwogXtHH7FNVoo
1456  sp6JS7f14BuwFY8MwoqYoA9P8gf3r
1457  sp6JS7f14BuwFY8MwoujwMJofGnsA
1458 
1459 33 account seeds that produce account IDs with low 32-bits 0xa1dcea4a:
1460  sp6JS7f14BuwFY8Mw5Ccov2N36QTy
1461  sp6JS7f14BuwFY8Mw5CuSemVb5p7w
1462  sp6JS7f14BuwFY8Mw5Ep8wpsTfpSz
1463  sp6JS7f14BuwFY8Mw5WtutJc2H45M
1464  sp6JS7f14BuwFY8Mw6vsDeaSKeUJZ
1465  sp6JS7f14BuwFY8Mw83t5BPWUAzzF
1466  sp6JS7f14BuwFY8Mw8FYGnK35mgkV
1467  sp6JS7f14BuwFY8Mw8huo1x5pfKKJ
1468  sp6JS7f14BuwFY8Mw8mPStxfMDrZa
1469  sp6JS7f14BuwFY8Mw8yC3A7aQJytK
1470  sp6JS7f14BuwFY8MwFCWCDmo9o3t8
1471  sp6JS7f14BuwFY8MwFjapa4gKxPhR
1472  sp6JS7f14BuwFY8Mwj8CWtG29uw71
1473  sp6JS7f14BuwFY8MwjHyU5KpEMLVT
1474  sp6JS7f14BuwFY8MwjMZSN7LZuWD8
1475  sp6JS7f14BuwFY8Mwja2TXJNBhKHU
1476  sp6JS7f14BuwFY8Mwjf3xNTopHKTF
1477  sp6JS7f14BuwFY8Mwjn5RAhedPeuM
1478  sp6JS7f14BuwFY8MwkJdr4d6QoE8K
1479  sp6JS7f14BuwFY8MwkmBryo3SUoLm
1480  sp6JS7f14BuwFY8MwkrPdsc4tR8yw
1481  sp6JS7f14BuwFY8Mwkttjcw2a65Fi
1482  sp6JS7f14BuwFY8Mwm19n3rSaNx5S
1483  sp6JS7f14BuwFY8Mwm3ryr4Xp2aQX
1484  sp6JS7f14BuwFY8MwmBnDmgnJLB6B
1485  sp6JS7f14BuwFY8MwmHgPjzrYjthq
1486  sp6JS7f14BuwFY8MwmeV55DAnWKdd
1487  sp6JS7f14BuwFY8Mwo49hK6BGrauT
1488  sp6JS7f14BuwFY8Mwo56vfKY9aoWu
1489  sp6JS7f14BuwFY8MwoU7tTTXLQTrh
1490  sp6JS7f14BuwFY8MwoXpogSF2KaZB
1491  sp6JS7f14BuwFY8MwoY9JYQAR16pc
1492  sp6JS7f14BuwFY8MwoozLzKNAEXKM
1493 
1494 33 account seeds that produce account IDs with low 32-bits 0xbd2116db:
1495  sp6JS7f14BuwFY8Mw5GrpkmPuA3Bw
1496  sp6JS7f14BuwFY8Mw5r1sLoQJZDc6
1497  sp6JS7f14BuwFY8Mw68zzRmezLdd6
1498  sp6JS7f14BuwFY8Mw6jDSyaiF1mRp
1499  sp6JS7f14BuwFY8Mw813wU9u5D6Uh
1500  sp6JS7f14BuwFY8Mw8BBvpf2JFGoJ
1501  sp6JS7f14BuwFY8Mw8F7zXxAiT263
1502  sp6JS7f14BuwFY8Mw8XG7WuVGHP2N
1503  sp6JS7f14BuwFY8Mw8eyWrcz91cz6
1504  sp6JS7f14BuwFY8Mw8yNVKFVYyk9u
1505  sp6JS7f14BuwFY8MwF2oA6ePqvZWP
1506  sp6JS7f14BuwFY8MwF9VkcSNh3keq
1507  sp6JS7f14BuwFY8MwFYsMWajgEf2j
1508  sp6JS7f14BuwFY8Mwj3Gu43jYoJ4n
1509  sp6JS7f14BuwFY8MwjJ5iRmYDHrW4
1510  sp6JS7f14BuwFY8MwjaUSSga93CiM
1511  sp6JS7f14BuwFY8MwjxgLh2FY4Lvt
1512  sp6JS7f14BuwFY8Mwk9hQdNZUgmTB
1513  sp6JS7f14BuwFY8MwkcMXqtFp1sMx
1514  sp6JS7f14BuwFY8MwkzZCDc56jsUB
1515  sp6JS7f14BuwFY8Mwm5Zz7fP24Qym
1516  sp6JS7f14BuwFY8MwmDWqizXSoJRG
1517  sp6JS7f14BuwFY8MwmKHmkNYdMqqi
1518  sp6JS7f14BuwFY8MwmRfAWHxWpGNK
1519  sp6JS7f14BuwFY8MwmjCdXwyhphZ1
1520  sp6JS7f14BuwFY8MwmmukDAm1w6FL
1521  sp6JS7f14BuwFY8Mwmmz2SzaR9TRH
1522  sp6JS7f14BuwFY8Mwmz2z5mKHXzfn
1523  sp6JS7f14BuwFY8Mwo2xNe5629r5k
1524  sp6JS7f14BuwFY8MwoKy8tZxZrfJw
1525  sp6JS7f14BuwFY8MwoLyQ9aMsq8Dm
1526  sp6JS7f14BuwFY8MwoqqYkewuyZck
1527  sp6JS7f14BuwFY8MwouvvhREVp6Pp
1528 
1529 33 account seeds that produce account IDs with low 32-bits 0xd80df065:
1530  sp6JS7f14BuwFY8Mw5B7ERyhAfgHA
1531  sp6JS7f14BuwFY8Mw5VuW3cF7bm2v
1532  sp6JS7f14BuwFY8Mw5py3t1j7YbFT
1533  sp6JS7f14BuwFY8Mw5qc84SzB6RHr
1534  sp6JS7f14BuwFY8Mw5vGHW1G1hAy8
1535  sp6JS7f14BuwFY8Mw6gVa8TYukws6
1536  sp6JS7f14BuwFY8Mw8K9w1RoUAv1w
1537  sp6JS7f14BuwFY8Mw8KvKtB7787CA
1538  sp6JS7f14BuwFY8Mw8Y7WhRbuFzRq
1539  sp6JS7f14BuwFY8Mw8cipw7inRmMn
1540  sp6JS7f14BuwFY8MwFM5fAUNLNB13
1541  sp6JS7f14BuwFY8MwFSe1zAsht3X3
1542  sp6JS7f14BuwFY8MwFYNdigqQuHZM
1543  sp6JS7f14BuwFY8MwjWkejj7V4V5Q
1544  sp6JS7f14BuwFY8Mwjd2JGpsjvynq
1545  sp6JS7f14BuwFY8Mwjg1xkducn751
1546  sp6JS7f14BuwFY8Mwjsp6LnaJvL1W
1547  sp6JS7f14BuwFY8MwjvSbLc9593yH
1548  sp6JS7f14BuwFY8Mwjw2h5wx7U6vZ
1549  sp6JS7f14BuwFY8MwjxKUjtRsmPLH
1550  sp6JS7f14BuwFY8Mwk1Yy8ginDfqv
1551  sp6JS7f14BuwFY8Mwk2HrWhWwZP12
1552  sp6JS7f14BuwFY8Mwk4SsqiexvpWs
1553  sp6JS7f14BuwFY8Mwk66zCs5ACpE6
1554  sp6JS7f14BuwFY8MwkCwx6vY97Nwh
1555  sp6JS7f14BuwFY8MwknrbjnhTTWU8
1556  sp6JS7f14BuwFY8MwkokDy2ShRzQx
1557  sp6JS7f14BuwFY8Mwm3BxnRPNxsuu
1558  sp6JS7f14BuwFY8MwmY9EWdQQsFVr
1559  sp6JS7f14BuwFY8MwmYTWjrDhmk8S
1560  sp6JS7f14BuwFY8Mwo9skXt9Y5BVS
1561  sp6JS7f14BuwFY8MwoZYKZybJ1Crp
1562  sp6JS7f14BuwFY8MwoyXqkhySfSmF
1563 
1564 33 account seeds that produce account IDs with low 32-bits 0xe2e44294:
1565  sp6JS7f14BuwFY8Mw53dmvTgNtBwi
1566  sp6JS7f14BuwFY8Mw5Wrxsqn6WrXW
1567  sp6JS7f14BuwFY8Mw5fGDT31RCXgC
1568  sp6JS7f14BuwFY8Mw5nKRkubwrLWM
1569  sp6JS7f14BuwFY8Mw5nXMajwKjriB
1570  sp6JS7f14BuwFY8Mw5xZybggrC9NG
1571  sp6JS7f14BuwFY8Mw5xea8f6dBMV5
1572  sp6JS7f14BuwFY8Mw5zDGofAHy5Lb
1573  sp6JS7f14BuwFY8Mw6eado41rQNVG
1574  sp6JS7f14BuwFY8Mw6yqKXQsQJPuU
1575  sp6JS7f14BuwFY8Mw83MSN4FDzSGH
1576  sp6JS7f14BuwFY8Mw8B3pUbzQqHe2
1577  sp6JS7f14BuwFY8Mw8WwRLnhBRvfk
1578  sp6JS7f14BuwFY8Mw8hDBpKbpJwJX
1579  sp6JS7f14BuwFY8Mw8jggRSZACe7M
1580  sp6JS7f14BuwFY8Mw8mJRpU3qWbwC
1581  sp6JS7f14BuwFY8MwFDnVozykN21u
1582  sp6JS7f14BuwFY8MwFGGRGY9fctgv
1583  sp6JS7f14BuwFY8MwjKznfChH9DQb
1584  sp6JS7f14BuwFY8MwjbC5GvngRCk6
1585  sp6JS7f14BuwFY8Mwk3Lb7FPe1629
1586  sp6JS7f14BuwFY8MwkCeS41BwVrBD
1587  sp6JS7f14BuwFY8MwkDnnvRyuWJ7d
1588  sp6JS7f14BuwFY8MwkbkRNnzDEFpf
1589  sp6JS7f14BuwFY8MwkiNhaVhGNk6v
1590  sp6JS7f14BuwFY8Mwm1X4UJXRZx3p
1591  sp6JS7f14BuwFY8Mwm7da9q5vfq7J
1592  sp6JS7f14BuwFY8MwmPLqfBPrHw5H
1593  sp6JS7f14BuwFY8MwmbJpxvVjEwm2
1594  sp6JS7f14BuwFY8MwoAVeA7ka37cD
1595  sp6JS7f14BuwFY8MwoTFFTAwFKmVM
1596  sp6JS7f14BuwFY8MwoYsne51VpDE3
1597  sp6JS7f14BuwFY8MwohLVnU1VTk5h
1598 
1599 #endif // 0
ripple::tfTransferable
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
ripple::NFTokenDir_test::testWithFeats
void testWithFeats(FeatureBitset features)
Definition: NFTokenDir_test.cpp:1063
ripple::tecINVARIANT_FAILED
@ tecINVARIANT_FAILED
Definition: TER.h:277
std::string
STL class.
beast::severities::kDisabled
@ kDisabled
Definition: Journal.h:41
std::string_view
STL class.
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
std::vector::reserve
T reserve(T... args)
ripple::TxSearched::all
@ all
Json::UInt
unsigned int UInt
Definition: json_forwards.h:27
std::vector
STL class.
std::find
T find(T... args)
std::vector::size
T size(T... args)
ripple::keylet::nftoffer
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:355
ripple::NFTokenDir_test::testTooManyEquivalent
void testTooManyEquivalent(FeatureBitset features)
Definition: NFTokenDir_test.cpp:594
std::reverse
T reverse(T... args)
Json::Value::toStyledString
std::string toStyledString() const
Definition: json_value.cpp:1039
ripple::nft::toTaxon
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
ripple::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(NFToken, tx, ripple, 2)
ripple::SField::jsonName
const Json::StaticString jsonName
Definition: SField.h:136
std::vector::front
T front(T... args)
std::string::clear
T clear(T... args)
ripple::nft::pageMask
constexpr uint256 pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
std::vector::push_back
T push_back(T... args)
ripple::NFTokenDir_test::testLopsidedSplits
void testLopsidedSplits(FeatureBitset features)
Definition: NFTokenDir_test.cpp:157
std::cout
ripple::Keylet::key
uint256 key
Definition: Keylet.h:40
ripple::base_uint< 256 >
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::NFTokenDir_test::testFixNFTokenDirV1
void testFixNFTokenDirV1(FeatureBitset features)
Definition: NFTokenDir_test.cpp:374
ripple::NFTokenDir_test::run
void run() override
Definition: NFTokenDir_test.cpp:1074
ripple::TERSubset< CanCvtToTER >
ripple::NFTokenDir_test::printNFTPages
void printNFTPages(test::jtx::Env &env, Volume vol)
Definition: NFTokenDir_test.cpp:42
std::array
STL class.
Json::Value::size
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
std::set::erase
T erase(T... args)
ripple::tfSellNFToken
constexpr const std::uint32_t tfSellNFToken
Definition: TxFlags.h:152
ripple::NFTokenDir_test::quiet
@ quiet
Definition: NFTokenDir_test.cpp:37
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
std::uint32_t
ripple::fixNFTokenDirV1
const uint256 fixNFTokenDirV1
ripple::NFTokenDir_test::noisy
@ noisy
Definition: NFTokenDir_test.cpp:38
Json::Value::isArray
bool isArray() const
Definition: json_value.cpp:1015
ripple::sfNFTokens
const SField sfNFTokens
ripple::NFTokenDir_test::testConsecutivePacking
void testConsecutivePacking(FeatureBitset features)
Definition: NFTokenDir_test.cpp:755
std::vector::emplace_back
T emplace_back(T... args)
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
std::endl
T endl(T... args)
ripple::NFTokenDir_test::Volume
Volume
Definition: NFTokenDir_test.cpp:36
std::set::insert
T insert(T... args)
ripple::featureNonFungibleTokensV1_1
const uint256 featureNonFungibleTokensV1_1
ripple::tecNO_SUITABLE_NFTOKEN_PAGE
@ tecNO_SUITABLE_NFTOKEN_PAGE
Definition: TER.h:285
ripple::FeatureBitset
Definition: Feature.h:113
std::string::empty
T empty(T... args)
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
std::set::end
T end(T... args)
ripple::base_uint::parseHex
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:495
ripple::nft::cipheredTaxon
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: NFTokenUtils.h:144
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:219
std::set
STL class.
ripple::NFTokenDir_test
Definition: NFTokenDir_test.cpp:30
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::NFTokenDir_test::testConsecutiveNFTs
void testConsecutiveNFTs(FeatureBitset features)
Definition: NFTokenDir_test.cpp:100
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
Json::Value
Represents a JSON value.
Definition: json_value.h:145
initializer_list
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469