112 using namespace test::jtx;
114 Env env{*
this, features};
122 AcctStat(
char const* name) : acct(name)
131 AcctStat alice{
"alice"};
132 AcctStat becky{
"becky"};
133 AcctStat minter{
"minter"};
135 env.fund(XRP(10000), alice, becky, minter);
139 env(token::setMinter(alice, minter));
159 alice.nfts.reserve(105);
160 while (alice.nfts.size() < 105)
168 minter.nfts.reserve(105);
169 while (minter.nfts.size() < 105)
173 env(token::mint(minter),
175 token::xferFee(xferFee),
176 token::issuer(alice));
182 becky.nfts.reserve(70);
184 auto aliceIter = alice.nfts.begin();
185 auto minterIter = minter.nfts.begin();
186 while (becky.nfts.size() < 70)
189 auto xferNFT = [&env, &becky](AcctStat& acct,
auto& iter) {
191 env(token::createOffer(acct, *iter, XRP(0)), txflags(
tfSellNFToken));
193 env(token::acceptSellOffer(becky, offerIndex));
195 becky.nfts.push_back(*iter);
196 iter = acct.nfts.erase(iter);
199 xferNFT(alice, aliceIter);
200 xferNFT(minter, minterIter);
202 BEAST_EXPECT(aliceIter == alice.nfts.end());
203 BEAST_EXPECT(minterIter == minter.nfts.end());
207 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
208 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
209 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
213 auto addOffers = [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
217 env(token::createOffer(owner, nft, drops(1)), txflags(
tfSellNFToken), token::destination(other1));
218 env(token::createOffer(owner, nft, drops(1)), txflags(
tfSellNFToken), token::destination(other2));
222 env(token::createOffer(other1, nft, drops(1)), token::owner(owner));
223 env(token::createOffer(other2, nft, drops(1)), token::owner(owner));
226 env(token::createOffer(other2, nft, drops(2)), token::owner(owner));
227 env(token::createOffer(other1, nft, drops(2)), token::owner(owner));
231 addOffers(alice, becky, minter);
232 addOffers(becky, minter, alice);
233 addOffers(minter, alice, becky);
234 BEAST_EXPECT(ownerCount(env, alice) == 424);
235 BEAST_EXPECT(ownerCount(env, becky) == 424);
236 BEAST_EXPECT(ownerCount(env, minter) == 424);
241 AcctStat*
const stats[3] = {&alice, &becky, &minter};
245 while (stats[0]->nfts.size() > 0 || stats[1]->nfts.size() > 0 || stats[2]->nfts.size() > 0)
249 AcctStat& owner = *(stats[acctDist(engine)]);
250 if (owner.nfts.empty())
255 auto nftIter = owner.nfts.begin() + nftDist(engine);
257 owner.nfts.erase(nftIter);
262 AcctStat& burner = owner.acct == becky.acct ? *(stats[acctDist(engine)])
263 : mintDist(engine) ? alice
266 if (owner.acct == burner.acct)
267 env(token::burn(burner, nft));
269 env(token::burn(burner, nft), token::owner(owner));
274 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
275 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
276 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
278 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
279 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
280 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
284 BEAST_EXPECT(ownerCount(env, alice) == 0);
285 BEAST_EXPECT(ownerCount(env, becky) == 0);
286 BEAST_EXPECT(ownerCount(env, minter) == 0);
298 using namespace test::jtx;
300 Account
const alice{
"alice"};
302 Env env{*
this, features};
303 env.fund(XRP(1000), alice);
307 auto genPackedTokens = [
this, &env, &alice]() {
320 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
323 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
336 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
337 uint32_t
const extTaxon = internalTaxon(alice, intTaxon);
338 nfts.
push_back(token::getNextID(env, alice, extTaxon));
339 env(token::mint(alice, extTaxon));
350 jvParams[jss::ledger_index] =
"current";
351 jvParams[jss::binary] =
false;
360 if (state[i].isMember(sfNFTokens.jsonName) && state[i][sfNFTokens.jsonName].
isArray())
362 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
368 BEAST_EXPECT(pageCount == 3);
377 BEAST_EXPECT(
nftCount(env, alice) == 96);
378 BEAST_EXPECT(ownerCount(env, alice) == 3);
380 for (
uint256 const& nft : nfts)
382 env(token::burn(alice, {nft}));
385 BEAST_EXPECT(
nftCount(env, alice) == 0);
386 BEAST_EXPECT(ownerCount(env, alice) == 0);
390 auto checkNoTokenPages = [
this, &env]() {
392 jvParams[jss::ledger_index] =
"current";
393 jvParams[jss::binary] =
false;
401 BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
411 BEAST_EXPECT(
nftCount(env, alice) == 96);
412 BEAST_EXPECT(ownerCount(env, alice) == 3);
417 if (!BEAST_EXPECT(lastNFTokenPage))
420 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
422 if (!BEAST_EXPECT(middleNFTokenPage))
425 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
427 if (!BEAST_EXPECT(firstNFTokenPage))
431 for (
int i = 0; i < 31; ++i)
433 env(token::burn(alice, {nfts.
back()}));
441 if (!BEAST_EXPECT(lastNFTokenPage))
444 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
445 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
446 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
449 env(token::burn(alice, {nfts.
back()}));
453 if (features[fixNFTokenPageLinks])
460 BEAST_EXPECT(lastNFTokenPage);
461 BEAST_EXPECT(lastNFTokenPage->at(~sfPreviousPageMin) == firstNFTokenPageIndex);
462 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
463 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
467 BEAST_EXPECT(!middleNFTokenPage);
472 BEAST_EXPECT(firstNFTokenPage);
473 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
474 BEAST_EXPECT(firstNFTokenPage->at(~sfNextPageMin) == lastNFTokenPage->key());
475 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
483 BEAST_EXPECT(!lastNFTokenPage);
488 if (!BEAST_EXPECT(middleNFTokenPage))
490 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
491 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
495 while (!nfts.
empty())
497 env(token::burn(alice, {nfts.
back()}));
501 BEAST_EXPECT(
nftCount(env, alice) == 0);
502 BEAST_EXPECT(ownerCount(env, alice) == 0);
510 BEAST_EXPECT(
nftCount(env, alice) == 96);
511 BEAST_EXPECT(ownerCount(env, alice) == 3);
516 if (!BEAST_EXPECT(lastNFTokenPage))
519 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
521 if (!BEAST_EXPECT(middleNFTokenPage))
524 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
526 if (!BEAST_EXPECT(firstNFTokenPage))
531 env(token::burn(alice, nfts[i]));
535 BEAST_EXPECT(
nftCount(env, alice) == 64);
536 BEAST_EXPECT(ownerCount(env, alice) == 2);
541 BEAST_EXPECT(!middleNFTokenPage);
544 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
545 BEAST_EXPECT(lastNFTokenPage->getFieldH256(sfPreviousPageMin) == firstNFTokenPageIndex);
549 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
552 for (
uint256 const& nft : nfts)
554 env(token::burn(alice, {nft}));
557 BEAST_EXPECT(
nftCount(env, alice) == 0);
558 BEAST_EXPECT(ownerCount(env, alice) == 0);
566 BEAST_EXPECT(
nftCount(env, alice) == 96);
567 BEAST_EXPECT(ownerCount(env, alice) == 3);
572 if (!BEAST_EXPECT(lastNFTokenPage))
575 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
577 if (!BEAST_EXPECT(middleNFTokenPage))
580 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
582 if (!BEAST_EXPECT(firstNFTokenPage))
587 for (
int i = 0; i < 32; ++i)
589 env(token::burn(alice, {nfts.
back()}));
596 BEAST_EXPECT(!firstNFTokenPage);
600 if (!BEAST_EXPECT(middleNFTokenPage))
602 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
603 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin));
606 if (!BEAST_EXPECT(lastNFTokenPage))
608 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
609 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
613 for (
int i = 0; i < 32; ++i)
615 env(token::burn(alice, {nfts.
back()}));
620 if (features[fixNFTokenPageLinks])
627 BEAST_EXPECT(lastNFTokenPage);
628 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
629 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
630 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
634 BEAST_EXPECT(!middleNFTokenPage);
638 BEAST_EXPECT(!firstNFTokenPage);
646 BEAST_EXPECT(!lastNFTokenPage);
651 if (!BEAST_EXPECT(middleNFTokenPage))
653 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
654 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
658 while (!nfts.
empty())
660 env(token::burn(alice, {nfts.
back()}));
664 BEAST_EXPECT(
nftCount(env, alice) == 0);
665 BEAST_EXPECT(ownerCount(env, alice) == 0);
669 if (features[fixNFTokenPageLinks])
683 BEAST_EXPECT(
nftCount(env, alice) == 96);
684 BEAST_EXPECT(ownerCount(env, alice) == 3);
687 for (
int i = 0; i < 31; ++i)
689 env(token::burn(alice, {nfts.
back()}));
704 if (!BEAST_EXPECT(lastNFTokenPage))
706 BEAST_EXPECT(lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
709 ac.view().erase(lastNFTokenPage);
715 terActual = ac.checkInvariants(terActual,
XRPAmount{});
716 BEAST_EXPECT(terExpect == terActual);
717 BEAST_EXPECT(sink.messages().str().starts_with(
"Invariant failed:"));
721 sink.messages().str().find(
"Last NFT page deleted with non-empty directory") !=
736 auto middleNFTokenPage = ac.view().peek(
738 BEAST_EXPECT(middleNFTokenPage);
742 middleNFTokenPage->makeFieldAbsent(sfNextPageMin);
743 ac.view().update(middleNFTokenPage);
749 terActual = ac.checkInvariants(terActual,
XRPAmount{});
750 BEAST_EXPECT(terExpect == terActual);
751 BEAST_EXPECT(sink.messages().str().starts_with(
"Invariant failed:"));
754 BEAST_EXPECT(sink.messages().str().find(
"Lost NextMinPage link") != std::string::npos);
766 using namespace test::jtx;
772 Env env{*
this, features};
774 Account
const alice(
"alice");
775 Account
const becky(
"becky");
776 env.fund(XRP(100000), alice, becky);
786 for (
uint256 const& offerIndex : offerIndexes)
793 env(token::createOffer(becky, nftokenID, drops(1)), token::owner(alice));
797 env(token::burn(alice, nftokenID));
802 for (
uint256 const& offerIndex : offerIndexes)
812 BEAST_EXPECT(ownerCount(env, alice) == 0);
813 BEAST_EXPECT(ownerCount(env, becky) == 0);
818 Env env{*
this, features};
820 Account
const alice(
"alice");
821 Account
const becky(
"becky");
822 env.fund(XRP(100000), alice, becky);
832 for (
uint256 const& offerIndex : offerIndexes)
838 env(token::burn(alice, nftokenID));
841 uint32_t offerDeletedCount = 0;
843 for (
uint256 const& offerIndex : offerIndexes)
855 BEAST_EXPECT(ownerCount(env, alice) == 1);
860 Env env{*
this, features};
862 Account
const alice(
"alice");
863 Account
const becky(
"becky");
864 env.fund(XRP(100000), alice, becky);
875 for (
uint256 const& offerIndex : offerIndexes)
881 env(token::createOffer(becky, nftokenID, drops(1)), token::owner(alice));
883 env(token::createOffer(becky, nftokenID, drops(1)), token::owner(alice));
887 env(token::burn(alice, nftokenID));
892 for (
uint256 const& offerIndex : offerIndexes)
899 BEAST_EXPECT(ownerCount(env, alice) == 0);
902 BEAST_EXPECT(ownerCount(env, becky) == 1);
911 if (features[fixNFTokenPageLinks])
919 using namespace test::jtx;
921 Account
const alice{
"alice"};
922 Account
const minter{
"minter"};
924 Env env{*
this, features};
925 env.fund(XRP(1000), alice, minter);
929 auto genPackedTokens = [
this, &env, &alice, &minter]() {
942 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
945 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
958 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
959 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
970 env(token::acceptSellOffer(alice, minterOfferIndex));
981 jvParams[jss::ledger_index] =
"current";
982 jvParams[jss::binary] =
false;
991 if (state[i].isMember(sfNFTokens.jsonName) && state[i][sfNFTokens.jsonName].
isArray())
993 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
999 BEAST_EXPECT(pageCount == 3);
1006 BEAST_EXPECT(
nftCount(env, alice) == 96);
1007 BEAST_EXPECT(ownerCount(env, alice) == 3);
1012 if (!BEAST_EXPECT(lastNFTokenPage))
1015 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
1017 if (!BEAST_EXPECT(middleNFTokenPage))
1020 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
1022 if (!BEAST_EXPECT(firstNFTokenPage))
1027 for (
int i = 0; i < 32; ++i)
1034 env(token::createOffer(alice, last32NFTs.
back(), XRP(0)), txflags(
tfSellNFToken));
1038 env(token::acceptSellOffer(minter, aliceOfferIndex));
1046 BEAST_EXPECT(!lastNFTokenPage);
1047 BEAST_EXPECT(ownerCount(env, alice) == 2);
1052 if (!BEAST_EXPECT(middleNFTokenPage))
1054 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1055 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1058 auto const acctDelFee{drops(env.current()->fees().increment)};
1063 for (
uint256 nftID : last32NFTs)
1067 env(token::createOffer(minter, nftID, XRP(0)), txflags(
tfSellNFToken));
1071 env(token::acceptSellOffer(alice, minterOfferIndex));
1074 BEAST_EXPECT(ownerCount(env, alice) == 3);
1082 params[jss::account] = alice.human();
1083 return env.rpc(
"json",
"account_objects",
to_string(params));
1085 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1086 BEAST_EXPECT(acctObjs[jss::result][jss::account_objects].size() == 2);
1093 params[jss::account] = alice.human();
1094 params[jss::type] =
"state";
1095 return env.rpc(
"json",
"account_nfts",
to_string(params));
1097 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1098 BEAST_EXPECT(aliceNFTs[jss::result][jss::account_nfts].size() == 64);