127 using namespace test::jtx;
129 Env env{*
this, features};
137 AcctStat(
char const* name) : acct(name)
146 AcctStat alice{
"alice"};
147 AcctStat becky{
"becky"};
148 AcctStat minter{
"minter"};
150 env.fund(XRP(10000), alice, becky, minter);
154 env(token::setMinter(alice, minter));
175 alice.nfts.reserve(105);
176 while (alice.nfts.size() < 105)
179 alice.nfts.push_back(token::getNextID(
181 env(token::mint(alice),
183 token::xferFee(xferFee));
187 minter.nfts.reserve(105);
188 while (minter.nfts.size() < 105)
191 minter.nfts.push_back(token::getNextID(
193 env(token::mint(minter),
195 token::xferFee(xferFee),
196 token::issuer(alice));
202 becky.nfts.reserve(70);
204 auto aliceIter = alice.nfts.begin();
205 auto minterIter = minter.nfts.begin();
206 while (becky.nfts.size() < 70)
209 auto xferNFT = [&env, &becky](AcctStat& acct,
auto& iter) {
212 env(token::createOffer(acct, *iter, XRP(0)),
215 env(token::acceptSellOffer(becky, offerIndex));
217 becky.nfts.push_back(*iter);
218 iter = acct.nfts.erase(iter);
221 xferNFT(alice, aliceIter);
222 xferNFT(minter, minterIter);
224 BEAST_EXPECT(aliceIter == alice.nfts.end());
225 BEAST_EXPECT(minterIter == minter.nfts.end());
229 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
230 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
231 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
236 [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
240 env(token::createOffer(owner, nft, drops(1)),
242 token::destination(other1));
243 env(token::createOffer(owner, nft, drops(1)),
245 token::destination(other2));
249 env(token::createOffer(other1, nft, drops(1)),
250 token::owner(owner));
251 env(token::createOffer(other2, nft, drops(1)),
252 token::owner(owner));
255 env(token::createOffer(other2, nft, drops(2)),
256 token::owner(owner));
257 env(token::createOffer(other1, nft, drops(2)),
258 token::owner(owner));
262 addOffers(alice, becky, minter);
263 addOffers(becky, minter, alice);
264 addOffers(minter, alice, becky);
265 BEAST_EXPECT(ownerCount(env, alice) == 424);
266 BEAST_EXPECT(ownerCount(env, becky) == 424);
267 BEAST_EXPECT(ownerCount(env, minter) == 424);
272 AcctStat*
const stats[3] = {&alice, &becky, &minter};
276 while (stats[0]->nfts.size() > 0 || stats[1]->nfts.size() > 0 ||
277 stats[2]->nfts.size() > 0)
281 AcctStat& owner = *(stats[acctDist(engine)]);
282 if (owner.nfts.empty())
287 0lu, owner.nfts.size() - 1);
288 auto nftIter = owner.nfts.begin() + nftDist(engine);
290 owner.nfts.erase(nftIter);
295 AcctStat& burner = owner.acct == becky.acct
296 ? *(stats[acctDist(engine)])
297 : mintDist(engine) ? alice
300 if (owner.acct == burner.acct)
301 env(token::burn(burner, nft));
303 env(token::burn(burner, nft), token::owner(owner));
308 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
309 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
310 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
312 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
313 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
314 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
318 BEAST_EXPECT(ownerCount(env, alice) == 0);
319 BEAST_EXPECT(ownerCount(env, becky) == 0);
320 BEAST_EXPECT(ownerCount(env, minter) == 0);
332 using namespace test::jtx;
334 Account
const alice{
"alice"};
336 Env env{*
this, features};
337 env.fund(XRP(1000), alice);
341 auto genPackedTokens = [
this, &env, &alice]() {
353 auto internalTaxon = [&env](
357 env.le(acct)->at(~sfMintedNFTokens).value_or(0);
360 tokenSeq += env.le(acct)
361 ->at(~sfFirstNFTokenSequence)
362 .value_or(env.seq(acct));
376 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
377 uint32_t
const extTaxon = internalTaxon(alice, intTaxon);
378 nfts.
push_back(token::getNextID(env, alice, extTaxon));
379 env(token::mint(alice, extTaxon));
390 jvParams[jss::ledger_index] =
"current";
391 jvParams[jss::binary] =
false;
394 env.rpc(
"json",
"ledger_data",
to_string(jvParams));
401 if (state[i].isMember(sfNFTokens.jsonName) &&
402 state[i][sfNFTokens.jsonName].
isArray())
405 state[i][sfNFTokens.jsonName].
size() == 32);
411 BEAST_EXPECT(pageCount == 3);
420 BEAST_EXPECT(
nftCount(env, alice) == 96);
421 BEAST_EXPECT(ownerCount(env, alice) == 3);
423 for (
uint256 const& nft : nfts)
425 env(token::burn(alice, {nft}));
428 BEAST_EXPECT(
nftCount(env, alice) == 0);
429 BEAST_EXPECT(ownerCount(env, alice) == 0);
433 auto checkNoTokenPages = [
this, &env]() {
435 jvParams[jss::ledger_index] =
"current";
436 jvParams[jss::binary] =
false;
439 env.rpc(
"json",
"ledger_data",
to_string(jvParams));
445 BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
455 BEAST_EXPECT(
nftCount(env, alice) == 96);
456 BEAST_EXPECT(ownerCount(env, alice) == 3);
461 if (!BEAST_EXPECT(lastNFTokenPage))
464 uint256 const middleNFTokenPageIndex =
465 lastNFTokenPage->at(sfPreviousPageMin);
468 if (!BEAST_EXPECT(middleNFTokenPage))
471 uint256 const firstNFTokenPageIndex =
472 middleNFTokenPage->at(sfPreviousPageMin);
475 if (!BEAST_EXPECT(firstNFTokenPage))
479 for (
int i = 0; i < 31; ++i)
481 env(token::burn(alice, {nfts.
back()}));
489 if (!BEAST_EXPECT(lastNFTokenPage))
493 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
494 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
495 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
498 env(token::burn(alice, {nfts.
back()}));
502 if (features[fixNFTokenPageLinks])
509 BEAST_EXPECT(lastNFTokenPage);
511 lastNFTokenPage->at(~sfPreviousPageMin) ==
512 firstNFTokenPageIndex);
513 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
515 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
520 BEAST_EXPECT(!middleNFTokenPage);
526 BEAST_EXPECT(firstNFTokenPage);
528 !firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
530 firstNFTokenPage->at(~sfNextPageMin) ==
531 lastNFTokenPage->key());
533 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
541 BEAST_EXPECT(!lastNFTokenPage);
547 if (!BEAST_EXPECT(middleNFTokenPage))
550 middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
551 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
555 while (!nfts.
empty())
557 env(token::burn(alice, {nfts.
back()}));
561 BEAST_EXPECT(
nftCount(env, alice) == 0);
562 BEAST_EXPECT(ownerCount(env, alice) == 0);
570 BEAST_EXPECT(
nftCount(env, alice) == 96);
571 BEAST_EXPECT(ownerCount(env, alice) == 3);
576 if (!BEAST_EXPECT(lastNFTokenPage))
579 uint256 const middleNFTokenPageIndex =
580 lastNFTokenPage->at(sfPreviousPageMin);
583 if (!BEAST_EXPECT(middleNFTokenPage))
586 uint256 const firstNFTokenPageIndex =
587 middleNFTokenPage->at(sfPreviousPageMin);
590 if (!BEAST_EXPECT(firstNFTokenPage))
595 env(token::burn(alice, nfts[i]));
599 BEAST_EXPECT(
nftCount(env, alice) == 64);
600 BEAST_EXPECT(ownerCount(env, alice) == 2);
606 BEAST_EXPECT(!middleNFTokenPage);
609 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
611 lastNFTokenPage->getFieldH256(sfPreviousPageMin) ==
612 firstNFTokenPageIndex);
617 firstNFTokenPage->getFieldH256(sfNextPageMin) ==
619 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
622 for (
uint256 const& nft : nfts)
624 env(token::burn(alice, {nft}));
627 BEAST_EXPECT(
nftCount(env, alice) == 0);
628 BEAST_EXPECT(ownerCount(env, alice) == 0);
636 BEAST_EXPECT(
nftCount(env, alice) == 96);
637 BEAST_EXPECT(ownerCount(env, alice) == 3);
642 if (!BEAST_EXPECT(lastNFTokenPage))
645 uint256 const middleNFTokenPageIndex =
646 lastNFTokenPage->at(sfPreviousPageMin);
649 if (!BEAST_EXPECT(middleNFTokenPage))
652 uint256 const firstNFTokenPageIndex =
653 middleNFTokenPage->at(sfPreviousPageMin);
656 if (!BEAST_EXPECT(firstNFTokenPage))
661 for (
int i = 0; i < 32; ++i)
663 env(token::burn(alice, {nfts.
back()}));
671 BEAST_EXPECT(!firstNFTokenPage);
676 if (!BEAST_EXPECT(middleNFTokenPage))
678 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
679 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin));
682 if (!BEAST_EXPECT(lastNFTokenPage))
684 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
685 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
689 for (
int i = 0; i < 32; ++i)
691 env(token::burn(alice, {nfts.
back()}));
696 if (features[fixNFTokenPageLinks])
703 BEAST_EXPECT(lastNFTokenPage);
705 !lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
706 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
708 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
713 BEAST_EXPECT(!middleNFTokenPage);
718 BEAST_EXPECT(!firstNFTokenPage);
726 BEAST_EXPECT(!lastNFTokenPage);
732 if (!BEAST_EXPECT(middleNFTokenPage))
735 !middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
736 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
740 while (!nfts.
empty())
742 env(token::burn(alice, {nfts.
back()}));
746 BEAST_EXPECT(
nftCount(env, alice) == 0);
747 BEAST_EXPECT(ownerCount(env, alice) == 0);
751 if (features[fixNFTokenPageLinks])
765 BEAST_EXPECT(
nftCount(env, alice) == 96);
766 BEAST_EXPECT(ownerCount(env, alice) == 3);
769 for (
int i = 0; i < 31; ++i)
771 env(token::burn(alice, {nfts.
back()}));
787 env.current()->fees().base,
792 auto lastNFTokenPage =
794 if (!BEAST_EXPECT(lastNFTokenPage))
797 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
800 ac.view().erase(lastNFTokenPage);
804 for (
TER const& terExpect :
807 terActual = ac.checkInvariants(terActual,
XRPAmount{});
808 BEAST_EXPECT(terExpect == terActual);
810 sink.messages().str().starts_with(
"Invariant failed:"));
814 sink.messages().str().find(
815 "Last NFT page deleted with non-empty directory") !=
831 env.current()->fees().base,
836 auto lastNFTokenPage =
840 lastNFTokenPage->getFieldH256(sfPreviousPageMin)));
841 BEAST_EXPECT(middleNFTokenPage);
845 middleNFTokenPage->makeFieldAbsent(sfNextPageMin);
846 ac.view().update(middleNFTokenPage);
850 for (
TER const& terExpect :
853 terActual = ac.checkInvariants(terActual,
XRPAmount{});
854 BEAST_EXPECT(terExpect == terActual);
856 sink.messages().str().starts_with(
"Invariant failed:"));
860 sink.messages().str().find(
"Lost NextMinPage link") !=
873 using namespace test::jtx;
879 Env env{*
this, features};
881 Account
const alice(
"alice");
882 Account
const becky(
"becky");
883 env.fund(XRP(100000), alice, becky);
894 for (
uint256 const& offerIndex : offerIndexes)
900 uint256 const beckyOfferIndex =
902 env(token::createOffer(becky, nftokenID, drops(1)),
903 token::owner(alice));
907 env(token::burn(alice, nftokenID));
912 for (
uint256 const& offerIndex : offerIndexes)
922 BEAST_EXPECT(ownerCount(env, alice) == 0);
923 BEAST_EXPECT(ownerCount(env, becky) == 0);
928 Env env{*
this, features};
930 Account
const alice(
"alice");
931 Account
const becky(
"becky");
932 env.fund(XRP(100000), alice, becky);
943 for (
uint256 const& offerIndex : offerIndexes)
949 env(token::burn(alice, nftokenID));
952 uint32_t offerDeletedCount = 0;
954 for (
uint256 const& offerIndex : offerIndexes)
966 BEAST_EXPECT(ownerCount(env, alice) == 1);
971 Env env{*
this, features};
973 Account
const alice(
"alice");
974 Account
const becky(
"becky");
975 env.fund(XRP(100000), alice, becky);
987 for (
uint256 const& offerIndex : offerIndexes)
993 env(token::createOffer(becky, nftokenID, drops(1)),
994 token::owner(alice));
996 env(token::createOffer(becky, nftokenID, drops(1)),
997 token::owner(alice));
1001 env(token::burn(alice, nftokenID));
1006 for (
uint256 const& offerIndex : offerIndexes)
1013 BEAST_EXPECT(ownerCount(env, alice) == 0);
1016 BEAST_EXPECT(ownerCount(env, becky) == 1);
1025 if (features[fixNFTokenPageLinks])
1033 using namespace test::jtx;
1035 Account
const alice{
"alice"};
1036 Account
const minter{
"minter"};
1038 Env env{*
this, features};
1039 env.fund(XRP(1000), alice, minter);
1043 auto genPackedTokens = [
this, &env, &alice, &minter]() {
1055 auto internalTaxon = [&env](
1056 Account
const& acct,
1059 env.le(acct)->at(~sfMintedNFTokens).value_or(0);
1062 tokenSeq += env.le(acct)
1063 ->at(~sfFirstNFTokenSequence)
1064 .value_or(env.seq(acct));
1078 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
1079 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
1086 uint256 const minterOfferIndex =
1088 env(token::createOffer(minter, nfts.
back(), XRP(0)),
1093 env(token::acceptSellOffer(alice, minterOfferIndex));
1104 jvParams[jss::ledger_index] =
"current";
1105 jvParams[jss::binary] =
false;
1108 env.rpc(
"json",
"ledger_data",
to_string(jvParams));
1110 Json::Value& state = jrr[jss::result][jss::state];
1115 if (state[i].isMember(sfNFTokens.jsonName) &&
1116 state[i][sfNFTokens.jsonName].
isArray())
1119 state[i][sfNFTokens.jsonName].
size() == 32);
1125 BEAST_EXPECT(pageCount == 3);
1132 BEAST_EXPECT(
nftCount(env, alice) == 96);
1133 BEAST_EXPECT(ownerCount(env, alice) == 3);
1138 if (!BEAST_EXPECT(lastNFTokenPage))
1141 uint256 const middleNFTokenPageIndex =
1142 lastNFTokenPage->at(sfPreviousPageMin);
1145 if (!BEAST_EXPECT(middleNFTokenPage))
1148 uint256 const firstNFTokenPageIndex =
1149 middleNFTokenPage->at(sfPreviousPageMin);
1150 auto firstNFTokenPage = env.le(
1152 if (!BEAST_EXPECT(firstNFTokenPage))
1157 for (
int i = 0; i < 32; ++i)
1163 uint256 const aliceOfferIndex =
1165 env(token::createOffer(alice, last32NFTs.
back(), XRP(0)),
1170 env(token::acceptSellOffer(minter, aliceOfferIndex));
1178 BEAST_EXPECT(!lastNFTokenPage);
1179 BEAST_EXPECT(ownerCount(env, alice) == 2);
1185 if (!BEAST_EXPECT(middleNFTokenPage))
1187 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1188 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1191 auto const acctDelFee{drops(env.current()->fees().increment)};
1192 env(acctdelete(alice, minter),
1198 for (
uint256 nftID : last32NFTs)
1201 uint256 const minterOfferIndex =
1203 env(token::createOffer(minter, nftID, XRP(0)),
1208 env(token::acceptSellOffer(alice, minterOfferIndex));
1211 BEAST_EXPECT(ownerCount(env, alice) == 3);
1219 params[jss::account] = alice.human();
1220 return env.rpc(
"json",
"account_objects",
to_string(params));
1222 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1224 acctObjs[jss::result][jss::account_objects].size() == 2);
1231 params[jss::account] = alice.human();
1232 params[jss::type] =
"state";
1233 return env.rpc(
"json",
"account_nfts",
to_string(params));
1235 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1237 aliceNFTs[jss::result][jss::account_nfts].size() == 64);