From 2377ca5536037b442af04f19ffa34174ff4a8444 Mon Sep 17 00:00:00 2001 From: kennyzlei Date: Tue, 14 Feb 2023 02:51:55 +0000 Subject: [PATCH] deploy: ac78b7a9a7beccfe397c3725ca18a680d7502278 --- AccountObjects_8cpp_source.html | 10 +- DeleteAccount_8cpp_source.html | 2 +- Feature_8cpp_source.html | 114 +- Feature_8h_source.html | 12 +- LedgerCleaner_8cpp_source.html | 2 +- LedgerMaster_8cpp_source.html | 2 +- NFTokenAcceptOffer_8cpp_source.html | 624 +- NFTokenAcceptOffer_8h_source.html | 6 +- NFTokenBurn_8cpp_source.html | 120 +- NFTokenBurn_8h_source.html | 2 +- NFTokenBurn__test_8cpp_source.html | 1314 ++-- NFTokenCancelOffer_8cpp_source.html | 2 +- NFTokenCreateOffer_8cpp_source.html | 254 +- NFTokenCreateOffer_8h_source.html | 2 +- NFTokenDir__test_8cpp_source.html | 2 +- NFTokenMint_8cpp_source.html | 2 +- NFTokenUtils_8cpp_source.html | 218 +- NFTokenUtils_8h_source.html | 254 +- NFToken__test_8cpp_source.html | 5325 ++++++++++------- RPCHelpers_8cpp_source.html | 2 +- Transactor_8cpp_source.html | 2 +- TxQ_8h_source.html | 2 +- View_8cpp_source.html | 2 +- View_8h_source.html | 467 +- classripple_1_1NFTokenAcceptOffer.html | 6 +- classripple_1_1NFTokenBurn.html | 2 +- classripple_1_1NFTokenBurn__test-members.html | 15 +- classripple_1_1NFTokenBurn__test.html | 62 +- classripple_1_1NFTokenCreateOffer.html | 2 +- classripple_1_1NFToken__test-members.html | 22 +- classripple_1_1NFToken__test.html | 84 +- functions_c.html | 21 +- functions_func_c.html | 21 +- functions_func_p.html | 4 +- functions_func_s.html | 6 +- functions_func_t.html | 46 +- functions_func_v.html | 6 +- functions_p.html | 16 +- functions_q.html | 8 +- functions_r.html | 2 +- functions_s.html | 12 +- functions_t.html | 42 +- functions_v.html | 20 +- namespacemembers_f.html | 7 +- namespacemembers_func_r.html | 12 +- namespacemembers_r.html | 6 +- namespacemembers_vars_f.html | 3 + namespaceripple.html | 132 +- namespaceripple_1_1detail.html | 4 +- namespaceripple_1_1nft.html | 40 +- search/all_10.js | 1246 ++-- search/all_11.js | 96 +- search/all_12.js | 1342 ++--- search/all_13.js | 2874 ++++----- search/all_14.js | 3272 +++++----- search/all_15.js | 708 +-- search/all_16.js | 500 +- search/all_17.js | 472 +- search/all_18.js | 144 +- search/all_19.js | 14 +- search/all_1a.js | 16 +- search/all_1b.js | 708 +-- search/all_3.js | 235 +- search/all_4.js | 1140 ++-- search/all_5.js | 572 +- search/all_6.js | 981 +-- search/all_7.js | 1294 ++-- search/all_8.js | 392 +- search/all_9.js | 1578 ++--- search/all_a.js | 226 +- search/all_b.js | 112 +- search/all_c.js | 918 +-- search/all_d.js | 1940 +++--- search/all_e.js | 658 +- search/all_f.js | 672 +-- search/classes_0.js | 4 +- search/classes_1.js | 394 +- search/classes_10.js | 268 +- search/classes_11.js | 22 +- search/classes_12.js | 256 +- search/classes_13.js | 664 +- search/classes_14.js | 302 +- search/classes_15.js | 408 +- search/classes_16.js | 252 +- search/classes_17.js | 144 +- search/classes_18.js | 36 +- search/classes_19.js | 4 +- search/classes_1a.js | 10 +- search/classes_2.js | 228 +- search/classes_3.js | 426 +- search/classes_4.js | 202 +- search/classes_5.js | 182 +- search/classes_6.js | 154 +- search/classes_7.js | 56 +- search/classes_8.js | 80 +- search/classes_9.js | 410 +- search/classes_a.js | 46 +- search/classes_b.js | 32 +- search/classes_c.js | 214 +- search/classes_d.js | 274 +- search/classes_e.js | 170 +- search/classes_f.js | 174 +- search/enums_0.js | 12 +- search/enums_1.js | 20 +- search/enums_10.js | 30 +- search/enums_11.js | 34 +- search/enums_12.js | 8 +- search/enums_13.js | 6 +- search/enums_14.js | 6 +- search/enums_2.js | 10 +- search/enums_3.js | 10 +- search/enums_4.js | 6 +- search/enums_5.js | 4 +- search/enums_6.js | 4 +- search/enums_7.js | 4 +- search/enums_8.js | 4 +- search/enums_9.js | 14 +- search/enums_a.js | 4 +- search/enums_b.js | 6 +- search/enums_c.js | 6 +- search/enums_d.js | 18 +- search/enums_e.js | 2 +- search/enums_f.js | 10 +- search/enumvalues_0.js | 44 +- search/enumvalues_1.js | 46 +- search/enumvalues_10.js | 4 +- search/enumvalues_11.js | 172 +- search/enumvalues_12.js | 176 +- search/enumvalues_13.js | 450 +- search/enumvalues_14.js | 28 +- search/enumvalues_15.js | 12 +- search/enumvalues_16.js | 18 +- search/enumvalues_17.js | 18 +- search/enumvalues_18.js | 2 +- search/enumvalues_2.js | 50 +- search/enumvalues_3.js | 52 +- search/enumvalues_4.js | 26 +- search/enumvalues_5.js | 36 +- search/enumvalues_6.js | 42 +- search/enumvalues_7.js | 24 +- search/enumvalues_8.js | 32 +- search/enumvalues_9.js | 96 +- search/enumvalues_a.js | 28 +- search/enumvalues_b.js | 164 +- search/enumvalues_c.js | 46 +- search/enumvalues_d.js | 66 +- search/enumvalues_e.js | 38 +- search/enumvalues_f.js | 42 +- search/files_0.js | 8 +- search/files_1.js | 4 +- search/files_10.js | 8 +- search/files_2.js | 68 +- search/files_3.js | 2 +- search/files_4.js | 4 +- search/files_5.js | 10 +- search/files_6.js | 14 +- search/files_7.js | 6 +- search/files_8.js | 8 +- search/files_9.js | 4 +- search/files_a.js | 4 +- search/files_b.js | 2 +- search/files_c.js | 8 +- search/files_d.js | 26 +- search/files_e.js | 10 +- search/files_f.js | 6 +- search/functions_0.js | 2 +- search/functions_1.js | 740 +-- search/functions_10.js | 750 +-- search/functions_11.js | 48 +- search/functions_12.js | 608 +- search/functions_13.js | 1342 ++--- search/functions_14.js | 2240 +++---- search/functions_15.js | 242 +- search/functions_16.js | 174 +- search/functions_17.js | 298 +- search/functions_18.js | 82 +- search/functions_19.js | 2 +- search/functions_1a.js | 10 +- search/functions_1b.js | 708 +-- search/functions_2.js | 302 +- search/functions_3.js | 923 +-- search/functions_4.js | 738 +-- search/functions_5.js | 298 +- search/functions_6.js | 534 +- search/functions_7.js | 1190 ++-- search/functions_8.js | 186 +- search/functions_9.js | 844 +-- search/functions_a.js | 48 +- search/functions_b.js | 34 +- search/functions_c.js | 356 +- search/functions_d.js | 606 +- search/functions_e.js | 324 +- search/functions_f.js | 428 +- search/groups_0.js | 2 +- search/namespaces_0.js | 24 +- search/namespaces_1.js | 4 +- search/namespaces_2.js | 2 +- search/namespaces_3.js | 80 +- search/namespaces_4.js | 24 +- search/pages_0.js | 4 +- search/pages_1.js | 10 +- search/pages_2.js | 4 +- search/pages_3.js | 4 +- search/pages_4.js | 6 +- search/pages_5.js | 4 +- search/pages_6.js | 4 +- search/pages_7.js | 2 +- search/pages_8.js | 4 +- search/pages_9.js | 6 +- search/pages_a.js | 18 +- search/pages_b.js | 8 +- search/pages_c.js | 2 +- search/pages_d.js | 2 +- search/related_0.js | 8 +- search/related_1.js | 16 +- search/related_2.js | 4 +- search/related_3.js | 6 +- search/related_4.js | 2 +- search/related_5.js | 2 +- search/related_6.js | 18 +- search/related_7.js | 14 +- search/related_8.js | 34 +- search/related_9.js | 6 +- search/related_a.js | 10 +- search/related_b.js | 12 +- search/related_c.js | 4 +- search/related_d.js | 6 +- search/typedefs_0.js | 50 +- search/typedefs_1.js | 28 +- search/typedefs_10.js | 2 +- search/typedefs_11.js | 54 +- search/typedefs_12.js | 154 +- search/typedefs_13.js | 80 +- search/typedefs_14.js | 16 +- search/typedefs_15.js | 14 +- search/typedefs_16.js | 10 +- search/typedefs_17.js | 2 +- search/typedefs_18.js | 2 +- search/typedefs_2.js | 62 +- search/typedefs_3.js | 28 +- search/typedefs_4.js | 36 +- search/typedefs_5.js | 20 +- search/typedefs_6.js | 2 +- search/typedefs_7.js | 38 +- search/typedefs_8.js | 52 +- search/typedefs_9.js | 6 +- search/typedefs_a.js | 14 +- search/typedefs_b.js | 52 +- search/typedefs_c.js | 58 +- search/typedefs_d.js | 22 +- search/typedefs_e.js | 20 +- search/typedefs_f.js | 56 +- search/variables_0.js | 328 +- search/variables_1.js | 148 +- search/variables_10.js | 42 +- search/variables_11.js | 330 +- search/variables_12.js | 926 +-- search/variables_13.js | 380 +- search/variables_14.js | 70 +- search/variables_15.js | 124 +- search/variables_16.js | 96 +- search/variables_17.js | 18 +- search/variables_18.js | 4 +- search/variables_19.js | 2 +- search/variables_2.js | 336 +- search/variables_3.js | 234 +- search/variables_4.js | 118 +- search/variables_5.js | 369 +- search/variables_6.js | 46 +- search/variables_7.js | 106 +- search/variables_8.js | 384 +- search/variables_9.js | 50 +- search/variables_a.js | 26 +- search/variables_b.js | 274 +- search/variables_c.js | 1122 ++-- search/variables_d.js | 200 +- search/variables_e.js | 98 +- search/variables_f.js | 312 +- structripple_1_1nft_1_1TokenAndPage.html | 8 +- 279 files changed, 30992 insertions(+), 29437 deletions(-) diff --git a/AccountObjects_8cpp_source.html b/AccountObjects_8cpp_source.html index d5e9c82254..9cc17c64d3 100644 --- a/AccountObjects_8cpp_source.html +++ b/AccountObjects_8cpp_source.html @@ -346,7 +346,7 @@ $(function() {
Definition: Context.h:53
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
-
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:116
+
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:120
STL class.
STL class.
Json::Value rpcError(int iError)
Definition: RPCErr.cpp:29
@@ -369,7 +369,7 @@ $(function() {
const SF_UINT16 sfTransferFee
constexpr uint256 pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:262
-
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:176
+
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:180
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:81
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:332
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition: Indexes.cpp:348
@@ -382,7 +382,7 @@ $(function() {
@ rpcACT_NOT_FOUND
Definition: ErrorCodes.h:70
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:340
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
-
std::uint32_t getSerial(uint256 const &id)
Definition: NFTokenUtils.h:132
+
std::uint32_t getSerial(uint256 const &id)
Definition: NFTokenUtils.h:136
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
static constexpr LimitRange accountObjects
Limits for the account_objects command.
@@ -401,9 +401,9 @@ $(function() {
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
const SF_UINT32 sfNFTokenTaxon
@ ltRIPPLE_STATE
A ledger object which describes a bidirectional trust line.
Definition: LedgerFormats.h:74
-
std::uint16_t getTransferFee(uint256 const &id)
Definition: NFTokenUtils.h:124
+
std::uint16_t getTransferFee(uint256 const &id)
Definition: NFTokenUtils.h:128
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition: base_uint.h:495
-
Taxon getTaxon(uint256 const &id)
Definition: NFTokenUtils.h:164
+
Taxon getTaxon(uint256 const &id)
Definition: NFTokenUtils.h:168
Json::Value params
Definition: Context.h:64
Json::Value invalid_field_error(std::string const &name)
Definition: ErrorCodes.h:304
Json::Value accountFromString(AccountID &result, std::string const &strIdent, bool bStrict)
Definition: RPCHelpers.cpp:85
diff --git a/DeleteAccount_8cpp_source.html b/DeleteAccount_8cpp_source.html index b69d42d323..2501207299 100644 --- a/DeleteAccount_8cpp_source.html +++ b/DeleteAccount_8cpp_source.html @@ -550,7 +550,7 @@ $(function() {
const AccountID account_
Definition: Transactor.h:91
STTx const & tx
Definition: ApplyContext.h:48
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition: AccountID.h:47
-
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
+
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
@ tecNO_DST
Definition: TER.h:254
Definition: XRPAmount.h:46
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525
diff --git a/Feature_8cpp_source.html b/Feature_8cpp_source.html index d35fa6bb8e..aac0b0896a 100644 --- a/Feature_8cpp_source.html +++ b/Feature_8cpp_source.html @@ -501,54 +501,55 @@ $(function() {
453 REGISTER_FEATURE(DisallowIncoming, Supported::yes, DefaultVote::no);
454 REGISTER_FEATURE(XRPFees, Supported::yes, DefaultVote::no);
455 REGISTER_FIX (fixUniversalNumber, Supported::yes, DefaultVote::no);
-
456 
-
457 // The following amendments have been active for at least two years. Their
-
458 // pre-amendment code has been removed and the identifiers are deprecated.
-
459 // All known amendments and amendments that may appear in a validated
-
460 // ledger must be registered either here or above with the "active" amendments
-
461 [[deprecated("The referenced amendment has been retired"), maybe_unused]]
-
462 uint256 const
-
463  retiredMultiSign = retireFeature("MultiSign"),
-
464  retiredTrustSetAuth = retireFeature("TrustSetAuth"),
-
465  retiredFeeEscalation = retireFeature("FeeEscalation"),
-
466  retiredPayChan = retireFeature("PayChan"),
-
467  retiredCryptoConditions = retireFeature("CryptoConditions"),
-
468  retiredTickSize = retireFeature("TickSize"),
-
469  retiredFix1368 = retireFeature("fix1368"),
-
470  retiredEscrow = retireFeature("Escrow"),
-
471  retiredFix1373 = retireFeature("fix1373"),
-
472  retiredEnforceInvariants = retireFeature("EnforceInvariants"),
-
473  retiredSortedDirectories = retireFeature("SortedDirectories"),
-
474  retiredFix1201 = retireFeature("fix1201"),
-
475  retiredFix1512 = retireFeature("fix1512"),
-
476  retiredFix1523 = retireFeature("fix1523"),
-
477  retiredFix1528 = retireFeature("fix1528");
-
478 
-
479 // clang-format on
-
480 
-
481 #undef REGISTER_FIX
-
482 #pragma pop_macro("REGISTER_FIX")
-
483 
-
484 #undef REGISTER_FEATURE
-
485 #pragma pop_macro("REGISTER_FEATURE")
-
486 
-
487 // All of the features should now be registered, since variables in a cpp file
-
488 // are initialized from top to bottom.
-
489 //
-
490 // Use initialization of one final static variable to set
-
491 // featureCollections::readOnly.
-
492 [[maybe_unused]] static const bool readOnlySet =
-
493  featureCollections.registrationIsDone();
-
494 
-
495 } // namespace ripple
+
456 REGISTER_FIX (fixNonFungibleTokensV1_2, Supported::yes, DefaultVote::no);
+
457 
+
458 // The following amendments have been active for at least two years. Their
+
459 // pre-amendment code has been removed and the identifiers are deprecated.
+
460 // All known amendments and amendments that may appear in a validated
+
461 // ledger must be registered either here or above with the "active" amendments
+
462 [[deprecated("The referenced amendment has been retired"), maybe_unused]]
+
463 uint256 const
+
464  retiredMultiSign = retireFeature("MultiSign"),
+
465  retiredTrustSetAuth = retireFeature("TrustSetAuth"),
+
466  retiredFeeEscalation = retireFeature("FeeEscalation"),
+
467  retiredPayChan = retireFeature("PayChan"),
+
468  retiredCryptoConditions = retireFeature("CryptoConditions"),
+
469  retiredTickSize = retireFeature("TickSize"),
+
470  retiredFix1368 = retireFeature("fix1368"),
+
471  retiredEscrow = retireFeature("Escrow"),
+
472  retiredFix1373 = retireFeature("fix1373"),
+
473  retiredEnforceInvariants = retireFeature("EnforceInvariants"),
+
474  retiredSortedDirectories = retireFeature("SortedDirectories"),
+
475  retiredFix1201 = retireFeature("fix1201"),
+
476  retiredFix1512 = retireFeature("fix1512"),
+
477  retiredFix1523 = retireFeature("fix1523"),
+
478  retiredFix1528 = retireFeature("fix1528");
+
479 
+
480 // clang-format on
+
481 
+
482 #undef REGISTER_FIX
+
483 #pragma pop_macro("REGISTER_FIX")
+
484 
+
485 #undef REGISTER_FEATURE
+
486 #pragma pop_macro("REGISTER_FEATURE")
+
487 
+
488 // All of the features should now be registered, since variables in a cpp file
+
489 // are initialized from top to bottom.
+
490 //
+
491 // Use initialization of one final static variable to set
+
492 // featureCollections::readOnly.
+
493 [[maybe_unused]] static const bool readOnlySet =
+
494  featureCollections.registrationIsDone();
+
495 
+
496 } // namespace ripple
const uint256 fixRemoveNFTokenAutoTrustLine
const uint256 fixQualityUpperBound
const uint256 fixNFTokenNegOffer
STL class.
-
const uint256 retiredFix1528
Definition: Feature.cpp:477
-
const uint256 retiredSortedDirectories
Definition: Feature.cpp:473
+
const uint256 retiredFix1528
Definition: Feature.cpp:478
+
const uint256 retiredSortedDirectories
Definition: Feature.cpp:474
const uint256 fix1515
static constexpr std::size_t numFeatures
Definition: Feature.h:77
std::size_t hash_value(ripple::uint256 const &feature)
Definition: Feature.cpp:35
@@ -557,26 +558,26 @@ $(function() {
const uint256 fix1781
REGISTER_FEATURE(OwnerPaysFee, Supported::no, DefaultVote::no)
void check(bool condition, std::string const &message)
Definition: json/Writer.h:252
-
static const bool readOnlySet
Definition: Feature.cpp:492
-
const uint256 retiredPayChan
Definition: Feature.cpp:466
+
static const bool readOnlySet
Definition: Feature.cpp:493
+
const uint256 retiredPayChan
Definition: Feature.cpp:467
Definition: IPAddress.h:103
-
const uint256 retiredFix1368
Definition: Feature.cpp:469
-
const uint256 retiredEnforceInvariants
Definition: Feature.cpp:472
-
const uint256 retiredMultiSign
Definition: Feature.cpp:463
+
const uint256 retiredFix1368
Definition: Feature.cpp:470
+
const uint256 retiredEnforceInvariants
Definition: Feature.cpp:473
+
const uint256 retiredMultiSign
Definition: Feature.cpp:464
base_uint< 256 > uint256
Definition: base_uint.h:549
-
const uint256 retiredTickSize
Definition: Feature.cpp:468
+
const uint256 retiredTickSize
Definition: Feature.cpp:469
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:333
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition: Feature.cpp:326
std::map< std::string, DefaultVote > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition: Feature.cpp:319
-
const uint256 retiredEscrow
Definition: Feature.cpp:470
+
const uint256 retiredEscrow
Definition: Feature.cpp:471
@ yes
const uint256 fix1513
const uint256 fixCheckThreading
-
const uint256 retiredFix1373
Definition: Feature.cpp:471
+
const uint256 retiredFix1373
Definition: Feature.cpp:472
uint256 registerFeature(std::string const &name, Supported support, DefaultVote vote)
Definition: Feature.cpp:347
const uint256 fixAmendmentMajorityCalc
-
const uint256 retiredTrustSetAuth
Definition: Feature.cpp:464
+
const uint256 retiredTrustSetAuth
Definition: Feature.cpp:465
const uint256 fixTakerDryOfferRemoval
const uint256 fix1623
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:368
@@ -584,13 +585,13 @@ $(function() {
const uint256 fixMasterKeyAsRegularKey
REGISTER_FIX(fix1513, Supported::yes, DefaultVote::yes)
-
const uint256 retiredCryptoConditions
Definition: Feature.cpp:467
+
const uint256 retiredCryptoConditions
Definition: Feature.cpp:468
STL class.
const uint256 fixNFTokenDirV1
const uint256 fixRmSmallIncreasedQOffers
const uint256 fixTrustLinesToSelf
const uint256 fix1543
-
const uint256 retiredFeeEscalation
Definition: Feature.cpp:465
+
const uint256 retiredFeeEscalation
Definition: Feature.cpp:466
const uint256 fix1578
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
@@ -598,13 +599,14 @@ $(function() {
const uint256 fixUniversalNumber
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:374
DefaultVote
Definition: Feature.h:69
-
const uint256 retiredFix1523
Definition: Feature.cpp:476
-
const uint256 retiredFix1201
Definition: Feature.cpp:474
+
const uint256 retiredFix1523
Definition: Feature.cpp:477
+
const uint256 retiredFix1201
Definition: Feature.cpp:475
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
uint256 retireFeature(std::string const &name)
Definition: Feature.cpp:355
-
const uint256 retiredFix1512
Definition: Feature.cpp:475
+
const uint256 fixNonFungibleTokensV1_2
+
const uint256 retiredFix1512
Definition: Feature.cpp:476
const uint256 fix1571
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:341
bool registrationIsDone()
Tell FeatureCollections when registration is complete.
Definition: Feature.cpp:362
diff --git a/Feature_8h_source.html b/Feature_8h_source.html index 16db9aab1e..857e79ccac 100644 --- a/Feature_8h_source.html +++ b/Feature_8h_source.html @@ -108,7 +108,7 @@ $(function() {
74 // Feature.cpp. Because it's only used to reserve storage, and determine how
75 // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
76 // the actual number of amendments. A LogicError on startup will verify this.
-
77 static constexpr std::size_t numFeatures = 56;
+
77 static constexpr std::size_t numFeatures = 57;
78 
82 std::map<std::string, DefaultVote> const&
83 supportedAmendments();
@@ -366,10 +366,11 @@ $(function() {
343 extern uint256 const featureDisallowIncoming;
344 extern uint256 const featureXRPFees;
345 extern uint256 const fixUniversalNumber;
-
346 
-
347 } // namespace ripple
-
348 
-
349 #endif
+
346 extern uint256 const fixNonFungibleTokensV1_2;
+
347 
+
348 } // namespace ripple
+
349 
+
350 #endif
FeatureBitset & operator|=(FeatureBitset const &rhs)
Definition: Feature.h:207
const uint256 fixRemoveNFTokenAutoTrustLine
@@ -458,6 +459,7 @@ $(function() {
T flip(T... args)
FeatureBitset & operator&=(FeatureBitset const &rhs)
Definition: Feature.h:200
const uint256 featureOwnerPaysFee
+
const uint256 fixNonFungibleTokensV1_2
const uint256 featureCheckCashMakesTrustLine
const uint256 fix1571
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:341
diff --git a/LedgerCleaner_8cpp_source.html b/LedgerCleaner_8cpp_source.html index 881e81920c..208d6e6973 100644 --- a/LedgerCleaner_8cpp_source.html +++ b/LedgerCleaner_8cpp_source.html @@ -585,7 +585,7 @@ $(function() {
@ cleaning
bool checkNodes_
bool fixTxns_
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:214
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:219
Check the ledger/transaction databases to make sure they have continuity.
Definition: LedgerCleaner.h:32
bool pendSaveValidated(Application &app, std::shared_ptr< Ledger const > const &ledger, bool isSynchronous, bool isCurrent)
Save, or arrange to save, a fully-validated ledger Returns false on error.
Definition: Ledger.cpp:976
int failures_
diff --git a/LedgerMaster_8cpp_source.html b/LedgerMaster_8cpp_source.html index a3439dbacc..fc74e681d1 100644 --- a/LedgerMaster_8cpp_source.html +++ b/LedgerMaster_8cpp_source.html @@ -2627,7 +2627,7 @@ $(function() {
@ jtADVANCE
Definition: Job.h:68
T max(T... args)
int getLength() const
Definition: Serializer.h:199
-
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:214
+
LedgerIndex getCandidateLedger(LedgerIndex requested)
Find a ledger index from which we could easily get the requested ledger.
Definition: View.h:219
void consensusBuilt(std::shared_ptr< Ledger const > const &ledger, uint256 const &consensusHash, Json::Value consensus)
Report that the consensus process built a particular ledger.
void tryFill(std::shared_ptr< Ledger const > ledger)
std::uint32_t get32()
Definition: Serializer.cpp:386
diff --git a/NFTokenAcceptOffer_8cpp_source.html b/NFTokenAcceptOffer_8cpp_source.html index 4163f6c378..5d412ebb93 100644 --- a/NFTokenAcceptOffer_8cpp_source.html +++ b/NFTokenAcceptOffer_8cpp_source.html @@ -178,285 +178,353 @@ $(function() {
107  if ((*bo)[sfAmount].issue() != (*so)[sfAmount].issue())
108  return tecNFTOKEN_BUY_SELL_MISMATCH;
109 
-
110  // Ensure that the buyer is willing to pay at least as much as the
-
111  // seller is requesting:
-
112  if ((*so)[sfAmount] > (*bo)[sfAmount])
-
113  return tecINSUFFICIENT_PAYMENT;
-
114 
-
115  // If the buyer specified a destination, that destination must be
-
116  // the seller or the broker.
-
117  if (auto const dest = bo->at(~sfDestination))
-
118  {
-
119  if (*dest != so->at(sfOwner) && *dest != ctx.tx[sfAccount])
-
120  return tecNFTOKEN_BUY_SELL_MISMATCH;
-
121  }
-
122 
-
123  // If the seller specified a destination, that destination must be
-
124  // the buyer or the broker.
-
125  if (auto const dest = so->at(~sfDestination))
-
126  {
-
127  if (*dest != bo->at(sfOwner) && *dest != ctx.tx[sfAccount])
-
128  return tecNFTOKEN_BUY_SELL_MISMATCH;
-
129  }
-
130 
-
131  // The broker can specify an amount that represents their cut; if they
-
132  // have, ensure that the seller will get at least as much as they want
-
133  // to get *after* this fee is accounted for (but before the issuer's
-
134  // cut, if any).
-
135  if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee])
-
136  {
-
137  if (brokerFee->issue() != (*bo)[sfAmount].issue())
-
138  return tecNFTOKEN_BUY_SELL_MISMATCH;
-
139 
-
140  if (brokerFee >= (*bo)[sfAmount])
-
141  return tecINSUFFICIENT_PAYMENT;
-
142 
-
143  if ((*so)[sfAmount] > (*bo)[sfAmount] - *brokerFee)
-
144  return tecINSUFFICIENT_PAYMENT;
-
145  }
-
146  }
-
147 
-
148  if (bo)
-
149  {
-
150  if (((*bo)[sfFlags] & lsfSellNFToken) == lsfSellNFToken)
-
151  return tecNFTOKEN_OFFER_TYPE_MISMATCH;
-
152 
-
153  // An account can't accept an offer it placed:
-
154  if ((*bo)[sfOwner] == ctx.tx[sfAccount])
-
155  return tecCANT_ACCEPT_OWN_NFTOKEN_OFFER;
-
156 
-
157  // If not in bridged mode, the account must own the token:
-
158  if (!so &&
-
159  !nft::findToken(ctx.view, ctx.tx[sfAccount], (*bo)[sfNFTokenID]))
-
160  return tecNO_PERMISSION;
-
161 
-
162  // If not in bridged mode...
-
163  if (!so)
-
164  {
-
165  // If the offer has a Destination field, the acceptor must be the
-
166  // Destination.
-
167  if (auto const dest = bo->at(~sfDestination);
-
168  dest.has_value() && *dest != ctx.tx[sfAccount])
-
169  return tecNO_PERMISSION;
-
170  }
-
171  // The account offering to buy must have funds:
-
172  auto const needed = bo->at(sfAmount);
-
173 
-
174  if (accountHolds(
-
175  ctx.view,
-
176  (*bo)[sfOwner],
-
177  needed.getCurrency(),
-
178  needed.getIssuer(),
-
179  fhZERO_IF_FROZEN,
-
180  ctx.j) < needed)
-
181  return tecINSUFFICIENT_FUNDS;
-
182  }
-
183 
-
184  if (so)
-
185  {
-
186  if (((*so)[sfFlags] & lsfSellNFToken) != lsfSellNFToken)
-
187  return tecNFTOKEN_OFFER_TYPE_MISMATCH;
-
188 
-
189  // An account can't accept an offer it placed:
-
190  if ((*so)[sfOwner] == ctx.tx[sfAccount])
-
191  return tecCANT_ACCEPT_OWN_NFTOKEN_OFFER;
-
192 
-
193  // The seller must own the token.
-
194  if (!nft::findToken(ctx.view, (*so)[sfOwner], (*so)[sfNFTokenID]))
-
195  return tecNO_PERMISSION;
-
196 
-
197  // If not in bridged mode...
-
198  if (!bo)
-
199  {
-
200  // If the offer has a Destination field, the acceptor must be the
-
201  // Destination.
-
202  if (auto const dest = so->at(~sfDestination);
-
203  dest.has_value() && *dest != ctx.tx[sfAccount])
-
204  return tecNO_PERMISSION;
-
205  }
-
206 
-
207  // The account offering to buy must have funds:
-
208  auto const needed = so->at(sfAmount);
-
209 
-
210  if (accountHolds(
-
211  ctx.view,
-
212  ctx.tx[sfAccount],
-
213  needed.getCurrency(),
-
214  needed.getIssuer(),
-
215  fhZERO_IF_FROZEN,
-
216  ctx.j) < needed)
-
217  return tecINSUFFICIENT_FUNDS;
-
218  }
-
219 
-
220  return tesSUCCESS;
-
221 }
-
222 
-
223 TER
-
224 NFTokenAcceptOffer::pay(
-
225  AccountID const& from,
-
226  AccountID const& to,
-
227  STAmount const& amount)
-
228 {
-
229  // This should never happen, but it's easy and quick to check.
-
230  if (amount < beast::zero)
-
231  return tecINTERNAL;
-
232 
-
233  return accountSend(view(), from, to, amount, j_);
-
234 }
+
110  // The two offers may not form a loop. A broker may not sell the
+
111  // token to the current owner of the token.
+
112  if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2) &&
+
113  ((*bo)[sfOwner] == (*so)[sfOwner]))
+
114  return tecCANT_ACCEPT_OWN_NFTOKEN_OFFER;
+
115 
+
116  // Ensure that the buyer is willing to pay at least as much as the
+
117  // seller is requesting:
+
118  if ((*so)[sfAmount] > (*bo)[sfAmount])
+
119  return tecINSUFFICIENT_PAYMENT;
+
120 
+
121  // If the buyer specified a destination
+
122  if (auto const dest = bo->at(~sfDestination))
+
123  {
+
124  // Before this fix the destination could be either the seller or
+
125  // a broker. After, it must be whoever is submitting the tx.
+
126  if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
+
127  {
+
128  if (*dest != ctx.tx[sfAccount])
+
129  return tecNO_PERMISSION;
+
130  }
+
131  else if (*dest != so->at(sfOwner) && *dest != ctx.tx[sfAccount])
+
132  return tecNFTOKEN_BUY_SELL_MISMATCH;
+
133  }
+
134 
+
135  // If the seller specified a destination
+
136  if (auto const dest = so->at(~sfDestination))
+
137  {
+
138  // Before this fix the destination could be either the seller or
+
139  // a broker. After, it must be whoever is submitting the tx.
+
140  if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
+
141  {
+
142  if (*dest != ctx.tx[sfAccount])
+
143  return tecNO_PERMISSION;
+
144  }
+
145  else if (*dest != bo->at(sfOwner) && *dest != ctx.tx[sfAccount])
+
146  return tecNFTOKEN_BUY_SELL_MISMATCH;
+
147  }
+
148 
+
149  // The broker can specify an amount that represents their cut; if they
+
150  // have, ensure that the seller will get at least as much as they want
+
151  // to get *after* this fee is accounted for (but before the issuer's
+
152  // cut, if any).
+
153  if (auto const brokerFee = ctx.tx[~sfNFTokenBrokerFee])
+
154  {
+
155  if (brokerFee->issue() != (*bo)[sfAmount].issue())
+
156  return tecNFTOKEN_BUY_SELL_MISMATCH;
+
157 
+
158  if (brokerFee >= (*bo)[sfAmount])
+
159  return tecINSUFFICIENT_PAYMENT;
+
160 
+
161  if ((*so)[sfAmount] > (*bo)[sfAmount] - *brokerFee)
+
162  return tecINSUFFICIENT_PAYMENT;
+
163  }
+
164  }
+
165 
+
166  if (bo)
+
167  {
+
168  if (((*bo)[sfFlags] & lsfSellNFToken) == lsfSellNFToken)
+
169  return tecNFTOKEN_OFFER_TYPE_MISMATCH;
+
170 
+
171  // An account can't accept an offer it placed:
+
172  if ((*bo)[sfOwner] == ctx.tx[sfAccount])
+
173  return tecCANT_ACCEPT_OWN_NFTOKEN_OFFER;
+
174 
+
175  // If not in bridged mode, the account must own the token:
+
176  if (!so &&
+
177  !nft::findToken(ctx.view, ctx.tx[sfAccount], (*bo)[sfNFTokenID]))
+
178  return tecNO_PERMISSION;
+
179 
+
180  // If not in bridged mode...
+
181  if (!so)
+
182  {
+
183  // If the offer has a Destination field, the acceptor must be the
+
184  // Destination.
+
185  if (auto const dest = bo->at(~sfDestination);
+
186  dest.has_value() && *dest != ctx.tx[sfAccount])
+
187  return tecNO_PERMISSION;
+
188  }
+
189 
+
190  // The account offering to buy must have funds:
+
191  //
+
192  // After this amendment, we allow an IOU issuer to buy an NFT with their
+
193  // own currency
+
194  auto const needed = bo->at(sfAmount);
+
195  if (ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
+
196  {
+
197  if (accountFunds(
+
198  ctx.view, (*bo)[sfOwner], needed, fhZERO_IF_FROZEN, ctx.j) <
+
199  needed)
+
200  return tecINSUFFICIENT_FUNDS;
+
201  }
+
202  else if (
+
203  accountHolds(
+
204  ctx.view,
+
205  (*bo)[sfOwner],
+
206  needed.getCurrency(),
+
207  needed.getIssuer(),
+
208  fhZERO_IF_FROZEN,
+
209  ctx.j) < needed)
+
210  return tecINSUFFICIENT_FUNDS;
+
211  }
+
212 
+
213  if (so)
+
214  {
+
215  if (((*so)[sfFlags] & lsfSellNFToken) != lsfSellNFToken)
+
216  return tecNFTOKEN_OFFER_TYPE_MISMATCH;
+
217 
+
218  // An account can't accept an offer it placed:
+
219  if ((*so)[sfOwner] == ctx.tx[sfAccount])
+
220  return tecCANT_ACCEPT_OWN_NFTOKEN_OFFER;
+
221 
+
222  // The seller must own the token.
+
223  if (!nft::findToken(ctx.view, (*so)[sfOwner], (*so)[sfNFTokenID]))
+
224  return tecNO_PERMISSION;
+
225 
+
226  // If not in bridged mode...
+
227  if (!bo)
+
228  {
+
229  // If the offer has a Destination field, the acceptor must be the
+
230  // Destination.
+
231  if (auto const dest = so->at(~sfDestination);
+
232  dest.has_value() && *dest != ctx.tx[sfAccount])
+
233  return tecNO_PERMISSION;
+
234  }
235 
-
236 TER
-
237 NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
-
238 {
-
239  bool const isSell = offer->isFlag(lsfSellNFToken);
-
240  AccountID const owner = (*offer)[sfOwner];
-
241  AccountID const& seller = isSell ? owner : account_;
-
242  AccountID const& buyer = isSell ? account_ : owner;
-
243 
-
244  auto const nftokenID = (*offer)[sfNFTokenID];
-
245 
-
246  if (auto amount = offer->getFieldAmount(sfAmount); amount != beast::zero)
-
247  {
-
248  // Calculate the issuer's cut from this sale, if any:
-
249  if (auto const fee = nft::getTransferFee(nftokenID); fee != 0)
+
236  // The account offering to buy must have funds:
+
237  auto const needed = so->at(sfAmount);
+
238  if (!ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
+
239  {
+
240  if (accountHolds(
+
241  ctx.view,
+
242  ctx.tx[sfAccount],
+
243  needed.getCurrency(),
+
244  needed.getIssuer(),
+
245  fhZERO_IF_FROZEN,
+
246  ctx.j) < needed)
+
247  return tecINSUFFICIENT_FUNDS;
+
248  }
+
249  else if (!bo)
250  {
-
251  auto const cut = multiply(amount, nft::transferFeeAsRate(fee));
-
252 
-
253  if (auto const issuer = nft::getIssuer(nftokenID);
-
254  cut != beast::zero && seller != issuer && buyer != issuer)
-
255  {
-
256  if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
-
257  return r;
-
258  amount -= cut;
-
259  }
-
260  }
-
261 
-
262  // Send the remaining funds to the seller of the NFT
-
263  if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
-
264  return r;
-
265  }
-
266 
-
267  // Now transfer the NFT:
-
268  auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
-
269 
-
270  if (!tokenAndPage)
-
271  return tecINTERNAL;
+
251  // After this amendment, we allow buyers to buy with their own
+
252  // issued currency.
+
253  //
+
254  // In the case of brokered mode, this check is essentially
+
255  // redundant, since we have already confirmed that buy offer is >
+
256  // than the sell offer, and that the buyer can cover the buy
+
257  // offer.
+
258  //
+
259  // We also _must not_ check the tx submitter in brokered
+
260  // mode, because then we are confirming that the broker can
+
261  // cover what the buyer will pay, which doesn't make sense, causes
+
262  // an unncessary tec, and is also resolved with this amendment.
+
263  if (accountFunds(
+
264  ctx.view,
+
265  ctx.tx[sfAccount],
+
266  needed,
+
267  fhZERO_IF_FROZEN,
+
268  ctx.j) < needed)
+
269  return tecINSUFFICIENT_FUNDS;
+
270  }
+
271  }
272 
-
273  if (auto const ret = nft::removeToken(
-
274  view(), seller, nftokenID, std::move(tokenAndPage->page));
-
275  !isTesSuccess(ret))
-
276  return ret;
-
277 
-
278  return nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
-
279 }
-
280 
-
281 TER
-
282 NFTokenAcceptOffer::doApply()
-
283 {
-
284  auto const loadToken = [this](std::optional<uint256> const& id) {
-
285  std::shared_ptr<SLE> sle;
-
286  if (id)
-
287  sle = view().peek(keylet::nftoffer(*id));
-
288  return sle;
-
289  };
-
290 
-
291  auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
-
292  auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
-
293 
-
294  if (bo && !nft::deleteTokenOffer(view(), bo))
-
295  {
-
296  JLOG(j_.fatal()) << "Unable to delete buy offer '"
-
297  << to_string(bo->key()) << "': ignoring";
-
298  return tecINTERNAL;
-
299  }
-
300 
-
301  if (so && !nft::deleteTokenOffer(view(), so))
-
302  {
-
303  JLOG(j_.fatal()) << "Unable to delete sell offer '"
-
304  << to_string(so->key()) << "': ignoring";
-
305  return tecINTERNAL;
-
306  }
-
307 
-
308  // Bridging two different offers
-
309  if (bo && so)
-
310  {
-
311  AccountID const buyer = (*bo)[sfOwner];
-
312  AccountID const seller = (*so)[sfOwner];
+
273  return tesSUCCESS;
+
274 }
+
275 
+
276 TER
+
277 NFTokenAcceptOffer::pay(
+
278  AccountID const& from,
+
279  AccountID const& to,
+
280  STAmount const& amount)
+
281 {
+
282  // This should never happen, but it's easy and quick to check.
+
283  if (amount < beast::zero)
+
284  return tecINTERNAL;
+
285 
+
286  auto const result = accountSend(view(), from, to, amount, j_);
+
287 
+
288  // After this amendment, if any payment would cause a non-IOU-issuer to
+
289  // have a negative balance, or an IOU-issuer to have a positive balance in
+
290  // their own currency, we know that something went wrong. This was
+
291  // originally found in the context of IOU transfer fees. Since there are
+
292  // several payouts in this tx, just confirm that the end state is OK.
+
293  if (!view().rules().enabled(fixNonFungibleTokensV1_2))
+
294  return result;
+
295  if (result != tesSUCCESS)
+
296  return result;
+
297  if (accountFunds(view(), from, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
+
298  return tecINSUFFICIENT_FUNDS;
+
299  if (accountFunds(view(), to, amount, fhZERO_IF_FROZEN, j_).signum() < 0)
+
300  return tecINSUFFICIENT_FUNDS;
+
301  return tesSUCCESS;
+
302 }
+
303 
+
304 TER
+
305 NFTokenAcceptOffer::acceptOffer(std::shared_ptr<SLE> const& offer)
+
306 {
+
307  bool const isSell = offer->isFlag(lsfSellNFToken);
+
308  AccountID const owner = (*offer)[sfOwner];
+
309  AccountID const& seller = isSell ? owner : account_;
+
310  AccountID const& buyer = isSell ? account_ : owner;
+
311 
+
312  auto const nftokenID = (*offer)[sfNFTokenID];
313 
-
314  auto const nftokenID = (*so)[sfNFTokenID];
-
315 
-
316  // The amount is what the buyer of the NFT pays:
-
317  STAmount amount = (*bo)[sfAmount];
-
318 
-
319  // Three different folks may be paid. The order of operations is
-
320  // important.
-
321  //
-
322  // o The broker is paid the cut they requested.
-
323  // o The issuer's cut is calculated from what remains after the
-
324  // broker is paid. The issuer can take up to 50% of the remainder.
-
325  // o Finally, the seller gets whatever is left.
-
326  //
-
327  // It is important that the issuer's cut be calculated after the
-
328  // broker's portion is already removed. Calculating the issuer's
-
329  // cut before the broker's cut is removed can result in more money
-
330  // being paid out than the seller authorized. That would be bad!
-
331 
-
332  // Send the broker the amount they requested.
-
333  if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee];
-
334  cut && cut.value() != beast::zero)
-
335  {
-
336  if (auto const r = pay(buyer, account_, cut.value());
-
337  !isTesSuccess(r))
-
338  return r;
-
339 
-
340  amount -= cut.value();
-
341  }
-
342 
-
343  // Calculate the issuer's cut, if any.
-
344  if (auto const fee = nft::getTransferFee(nftokenID);
-
345  amount != beast::zero && fee != 0)
-
346  {
-
347  auto cut = multiply(amount, nft::transferFeeAsRate(fee));
+
314  if (auto amount = offer->getFieldAmount(sfAmount); amount != beast::zero)
+
315  {
+
316  // Calculate the issuer's cut from this sale, if any:
+
317  if (auto const fee = nft::getTransferFee(nftokenID); fee != 0)
+
318  {
+
319  auto const cut = multiply(amount, nft::transferFeeAsRate(fee));
+
320 
+
321  if (auto const issuer = nft::getIssuer(nftokenID);
+
322  cut != beast::zero && seller != issuer && buyer != issuer)
+
323  {
+
324  if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
+
325  return r;
+
326  amount -= cut;
+
327  }
+
328  }
+
329 
+
330  // Send the remaining funds to the seller of the NFT
+
331  if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
+
332  return r;
+
333  }
+
334 
+
335  // Now transfer the NFT:
+
336  auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
+
337 
+
338  if (!tokenAndPage)
+
339  return tecINTERNAL;
+
340 
+
341  if (auto const ret = nft::removeToken(
+
342  view(), seller, nftokenID, std::move(tokenAndPage->page));
+
343  !isTesSuccess(ret))
+
344  return ret;
+
345 
+
346  return nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
+
347 }
348 
-
349  if (auto const issuer = nft::getIssuer(nftokenID);
-
350  seller != issuer && buyer != issuer)
-
351  {
-
352  if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
-
353  return r;
-
354 
-
355  amount -= cut;
-
356  }
-
357  }
+
349 TER
+
350 NFTokenAcceptOffer::doApply()
+
351 {
+
352  auto const loadToken = [this](std::optional<uint256> const& id) {
+
353  std::shared_ptr<SLE> sle;
+
354  if (id)
+
355  sle = view().peek(keylet::nftoffer(*id));
+
356  return sle;
+
357  };
358 
-
359  // And send whatever remains to the seller.
-
360  if (amount > beast::zero)
-
361  {
-
362  if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
-
363  return r;
-
364  }
-
365 
-
366  auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
-
367 
-
368  if (!tokenAndPage)
-
369  return tecINTERNAL;
-
370 
-
371  if (auto const ret = nft::removeToken(
-
372  view(), seller, nftokenID, std::move(tokenAndPage->page));
-
373  !isTesSuccess(ret))
-
374  return ret;
+
359  auto bo = loadToken(ctx_.tx[~sfNFTokenBuyOffer]);
+
360  auto so = loadToken(ctx_.tx[~sfNFTokenSellOffer]);
+
361 
+
362  if (bo && !nft::deleteTokenOffer(view(), bo))
+
363  {
+
364  JLOG(j_.fatal()) << "Unable to delete buy offer '"
+
365  << to_string(bo->key()) << "': ignoring";
+
366  return tecINTERNAL;
+
367  }
+
368 
+
369  if (so && !nft::deleteTokenOffer(view(), so))
+
370  {
+
371  JLOG(j_.fatal()) << "Unable to delete sell offer '"
+
372  << to_string(so->key()) << "': ignoring";
+
373  return tecINTERNAL;
+
374  }
375 
-
376  return nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
-
377  }
-
378 
-
379  if (bo)
-
380  return acceptOffer(bo);
+
376  // Bridging two different offers
+
377  if (bo && so)
+
378  {
+
379  AccountID const buyer = (*bo)[sfOwner];
+
380  AccountID const seller = (*so)[sfOwner];
381 
-
382  if (so)
-
383  return acceptOffer(so);
-
384 
-
385  return tecINTERNAL;
-
386 }
-
387 
-
388 } // namespace ripple
+
382  auto const nftokenID = (*so)[sfNFTokenID];
+
383 
+
384  // The amount is what the buyer of the NFT pays:
+
385  STAmount amount = (*bo)[sfAmount];
+
386 
+
387  // Three different folks may be paid. The order of operations is
+
388  // important.
+
389  //
+
390  // o The broker is paid the cut they requested.
+
391  // o The issuer's cut is calculated from what remains after the
+
392  // broker is paid. The issuer can take up to 50% of the remainder.
+
393  // o Finally, the seller gets whatever is left.
+
394  //
+
395  // It is important that the issuer's cut be calculated after the
+
396  // broker's portion is already removed. Calculating the issuer's
+
397  // cut before the broker's cut is removed can result in more money
+
398  // being paid out than the seller authorized. That would be bad!
+
399 
+
400  // Send the broker the amount they requested.
+
401  if (auto const cut = ctx_.tx[~sfNFTokenBrokerFee];
+
402  cut && cut.value() != beast::zero)
+
403  {
+
404  if (auto const r = pay(buyer, account_, cut.value());
+
405  !isTesSuccess(r))
+
406  return r;
+
407 
+
408  amount -= cut.value();
+
409  }
+
410 
+
411  // Calculate the issuer's cut, if any.
+
412  if (auto const fee = nft::getTransferFee(nftokenID);
+
413  amount != beast::zero && fee != 0)
+
414  {
+
415  auto cut = multiply(amount, nft::transferFeeAsRate(fee));
+
416 
+
417  if (auto const issuer = nft::getIssuer(nftokenID);
+
418  seller != issuer && buyer != issuer)
+
419  {
+
420  if (auto const r = pay(buyer, issuer, cut); !isTesSuccess(r))
+
421  return r;
+
422 
+
423  amount -= cut;
+
424  }
+
425  }
+
426 
+
427  // And send whatever remains to the seller.
+
428  if (amount > beast::zero)
+
429  {
+
430  if (auto const r = pay(buyer, seller, amount); !isTesSuccess(r))
+
431  return r;
+
432  }
+
433 
+
434  auto tokenAndPage = nft::findTokenAndPage(view(), seller, nftokenID);
+
435 
+
436  if (!tokenAndPage)
+
437  return tecINTERNAL;
+
438 
+
439  if (auto const ret = nft::removeToken(
+
440  view(), seller, nftokenID, std::move(tokenAndPage->page));
+
441  !isTesSuccess(ret))
+
442  return ret;
+
443 
+
444  return nft::insertToken(view(), buyer, std::move(tokenAndPage->token));
+
445  }
+
446 
+
447  if (bo)
+
448  return acceptOffer(bo);
+
449 
+
450  if (so)
+
451  return acceptOffer(so);
+
452 
+
453  return tecINTERNAL;
+
454 }
+
455 
+
456 } // namespace ripple
Stream fatal() const
Definition: Journal.h:339
@ tecOBJECT_NOT_FOUND
Definition: TER.h:290
@@ -476,7 +544,7 @@ $(function() {
const beast::Journal j_
Definition: Transactor.h:89
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
bool isTesSuccess(TER x)
Definition: TER.h:594
-
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
+
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:289
const SF_ACCOUNT sfOwner
@@ -492,7 +560,7 @@ $(function() {
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:57
@ lsfSellNFToken
const SF_UINT32 sfExpiration
-
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:176
+
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:180
@ temINVALID_FLAG
Definition: TER.h:106
@ tecNFTOKEN_OFFER_TYPE_MISMATCH
Definition: TER.h:287
@@ -502,13 +570,14 @@ $(function() {
Definition: STAmount.h:45
@ tecINTERNAL
Definition: TER.h:274
std::uint32_t getFlags() const
Definition: STObject.cpp:481
-
TER acceptOffer(std::shared_ptr< SLE > const &offer)
+
TER acceptOffer(std::shared_ptr< SLE > const &offer)
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
STTx const & tx
Definition: Transactor.h:58
+
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
Definition: View.cpp:267
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:47
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
const SF_UINT256 sfNFTokenBuyOffer
-
TER doApply() override
+
TER doApply() override
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
const uint256 featureNonFungibleTokensV1
@ tecNFTOKEN_BUY_SELL_MISMATCH
Definition: TER.h:286
@@ -524,8 +593,9 @@ $(function() {
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
const SF_ACCOUNT sfAccount
@ temMALFORMED
Definition: TER.h:82
+
const uint256 fixNonFungibleTokensV1_2
STTx const & tx
Definition: Transactor.h:35
-
std::uint16_t getTransferFee(uint256 const &id)
Definition: NFTokenUtils.h:124
+
std::uint16_t getTransferFee(uint256 const &id)
Definition: NFTokenUtils.h:128
State information when preflighting a tx.
Definition: Transactor.h:31
const Rules rules
Definition: Transactor.h:36
@ tesSUCCESS
Definition: TER.h:219
@@ -533,7 +603,7 @@ $(function() {
const SF_UINT256 sfNFTokenSellOffer
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
STTx const & tx
Definition: ApplyContext.h:48
-
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
+
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
const SF_AMOUNT sfNFTokenBrokerFee
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525
diff --git a/NFTokenAcceptOffer_8h_source.html b/NFTokenAcceptOffer_8h_source.html index 40bca2d83a..659e86a9d2 100644 --- a/NFTokenAcceptOffer_8h_source.html +++ b/NFTokenAcceptOffer_8h_source.html @@ -132,7 +132,7 @@ $(function() {
STL class.
Definition: Transactor.h:85
-
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
+
TER pay(AccountID const &from, AccountID const &to, STAmount const &amount)
NFTokenAcceptOffer(ApplyContext &ctx)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
@@ -142,9 +142,9 @@ $(function() {
Definition: STAmount.h:45
State information when applying a tx.
Definition: ApplyContext.h:35
-
TER acceptOffer(std::shared_ptr< SLE > const &offer)
+
TER acceptOffer(std::shared_ptr< SLE > const &offer)
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
-
TER doApply() override
+
TER doApply() override
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ Normal
Definition: Transactor.h:101
State information when preflighting a tx.
Definition: Transactor.h:31
diff --git a/NFTokenBurn_8cpp_source.html b/NFTokenBurn_8cpp_source.html index c26a4eba40..7074ef351a 100644 --- a/NFTokenBurn_8cpp_source.html +++ b/NFTokenBurn_8cpp_source.html @@ -148,58 +148,94 @@ $(function() {
77  }
78  }
79 
-
80  // If there are too many offers, then burning the token would produce too
-
81  // much metadata. Disallow burning a token with too many offers.
-
82  return nft::notTooManyOffers(ctx.view, ctx.tx[sfNFTokenID]);
-
83 }
-
84 
-
85 TER
-
86 NFTokenBurn::doApply()
-
87 {
-
88  // Remove the token, effectively burning it:
-
89  auto const ret = nft::removeToken(
-
90  view(),
-
91  ctx_.tx.isFieldPresent(sfOwner) ? ctx_.tx.getAccountID(sfOwner)
-
92  : ctx_.tx.getAccountID(sfAccount),
-
93  ctx_.tx[sfNFTokenID]);
-
94 
-
95  // Should never happen since preclaim() verified the token is present.
-
96  if (!isTesSuccess(ret))
-
97  return ret;
-
98 
-
99  if (auto issuer =
-
100  view().peek(keylet::account(nft::getIssuer(ctx_.tx[sfNFTokenID]))))
-
101  {
-
102  (*issuer)[~sfBurnedNFTokens] =
-
103  (*issuer)[~sfBurnedNFTokens].value_or(0) + 1;
-
104  view().update(issuer);
-
105  }
-
106 
-
107  // Optimized deletion of all offers.
-
108  nft::removeAllTokenOffers(view(), keylet::nft_sells(ctx_.tx[sfNFTokenID]));
-
109  nft::removeAllTokenOffers(view(), keylet::nft_buys(ctx_.tx[sfNFTokenID]));
-
110 
-
111  return tesSUCCESS;
-
112 }
-
113 
-
114 } // namespace ripple
+
80  if (!ctx.view.rules().enabled(fixNonFungibleTokensV1_2))
+
81  {
+
82  // If there are too many offers, then burning the token would produce
+
83  // too much metadata. Disallow burning a token with too many offers.
+
84  return nft::notTooManyOffers(ctx.view, ctx.tx[sfNFTokenID]);
+
85  }
+
86 
+
87  return tesSUCCESS;
+
88 }
+
89 
+
90 TER
+
91 NFTokenBurn::doApply()
+
92 {
+
93  // Remove the token, effectively burning it:
+
94  auto const ret = nft::removeToken(
+
95  view(),
+
96  ctx_.tx.isFieldPresent(sfOwner) ? ctx_.tx.getAccountID(sfOwner)
+
97  : ctx_.tx.getAccountID(sfAccount),
+
98  ctx_.tx[sfNFTokenID]);
+
99 
+
100  // Should never happen since preclaim() verified the token is present.
+
101  if (!isTesSuccess(ret))
+
102  return ret;
+
103 
+
104  if (auto issuer =
+
105  view().peek(keylet::account(nft::getIssuer(ctx_.tx[sfNFTokenID]))))
+
106  {
+
107  (*issuer)[~sfBurnedNFTokens] =
+
108  (*issuer)[~sfBurnedNFTokens].value_or(0) + 1;
+
109  view().update(issuer);
+
110  }
+
111 
+
112  if (ctx_.view().rules().enabled(fixNonFungibleTokensV1_2))
+
113  {
+
114  // Delete up to 500 offers in total.
+
115  // Because the number of sell offers is likely to be less than
+
116  // the number of buy offers, we prioritize the deletion of sell
+
117  // offers in order to clean up sell offer directory
+
118  std::size_t const deletedSellOffers = nft::removeTokenOffersWithLimit(
+
119  view(),
+
120  keylet::nft_sells(ctx_.tx[sfNFTokenID]),
+
121  maxDeletableTokenOfferEntries);
+
122 
+
123  if (maxDeletableTokenOfferEntries > deletedSellOffers)
+
124  {
+
125  nft::removeTokenOffersWithLimit(
+
126  view(),
+
127  keylet::nft_buys(ctx_.tx[sfNFTokenID]),
+
128  maxDeletableTokenOfferEntries - deletedSellOffers);
+
129  }
+
130  }
+
131  else
+
132  {
+
133  // Deletion of all offers.
+
134  nft::removeTokenOffersWithLimit(
+
135  view(),
+
136  keylet::nft_sells(ctx_.tx[sfNFTokenID]),
+
137  std::numeric_limits<int>::max());
+
138 
+
139  nft::removeTokenOffersWithLimit(
+
140  view(),
+
141  keylet::nft_buys(ctx_.tx[sfNFTokenID]),
+
142  std::numeric_limits<int>::max());
+
143  }
+
144 
+
145  return tesSUCCESS;
+
146 }
+
147 
+
148 } // namespace ripple
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenBurn.cpp:34
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:109
-
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:116
+
std::uint16_t getFlags(uint256 const &id)
Definition: NFTokenUtils.h:120
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:81
ReadView const & view
Definition: Transactor.h:56
+
constexpr std::size_t maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
Definition: Protocol.h:70
const SF_UINT256 sfNFTokenID
bool isTesSuccess(TER x)
Definition: TER.h:594
+
std::size_t removeTokenOffersWithLimit(ApplyView &view, Keylet const &directory, std::size_t maxDeletableOffers)
Delete up to a specified number of offers from the specified token offer directory.
const SF_ACCOUNT sfOwner
-
TER notTooManyOffers(ReadView const &view, uint256 const &nftokenID)
Returns tesSUCCESS if NFToken has few enough offers that it can be burned.
-
TER doApply() override
Definition: NFTokenBurn.cpp:86
+
TER notTooManyOffers(ReadView const &view, uint256 const &nftokenID)
Returns tesSUCCESS if NFToken has few enough offers that it can be burned.
+
TER doApply() override
Definition: NFTokenBurn.cpp:91
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:57
static TER preclaim(PreclaimContext const &ctx)
Definition: NFTokenBurn.cpp:49
-
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:176
+
AccountID getIssuer(uint256 const &id)
Definition: NFTokenUtils.h:180
@ temINVALID_FLAG
Definition: TER.h:106
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
const SF_ACCOUNT sfNFTokenMinter
@@ -210,6 +246,7 @@ $(function() {
std::uint32_t getFlags() const
Definition: STObject.cpp:481
Keylet nft_buys(uint256 const &id) noexcept
The directory of buy offers for the specified NFT.
Definition: Indexes.cpp:362
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
+
ApplyView & view()
Definition: ApplyContext.h:54
STTx const & tx
Definition: Transactor.h:58
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:52
constexpr const std::uint16_t flagBurnable
Definition: NFTokenUtils.h:51
@@ -217,17 +254,20 @@ $(function() {
const uint256 featureNonFungibleTokensV1
ApplyView & view()
Definition: Transactor.h:107
@ temDISABLED
Definition: TER.h:109
-
void removeAllTokenOffers(ApplyView &view, Keylet const &directory)
Deletes all offers from the specified token offer directory.
+
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:428
@ tecNO_PERMISSION
Definition: TER.h:269
ApplyContext & ctx_
Definition: Transactor.h:88
+
const SF_ACCOUNT sfAccount
@ tecNO_ENTRY
Definition: TER.h:270
+
const uint256 fixNonFungibleTokensV1_2
STTx const & tx
Definition: Transactor.h:35
State information when preflighting a tx.
Definition: Transactor.h:31
const SF_UINT32 sfBurnedNFTokens
const Rules rules
Definition: Transactor.h:36
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:58
+
@ tesSUCCESS
Definition: TER.h:219
STTx const & tx
Definition: ApplyContext.h:48
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525
diff --git a/NFTokenBurn_8h_source.html b/NFTokenBurn_8h_source.html index eaf73fc383..7144a51eaa 100644 --- a/NFTokenBurn_8h_source.html +++ b/NFTokenBurn_8h_source.html @@ -121,7 +121,7 @@ $(function() {
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenBurn.cpp:34
NFTokenBurn(ApplyContext &ctx)
Definition: NFTokenBurn.h:32
Definition: Transactor.h:85
-
TER doApply() override
Definition: NFTokenBurn.cpp:86
+
TER doApply() override
Definition: NFTokenBurn.cpp:91
static TER preclaim(PreclaimContext const &ctx)
Definition: NFTokenBurn.cpp:49
ConsequencesFactoryType
Definition: Transactor.h:101
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: NFTokenBurn.h:30
diff --git a/NFTokenBurn__test_8cpp_source.html b/NFTokenBurn__test_8cpp_source.html index 9871ac4091..af398411d9 100644 --- a/NFTokenBurn__test_8cpp_source.html +++ b/NFTokenBurn__test_8cpp_source.html @@ -120,571 +120,761 @@ $(function() {
49  return nfts[jss::result][jss::account_nfts].size();
50  };
51 
-
52  void
-
53  testBurnRandom(FeatureBitset features)
-
54  {
-
55  // Exercise a number of conditions with NFT burning.
-
56  testcase("Burn random");
-
57 
-
58  using namespace test::jtx;
-
59 
-
60  Env env{*this, features};
-
61 
-
62  // Keep information associated with each account together.
-
63  struct AcctStat
-
64  {
-
65  test::jtx::Account const acct;
-
66  std::vector<uint256> nfts;
-
67 
-
68  AcctStat(char const* name) : acct(name)
-
69  {
-
70  }
-
71 
-
72  operator test::jtx::Account() const
-
73  {
-
74  return acct;
-
75  }
-
76  };
-
77  AcctStat alice{"alice"};
-
78  AcctStat becky{"becky"};
-
79  AcctStat minter{"minter"};
-
80 
-
81  env.fund(XRP(10000), alice, becky, minter);
-
82  env.close();
-
83 
-
84  // Both alice and minter mint nfts in case that makes any difference.
-
85  env(token::setMinter(alice, minter));
-
86  env.close();
-
87 
-
88  // Create enough NFTs that alice, becky, and minter can all have
-
89  // at least three pages of NFTs. This will cause more activity in
-
90  // the page coalescing code. If we make 210 NFTs in total, we can
-
91  // have alice and minter each make 105. That will allow us to
-
92  // distribute 70 NFTs to our three participants.
-
93  //
-
94  // Give each NFT a pseudo-randomly chosen fee so the NFTs are
-
95  // distributed pseudo-randomly through the pages. This should
-
96  // prevent alice's and minter's NFTs from clustering together
-
97  // in becky's directory.
-
98  //
-
99  // Use a default initialized mercenne_twister because we want the
-
100  // effect of random numbers, but we want the test to run the same
-
101  // way each time.
-
102  std::mt19937 engine;
-
103  std::uniform_int_distribution<std::size_t> feeDist(
-
104  decltype(maxTransferFee){}, maxTransferFee);
-
105 
-
106  alice.nfts.reserve(105);
-
107  while (alice.nfts.size() < 105)
-
108  {
-
109  std::uint16_t const xferFee = feeDist(engine);
-
110  alice.nfts.push_back(token::getNextID(
-
111  env, alice, 0u, tfTransferable | tfBurnable, xferFee));
-
112  env(token::mint(alice),
-
113  txflags(tfTransferable | tfBurnable),
-
114  token::xferFee(xferFee));
-
115  env.close();
-
116  }
-
117 
-
118  minter.nfts.reserve(105);
-
119  while (minter.nfts.size() < 105)
-
120  {
-
121  std::uint16_t const xferFee = feeDist(engine);
-
122  minter.nfts.push_back(token::getNextID(
-
123  env, alice, 0u, tfTransferable | tfBurnable, xferFee));
-
124  env(token::mint(minter),
-
125  txflags(tfTransferable | tfBurnable),
-
126  token::xferFee(xferFee),
-
127  token::issuer(alice));
-
128  env.close();
-
129  }
-
130 
-
131  // All of the NFTs are now minted. Transfer 35 each over to becky so
-
132  // we end up with 70 NFTs in each account.
-
133  becky.nfts.reserve(70);
-
134  {
-
135  auto aliceIter = alice.nfts.begin();
-
136  auto minterIter = minter.nfts.begin();
-
137  while (becky.nfts.size() < 70)
-
138  {
-
139  // We do the same work on alice and minter, so make a lambda.
-
140  auto xferNFT = [&env, &becky](AcctStat& acct, auto& iter) {
-
141  uint256 offerIndex =
-
142  keylet::nftoffer(acct.acct, env.seq(acct.acct)).key;
-
143  env(token::createOffer(acct, *iter, XRP(0)),
-
144  txflags(tfSellNFToken));
-
145  env.close();
-
146  env(token::acceptSellOffer(becky, offerIndex));
-
147  env.close();
-
148  becky.nfts.push_back(*iter);
-
149  iter = acct.nfts.erase(iter);
-
150  iter += 2;
-
151  };
-
152  xferNFT(alice, aliceIter);
-
153  xferNFT(minter, minterIter);
-
154  }
-
155  BEAST_EXPECT(aliceIter == alice.nfts.end());
-
156  BEAST_EXPECT(minterIter == minter.nfts.end());
-
157  }
-
158 
-
159  // Now all three participants have 70 NFTs.
-
160  BEAST_EXPECT(nftCount(env, alice.acct) == 70);
-
161  BEAST_EXPECT(nftCount(env, becky.acct) == 70);
-
162  BEAST_EXPECT(nftCount(env, minter.acct) == 70);
-
163 
-
164  // Next we'll create offers for all of those NFTs. This calls for
-
165  // another lambda.
-
166  auto addOffers =
-
167  [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
-
168  for (uint256 nft : owner.nfts)
-
169  {
-
170  // Create sell offers for owner.
-
171  env(token::createOffer(owner, nft, drops(1)),
-
172  txflags(tfSellNFToken),
-
173  token::destination(other1));
-
174  env(token::createOffer(owner, nft, drops(1)),
-
175  txflags(tfSellNFToken),
-
176  token::destination(other2));
-
177  env.close();
-
178 
-
179  // Create buy offers for other1 and other2.
-
180  env(token::createOffer(other1, nft, drops(1)),
-
181  token::owner(owner));
-
182  env(token::createOffer(other2, nft, drops(1)),
-
183  token::owner(owner));
-
184  env.close();
-
185 
-
186  env(token::createOffer(other2, nft, drops(2)),
-
187  token::owner(owner));
-
188  env(token::createOffer(other1, nft, drops(2)),
-
189  token::owner(owner));
-
190  env.close();
-
191  }
-
192  };
-
193  addOffers(alice, becky, minter);
-
194  addOffers(becky, minter, alice);
-
195  addOffers(minter, alice, becky);
-
196  BEAST_EXPECT(ownerCount(env, alice) == 424);
-
197  BEAST_EXPECT(ownerCount(env, becky) == 424);
-
198  BEAST_EXPECT(ownerCount(env, minter) == 424);
-
199 
-
200  // Now each of the 270 NFTs has six offers associated with it.
-
201  // Randomly select an NFT out of the pile and burn it. Continue
-
202  // the process until all NFTs are burned.
-
203  AcctStat* const stats[3] = {&alice, &becky, &minter};
-
204  std::uniform_int_distribution<std::size_t> acctDist(0, 2);
-
205  std::uniform_int_distribution<std::size_t> mintDist(0, 1);
-
206 
-
207  while (stats[0]->nfts.size() > 0 || stats[1]->nfts.size() > 0 ||
-
208  stats[2]->nfts.size() > 0)
-
209  {
-
210  // Pick an account to burn an nft. If there are no nfts left
-
211  // pick again.
-
212  AcctStat& owner = *(stats[acctDist(engine)]);
-
213  if (owner.nfts.empty())
-
214  continue;
-
215 
-
216  // Pick one of the nfts.
-
217  std::uniform_int_distribution<std::size_t> nftDist(
-
218  0lu, owner.nfts.size() - 1);
-
219  auto nftIter = owner.nfts.begin() + nftDist(engine);
-
220  uint256 const nft = *nftIter;
-
221  owner.nfts.erase(nftIter);
-
222 
-
223  // Decide which of the accounts should burn the nft. If the
-
224  // owner is becky then any of the three accounts can burn.
-
225  // Otherwise either alice or minter can burn.
-
226  AcctStat& burner = owner.acct == becky.acct
-
227  ? *(stats[acctDist(engine)])
-
228  : mintDist(engine) ? alice : minter;
-
229 
-
230  if (owner.acct == burner.acct)
-
231  env(token::burn(burner, nft));
-
232  else
-
233  env(token::burn(burner, nft), token::owner(owner));
-
234  env.close();
-
235 
-
236  // Every time we burn an nft, the number of nfts they hold should
-
237  // match the number of nfts we think they hold.
-
238  BEAST_EXPECT(nftCount(env, alice.acct) == alice.nfts.size());
-
239  BEAST_EXPECT(nftCount(env, becky.acct) == becky.nfts.size());
-
240  BEAST_EXPECT(nftCount(env, minter.acct) == minter.nfts.size());
-
241  }
-
242  BEAST_EXPECT(nftCount(env, alice.acct) == 0);
-
243  BEAST_EXPECT(nftCount(env, becky.acct) == 0);
-
244  BEAST_EXPECT(nftCount(env, minter.acct) == 0);
-
245 
-
246  // When all nfts are burned none of the accounts should have
-
247  // an ownerCount.
-
248  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
249  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
250  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
251  }
-
252 
-
253  void
-
254  testBurnSequential(FeatureBitset features)
-
255  {
-
256  // The earlier burn test randomizes which nft is burned. There are
-
257  // a couple of directory merging scenarios that can only be tested by
-
258  // inserting and deleting in an ordered fashion. We do that testing
-
259  // now.
-
260  testcase("Burn sequential");
-
261 
-
262  using namespace test::jtx;
-
263 
-
264  Account const alice{"alice"};
-
265 
-
266  Env env{*this, features};
-
267  env.fund(XRP(1000), alice);
-
268 
-
269  // printNFTPages is a lambda that may be used for debugging.
-
270  //
-
271  // It uses the ledger RPC command to show the NFT pages in the ledger.
-
272  // This parameter controls how noisy the output is.
-
273  enum Volume : bool {
-
274  quiet = false,
-
275  noisy = true,
-
276  };
-
277 
-
278  [[maybe_unused]] auto printNFTPages = [&env](Volume vol) {
-
279  Json::Value jvParams;
-
280  jvParams[jss::ledger_index] = "current";
-
281  jvParams[jss::binary] = false;
-
282  {
-
283  Json::Value jrr = env.rpc(
-
284  "json",
-
285  "ledger_data",
-
286  boost::lexical_cast<std::string>(jvParams));
-
287 
-
288  // Iterate the state and print all NFTokenPages.
-
289  if (!jrr.isMember(jss::result) ||
-
290  !jrr[jss::result].isMember(jss::state))
-
291  {
-
292  std::cout << "No ledger state found!" << std::endl;
-
293  return;
-
294  }
-
295  Json::Value& state = jrr[jss::result][jss::state];
-
296  if (!state.isArray())
-
297  {
-
298  std::cout << "Ledger state is not array!" << std::endl;
-
299  return;
-
300  }
-
301  for (Json::UInt i = 0; i < state.size(); ++i)
-
302  {
-
303  if (state[i].isMember(sfNFTokens.jsonName) &&
-
304  state[i][sfNFTokens.jsonName].isArray())
-
305  {
-
306  std::uint32_t tokenCount =
-
307  state[i][sfNFTokens.jsonName].size();
-
308  std::cout << tokenCount << " NFTokens in page "
-
309  << state[i][jss::index].asString()
-
310  << std::endl;
-
311 
-
312  if (vol == noisy)
-
313  {
-
314  std::cout << state[i].toStyledString() << std::endl;
-
315  }
-
316  else
-
317  {
-
318  if (tokenCount > 0)
-
319  std::cout << "first: "
-
320  << state[i][sfNFTokens.jsonName][0u]
-
321  .toStyledString()
-
322  << std::endl;
-
323  if (tokenCount > 1)
-
324  std::cout << "last: "
-
325  << state[i][sfNFTokens.jsonName]
-
326  [tokenCount - 1]
-
327  .toStyledString()
-
328  << std::endl;
-
329  }
-
330  }
+
52  // Helper function that returns new nft id for an account and create
+
53  // specified number of sell offers
+
54  uint256
+
55  createNftAndOffers(
+
56  test::jtx::Env& env,
+
57  test::jtx::Account const& owner,
+
58  std::vector<uint256>& offerIndexes,
+
59  size_t const tokenCancelCount)
+
60  {
+
61  using namespace test::jtx;
+
62  uint256 const nftokenID =
+
63  token::getNextID(env, owner, 0, tfTransferable);
+
64  env(token::mint(owner, 0),
+
65  token::uri(std::string(maxTokenURILength, 'u')),
+
66  txflags(tfTransferable));
+
67  env.close();
+
68 
+
69  offerIndexes.reserve(tokenCancelCount);
+
70 
+
71  for (uint32_t i = 0; i < tokenCancelCount; ++i)
+
72  {
+
73  // Create sell offer
+
74  offerIndexes.push_back(keylet::nftoffer(owner, env.seq(owner)).key);
+
75  env(token::createOffer(owner, nftokenID, drops(1)),
+
76  txflags(tfSellNFToken));
+
77  env.close();
+
78  }
+
79 
+
80  return nftokenID;
+
81  };
+
82 
+
83  void
+
84  testBurnRandom(FeatureBitset features)
+
85  {
+
86  // Exercise a number of conditions with NFT burning.
+
87  testcase("Burn random");
+
88 
+
89  using namespace test::jtx;
+
90 
+
91  Env env{*this, features};
+
92 
+
93  // Keep information associated with each account together.
+
94  struct AcctStat
+
95  {
+
96  test::jtx::Account const acct;
+
97  std::vector<uint256> nfts;
+
98 
+
99  AcctStat(char const* name) : acct(name)
+
100  {
+
101  }
+
102 
+
103  operator test::jtx::Account() const
+
104  {
+
105  return acct;
+
106  }
+
107  };
+
108  AcctStat alice{"alice"};
+
109  AcctStat becky{"becky"};
+
110  AcctStat minter{"minter"};
+
111 
+
112  env.fund(XRP(10000), alice, becky, minter);
+
113  env.close();
+
114 
+
115  // Both alice and minter mint nfts in case that makes any difference.
+
116  env(token::setMinter(alice, minter));
+
117  env.close();
+
118 
+
119  // Create enough NFTs that alice, becky, and minter can all have
+
120  // at least three pages of NFTs. This will cause more activity in
+
121  // the page coalescing code. If we make 210 NFTs in total, we can
+
122  // have alice and minter each make 105. That will allow us to
+
123  // distribute 70 NFTs to our three participants.
+
124  //
+
125  // Give each NFT a pseudo-randomly chosen fee so the NFTs are
+
126  // distributed pseudo-randomly through the pages. This should
+
127  // prevent alice's and minter's NFTs from clustering together
+
128  // in becky's directory.
+
129  //
+
130  // Use a default initialized mercenne_twister because we want the
+
131  // effect of random numbers, but we want the test to run the same
+
132  // way each time.
+
133  std::mt19937 engine;
+
134  std::uniform_int_distribution<std::size_t> feeDist(
+
135  decltype(maxTransferFee){}, maxTransferFee);
+
136 
+
137  alice.nfts.reserve(105);
+
138  while (alice.nfts.size() < 105)
+
139  {
+
140  std::uint16_t const xferFee = feeDist(engine);
+
141  alice.nfts.push_back(token::getNextID(
+
142  env, alice, 0u, tfTransferable | tfBurnable, xferFee));
+
143  env(token::mint(alice),
+
144  txflags(tfTransferable | tfBurnable),
+
145  token::xferFee(xferFee));
+
146  env.close();
+
147  }
+
148 
+
149  minter.nfts.reserve(105);
+
150  while (minter.nfts.size() < 105)
+
151  {
+
152  std::uint16_t const xferFee = feeDist(engine);
+
153  minter.nfts.push_back(token::getNextID(
+
154  env, alice, 0u, tfTransferable | tfBurnable, xferFee));
+
155  env(token::mint(minter),
+
156  txflags(tfTransferable | tfBurnable),
+
157  token::xferFee(xferFee),
+
158  token::issuer(alice));
+
159  env.close();
+
160  }
+
161 
+
162  // All of the NFTs are now minted. Transfer 35 each over to becky so
+
163  // we end up with 70 NFTs in each account.
+
164  becky.nfts.reserve(70);
+
165  {
+
166  auto aliceIter = alice.nfts.begin();
+
167  auto minterIter = minter.nfts.begin();
+
168  while (becky.nfts.size() < 70)
+
169  {
+
170  // We do the same work on alice and minter, so make a lambda.
+
171  auto xferNFT = [&env, &becky](AcctStat& acct, auto& iter) {
+
172  uint256 offerIndex =
+
173  keylet::nftoffer(acct.acct, env.seq(acct.acct)).key;
+
174  env(token::createOffer(acct, *iter, XRP(0)),
+
175  txflags(tfSellNFToken));
+
176  env.close();
+
177  env(token::acceptSellOffer(becky, offerIndex));
+
178  env.close();
+
179  becky.nfts.push_back(*iter);
+
180  iter = acct.nfts.erase(iter);
+
181  iter += 2;
+
182  };
+
183  xferNFT(alice, aliceIter);
+
184  xferNFT(minter, minterIter);
+
185  }
+
186  BEAST_EXPECT(aliceIter == alice.nfts.end());
+
187  BEAST_EXPECT(minterIter == minter.nfts.end());
+
188  }
+
189 
+
190  // Now all three participants have 70 NFTs.
+
191  BEAST_EXPECT(nftCount(env, alice.acct) == 70);
+
192  BEAST_EXPECT(nftCount(env, becky.acct) == 70);
+
193  BEAST_EXPECT(nftCount(env, minter.acct) == 70);
+
194 
+
195  // Next we'll create offers for all of those NFTs. This calls for
+
196  // another lambda.
+
197  auto addOffers =
+
198  [&env](AcctStat& owner, AcctStat& other1, AcctStat& other2) {
+
199  for (uint256 nft : owner.nfts)
+
200  {
+
201  // Create sell offers for owner.
+
202  env(token::createOffer(owner, nft, drops(1)),
+
203  txflags(tfSellNFToken),
+
204  token::destination(other1));
+
205  env(token::createOffer(owner, nft, drops(1)),
+
206  txflags(tfSellNFToken),
+
207  token::destination(other2));
+
208  env.close();
+
209 
+
210  // Create buy offers for other1 and other2.
+
211  env(token::createOffer(other1, nft, drops(1)),
+
212  token::owner(owner));
+
213  env(token::createOffer(other2, nft, drops(1)),
+
214  token::owner(owner));
+
215  env.close();
+
216 
+
217  env(token::createOffer(other2, nft, drops(2)),
+
218  token::owner(owner));
+
219  env(token::createOffer(other1, nft, drops(2)),
+
220  token::owner(owner));
+
221  env.close();
+
222  }
+
223  };
+
224  addOffers(alice, becky, minter);
+
225  addOffers(becky, minter, alice);
+
226  addOffers(minter, alice, becky);
+
227  BEAST_EXPECT(ownerCount(env, alice) == 424);
+
228  BEAST_EXPECT(ownerCount(env, becky) == 424);
+
229  BEAST_EXPECT(ownerCount(env, minter) == 424);
+
230 
+
231  // Now each of the 270 NFTs has six offers associated with it.
+
232  // Randomly select an NFT out of the pile and burn it. Continue
+
233  // the process until all NFTs are burned.
+
234  AcctStat* const stats[3] = {&alice, &becky, &minter};
+
235  std::uniform_int_distribution<std::size_t> acctDist(0, 2);
+
236  std::uniform_int_distribution<std::size_t> mintDist(0, 1);
+
237 
+
238  while (stats[0]->nfts.size() > 0 || stats[1]->nfts.size() > 0 ||
+
239  stats[2]->nfts.size() > 0)
+
240  {
+
241  // Pick an account to burn an nft. If there are no nfts left
+
242  // pick again.
+
243  AcctStat& owner = *(stats[acctDist(engine)]);
+
244  if (owner.nfts.empty())
+
245  continue;
+
246 
+
247  // Pick one of the nfts.
+
248  std::uniform_int_distribution<std::size_t> nftDist(
+
249  0lu, owner.nfts.size() - 1);
+
250  auto nftIter = owner.nfts.begin() + nftDist(engine);
+
251  uint256 const nft = *nftIter;
+
252  owner.nfts.erase(nftIter);
+
253 
+
254  // Decide which of the accounts should burn the nft. If the
+
255  // owner is becky then any of the three accounts can burn.
+
256  // Otherwise either alice or minter can burn.
+
257  AcctStat& burner = owner.acct == becky.acct
+
258  ? *(stats[acctDist(engine)])
+
259  : mintDist(engine) ? alice : minter;
+
260 
+
261  if (owner.acct == burner.acct)
+
262  env(token::burn(burner, nft));
+
263  else
+
264  env(token::burn(burner, nft), token::owner(owner));
+
265  env.close();
+
266 
+
267  // Every time we burn an nft, the number of nfts they hold should
+
268  // match the number of nfts we think they hold.
+
269  BEAST_EXPECT(nftCount(env, alice.acct) == alice.nfts.size());
+
270  BEAST_EXPECT(nftCount(env, becky.acct) == becky.nfts.size());
+
271  BEAST_EXPECT(nftCount(env, minter.acct) == minter.nfts.size());
+
272  }
+
273  BEAST_EXPECT(nftCount(env, alice.acct) == 0);
+
274  BEAST_EXPECT(nftCount(env, becky.acct) == 0);
+
275  BEAST_EXPECT(nftCount(env, minter.acct) == 0);
+
276 
+
277  // When all nfts are burned none of the accounts should have
+
278  // an ownerCount.
+
279  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
280  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
281  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
282  }
+
283 
+
284  void
+
285  testBurnSequential(FeatureBitset features)
+
286  {
+
287  // The earlier burn test randomizes which nft is burned. There are
+
288  // a couple of directory merging scenarios that can only be tested by
+
289  // inserting and deleting in an ordered fashion. We do that testing
+
290  // now.
+
291  testcase("Burn sequential");
+
292 
+
293  using namespace test::jtx;
+
294 
+
295  Account const alice{"alice"};
+
296 
+
297  Env env{*this, features};
+
298  env.fund(XRP(1000), alice);
+
299 
+
300  // printNFTPages is a lambda that may be used for debugging.
+
301  //
+
302  // It uses the ledger RPC command to show the NFT pages in the ledger.
+
303  // This parameter controls how noisy the output is.
+
304  enum Volume : bool {
+
305  quiet = false,
+
306  noisy = true,
+
307  };
+
308 
+
309  [[maybe_unused]] auto printNFTPages = [&env](Volume vol) {
+
310  Json::Value jvParams;
+
311  jvParams[jss::ledger_index] = "current";
+
312  jvParams[jss::binary] = false;
+
313  {
+
314  Json::Value jrr = env.rpc(
+
315  "json",
+
316  "ledger_data",
+
317  boost::lexical_cast<std::string>(jvParams));
+
318 
+
319  // Iterate the state and print all NFTokenPages.
+
320  if (!jrr.isMember(jss::result) ||
+
321  !jrr[jss::result].isMember(jss::state))
+
322  {
+
323  std::cout << "No ledger state found!" << std::endl;
+
324  return;
+
325  }
+
326  Json::Value& state = jrr[jss::result][jss::state];
+
327  if (!state.isArray())
+
328  {
+
329  std::cout << "Ledger state is not array!" << std::endl;
+
330  return;
331  }
-
332  }
-
333  };
-
334 
-
335  // A lambda that generates 96 nfts packed into three pages of 32 each.
-
336  auto genPackedTokens = [this, &env, &alice](
-
337  std::vector<uint256>& nfts) {
-
338  nfts.clear();
-
339  nfts.reserve(96);
-
340 
-
341  // We want to create fully packed NFT pages. This is a little
-
342  // tricky since the system currently in place is inclined to
-
343  // assign consecutive tokens to only 16 entries per page.
-
344  //
-
345  // By manipulating the internal form of the taxon we can force
-
346  // creation of NFT pages that are completely full. This lambda
-
347  // tells us the taxon value we should pass in in order for the
-
348  // internal representation to match the passed in value.
-
349  auto internalTaxon = [&env](
-
350  Account const& acct,
-
351  std::uint32_t taxon) -> std::uint32_t {
-
352  std::uint32_t const tokenSeq = {
-
353  env.le(acct)->at(~sfMintedNFTokens).value_or(0)};
-
354  return toUInt32(
-
355  nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon)));
-
356  };
-
357 
-
358  for (std::uint32_t i = 0; i < 96; ++i)
-
359  {
-
360  // In order to fill the pages we use the taxon to break them
-
361  // into groups of 16 entries. By having the internal
-
362  // representation of the taxon go...
-
363  // 0, 3, 2, 5, 4, 7...
-
364  // in sets of 16 NFTs we can get each page to be fully
-
365  // populated.
-
366  std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
-
367  uint32_t const extTaxon = internalTaxon(alice, intTaxon);
-
368  nfts.push_back(token::getNextID(env, alice, extTaxon));
-
369  env(token::mint(alice, extTaxon));
-
370  env.close();
-
371  }
-
372 
-
373  // Sort the NFTs so they are listed in storage order, not
-
374  // creation order.
-
375  std::sort(nfts.begin(), nfts.end());
-
376 
-
377  // Verify that the ledger does indeed contain exactly three pages
-
378  // of NFTs with 32 entries in each page.
-
379  Json::Value jvParams;
-
380  jvParams[jss::ledger_index] = "current";
-
381  jvParams[jss::binary] = false;
-
382  {
-
383  Json::Value jrr = env.rpc(
-
384  "json",
-
385  "ledger_data",
-
386  boost::lexical_cast<std::string>(jvParams));
-
387 
-
388  Json::Value& state = jrr[jss::result][jss::state];
-
389 
-
390  int pageCount = 0;
-
391  for (Json::UInt i = 0; i < state.size(); ++i)
-
392  {
-
393  if (state[i].isMember(sfNFTokens.jsonName) &&
-
394  state[i][sfNFTokens.jsonName].isArray())
-
395  {
-
396  BEAST_EXPECT(
-
397  state[i][sfNFTokens.jsonName].size() == 32);
-
398  ++pageCount;
-
399  }
-
400  }
-
401  // If this check fails then the internal NFT directory logic
-
402  // has changed.
-
403  BEAST_EXPECT(pageCount == 3);
-
404  }
-
405  };
-
406 
-
407  // Generate three packed pages. Then burn the tokens in order from
-
408  // first to last. This exercises specific cases where coalescing
-
409  // pages is not possible.
-
410  std::vector<uint256> nfts;
-
411  genPackedTokens(nfts);
-
412  BEAST_EXPECT(nftCount(env, alice) == 96);
-
413  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
414 
-
415  for (uint256 const& nft : nfts)
-
416  {
-
417  env(token::burn(alice, {nft}));
-
418  env.close();
-
419  }
-
420  BEAST_EXPECT(nftCount(env, alice) == 0);
-
421  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
422 
-
423  // A lambda verifies that the ledger no longer contains any NFT pages.
-
424  auto checkNoTokenPages = [this, &env]() {
-
425  Json::Value jvParams;
-
426  jvParams[jss::ledger_index] = "current";
-
427  jvParams[jss::binary] = false;
-
428  {
-
429  Json::Value jrr = env.rpc(
-
430  "json",
-
431  "ledger_data",
-
432  boost::lexical_cast<std::string>(jvParams));
-
433 
-
434  Json::Value& state = jrr[jss::result][jss::state];
-
435 
-
436  for (Json::UInt i = 0; i < state.size(); ++i)
-
437  {
-
438  BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
-
439  }
-
440  }
-
441  };
-
442  checkNoTokenPages();
-
443 
-
444  // Generate three packed pages. Then burn the tokens in order from
-
445  // last to first. This exercises different specific cases where
-
446  // coalescing pages is not possible.
-
447  genPackedTokens(nfts);
-
448  BEAST_EXPECT(nftCount(env, alice) == 96);
-
449  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
450 
-
451  std::reverse(nfts.begin(), nfts.end());
-
452  for (uint256 const& nft : nfts)
-
453  {
-
454  env(token::burn(alice, {nft}));
-
455  env.close();
-
456  }
-
457  BEAST_EXPECT(nftCount(env, alice) == 0);
-
458  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
459  checkNoTokenPages();
-
460 
-
461  // Generate three packed pages. Then burn all tokens in the middle
-
462  // page. This exercises the case where a page is removed between
-
463  // two fully populated pages.
-
464  genPackedTokens(nfts);
-
465  BEAST_EXPECT(nftCount(env, alice) == 96);
-
466  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
467 
-
468  for (std::size_t i = 32; i < 64; ++i)
-
469  {
-
470  env(token::burn(alice, nfts[i]));
-
471  env.close();
-
472  }
-
473  nfts.erase(nfts.begin() + 32, nfts.begin() + 64);
-
474  BEAST_EXPECT(nftCount(env, alice) == 64);
-
475  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
476 
-
477  // Burn the remaining nfts.
-
478  for (uint256 const& nft : nfts)
-
479  {
-
480  env(token::burn(alice, {nft}));
-
481  env.close();
-
482  }
-
483  BEAST_EXPECT(nftCount(env, alice) == 0);
-
484  checkNoTokenPages();
-
485  }
-
486 
-
487  void
-
488  testBurnTooManyOffers(FeatureBitset features)
-
489  {
-
490  // Look at the case where too many offers prevents burning a token.
-
491  testcase("Burn too many offers");
-
492 
-
493  using namespace test::jtx;
-
494 
-
495  Env env{*this, features};
-
496 
-
497  Account const alice("alice");
-
498  Account const becky("becky");
-
499  env.fund(XRP(1000), alice, becky);
-
500  env.close();
-
501 
-
502  // We structure the test to try and maximize the metadata produced.
-
503  // This verifies that we don't create too much metadata during a
-
504  // maximal burn operation.
-
505  //
-
506  // 1. alice mints an nft with a full-sized URI.
-
507  // 2. We create 1000 new accounts, each of which creates an offer for
-
508  // alice's nft.
-
509  // 3. becky creates one more offer for alice's NFT
-
510  // 4. Attempt to burn the nft which fails because there are too
-
511  // many offers.
-
512  // 5. Cancel becky's offer and the nft should become burnable.
-
513  uint256 const nftokenID =
-
514  token::getNextID(env, alice, 0, tfTransferable);
-
515  env(token::mint(alice, 0),
-
516  token::uri(std::string(maxTokenURILength, 'u')),
-
517  txflags(tfTransferable));
-
518  env.close();
-
519 
-
520  std::vector<uint256> offerIndexes;
-
521  offerIndexes.reserve(maxTokenOfferCancelCount);
-
522  for (uint32_t i = 0; i < maxTokenOfferCancelCount; ++i)
-
523  {
-
524  Account const acct(std::string("acct") + std::to_string(i));
-
525  env.fund(XRP(1000), acct);
-
526  env.close();
-
527 
-
528  offerIndexes.push_back(keylet::nftoffer(acct, env.seq(acct)).key);
-
529  env(token::createOffer(acct, nftokenID, drops(1)),
-
530  token::owner(alice));
-
531  env.close();
-
532  }
-
533 
-
534  // Verify all offers are present in the ledger.
-
535  for (uint256 const& offerIndex : offerIndexes)
-
536  {
-
537  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
538  }
-
539 
-
540  // Create one too many offers.
-
541  uint256 const beckyOfferIndex =
-
542  keylet::nftoffer(becky, env.seq(becky)).key;
-
543  env(token::createOffer(becky, nftokenID, drops(1)),
-
544  token::owner(alice));
-
545 
-
546  // Attempt to burn the nft which should fail.
-
547  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
-
548 
-
549  // Close enough ledgers that the burn transaction is no longer retried.
-
550  for (int i = 0; i < 10; ++i)
-
551  env.close();
-
552 
-
553  // Cancel becky's offer, but alice adds a sell offer. The token
-
554  // should still not be burnable.
-
555  env(token::cancelOffer(becky, {beckyOfferIndex}));
-
556  env.close();
-
557 
-
558  uint256 const aliceOfferIndex =
-
559  keylet::nftoffer(alice, env.seq(alice)).key;
-
560  env(token::createOffer(alice, nftokenID, drops(1)),
-
561  txflags(tfSellNFToken));
-
562  env.close();
-
563 
-
564  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
-
565  env.close();
-
566 
-
567  // Cancel alice's sell offer. Now the token should be burnable.
-
568  env(token::cancelOffer(alice, {aliceOfferIndex}));
-
569  env.close();
-
570 
-
571  env(token::burn(alice, nftokenID));
-
572  env.close();
-
573 
-
574  // Burning the token should remove all the offers from the ledger.
-
575  for (uint256 const& offerIndex : offerIndexes)
-
576  {
-
577  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
-
578  }
-
579 
-
580  // Both alice and becky should have ownerCounts of zero.
-
581  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
582  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
583  }
+
332  for (Json::UInt i = 0; i < state.size(); ++i)
+
333  {
+
334  if (state[i].isMember(sfNFTokens.jsonName) &&
+
335  state[i][sfNFTokens.jsonName].isArray())
+
336  {
+
337  std::uint32_t tokenCount =
+
338  state[i][sfNFTokens.jsonName].size();
+
339  std::cout << tokenCount << " NFTokens in page "
+
340  << state[i][jss::index].asString()
+
341  << std::endl;
+
342 
+
343  if (vol == noisy)
+
344  {
+
345  std::cout << state[i].toStyledString() << std::endl;
+
346  }
+
347  else
+
348  {
+
349  if (tokenCount > 0)
+
350  std::cout << "first: "
+
351  << state[i][sfNFTokens.jsonName][0u]
+
352  .toStyledString()
+
353  << std::endl;
+
354  if (tokenCount > 1)
+
355  std::cout << "last: "
+
356  << state[i][sfNFTokens.jsonName]
+
357  [tokenCount - 1]
+
358  .toStyledString()
+
359  << std::endl;
+
360  }
+
361  }
+
362  }
+
363  }
+
364  };
+
365 
+
366  // A lambda that generates 96 nfts packed into three pages of 32 each.
+
367  auto genPackedTokens = [this, &env, &alice](
+
368  std::vector<uint256>& nfts) {
+
369  nfts.clear();
+
370  nfts.reserve(96);
+
371 
+
372  // We want to create fully packed NFT pages. This is a little
+
373  // tricky since the system currently in place is inclined to
+
374  // assign consecutive tokens to only 16 entries per page.
+
375  //
+
376  // By manipulating the internal form of the taxon we can force
+
377  // creation of NFT pages that are completely full. This lambda
+
378  // tells us the taxon value we should pass in in order for the
+
379  // internal representation to match the passed in value.
+
380  auto internalTaxon = [&env](
+
381  Account const& acct,
+
382  std::uint32_t taxon) -> std::uint32_t {
+
383  std::uint32_t const tokenSeq = {
+
384  env.le(acct)->at(~sfMintedNFTokens).value_or(0)};
+
385  return toUInt32(
+
386  nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon)));
+
387  };
+
388 
+
389  for (std::uint32_t i = 0; i < 96; ++i)
+
390  {
+
391  // In order to fill the pages we use the taxon to break them
+
392  // into groups of 16 entries. By having the internal
+
393  // representation of the taxon go...
+
394  // 0, 3, 2, 5, 4, 7...
+
395  // in sets of 16 NFTs we can get each page to be fully
+
396  // populated.
+
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));
+
401  env.close();
+
402  }
+
403 
+
404  // Sort the NFTs so they are listed in storage order, not
+
405  // creation order.
+
406  std::sort(nfts.begin(), nfts.end());
+
407 
+
408  // Verify that the ledger does indeed contain exactly three pages
+
409  // of NFTs with 32 entries in each page.
+
410  Json::Value jvParams;
+
411  jvParams[jss::ledger_index] = "current";
+
412  jvParams[jss::binary] = false;
+
413  {
+
414  Json::Value jrr = env.rpc(
+
415  "json",
+
416  "ledger_data",
+
417  boost::lexical_cast<std::string>(jvParams));
+
418 
+
419  Json::Value& state = jrr[jss::result][jss::state];
+
420 
+
421  int pageCount = 0;
+
422  for (Json::UInt i = 0; i < state.size(); ++i)
+
423  {
+
424  if (state[i].isMember(sfNFTokens.jsonName) &&
+
425  state[i][sfNFTokens.jsonName].isArray())
+
426  {
+
427  BEAST_EXPECT(
+
428  state[i][sfNFTokens.jsonName].size() == 32);
+
429  ++pageCount;
+
430  }
+
431  }
+
432  // If this check fails then the internal NFT directory logic
+
433  // has changed.
+
434  BEAST_EXPECT(pageCount == 3);
+
435  }
+
436  };
+
437 
+
438  // Generate three packed pages. Then burn the tokens in order from
+
439  // first to last. This exercises specific cases where coalescing
+
440  // pages is not possible.
+
441  std::vector<uint256> nfts;
+
442  genPackedTokens(nfts);
+
443  BEAST_EXPECT(nftCount(env, alice) == 96);
+
444  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
445 
+
446  for (uint256 const& nft : nfts)
+
447  {
+
448  env(token::burn(alice, {nft}));
+
449  env.close();
+
450  }
+
451  BEAST_EXPECT(nftCount(env, alice) == 0);
+
452  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
453 
+
454  // A lambda verifies that the ledger no longer contains any NFT pages.
+
455  auto checkNoTokenPages = [this, &env]() {
+
456  Json::Value jvParams;
+
457  jvParams[jss::ledger_index] = "current";
+
458  jvParams[jss::binary] = false;
+
459  {
+
460  Json::Value jrr = env.rpc(
+
461  "json",
+
462  "ledger_data",
+
463  boost::lexical_cast<std::string>(jvParams));
+
464 
+
465  Json::Value& state = jrr[jss::result][jss::state];
+
466 
+
467  for (Json::UInt i = 0; i < state.size(); ++i)
+
468  {
+
469  BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
+
470  }
+
471  }
+
472  };
+
473  checkNoTokenPages();
+
474 
+
475  // Generate three packed pages. Then burn the tokens in order from
+
476  // last to first. This exercises different specific cases where
+
477  // coalescing pages is not possible.
+
478  genPackedTokens(nfts);
+
479  BEAST_EXPECT(nftCount(env, alice) == 96);
+
480  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
481 
+
482  std::reverse(nfts.begin(), nfts.end());
+
483  for (uint256 const& nft : nfts)
+
484  {
+
485  env(token::burn(alice, {nft}));
+
486  env.close();
+
487  }
+
488  BEAST_EXPECT(nftCount(env, alice) == 0);
+
489  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
490  checkNoTokenPages();
+
491 
+
492  // Generate three packed pages. Then burn all tokens in the middle
+
493  // page. This exercises the case where a page is removed between
+
494  // two fully populated pages.
+
495  genPackedTokens(nfts);
+
496  BEAST_EXPECT(nftCount(env, alice) == 96);
+
497  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
498 
+
499  for (std::size_t i = 32; i < 64; ++i)
+
500  {
+
501  env(token::burn(alice, nfts[i]));
+
502  env.close();
+
503  }
+
504  nfts.erase(nfts.begin() + 32, nfts.begin() + 64);
+
505  BEAST_EXPECT(nftCount(env, alice) == 64);
+
506  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
507 
+
508  // Burn the remaining nfts.
+
509  for (uint256 const& nft : nfts)
+
510  {
+
511  env(token::burn(alice, {nft}));
+
512  env.close();
+
513  }
+
514  BEAST_EXPECT(nftCount(env, alice) == 0);
+
515  checkNoTokenPages();
+
516  }
+
517 
+
518  void
+
519  testBurnTooManyOffers(FeatureBitset features)
+
520  {
+
521  // Look at the case where too many offers prevents burning a token.
+
522  testcase("Burn too many offers");
+
523 
+
524  using namespace test::jtx;
+
525 
+
526  // Test what happens if a NFT is unburnable when there are
+
527  // more than 500 offers, before fixNonFungibleTokensV1_2 goes live
+
528  if (!features[fixNonFungibleTokensV1_2])
+
529  {
+
530  Env env{*this, features};
+
531 
+
532  Account const alice("alice");
+
533  Account const becky("becky");
+
534  env.fund(XRP(1000), alice, becky);
+
535  env.close();
+
536 
+
537  // We structure the test to try and maximize the metadata produced.
+
538  // This verifies that we don't create too much metadata during a
+
539  // maximal burn operation.
+
540  //
+
541  // 1. alice mints an nft with a full-sized URI.
+
542  // 2. We create 500 new accounts, each of which creates an offer
+
543  // for alice's nft.
+
544  // 3. becky creates one more offer for alice's NFT
+
545  // 4. Attempt to burn the nft which fails because there are too
+
546  // many offers.
+
547  // 5. Cancel becky's offer and the nft should become burnable.
+
548  uint256 const nftokenID =
+
549  token::getNextID(env, alice, 0, tfTransferable);
+
550  env(token::mint(alice, 0),
+
551  token::uri(std::string(maxTokenURILength, 'u')),
+
552  txflags(tfTransferable));
+
553  env.close();
+
554 
+
555  std::vector<uint256> offerIndexes;
+
556  offerIndexes.reserve(maxTokenOfferCancelCount);
+
557  for (std::uint32_t i = 0; i < maxTokenOfferCancelCount; ++i)
+
558  {
+
559  Account const acct(std::string("acct") + std::to_string(i));
+
560  env.fund(XRP(1000), acct);
+
561  env.close();
+
562 
+
563  offerIndexes.push_back(
+
564  keylet::nftoffer(acct, env.seq(acct)).key);
+
565  env(token::createOffer(acct, nftokenID, drops(1)),
+
566  token::owner(alice));
+
567  env.close();
+
568  }
+
569 
+
570  // Verify all offers are present in the ledger.
+
571  for (uint256 const& offerIndex : offerIndexes)
+
572  {
+
573  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
574  }
+
575 
+
576  // Create one too many offers.
+
577  uint256 const beckyOfferIndex =
+
578  keylet::nftoffer(becky, env.seq(becky)).key;
+
579  env(token::createOffer(becky, nftokenID, drops(1)),
+
580  token::owner(alice));
+
581 
+
582  // Attempt to burn the nft which should fail.
+
583  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
584 
-
585  void
-
586  testWithFeats(FeatureBitset features)
-
587  {
-
588  testBurnRandom(features);
-
589  testBurnSequential(features);
-
590  testBurnTooManyOffers(features);
-
591  }
-
592 
-
593 public:
-
594  void
-
595  run() override
-
596  {
-
597  using namespace test::jtx;
-
598  FeatureBitset const all{supported_amendments()};
-
599  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
+
585  // Close enough ledgers that the burn transaction is no longer
+
586  // retried.
+
587  for (int i = 0; i < 10; ++i)
+
588  env.close();
+
589 
+
590  // Cancel becky's offer, but alice adds a sell offer. The token
+
591  // should still not be burnable.
+
592  env(token::cancelOffer(becky, {beckyOfferIndex}));
+
593  env.close();
+
594 
+
595  uint256 const aliceOfferIndex =
+
596  keylet::nftoffer(alice, env.seq(alice)).key;
+
597  env(token::createOffer(alice, nftokenID, drops(1)),
+
598  txflags(tfSellNFToken));
+
599  env.close();
600 
-
601  testWithFeats(all - fixNFTDir);
-
602  testWithFeats(all);
-
603  }
-
604 };
-
605 
-
606 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn, tx, ripple, 3);
+
601  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
+
602  env.close();
+
603 
+
604  // Cancel alice's sell offer. Now the token should be burnable.
+
605  env(token::cancelOffer(alice, {aliceOfferIndex}));
+
606  env.close();
607 
-
608 } // namespace ripple
+
608  env(token::burn(alice, nftokenID));
+
609  env.close();
+
610 
+
611  // Burning the token should remove all the offers from the ledger.
+
612  for (uint256 const& offerIndex : offerIndexes)
+
613  {
+
614  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
615  }
+
616 
+
617  // Both alice and becky should have ownerCounts of zero.
+
618  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
619  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
620  }
+
621 
+
622  // Test that up to 499 buy/sell offers will be removed when NFT is
+
623  // burned after fixNonFungibleTokensV1_2 is enabled. This is to test
+
624  // that we can successfully remove all offers if the number of offers is
+
625  // less than 500.
+
626  if (features[fixNonFungibleTokensV1_2])
+
627  {
+
628  Env env{*this, features};
+
629 
+
630  Account const alice("alice");
+
631  Account const becky("becky");
+
632  env.fund(XRP(100000), alice, becky);
+
633  env.close();
+
634 
+
635  // alice creates 498 sell offers and becky creates 1 buy offers.
+
636  // When the token is burned, 498 sell offers and 1 buy offer are
+
637  // removed. In total, 499 offers are removed
+
638  std::vector<uint256> offerIndexes;
+
639  auto const nftokenID = createNftAndOffers(
+
640  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 2);
+
641 
+
642  // Verify all sell offers are present in the ledger.
+
643  for (uint256 const& offerIndex : offerIndexes)
+
644  {
+
645  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
646  }
+
647 
+
648  // Becky creates a buy offer
+
649  uint256 const beckyOfferIndex =
+
650  keylet::nftoffer(becky, env.seq(becky)).key;
+
651  env(token::createOffer(becky, nftokenID, drops(1)),
+
652  token::owner(alice));
+
653  env.close();
+
654 
+
655  // Burn the token
+
656  env(token::burn(alice, nftokenID));
+
657  env.close();
+
658 
+
659  // Burning the token should remove all 498 sell offers
+
660  // that alice created
+
661  for (uint256 const& offerIndex : offerIndexes)
+
662  {
+
663  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
664  }
+
665 
+
666  // Burning the token should also remove the one buy offer
+
667  // that becky created
+
668  BEAST_EXPECT(!env.le(keylet::nftoffer(beckyOfferIndex)));
+
669 
+
670  // alice and becky should have ownerCounts of zero
+
671  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
672  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
673  }
+
674 
+
675  // Test that up to 500 buy offers are removed when NFT is burned
+
676  // after fixNonFungibleTokensV1_2 is enabled
+
677  if (features[fixNonFungibleTokensV1_2])
+
678  {
+
679  Env env{*this, features};
+
680 
+
681  Account const alice("alice");
+
682  Account const becky("becky");
+
683  env.fund(XRP(100000), alice, becky);
+
684  env.close();
+
685 
+
686  // alice creates 501 sell offers for the token
+
687  // After we burn the token, 500 of the sell offers should be
+
688  // removed, and one is left over
+
689  std::vector<uint256> offerIndexes;
+
690  auto const nftokenID = createNftAndOffers(
+
691  env, alice, offerIndexes, maxDeletableTokenOfferEntries + 1);
+
692 
+
693  // Verify all sell offers are present in the ledger.
+
694  for (uint256 const& offerIndex : offerIndexes)
+
695  {
+
696  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
697  }
+
698 
+
699  // Burn the token
+
700  env(token::burn(alice, nftokenID));
+
701  env.close();
+
702 
+
703  uint32_t offerDeletedCount = 0;
+
704  // Count the number of sell offers that have been deleted
+
705  for (uint256 const& offerIndex : offerIndexes)
+
706  {
+
707  if (!env.le(keylet::nftoffer(offerIndex)))
+
708  offerDeletedCount++;
+
709  }
+
710 
+
711  BEAST_EXPECT(offerIndexes.size() == maxTokenOfferCancelCount + 1);
+
712 
+
713  // 500 sell offers should be removed
+
714  BEAST_EXPECT(offerDeletedCount == maxTokenOfferCancelCount);
+
715 
+
716  // alice should have ownerCounts of one for the orphaned sell offer
+
717  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
718  }
+
719 
+
720  // Test that up to 500 buy/sell offers are removed when NFT is burned
+
721  // after fixNonFungibleTokensV1_2 is enabled
+
722  if (features[fixNonFungibleTokensV1_2])
+
723  {
+
724  Env env{*this, features};
+
725 
+
726  Account const alice("alice");
+
727  Account const becky("becky");
+
728  env.fund(XRP(100000), alice, becky);
+
729  env.close();
+
730 
+
731  // alice creates 499 sell offers and becky creates 2 buy offers.
+
732  // When the token is burned, 499 sell offers and 1 buy offer
+
733  // are removed.
+
734  // In total, 500 offers are removed
+
735  std::vector<uint256> offerIndexes;
+
736  auto const nftokenID = createNftAndOffers(
+
737  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 1);
+
738 
+
739  // Verify all sell offers are present in the ledger.
+
740  for (uint256 const& offerIndex : offerIndexes)
+
741  {
+
742  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
743  }
+
744 
+
745  // becky creates 2 buy offers
+
746  env(token::createOffer(becky, nftokenID, drops(1)),
+
747  token::owner(alice));
+
748  env.close();
+
749  env(token::createOffer(becky, nftokenID, drops(1)),
+
750  token::owner(alice));
+
751  env.close();
+
752 
+
753  // Burn the token
+
754  env(token::burn(alice, nftokenID));
+
755  env.close();
+
756 
+
757  // Burning the token should remove all 499 sell offers from the
+
758  // ledger.
+
759  for (uint256 const& offerIndex : offerIndexes)
+
760  {
+
761  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
762  }
+
763 
+
764  // alice should have ownerCount of zero because all her
+
765  // sell offers have been deleted
+
766  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
767 
+
768  // becky has ownerCount of one due to an orphaned buy offer
+
769  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
770  }
+
771  }
+
772 
+
773  void
+
774  testWithFeats(FeatureBitset features)
+
775  {
+
776  testBurnRandom(features);
+
777  testBurnSequential(features);
+
778  testBurnTooManyOffers(features);
+
779  }
+
780 
+
781 public:
+
782  void
+
783  run() override
+
784  {
+
785  using namespace test::jtx;
+
786  FeatureBitset const all{supported_amendments()};
+
787  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
+
788 
+
789  testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTDir);
+
790  testWithFeats(all - fixNonFungibleTokensV1_2);
+
791  testWithFeats(all);
+
792  }
+
793 };
+
794 
+
795 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn, tx, ripple, 3);
+
796 
+
797 } // namespace ripple
constexpr std::uint16_t maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:81
const SF_UINT32 sfOwnerCount
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:128
STL class.
-
void testBurnRandom(FeatureBitset features)
-
void testBurnSequential(FeatureBitset features)
+
constexpr std::size_t maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
Definition: Protocol.h:70
+
void testBurnRandom(FeatureBitset features)
+
void testBurnSequential(FeatureBitset features)
T reserve(T... args)
@ all
unsigned int UInt
Definition: json_forwards.h:27
@@ -701,18 +891,21 @@ $(function() {
static std::uint32_t ownerCount(test::jtx::Env const &env, test::jtx::Account const &acct)
const Json::StaticString jsonName
Definition: SField.h:136
T sort(T... args)
+
uint256 createNftAndOffers(test::jtx::Env &env, test::jtx::Account const &owner, std::vector< uint256 > &offerIndexes, size_t const tokenCancelCount)
T push_back(T... args)
uint256 key
Definition: Keylet.h:40
-
+
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:81
constexpr const std::uint32_t tfBurnable
Definition: TxFlags.h:125
T to_string(T... args)
+
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
constexpr const std::uint32_t tfSellNFToken
Definition: TxFlags.h:150
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
constexpr std::size_t maxTokenOfferCancelCount
The maximum number of token offers that can be canceled at once.
Definition: Protocol.h:67
+
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:204
const uint256 fixNFTokenDirV1
constexpr std::size_t maxTokenURILength
The maximum length of a URI inside an NFT.
Definition: Protocol.h:84
bool isArray() const
@@ -725,13 +918,14 @@ $(function() {
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Immutable cryptographic account descriptor.
Definition: Account.h:37
-
void testWithFeats(FeatureBitset features)
-
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: NFTokenUtils.h:140
+
void testWithFeats(FeatureBitset features)
+
const uint256 fixNonFungibleTokensV1_2
+
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: NFTokenUtils.h:144
static std::uint32_t nftCount(test::jtx::Env &env, test::jtx::Account const &acct)
-
void testBurnTooManyOffers(FeatureBitset features)
+
void testBurnTooManyOffers(FeatureBitset features)
A transaction testing environment.
Definition: Env.h:116
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
-
void run() override
+
void run() override
Represents a JSON value.
Definition: json_value.h:145
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
diff --git a/NFTokenCancelOffer_8cpp_source.html b/NFTokenCancelOffer_8cpp_source.html index 7f29c48992..3d0ae2a2ea 100644 --- a/NFTokenCancelOffer_8cpp_source.html +++ b/NFTokenCancelOffer_8cpp_source.html @@ -227,7 +227,7 @@ $(function() {
const Rules rules
Definition: Transactor.h:36
@ tesSUCCESS
Definition: TER.h:219
STTx const & tx
Definition: ApplyContext.h:48
-
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
+
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:525