22#include <xrpld/app/tx/detail/NFTokenUtils.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/jss.h>
38 params[jss::account] = acct.
human();
39 params[jss::type] =
"state";
41 return nfts[jss::result][jss::account_nfts].
size();
51 size_t const tokenCancelCount)
53 using namespace test::jtx;
61 offerIndexes.
reserve(tokenCancelCount);
63 for (uint32_t i = 0; i < tokenCancelCount; ++i)
88 jvParams[jss::ledger_index] =
"current";
89 jvParams[jss::binary] =
false;
94 boost::lexical_cast<std::string>(jvParams));
98 !jrr[jss::result].
isMember(jss::state))
111 if (state[i].isMember(sfNFTokens.jsonName) &&
112 state[i][sfNFTokens.jsonName].
isArray())
115 state[i][sfNFTokens.jsonName].
size();
116 std::cout << tokenCount <<
" NFtokens in page "
127 << state[i][sfNFTokens.jsonName][0u]
133 << state[i][sfNFTokens.jsonName][tokenCount - 1]
148 using namespace test::jtx;
150 Env env{*
this, features};
158 AcctStat(
char const* name) : acct(name)
167 AcctStat alice{
"alice"};
168 AcctStat becky{
"becky"};
169 AcctStat minter{
"minter"};
171 env.fund(
XRP(10000), alice, becky, minter);
196 alice.nfts.reserve(105);
197 while (alice.nfts.size() < 105)
208 minter.nfts.reserve(105);
209 while (minter.nfts.size() < 105)
223 becky.nfts.reserve(70);
225 auto aliceIter = alice.nfts.begin();
226 auto minterIter = minter.nfts.begin();
227 while (becky.nfts.size() < 70)
230 auto xferNFT = [&env, &becky](AcctStat& acct,
auto& iter) {
238 becky.nfts.push_back(*iter);
239 iter = acct.nfts.erase(iter);
242 xferNFT(alice, aliceIter);
243 xferNFT(minter, minterIter);
245 BEAST_EXPECT(aliceIter == alice.nfts.end());
246 BEAST_EXPECT(minterIter == minter.nfts.end());
250 BEAST_EXPECT(
nftCount(env, alice.acct) == 70);
251 BEAST_EXPECT(
nftCount(env, becky.acct) == 70);
252 BEAST_EXPECT(
nftCount(env, minter.acct) == 70);
257 [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
283 addOffers(alice, becky, minter);
284 addOffers(becky, minter, alice);
285 addOffers(minter, alice, becky);
293 AcctStat*
const stats[3] = {&alice, &becky, &minter};
297 while (stats[0]->nfts.size() > 0 || stats[1]->nfts.size() > 0 ||
298 stats[2]->nfts.size() > 0)
302 AcctStat& owner = *(stats[acctDist(engine)]);
303 if (owner.nfts.empty())
308 0lu, owner.nfts.size() - 1);
309 auto nftIter = owner.nfts.begin() + nftDist(engine);
311 owner.nfts.erase(nftIter);
316 AcctStat& burner = owner.acct == becky.acct
317 ? *(stats[acctDist(engine)])
318 : mintDist(engine) ? alice
321 if (owner.acct == burner.acct)
329 BEAST_EXPECT(
nftCount(env, alice.acct) == alice.nfts.size());
330 BEAST_EXPECT(
nftCount(env, becky.acct) == becky.nfts.size());
331 BEAST_EXPECT(
nftCount(env, minter.acct) == minter.nfts.size());
333 BEAST_EXPECT(
nftCount(env, alice.acct) == 0);
334 BEAST_EXPECT(
nftCount(env, becky.acct) == 0);
335 BEAST_EXPECT(
nftCount(env, minter.acct) == 0);
353 using namespace test::jtx;
357 Env env{*
this, features};
358 env.fund(
XRP(1000), alice);
362 auto genPackedTokens = [
this, &env, &alice]() {
374 auto internalTaxon = [&env](
378 env.le(acct)->at(~sfMintedNFTokens).value_or(0);
382 if (env.current()->rules().enabled(fixNFTokenRemint))
383 tokenSeq += env.le(acct)
384 ->at(~sfFirstNFTokenSequence)
385 .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);
413 jvParams[jss::ledger_index] =
"current";
414 jvParams[jss::binary] =
false;
419 boost::lexical_cast<std::string>(jvParams));
426 if (state[i].isMember(sfNFTokens.jsonName) &&
427 state[i][sfNFTokens.jsonName].
isArray())
430 state[i][sfNFTokens.jsonName].
size() == 32);
436 BEAST_EXPECT(pageCount == 3);
445 BEAST_EXPECT(
nftCount(env, alice) == 96);
448 for (
uint256 const& nft : nfts)
453 BEAST_EXPECT(
nftCount(env, alice) == 0);
458 auto checkNoTokenPages = [
this, &env]() {
460 jvParams[jss::ledger_index] =
"current";
461 jvParams[jss::binary] =
false;
466 boost::lexical_cast<std::string>(jvParams));
472 BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
482 BEAST_EXPECT(
nftCount(env, alice) == 96);
488 if (!BEAST_EXPECT(lastNFTokenPage))
491 uint256 const middleNFTokenPageIndex =
492 lastNFTokenPage->at(sfPreviousPageMin);
495 if (!BEAST_EXPECT(middleNFTokenPage))
498 uint256 const firstNFTokenPageIndex =
499 middleNFTokenPage->at(sfPreviousPageMin);
502 if (!BEAST_EXPECT(firstNFTokenPage))
506 for (
int i = 0; i < 31; ++i)
516 if (!BEAST_EXPECT(lastNFTokenPage))
520 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
521 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
522 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
529 if (features[fixNFTokenPageLinks])
536 BEAST_EXPECT(lastNFTokenPage);
538 lastNFTokenPage->at(~sfPreviousPageMin) ==
539 firstNFTokenPageIndex);
540 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
542 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
547 BEAST_EXPECT(!middleNFTokenPage);
553 BEAST_EXPECT(firstNFTokenPage);
555 !firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
557 firstNFTokenPage->at(~sfNextPageMin) ==
558 lastNFTokenPage->key());
560 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
568 BEAST_EXPECT(!lastNFTokenPage);
574 if (!BEAST_EXPECT(middleNFTokenPage))
577 middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
578 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
582 while (!nfts.
empty())
588 BEAST_EXPECT(
nftCount(env, alice) == 0);
597 BEAST_EXPECT(
nftCount(env, alice) == 96);
603 if (!BEAST_EXPECT(lastNFTokenPage))
606 uint256 const middleNFTokenPageIndex =
607 lastNFTokenPage->at(sfPreviousPageMin);
610 if (!BEAST_EXPECT(middleNFTokenPage))
613 uint256 const firstNFTokenPageIndex =
614 middleNFTokenPage->at(sfPreviousPageMin);
617 if (!BEAST_EXPECT(firstNFTokenPage))
626 BEAST_EXPECT(
nftCount(env, alice) == 64);
633 BEAST_EXPECT(!middleNFTokenPage);
636 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
638 lastNFTokenPage->getFieldH256(sfPreviousPageMin) ==
639 firstNFTokenPageIndex);
644 firstNFTokenPage->getFieldH256(sfNextPageMin) ==
646 BEAST_EXPECT(!firstNFTokenPage->isFieldPresent(sfPreviousPageMin));
649 for (
uint256 const& nft : nfts)
654 BEAST_EXPECT(
nftCount(env, alice) == 0);
663 BEAST_EXPECT(
nftCount(env, alice) == 96);
669 if (!BEAST_EXPECT(lastNFTokenPage))
672 uint256 const middleNFTokenPageIndex =
673 lastNFTokenPage->at(sfPreviousPageMin);
676 if (!BEAST_EXPECT(middleNFTokenPage))
679 uint256 const firstNFTokenPageIndex =
680 middleNFTokenPage->at(sfPreviousPageMin);
683 if (!BEAST_EXPECT(firstNFTokenPage))
688 for (
int i = 0; i < 32; ++i)
698 BEAST_EXPECT(!firstNFTokenPage);
703 if (!BEAST_EXPECT(middleNFTokenPage))
705 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
706 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfNextPageMin));
709 if (!BEAST_EXPECT(lastNFTokenPage))
711 BEAST_EXPECT(lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
712 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
716 for (
int i = 0; i < 32; ++i)
723 if (features[fixNFTokenPageLinks])
730 BEAST_EXPECT(lastNFTokenPage);
732 !lastNFTokenPage->isFieldPresent(sfPreviousPageMin));
733 BEAST_EXPECT(!lastNFTokenPage->isFieldPresent(sfNextPageMin));
735 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 32);
740 BEAST_EXPECT(!middleNFTokenPage);
745 BEAST_EXPECT(!firstNFTokenPage);
753 BEAST_EXPECT(!lastNFTokenPage);
759 if (!BEAST_EXPECT(middleNFTokenPage))
762 !middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
763 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
767 while (!nfts.
empty())
773 BEAST_EXPECT(
nftCount(env, alice) == 0);
778 if (features[fixNFTokenPageLinks])
792 BEAST_EXPECT(
nftCount(env, alice) == 96);
796 for (
int i = 0; i < 31; ++i)
814 env.current()->fees().base,
819 auto lastNFTokenPage =
821 if (!BEAST_EXPECT(lastNFTokenPage))
824 lastNFTokenPage->getFieldArray(sfNFTokens).size() == 1);
827 ac.view().erase(lastNFTokenPage);
831 for (
TER const& terExpect :
834 terActual = ac.checkInvariants(terActual,
XRPAmount{});
835 BEAST_EXPECT(terExpect == terActual);
837 sink.messages().str().starts_with(
"Invariant failed:"));
841 sink.messages().str().find(
842 "Last NFT page deleted with non-empty directory") !=
858 env.current()->fees().base,
863 auto lastNFTokenPage =
867 lastNFTokenPage->getFieldH256(sfPreviousPageMin)));
868 BEAST_EXPECT(middleNFTokenPage);
872 middleNFTokenPage->makeFieldAbsent(sfNextPageMin);
873 ac.view().update(middleNFTokenPage);
877 for (
TER const& terExpect :
880 terActual = ac.checkInvariants(terActual,
XRPAmount{});
881 BEAST_EXPECT(terExpect == terActual);
883 sink.messages().str().starts_with(
"Invariant failed:"));
887 sink.messages().str().find(
"Lost NextMinPage link") !=
900 using namespace test::jtx;
904 if (!features[fixNonFungibleTokensV1_2])
906 Env env{*
this, features};
910 env.fund(
XRP(1000), alice, becky);
936 env.fund(
XRP(1000), acct);
947 for (
uint256 const& offerIndex : offerIndexes)
953 uint256 const beckyOfferIndex =
963 for (
int i = 0; i < 10; ++i)
971 uint256 const aliceOfferIndex =
988 for (
uint256 const& offerIndex : offerIndexes)
1002 if (features[fixNonFungibleTokensV1_2])
1004 Env env{*
this, features};
1008 env.fund(
XRP(100000), alice, becky);
1019 for (
uint256 const& offerIndex : offerIndexes)
1025 uint256 const beckyOfferIndex =
1037 for (
uint256 const& offerIndex : offerIndexes)
1053 if (features[fixNonFungibleTokensV1_2])
1055 Env env{*
this, features};
1059 env.fund(
XRP(100000), alice, becky);
1070 for (
uint256 const& offerIndex : offerIndexes)
1079 uint32_t offerDeletedCount = 0;
1081 for (
uint256 const& offerIndex : offerIndexes)
1084 offerDeletedCount++;
1098 if (features[fixNonFungibleTokensV1_2])
1100 Env env{*
this, features};
1104 env.fund(
XRP(100000), alice, becky);
1116 for (
uint256 const& offerIndex : offerIndexes)
1135 for (
uint256 const& offerIndex : offerIndexes)
1154 if (features[fixNFTokenPageLinks])
1162 using namespace test::jtx;
1165 Account const minter{
"minter"};
1167 Env env{*
this, features};
1168 env.fund(
XRP(1000), alice, minter);
1172 auto genPackedTokens = [
this, &env, &alice, &minter]() {
1184 auto internalTaxon = [&env](
1188 env.le(acct)->at(~sfMintedNFTokens).value_or(0);
1192 if (env.current()->rules().enabled(fixNFTokenRemint))
1193 tokenSeq += env.le(acct)
1194 ->at(~sfFirstNFTokenSequence)
1195 .value_or(env.seq(acct));
1209 std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
1210 uint32_t
const extTaxon = internalTaxon(minter, intTaxon);
1217 uint256 const minterOfferIndex =
1235 jvParams[jss::ledger_index] =
"current";
1236 jvParams[jss::binary] =
false;
1241 boost::lexical_cast<std::string>(jvParams));
1243 Json::Value& state = jrr[jss::result][jss::state];
1248 if (state[i].isMember(sfNFTokens.jsonName) &&
1249 state[i][sfNFTokens.jsonName].
isArray())
1252 state[i][sfNFTokens.jsonName].
size() == 32);
1258 BEAST_EXPECT(pageCount == 3);
1265 BEAST_EXPECT(
nftCount(env, alice) == 96);
1271 if (!BEAST_EXPECT(lastNFTokenPage))
1274 uint256 const middleNFTokenPageIndex =
1275 lastNFTokenPage->at(sfPreviousPageMin);
1278 if (!BEAST_EXPECT(middleNFTokenPage))
1281 uint256 const firstNFTokenPageIndex =
1282 middleNFTokenPage->at(sfPreviousPageMin);
1283 auto firstNFTokenPage = env.le(
1285 if (!BEAST_EXPECT(firstNFTokenPage))
1290 for (
int i = 0; i < 32; ++i)
1296 uint256 const aliceOfferIndex =
1311 BEAST_EXPECT(!lastNFTokenPage);
1318 if (!BEAST_EXPECT(middleNFTokenPage))
1320 BEAST_EXPECT(middleNFTokenPage->isFieldPresent(sfPreviousPageMin));
1321 BEAST_EXPECT(!middleNFTokenPage->isFieldPresent(sfNextPageMin));
1324 auto const acctDelFee{
drops(env.current()->fees().increment)};
1331 for (
uint256 nftID : last32NFTs)
1334 uint256 const minterOfferIndex =
1352 params[jss::account] = alice.human();
1353 return env.rpc(
"json",
"account_objects",
to_string(params));
1355 BEAST_EXPECT(!acctObjs.
isMember(jss::marker));
1357 acctObjs[jss::result][jss::account_objects].size() == 2);
1364 params[jss::account] = alice.human();
1365 params[jss::type] =
"state";
1366 return env.rpc(
"json",
"account_nfts",
to_string(params));
1368 BEAST_EXPECT(!aliceNFTs.
isMember(jss::marker));
1370 aliceNFTs[jss::result][jss::account_nfts].size() == 64);
1387 using namespace test::jtx;
1389 static FeatureBitset const fixNFTV1_2{fixNonFungibleTokensV1_2};
1392 static FeatureBitset const fixNFTPageLinks{fixNFTokenPageLinks};
1395 all - fixNFTV1_2 - fixNFTDir - fixNFTRemint - fixNFTPageLinks,
1396 all - fixNFTV1_2 - fixNFTRemint - fixNFTPageLinks,
1397 all - fixNFTRemint - fixNFTPageLinks,
1398 all - fixNFTPageLinks,
1402 if (BEAST_EXPECT(instance < feats.size()))
1406 BEAST_EXPECT(!last || instance == feats.size() - 1);
1457BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnBaseUtil, tx,
ripple, 3);
1458BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOfixFungTokens, tx,
ripple, 3);
1459BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOFixTokenRemint, tx,
ripple, 3);
1460BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnWOFixNFTPageLinks, tx,
ripple, 3);
1461BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurnAllFeatures, tx,
ripple, 3);
UInt size() const
Number of values in array or object.
std::string toStyledString() const
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
State information when applying a tx.
void run() override
Runs the suite.
static std::uint32_t nftCount(test::jtx::Env &env, test::jtx::Account const &acct)
uint256 createNftAndOffers(test::jtx::Env &env, test::jtx::Account const &owner, std::vector< uint256 > &offerIndexes, size_t const tokenCancelCount)
void exerciseBrokenLinks(FeatureBitset features)
void run(std::uint32_t instance, bool last=false)
void run() override
Runs the suite.
void testWithFeats(FeatureBitset features)
void testBurnTooManyOffers(FeatureBitset features)
void printNFTPages(test::jtx::Env &env, Volume vol)
void testBurnRandom(FeatureBitset features)
void testBurnSequential(FeatureBitset features)
void run() override
Runs the suite.
void run() override
Runs the suite.
void run() override
Runs the suite.
Writable ledger view that accumulates state and tx changes.
Immutable cryptographic account descriptor.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Sets the optional Destination field on an NFTokenOffer.
Sets the optional Issuer on an NFTokenMint.
Sets the optional Owner on an NFTokenOffer.
Sets the optional URI on an NFTokenMint.
Sets the optional TransferFee on an NFTokenMint.
Keylet nftpage(Keylet const &k, uint256 const &token)
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Taxon toTaxon(std::uint32_t i)
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Json::Value setMinter(jtx::Account const &account, jtx::Account const &minter)
Set the authorized minter on an account root.
Json::Value createOffer(jtx::Account const &account, uint256 const &nftokenID, STAmount const &amount)
Create an NFTokenOffer.
Json::Value acceptSellOffer(jtx::Account const &account, uint256 const &offerIndex)
Accept an NFToken sell offer.
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Json::Value burn(jtx::Account const &account, uint256 const &nftokenID)
Burn an NFToken.
Json::Value cancelOffer(jtx::Account const &account, std::initializer_list< uint256 > const &nftokenOffers)
Cancel NFTokenOffers.
std::uint32_t ownerCount(Env const &env, Account const &account)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
FeatureBitset supported_amendments()
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr std::uint32_t const tfSellNFToken
std::size_t constexpr maxTokenOfferCancelCount
The maximum number of token offers that can be canceled at once.
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
std::size_t constexpr maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
constexpr std::uint32_t const tfBurnable
std::size_t constexpr maxTokenURILength
The maximum length of a URI inside an NFT.
std::string to_string(base_uint< Bits, Tag > const &a)
TERSubset< CanCvtToTER > TER
constexpr std::uint32_t const tfTransferable