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