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