161 Env env{*
this, features};
169 AcctStat(
char const* name) : acct(name)
178 AcctStat alice{
"alice"};
179 AcctStat becky{
"becky"};
180 AcctStat minter{
"minter"};
182 env.fund(XRP(10000), alice, becky, minter);
186 env(token::setMinter(alice, minter));
208 alice.nfts.reserve(105);
209 while (alice.nfts.size() < 105)
212 alice.nfts.push_back(
213 token::getNextID(env, alice, 0u, tfTransferable | tfBurnable, xferFee));
214 env(token::mint(alice), Txflags(tfTransferable | tfBurnable), token::XferFee(xferFee));
218 minter.nfts.reserve(105);
219 while (minter.nfts.size() < 105)
222 minter.nfts.push_back(
223 token::getNextID(env, alice, 0u, tfTransferable | tfBurnable, xferFee));
224 env(token::mint(minter),
225 Txflags(tfTransferable | tfBurnable),
226 token::XferFee(xferFee),
227 token::Issuer(alice));
233 becky.nfts.reserve(70);
235 auto aliceIter = alice.nfts.begin();
236 auto minterIter = minter.nfts.begin();
237 while (becky.nfts.size() < 70)
240 auto xferNFT = [&env, &becky](AcctStat& acct,
auto& iter) {
244 env(token::createOffer(acct, *iter, XRP(0)), Txflags(tfSellNFToken));
246 env(token::acceptSellOffer(becky, offerIndex));
248 becky.nfts.push_back(*iter);
249 iter = acct.nfts.erase(iter);
252 xferNFT(alice, aliceIter);
253 xferNFT(minter, minterIter);
255 BEAST_EXPECT(aliceIter == alice.nfts.end());
256 BEAST_EXPECT(minterIter == minter.nfts.end());
260 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
261 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
262 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
266 auto addOffers = [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
270 env(token::createOffer(owner,
nft, drops(1)),
271 Txflags(tfSellNFToken),
272 token::Destination(other1));
273 env(token::createOffer(owner,
nft, drops(1)),
274 Txflags(tfSellNFToken),
275 token::Destination(other2));
279 env(token::createOffer(other1,
nft, drops(1)), token::Owner(owner));
280 env(token::createOffer(other2,
nft, drops(1)), token::Owner(owner));
283 env(token::createOffer(other2,
nft, drops(2)), token::Owner(owner));
284 env(token::createOffer(other1,
nft, drops(2)), token::Owner(owner));
288 addOffers(alice, becky, minter);
289 addOffers(becky, minter, alice);
290 addOffers(minter, alice, becky);
298 AcctStat*
const stats[3] = {&alice, &becky, &minter};
302 while (!stats[0]->nfts.empty() || !stats[1]->nfts.empty() || !stats[2]->nfts.empty())
306 AcctStat& owner = *(stats[acctDist(engine)]);
307 if (owner.nfts.empty())
312 auto nftIter = owner.nfts.begin() + nftDist(engine);
314 owner.nfts.erase(nftIter);
319 AcctStat
const& burner = [&]() -> AcctStat& {
320 if (owner.acct == becky.acct)
321 return *(stats[acctDist(engine)]);
322 return mintDist(engine) ? alice : minter;
325 if (owner.acct == burner.acct)
327 env(token::burn(burner,
nft));
331 env(token::burn(burner,
nft), token::Owner(owner));
337 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
338 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
339 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
341 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
342 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
343 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
365 Env env{*
this, features};
366 env.fund(XRP(1000), alice);
370 auto genPackedTokens = [
this, &env, &alice]() {
383 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
386 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
399 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
400 uint32_t
const extTaxon = internalTaxon(alice, intTaxon);
401 nfts.
push_back(token::getNextID(env, alice, extTaxon));
402 env(token::mint(alice, extTaxon));
413 jvParams[jss::ledger_index] =
"current";
414 jvParams[jss::binary] =
false;
423 if (state[i].isMember(sfNFTokens.jsonName) &&
424 state[i][sfNFTokens.jsonName].
isArray())
426 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
432 BEAST_EXPECT(pageCount == 3);
441 BEAST_EXPECT(
nftCount(env, alice) == 96);
446 env(token::burn(alice, {
nft}));
449 BEAST_EXPECT(
nftCount(env, alice) == 0);
454 auto checkNoTokenPages = [
this, &env]() {
456 jvParams[jss::ledger_index] =
"current";
457 jvParams[jss::binary] =
false;
465 BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
475 BEAST_EXPECT(
nftCount(env, alice) == 96);
481 if (!BEAST_EXPECT(lastNFTokenPage))
484 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
485 auto middleNFTokenPage =
487 if (!BEAST_EXPECT(middleNFTokenPage))
490 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
491 auto firstNFTokenPage =
493 if (!BEAST_EXPECT(firstNFTokenPage))
497 for (
int i = 0; i < 31; ++i)
499 env(token::burn(alice, {nfts.
back()}));
507 if (!BEAST_EXPECT(lastNFTokenPage))
510 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
511 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
512 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
515 env(token::burn(alice, {nfts.
back()}));
519 if (features[fixNFTokenPageLinks])
526 BEAST_EXPECT(lastNFTokenPage);
527 BEAST_EXPECT(lastNFTokenPage->at(~sfPreviousPageMin) == firstNFTokenPageIndex);
528 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
529 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
532 middleNFTokenPage = env.le(
534 BEAST_EXPECT(!middleNFTokenPage);
538 firstNFTokenPage = env.le(
540 BEAST_EXPECT(firstNFTokenPage);
541 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
542 BEAST_EXPECT(firstNFTokenPage->at(~sfNextPageMin) == lastNFTokenPage->key());
543 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
551 BEAST_EXPECT(!lastNFTokenPage);
555 middleNFTokenPage = env.le(
557 if (!BEAST_EXPECT(middleNFTokenPage))
559 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
560 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
564 while (!nfts.
empty())
566 env(token::burn(alice, {nfts.
back()}));
570 BEAST_EXPECT(
nftCount(env, alice) == 0);
579 BEAST_EXPECT(
nftCount(env, alice) == 96);
585 if (!BEAST_EXPECT(lastNFTokenPage))
588 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
589 auto middleNFTokenPage =
591 if (!BEAST_EXPECT(middleNFTokenPage))
594 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
595 auto firstNFTokenPage =
597 if (!BEAST_EXPECT(firstNFTokenPage))
602 env(token::burn(alice, nfts[i]));
606 BEAST_EXPECT(
nftCount(env, alice) == 64);
613 BEAST_EXPECT(!middleNFTokenPage);
616 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
617 BEAST_EXPECT(lastNFTokenPage->getFieldH256(sfPreviousPageMin) == firstNFTokenPageIndex);
623 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
628 env(token::burn(alice, {
nft}));
631 BEAST_EXPECT(
nftCount(env, alice) == 0);
640 BEAST_EXPECT(
nftCount(env, alice) == 96);
646 if (!BEAST_EXPECT(lastNFTokenPage))
649 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
650 auto middleNFTokenPage =
652 if (!BEAST_EXPECT(middleNFTokenPage))
655 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
656 auto firstNFTokenPage =
658 if (!BEAST_EXPECT(firstNFTokenPage))
663 for (
int i = 0; i < 32; ++i)
665 env(token::burn(alice, {nfts.
back()}));
673 BEAST_EXPECT(!firstNFTokenPage);
678 if (!BEAST_EXPECT(middleNFTokenPage))
680 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
681 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin));
684 if (!BEAST_EXPECT(lastNFTokenPage))
686 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
687 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
691 for (
int i = 0; i < 32; ++i)
693 env(token::burn(alice, {nfts.
back()}));
698 if (features[fixNFTokenPageLinks])
705 BEAST_EXPECT(lastNFTokenPage);
706 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
707 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
708 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
711 middleNFTokenPage = env.le(
713 BEAST_EXPECT(!middleNFTokenPage);
716 firstNFTokenPage = env.le(
718 BEAST_EXPECT(!firstNFTokenPage);
726 BEAST_EXPECT(!lastNFTokenPage);
730 middleNFTokenPage = env.le(
732 if (!BEAST_EXPECT(middleNFTokenPage))
734 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
735 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
739 while (!nfts.
empty())
741 env(token::burn(alice, {nfts.
back()}));
745 BEAST_EXPECT(
nftCount(env, alice) == 0);
750 if (features[fixNFTokenPageLinks])
764 BEAST_EXPECT(
nftCount(env, alice) == 96);
768 for (
int i = 0; i < 31; ++i)
770 env(token::burn(alice, {nfts.
back()}));
786 if (!BEAST_EXPECT(lastNFTokenPage))
788 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
798 BEAST_EXPECT(terExpect == terActual);
799 BEAST_EXPECT(sink.
messages().
str().starts_with(
"Invariant failed:"));
803 "Last NFT page deleted with non-empty directory"));
818 auto middleNFTokenPage = ac.
view().
peek(
821 lastNFTokenPage->getFieldH256(sfPreviousPageMin)));
822 BEAST_EXPECT(middleNFTokenPage);
826 middleNFTokenPage->makeFieldAbsent(sfNextPageMin);
834 BEAST_EXPECT(terExpect == terActual);
835 BEAST_EXPECT(sink.
messages().
str().starts_with(
"Invariant failed:"));
838 BEAST_EXPECT(sink.
messages().
str().contains(
"Lost NextMinPage link"));
856 Env env{*
this, features};
860 env.fund(XRP(100000), alice, becky);
867 auto const nftokenID =
871 for (
uint256 const& offerIndex : offerIndexes)
877 uint256 const beckyOfferIndex =
879 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
883 env(token::burn(alice, nftokenID));
888 for (
uint256 const& offerIndex : offerIndexes)
904 Env env{*
this, features};
908 env.fund(XRP(100000), alice, becky);
915 auto const nftokenID =
919 for (
uint256 const& offerIndex : offerIndexes)
925 env(token::burn(alice, nftokenID));
928 uint32_t offerDeletedCount = 0;
930 for (
uint256 const& offerIndex : offerIndexes)
947 Env env{*
this, features};
951 env.fund(XRP(100000), alice, becky);
959 auto const nftokenID =
963 for (
uint256 const& offerIndex : offerIndexes)
969 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
971 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
975 env(token::burn(alice, nftokenID));
980 for (
uint256 const& offerIndex : offerIndexes)
999 if (features[fixNFTokenPageLinks])
1010 Account const minter{
"minter"};
1012 Env env{*
this, features};
1013 env.fund(XRP(1000), alice, minter);
1017 auto genPackedTokens = [
this, &env, &alice, &minter]() {
1030 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
1033 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
1046 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
1047 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
1048 nfts.
push_back(token::getNextID(env, minter, extTaxon, tfTransferable));
1049 env(token::mint(minter, extTaxon), Txflags(tfTransferable));
1053 uint256 const minterOfferIndex =
1055 env(token::createOffer(minter, nfts.
back(), XRP(0)), Txflags(tfSellNFToken));
1059 env(token::acceptSellOffer(alice, minterOfferIndex));
1070 jvParams[jss::ledger_index] =
"current";
1071 jvParams[jss::binary] =
false;
1075 json::Value& state = jrr[jss::result][jss::state];
1080 if (state[i].isMember(sfNFTokens.jsonName) &&
1081 state[i][sfNFTokens.jsonName].
isArray())
1083 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
1089 BEAST_EXPECT(pageCount == 3);
1096 BEAST_EXPECT(
nftCount(env, alice) == 96);
1102 if (!BEAST_EXPECT(lastNFTokenPage))
1105 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
1106 auto middleNFTokenPage =
1108 if (!BEAST_EXPECT(middleNFTokenPage))
1111 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
1112 auto firstNFTokenPage =
1114 if (!BEAST_EXPECT(firstNFTokenPage))
1119 for (
int i = 0; i < 32; ++i)
1125 uint256 const aliceOfferIndex =
1127 env(token::createOffer(alice, last32NFTs.
back(), XRP(0)), Txflags(tfSellNFToken));
1131 env(token::acceptSellOffer(minter, aliceOfferIndex));
1139 BEAST_EXPECT(!lastNFTokenPage);
1146 if (!BEAST_EXPECT(middleNFTokenPage))
1148 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1149 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1152 auto const acctDelFee{drops(env.current()->fees().increment)};
1157 for (
uint256 const nftID : last32NFTs)
1160 uint256 const minterOfferIndex =
1162 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
1166 env(token::acceptSellOffer(alice, minterOfferIndex));
1177 params[jss::account] = alice.human();
1178 return env.rpc(
"json",
"account_objects",
to_string(params));
1180 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1181 BEAST_EXPECT(acctObjs[jss::result][jss::account_objects].size() == 2);
1188 params[jss::account] = alice.human();
1189 params[jss::type] =
"state";
1190 return env.rpc(
"json",
"account_nfts",
to_string(params));
1192 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1193 BEAST_EXPECT(aliceNFTs[jss::result][jss::account_nfts].size() == 64);