1#include <xrpl/basics/algorithm.h>
2#include <xrpl/ledger/Dir.h>
3#include <xrpl/ledger/View.h>
4#include <xrpl/protocol/Feature.h>
5#include <xrpl/protocol/STArray.h>
6#include <xrpl/protocol/TxFlags.h>
7#include <xrpl/protocol/nftPageMask.h>
8#include <xrpl/tx/transactors/NFT/NFTokenUtils.h>
27 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
40 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
58 view.
peek(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
65 cp->setFieldArray(sfNFTokens, arr);
67 createCallback(view, owner);
71 STArray narr = cp->getFieldArray(sfNFTokens);
97 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
103 if (splitIter == narr.
end())
105 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
110 if (splitIter == narr.
end())
115 if (splitIter == narr.
begin())
131 splitIter = narr.
end();
154 : carr[0].getFieldH256(sfNFTokenID);
157 XRPL_ASSERT(np->key() > base.key,
"xrpl::nft::getPageForToken : valid NFT page index");
158 np->setFieldArray(sfNFTokens, narr);
159 np->setFieldH256(sfNextPageMin, cp->key());
161 if (
auto ppm = (*cp)[~sfPreviousPageMin])
163 np->setFieldH256(sfPreviousPageMin, *ppm);
165 if (
auto p3 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm)))
167 p3->setFieldH256(sfNextPageMin, np->key());
174 cp->setFieldArray(sfNFTokens, carr);
175 cp->setFieldH256(sfPreviousPageMin, np->key());
178 createCallback(view, owner);
180 return (first.key < np->key()) ? np : cp;
192 return lowBitsCmp < 0;
211 STArray& arr = page->peekFieldArray(sfNFTokens);
214 return (obj[sfNFTokenID] == nftokenID);
217 if (nftIter == arr.
end())
221 nftIter->setFieldVL(sfURI, *uri);
222 else if (nftIter->isFieldPresent(sfURI))
223 nftIter->makeFieldAbsent(sfURI);
233 XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID),
"xrpl::nft::insertToken : has NFT token");
251 auto arr = page->getFieldArray(sfNFTokens);
252 arr.push_back(std::move(nft));
258 page->setFieldArray(sfNFTokens, arr);
269 if (p1->key() >= p2->key())
270 Throw<std::runtime_error>(
"mergePages: pages passed in out of order!");
272 if ((*p1)[~sfNextPageMin] != p2->key())
273 Throw<std::runtime_error>(
"mergePages: next link broken!");
275 if ((*p2)[~sfPreviousPageMin] != p1->key())
276 Throw<std::runtime_error>(
"mergePages: previous link broken!");
278 auto const p1arr = p1->getFieldArray(sfNFTokens);
279 auto const p2arr = p2->getFieldArray(sfNFTokens);
288 STArray x(p1arr.size() + p2arr.size());
297 return compareTokens(a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID));
300 p2->setFieldArray(sfNFTokens, x);
306 p2->makeFieldAbsent(sfPreviousPageMin);
308 if (
auto const ppm = (*p1)[~sfPreviousPageMin])
310 auto p0 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm));
313 Throw<std::runtime_error>(
"mergePages: p0 can't be located!");
315 p0->setFieldH256(sfNextPageMin, p2->key());
318 p2->setFieldH256(sfPreviousPageMin, *ppm);
337 return removeToken(view, owner, nftokenID, std::move(page));
349 auto arr = curr->getFieldArray(sfNFTokens);
353 return (obj[sfNFTokenID] == nftokenID);
366 if (
auto const id = (*page1)[~field])
368 page2 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *
id));
371 Throw<std::runtime_error>(
372 "page " +
to_string(page1->key()) +
" has a broken " + field.getName() +
379 auto const prev = loadPage(curr, sfPreviousPageMin);
380 auto const next = loadPage(curr, sfNextPageMin);
387 curr->setFieldArray(sfNFTokens, arr);
421 curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens);
423 if (
auto const prevLink = prev->at(~sfPreviousPageMin))
425 curr->at(sfPreviousPageMin) = *prevLink;
428 auto const newPrev = loadPage(curr, sfPreviousPageMin);
429 newPrev->at(sfNextPageMin) = curr->key();
434 curr->makeFieldAbsent(sfPreviousPageMin);
451 prev->setFieldH256(sfNextPageMin, next->key());
453 prev->makeFieldAbsent(sfNextPageMin);
462 next->setFieldH256(sfPreviousPageMin, prev->key());
464 next->makeFieldAbsent(sfPreviousPageMin);
484 view.
peek(
Keylet(ltNFTOKEN_PAGE, prev->key())),
485 view.
peek(
Keylet(ltNFTOKEN_PAGE, next->key()))))
507 for (
auto const& t : page->getFieldArray(sfNFTokens))
509 if (t[sfNFTokenID] == nftokenID)
526 for (
auto const& t : page->getFieldArray(sfNFTokens))
528 if (t[sfNFTokenID] == nftokenID)
538 if (maxDeletableOffers == 0)
552 pageIndex = (*page)[~sfIndexNext];
554 auto offerIndexes = page->getFieldV256(sfIndexes);
562 for (
int i = offerIndexes.size() - 1; i >= 0; --i)
567 ++deletedOffersCount;
569 Throw<std::runtime_error>(
570 "Offer " +
to_string(offerIndexes[i]) +
" cannot be deleted!");
573 if (maxDeletableOffers == deletedOffersCount)
576 }
while (pageIndex.value_or(0) && maxDeletableOffers != deletedOffersCount);
578 return deletedOffersCount;
590 totalOffers += iter.page_size();
600 totalOffers += iter.page_size();
611 if (offer->getType() != ltNFTOKEN_OFFER)
614 auto const owner = (*offer)[sfOwner];
619 auto const nftokenID = (*offer)[sfNFTokenID];
624 (*offer)[sfNFTokenOfferNode],
639 bool didRepair =
false;
650 if (page->
key() == last.key)
654 bool const nextPresent = page->isFieldPresent(sfNextPageMin);
655 bool const prevPresent = page->isFieldPresent(sfPreviousPageMin);
656 if (nextPresent || prevPresent)
660 page->makeFieldAbsent(sfPreviousPageMin);
662 page->makeFieldAbsent(sfNextPageMin);
670 if (page->isFieldPresent(sfPreviousPageMin))
673 page->makeFieldAbsent(sfPreviousPageMin);
680 ltNFTOKEN_PAGE, view.
succ(page->
key().
next(), last.key.
next()).value_or(last.key)))))
682 if (!page->isFieldPresent(sfNextPageMin) ||
683 page->getFieldH256(sfNextPageMin) != nextPage->key())
686 page->setFieldH256(sfNextPageMin, nextPage->key());
690 if (!nextPage->isFieldPresent(sfPreviousPageMin) ||
691 nextPage->getFieldH256(sfPreviousPageMin) != page->
key())
694 nextPage->setFieldH256(sfPreviousPageMin, page->
key());
698 if (nextPage->key() == last.key)
721 nextPage->peekFieldArray(sfNFTokens) = page->peekFieldArray(sfNFTokens);
723 if (
auto const prevLink = page->at(~sfPreviousPageMin))
725 nextPage->at(sfPreviousPageMin) = *prevLink;
728 auto const newPrev = view.
peek(
Keylet(ltNFTOKEN_PAGE, *prevLink));
730 Throw<std::runtime_error>(
731 "NFTokenPage directory for " +
to_string(owner) +
732 " cannot be repaired. Unexpected link problem.");
733 newPrev->at(sfNextPageMin) = nextPage->key();
741 XRPL_ASSERT(nextPage,
"xrpl::nft::repairNFTokenDirectoryLinks : next page is available");
742 if (nextPage->isFieldPresent(sfNextPageMin))
745 nextPage->makeFieldAbsent(sfNextPageMin);
762 if (amount.negative())
778 if (!isSellOffer && !amount)
789 if (owner && owner == acctID)
793 if (dest && dest == acctID)
822 if (nftIssuer != amount.getIssuer() &&
831 if (
isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
838 XRPL_ASSERT(
root,
"xrpl::nft::tokenOfferCreatePreclaim : non-null account");
840 if (
auto minter = (*
root)[~sfNFTokenMinter]; minter != acctID)
844 if (
isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
885 if (view.
rules().
enabled(fixEnforceNFTokenTrustlineV2) && !amount.native())
914 if (
auto const acct = view.
read(acctKeylet);
923 auto const ownerNode =
938 (*sle)[sfNFTokenID] = nftokenID;
950 (*offer)[sfOwner] = acctID;
951 (*offer)[sfNFTokenID] = nftokenID;
952 (*offer)[sfAmount] = amount;
953 (*offer)[sfFlags] = sleFlags;
954 (*offer)[sfOwnerNode] = *ownerNode;
955 (*offer)[sfNFTokenOfferNode] = *offerNode;
958 (*offer)[sfExpiration] = *expiration;
961 (*offer)[sfDestination] = *dest;
980 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineAuthorized : valid to check.");
987 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineAuthorized: can't "
988 "receive IOUs from non-existent issuer: "
1032 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineDeepFrozen : valid to check.");
1039 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineDeepFrozen: can't "
1040 "receive IOUs from non-existent issuer: "
T back_inserter(T... args)
A generic endpoint for log messages.
Writeable view to a ledger, for applying a transaction.
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
const_iterator & next_page()
A class that simplifies iterating ledger directory pages.
const_iterator begin() const
const_iterator end() const
A currency issued by an account.
virtual Rules const & rules() const =0
Returns the tx processing rules.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
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.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
iterator erase(iterator pos)
uint256 getFieldH256(SField const &field) const
A type that represents either a sequence value or a ticket value.
constexpr std::uint32_t value() const
T make_move_iterator(T... args)
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Keylet nftpage(Keylet const &k, uint256 const &token)
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet nft_buys(uint256 const &id) noexcept
The directory of buy offers for the specified NFT.
Keylet line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Keylet nft_sells(uint256 const &id) noexcept
The directory of sell offers for the specified NFT.
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
constexpr std::uint16_t const flagCreateTrustLines
NotTEC tokenOfferCreatePreflight(AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, std::uint16_t nftFlags, Rules const &rules, std::optional< AccountID > const &owner=std::nullopt, std::uint32_t txFlags=lsfSellNFToken)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
constexpr std::uint16_t const flagOnlyXRP
TER changeTokenURI(ApplyView &view, AccountID const &owner, uint256 const &nftokenID, std::optional< xrpl::Slice > const &uri)
constexpr std::uint16_t const flagTransferable
TER tokenOfferCreatePreclaim(ReadView const &view, AccountID const &acctID, AccountID const &nftIssuer, STAmount const &amount, std::optional< AccountID > const &dest, std::uint16_t nftFlags, std::uint16_t xferFee, beast::Journal j, std::optional< AccountID > const &owner=std::nullopt, std::uint32_t txFlags=lsfSellNFToken)
Preclaim checks shared by NFTokenCreateOffer and NFTokenMint.
TER tokenOfferCreateApply(ApplyView &view, AccountID const &acctID, STAmount const &amount, std::optional< AccountID > const &dest, std::optional< std::uint32_t > const &expiration, SeqProxy seqProxy, uint256 const &nftokenID, XRPAmount const &priorBalance, beast::Journal j, std::uint32_t txFlags=lsfSellNFToken)
doApply implementation shared by NFTokenCreateOffer and NFTokenMint
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
TER checkTrustlineDeepFrozen(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
TER checkTrustlineAuthorized(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
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.
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
bool compareTokens(uint256 const &a, uint256 const &b)
TER notTooManyOffers(ReadView const &view, uint256 const &nftokenID)
Returns tesSUCCESS if NFToken has few enough offers that it can be burned.
bool repairNFTokenDirectoryLinks(ApplyView &view, AccountID const &owner)
Repairs the links in an NFTokenPage directory.
static std::shared_ptr< SLE > getPageForToken(ApplyView &view, AccountID const &owner, uint256 const &id, std::function< void(ApplyView &, AccountID const &)> const &createCallback)
static bool mergePages(ApplyView &view, std::shared_ptr< SLE > const &p1, std::shared_ptr< SLE > const &p2)
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
static std::shared_ptr< SLE const > locatePage(ReadView const &view, AccountID const &owner, uint256 const &id)
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::size_t constexpr dirMaxTokensPerPage
The maximum number of items in an NFT page.
bool isXRP(AccountID const &c)
std::string to_string(base_uint< Bits, Tag > const &a)
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Number root(Number f, unsigned d)
std::size_t constexpr maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
bool isFrozen(ReadView const &view, AccountID const &account, Currency const ¤cy, AccountID const &issuer)
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
@ tecNO_SUITABLE_NFTOKEN_PAGE
@ tecINSUFFICIENT_RESERVE
@ lsfDisallowIncomingNFTokenOffer
constexpr std::uint32_t const tfSellNFToken
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.
A field with a type known at compile time.