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) {
241 env(token::createOffer(acct, *iter, XRP(0)), Txflags(tfSellNFToken));
243 env(token::acceptSellOffer(becky, offerIndex));
245 becky.nfts.push_back(*iter);
246 iter = acct.nfts.erase(iter);
249 xferNFT(alice, aliceIter);
250 xferNFT(minter, minterIter);
252 BEAST_EXPECT(aliceIter == alice.nfts.end());
253 BEAST_EXPECT(minterIter == minter.nfts.end());
257 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
258 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
259 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
263 auto addOffers = [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
267 env(token::createOffer(owner,
nft, drops(1)),
268 Txflags(tfSellNFToken),
269 token::Destination(other1));
270 env(token::createOffer(owner,
nft, drops(1)),
271 Txflags(tfSellNFToken),
272 token::Destination(other2));
276 env(token::createOffer(other1,
nft, drops(1)), token::Owner(owner));
277 env(token::createOffer(other2,
nft, drops(1)), token::Owner(owner));
280 env(token::createOffer(other2,
nft, drops(2)), token::Owner(owner));
281 env(token::createOffer(other1,
nft, drops(2)), token::Owner(owner));
285 addOffers(alice, becky, minter);
286 addOffers(becky, minter, alice);
287 addOffers(minter, alice, becky);
288 BEAST_EXPECT(ownerCount(env, alice) == 424);
289 BEAST_EXPECT(ownerCount(env, becky) == 424);
290 BEAST_EXPECT(ownerCount(env, minter) == 424);
295 AcctStat*
const stats[3] = {&alice, &becky, &minter};
299 while (!stats[0]->nfts.empty() || !stats[1]->nfts.empty() || !stats[2]->nfts.empty())
303 AcctStat& owner = *(stats[acctDist(engine)]);
304 if (owner.nfts.empty())
309 auto nftIter = owner.nfts.begin() + nftDist(engine);
311 owner.nfts.erase(nftIter);
316 AcctStat
const& burner = [&]() -> AcctStat& {
317 if (owner.acct == becky.acct)
318 return *(stats[acctDist(engine)]);
319 return mintDist(engine) ? alice : minter;
322 if (owner.acct == burner.acct)
324 env(token::burn(burner,
nft));
328 env(token::burn(burner,
nft), token::Owner(owner));
334 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
335 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
336 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
338 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
339 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
340 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
344 BEAST_EXPECT(ownerCount(env, alice) == 0);
345 BEAST_EXPECT(ownerCount(env, becky) == 0);
346 BEAST_EXPECT(ownerCount(env, minter) == 0);
362 Env env{*
this, features};
363 env.fund(XRP(1000), alice);
367 auto genPackedTokens = [
this, &env, &alice]() {
380 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
383 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
396 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
397 uint32_t
const extTaxon = internalTaxon(alice, intTaxon);
398 nfts.
push_back(token::getNextID(env, alice, extTaxon));
399 env(token::mint(alice, extTaxon));
410 jvParams[jss::ledger_index] =
"current";
411 jvParams[jss::binary] =
false;
420 if (state[i].isMember(sfNFTokens.jsonName) &&
421 state[i][sfNFTokens.jsonName].
isArray())
423 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
429 BEAST_EXPECT(pageCount == 3);
438 BEAST_EXPECT(
nftCount(env, alice) == 96);
439 BEAST_EXPECT(ownerCount(env, alice) == 3);
443 env(token::burn(alice, {
nft}));
446 BEAST_EXPECT(
nftCount(env, alice) == 0);
447 BEAST_EXPECT(ownerCount(env, alice) == 0);
451 auto checkNoTokenPages = [
this, &env]() {
453 jvParams[jss::ledger_index] =
"current";
454 jvParams[jss::binary] =
false;
462 BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
472 BEAST_EXPECT(
nftCount(env, alice) == 96);
473 BEAST_EXPECT(ownerCount(env, alice) == 3);
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);
568 BEAST_EXPECT(ownerCount(env, alice) == 0);
576 BEAST_EXPECT(
nftCount(env, alice) == 96);
577 BEAST_EXPECT(ownerCount(env, alice) == 3);
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);
604 BEAST_EXPECT(ownerCount(env, alice) == 2);
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);
629 BEAST_EXPECT(ownerCount(env, alice) == 0);
637 BEAST_EXPECT(
nftCount(env, alice) == 96);
638 BEAST_EXPECT(ownerCount(env, alice) == 3);
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);
743 BEAST_EXPECT(ownerCount(env, alice) == 0);
747 if (features[fixNFTokenPageLinks])
761 BEAST_EXPECT(
nftCount(env, alice) == 96);
762 BEAST_EXPECT(ownerCount(env, alice) == 3);
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)
875 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
879 env(token::burn(alice, nftokenID));
884 for (
uint256 const& offerIndex : offerIndexes)
894 BEAST_EXPECT(ownerCount(env, alice) == 0);
895 BEAST_EXPECT(ownerCount(env, becky) == 0);
900 Env env{*
this, features};
904 env.fund(XRP(100000), alice, becky);
911 auto const nftokenID =
915 for (
uint256 const& offerIndex : offerIndexes)
921 env(token::burn(alice, nftokenID));
924 uint32_t offerDeletedCount = 0;
926 for (
uint256 const& offerIndex : offerIndexes)
938 BEAST_EXPECT(ownerCount(env, alice) == 1);
943 Env env{*
this, features};
947 env.fund(XRP(100000), alice, becky);
955 auto const nftokenID =
959 for (
uint256 const& offerIndex : offerIndexes)
965 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
967 env(token::createOffer(becky, nftokenID, drops(1)), token::Owner(alice));
971 env(token::burn(alice, nftokenID));
976 for (
uint256 const& offerIndex : offerIndexes)
983 BEAST_EXPECT(ownerCount(env, alice) == 0);
986 BEAST_EXPECT(ownerCount(env, becky) == 1);
995 if (features[fixNFTokenPageLinks])
1006 Account const minter{
"minter"};
1008 Env env{*
this, features};
1009 env.fund(XRP(1000), alice, minter);
1013 auto genPackedTokens = [
this, &env, &alice, &minter]() {
1026 std::uint32_t tokenSeq = env.le(acct)->at(~sfMintedNFTokens).value_or(0);
1029 tokenSeq += env.le(acct)->at(~sfFirstNFTokenSequence).value_or(env.seq(acct));
1042 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
1043 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
1044 nfts.
push_back(token::getNextID(env, minter, extTaxon, tfTransferable));
1045 env(token::mint(minter, extTaxon), Txflags(tfTransferable));
1050 env(token::createOffer(minter, nfts.
back(), XRP(0)), Txflags(tfSellNFToken));
1054 env(token::acceptSellOffer(alice, minterOfferIndex));
1065 jvParams[jss::ledger_index] =
"current";
1066 jvParams[jss::binary] =
false;
1070 json::Value& state = jrr[jss::result][jss::state];
1075 if (state[i].isMember(sfNFTokens.jsonName) &&
1076 state[i][sfNFTokens.jsonName].
isArray())
1078 BEAST_EXPECT(state[i][sfNFTokens.jsonName].
size() == 32);
1084 BEAST_EXPECT(pageCount == 3);
1091 BEAST_EXPECT(
nftCount(env, alice) == 96);
1092 BEAST_EXPECT(ownerCount(env, alice) == 3);
1097 if (!BEAST_EXPECT(lastNFTokenPage))
1100 uint256 const middleNFTokenPageIndex = lastNFTokenPage->at(sfPreviousPageMin);
1101 auto middleNFTokenPage =
1103 if (!BEAST_EXPECT(middleNFTokenPage))
1106 uint256 const firstNFTokenPageIndex = middleNFTokenPage->at(sfPreviousPageMin);
1107 auto firstNFTokenPage =
1109 if (!BEAST_EXPECT(firstNFTokenPage))
1114 for (
int i = 0; i < 32; ++i)
1121 env(token::createOffer(alice, last32NFTs.
back(), XRP(0)), Txflags(tfSellNFToken));
1125 env(token::acceptSellOffer(minter, aliceOfferIndex));
1133 BEAST_EXPECT(!lastNFTokenPage);
1134 BEAST_EXPECT(ownerCount(env, alice) == 2);
1140 if (!BEAST_EXPECT(middleNFTokenPage))
1142 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1143 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1146 auto const acctDelFee{drops(env.current()->fees().increment)};
1151 for (
uint256 const nftID : last32NFTs)
1155 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
1159 env(token::acceptSellOffer(alice, minterOfferIndex));
1162 BEAST_EXPECT(ownerCount(env, alice) == 3);
1170 params[jss::account] = alice.human();
1171 return env.rpc(
"json",
"account_objects",
to_string(params));
1173 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1174 BEAST_EXPECT(acctObjs[jss::result][jss::account_objects].size() == 2);
1181 params[jss::account] = alice.human();
1182 params[jss::type] =
"state";
1183 return env.rpc(
"json",
"account_nfts",
to_string(params));
1185 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1186 BEAST_EXPECT(aliceNFTs[jss::result][jss::account_nfts].size() == 64);