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