159 Env env{*
this, features};
167 AcctStat(
char const* name) : acct(name)
176 AcctStat alice{
"alice"};
177 AcctStat becky{
"becky"};
178 AcctStat minter{
"minter"};
180 env.fund(XRP(10000), alice, becky, minter);
184 env(token::setMinter(alice, minter));
206 alice.nfts.reserve(105);
207 while (alice.nfts.size() < 105)
210 alice.nfts.push_back(
211 token::getNextID(env, alice, 0u, tfTransferable | tfBurnable, xferFee));
212 env(token::mint(alice), Txflags(tfTransferable | tfBurnable), token::XferFee(xferFee));
216 minter.nfts.reserve(105);
217 while (minter.nfts.size() < 105)
220 minter.nfts.push_back(
221 token::getNextID(env, alice, 0u, tfTransferable | tfBurnable, xferFee));
222 env(token::mint(minter),
223 Txflags(tfTransferable | tfBurnable),
224 token::XferFee(xferFee),
225 token::Issuer(alice));
231 becky.nfts.reserve(70);
233 auto aliceIter = alice.nfts.begin();
234 auto minterIter = minter.nfts.begin();
235 while (becky.nfts.size() < 70)
238 auto xferNFT = [&env, &becky](AcctStat& acct,
auto& iter) {
242 env(token::createOffer(acct, *iter, XRP(0)), Txflags(tfSellNFToken));
244 env(token::acceptSellOffer(becky, offerIndex));
246 becky.nfts.push_back(*iter);
247 iter = acct.nfts.erase(iter);
250 xferNFT(alice, aliceIter);
251 xferNFT(minter, minterIter);
253 BEAST_EXPECT(aliceIter == alice.nfts.end());
254 BEAST_EXPECT(minterIter == minter.nfts.end());
258 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
259 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
260 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
264 auto addOffers = [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
268 env(token::createOffer(owner,
nft, drops(1)),
269 Txflags(tfSellNFToken),
270 token::Destination(other1));
271 env(token::createOffer(owner,
nft, drops(1)),
272 Txflags(tfSellNFToken),
273 token::Destination(other2));
277 env(token::createOffer(other1,
nft, drops(1)), token::Owner(owner));
278 env(token::createOffer(other2,
nft, drops(1)), token::Owner(owner));
281 env(token::createOffer(other2,
nft, drops(2)), token::Owner(owner));
282 env(token::createOffer(other1,
nft, drops(2)), token::Owner(owner));
286 addOffers(alice, becky, minter);
287 addOffers(becky, minter, alice);
288 addOffers(minter, alice, becky);
296 AcctStat*
const stats[3] = {&alice, &becky, &minter};
300 while (!stats[0]->nfts.empty() || !stats[1]->nfts.empty() || !stats[2]->nfts.empty())
304 AcctStat& owner = *(stats[acctDist(engine)]);
305 if (owner.nfts.empty())
310 auto nftIter = owner.nfts.begin() + nftDist(engine);
312 owner.nfts.erase(nftIter);
317 AcctStat
const& burner = [&]() -> AcctStat& {
318 if (owner.acct == becky.acct)
319 return *(stats[acctDist(engine)]);
320 return mintDist(engine) ? alice : minter;
323 if (owner.acct == burner.acct)
325 env(token::burn(burner,
nft));
329 env(token::burn(burner,
nft), token::Owner(owner));
335 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
336 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
337 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
339 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
340 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
341 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
363 Env env{*
this, features};
364 env.fund(XRP(1000), alice);
368 auto genPackedTokens = [
this, &env, &alice]() {
381 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
384 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
397 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
398 uint32_t
const extTaxon = internalTaxon(alice, intTaxon);
399 nfts.
push_back(token::getNextID(env, alice, extTaxon));
400 env(token::mint(alice, extTaxon));
411 jvParams[jss::ledger_index] =
"current";
412 jvParams[jss::binary] =
false;
419 for (
auto& i : state)
421 if (i.isMember(sfNFTokens.jsonName) && i[sfNFTokens.jsonName].isArray())
423 BEAST_EXPECT(i[sfNFTokens.jsonName].size() == 32);
429 BEAST_EXPECT(pageCount == 3);
438 BEAST_EXPECT(
nftCount(env, alice) == 96);
443 env(token::burn(alice, {
nft}));
446 BEAST_EXPECT(
nftCount(env, alice) == 0);
451 auto checkNoTokenPages = [
this, &env]() {
453 jvParams[jss::ledger_index] =
"current";
454 jvParams[jss::binary] =
false;
458 json::Value const& state = jrr[jss::result][jss::state];
460 for (
auto const& i : state)
462 BEAST_EXPECT(!i.isMember(sfNFTokens.jsonName));
472 BEAST_EXPECT(
nftCount(env, alice) == 96);
478 if (!BEAST_EXPECT(lastNFTokenPage))
481 UInt256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
482 auto middleNFTokenPage =
484 if (!BEAST_EXPECT(middleNFTokenPage))
487 UInt256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
488 auto firstNFTokenPage =
490 if (!BEAST_EXPECT(firstNFTokenPage))
494 for (
int i = 0; i < 31; ++i)
496 env(token::burn(alice, {nfts.
back()}));
504 if (!BEAST_EXPECT(lastNFTokenPage))
507 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
508 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
509 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
512 env(token::burn(alice, {nfts.
back()}));
516 if (features[fixNFTokenPageLinks])
523 BEAST_EXPECT(lastNFTokenPage);
524 BEAST_EXPECT(lastNFTokenPage->at(~sfPreviousPageMin) == firstNFTokenPageIndex);
525 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
526 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
529 middleNFTokenPage = env.le(
531 BEAST_EXPECT(!middleNFTokenPage);
535 firstNFTokenPage = env.le(
537 BEAST_EXPECT(firstNFTokenPage);
538 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
539 BEAST_EXPECT(firstNFTokenPage->at(~sfNextPageMin) == lastNFTokenPage->key());
540 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
548 BEAST_EXPECT(!lastNFTokenPage);
552 middleNFTokenPage = env.le(
554 if (!BEAST_EXPECT(middleNFTokenPage))
556 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
557 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
561 while (!nfts.
empty())
563 env(token::burn(alice, {nfts.
back()}));
567 BEAST_EXPECT(
nftCount(env, alice) == 0);
576 BEAST_EXPECT(
nftCount(env, alice) == 96);
582 if (!BEAST_EXPECT(lastNFTokenPage))
585 UInt256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
586 auto middleNFTokenPage =
588 if (!BEAST_EXPECT(middleNFTokenPage))
591 UInt256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
592 auto firstNFTokenPage =
594 if (!BEAST_EXPECT(firstNFTokenPage))
599 env(token::burn(alice, nfts[i]));
603 BEAST_EXPECT(
nftCount(env, alice) == 64);
610 BEAST_EXPECT(!middleNFTokenPage);
613 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
614 BEAST_EXPECT(lastNFTokenPage->getFieldH256(sfPreviousPageMin) == firstNFTokenPageIndex);
620 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
625 env(token::burn(alice, {
nft}));
628 BEAST_EXPECT(
nftCount(env, alice) == 0);
637 BEAST_EXPECT(
nftCount(env, alice) == 96);
643 if (!BEAST_EXPECT(lastNFTokenPage))
646 UInt256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
647 auto middleNFTokenPage =
649 if (!BEAST_EXPECT(middleNFTokenPage))
652 UInt256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
653 auto firstNFTokenPage =
655 if (!BEAST_EXPECT(firstNFTokenPage))
660 for (
int i = 0; i < 32; ++i)
662 env(token::burn(alice, {nfts.
back()}));
670 BEAST_EXPECT(!firstNFTokenPage);
675 if (!BEAST_EXPECT(middleNFTokenPage))
677 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
678 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin));
681 if (!BEAST_EXPECT(lastNFTokenPage))
683 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
684 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
688 for (
int i = 0; i < 32; ++i)
690 env(token::burn(alice, {nfts.
back()}));
695 if (features[fixNFTokenPageLinks])
702 BEAST_EXPECT(lastNFTokenPage);
703 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
704 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
705 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
708 middleNFTokenPage = env.le(
710 BEAST_EXPECT(!middleNFTokenPage);
713 firstNFTokenPage = env.le(
715 BEAST_EXPECT(!firstNFTokenPage);
723 BEAST_EXPECT(!lastNFTokenPage);
727 middleNFTokenPage = env.le(
729 if (!BEAST_EXPECT(middleNFTokenPage))
731 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
732 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
736 while (!nfts.
empty())
738 env(token::burn(alice, {nfts.
back()}));
742 BEAST_EXPECT(
nftCount(env, alice) == 0);
747 if (features[fixNFTokenPageLinks])
761 BEAST_EXPECT(
nftCount(env, alice) == 96);
765 for (
int i = 0; i < 31; ++i)
767 env(token::burn(alice, {nfts.
back()}));
783 if (!BEAST_EXPECT(lastNFTokenPage))
785 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
795 BEAST_EXPECT(terExpect == terActual);
796 BEAST_EXPECT(sink.
messages().
str().starts_with(
"Invariant failed:"));
800 "Last NFT page deleted with non-empty directory"));
815 auto middleNFTokenPage = ac.
view().
peek(
818 lastNFTokenPage->getFieldH256(sfPreviousPageMin)));
819 BEAST_EXPECT(middleNFTokenPage);
823 middleNFTokenPage->makeFieldAbsent(sfNextPageMin);
831 BEAST_EXPECT(terExpect == terActual);
832 BEAST_EXPECT(sink.
messages().
str().starts_with(
"Invariant failed:"));
835 BEAST_EXPECT(sink.
messages().
str().contains(
"Lost NextMinPage link"));
853 Env env{*
this, features};
857 env.fund(XRP(100000), alice, becky);
864 auto const nftokenID =
868 for (
UInt256 const& offerIndex : offerIndexes)
874 UInt256 const beckyOfferIndex =
876 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
880 env(token::burn(alice, nftokenID));
885 for (
UInt256 const& offerIndex : offerIndexes)
901 Env env{*
this, features};
905 env.fund(XRP(100000), alice, becky);
912 auto const nftokenID =
916 for (
UInt256 const& offerIndex : offerIndexes)
922 env(token::burn(alice, nftokenID));
925 uint32_t offerDeletedCount = 0;
927 for (
UInt256 const& offerIndex : offerIndexes)
944 Env env{*
this, features};
948 env.fund(XRP(100000), alice, becky);
956 auto const nftokenID =
960 for (
UInt256 const& offerIndex : offerIndexes)
966 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
968 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
972 env(token::burn(alice, nftokenID));
977 for (
UInt256 const& offerIndex : offerIndexes)
996 if (features[fixNFTokenPageLinks])
1007 Account const minter{
"minter"};
1009 Env env{*
this, features};
1010 env.fund(XRP(1000), alice, minter);
1014 auto genPackedTokens = [
this, &env, &alice, &minter]() {
1027 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
1030 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
1043 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
1044 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
1045 nfts.
push_back(token::getNextID(env, minter, extTaxon, tfTransferable));
1046 env(token::mint(minter, extTaxon), Txflags(tfTransferable));
1050 UInt256 const minterOfferIndex =
1052 env(token::createOffer(minter, nfts.
back(), XRP(0)), Txflags(tfSellNFToken));
1056 env(token::acceptSellOffer(alice, minterOfferIndex));
1067 jvParams[jss::ledger_index] =
"current";
1068 jvParams[jss::binary] =
false;
1072 json::Value& state = jrr[jss::result][jss::state];
1075 for (
auto& i : state)
1077 if (i.isMember(sfNFTokens.jsonName) && i[sfNFTokens.jsonName].isArray())
1079 BEAST_EXPECT(i[sfNFTokens.jsonName].size() == 32);
1085 BEAST_EXPECT(pageCount == 3);
1092 BEAST_EXPECT(
nftCount(env, alice) == 96);
1098 if (!BEAST_EXPECT(lastNFTokenPage))
1101 UInt256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
1102 auto middleNFTokenPage =
1104 if (!BEAST_EXPECT(middleNFTokenPage))
1107 UInt256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
1108 auto firstNFTokenPage =
1110 if (!BEAST_EXPECT(firstNFTokenPage))
1115 for (
int i = 0; i < 32; ++i)
1121 UInt256 const aliceOfferIndex =
1123 env(token::createOffer(alice, last32NFTs.
back(), XRP(0)), Txflags(tfSellNFToken));
1127 env(token::acceptSellOffer(minter, aliceOfferIndex));
1135 BEAST_EXPECT(!lastNFTokenPage);
1142 if (!BEAST_EXPECT(middleNFTokenPage))
1144 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1145 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1148 auto const acctDelFee{drops(env.current()->fees().increment)};
1153 for (
UInt256 const nftID : last32NFTs)
1156 UInt256 const minterOfferIndex =
1158 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
1162 env(token::acceptSellOffer(alice, minterOfferIndex));
1173 params[jss::account] = alice.human();
1174 return env.rpc(
"json",
"account_objects",
to_string(params));
1176 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1177 BEAST_EXPECT(acctObjs[jss::result][jss::account_objects].size() == 2);
1184 params[jss::account] = alice.human();
1185 params[jss::type] =
"state";
1186 return env.rpc(
"json",
"account_nfts",
to_string(params));
1188 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1189 BEAST_EXPECT(aliceNFTs[jss::result][jss::account_nfts].size() == 64);