1#include <xrpld/app/tx/detail/NFTokenUtils.h>
3#include <xrpl/basics/algorithm.h>
4#include <xrpl/ledger/Dir.h>
5#include <xrpl/ledger/View.h>
6#include <xrpl/protocol/Feature.h>
7#include <xrpl/protocol/STArray.h>
8#include <xrpl/protocol/TxFlags.h>
9#include <xrpl/protocol/nftPageMask.h>
27 return view.
read(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
39 return view.
peek(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
56 auto cp = view.
peek(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
63 cp->setFieldArray(sfNFTokens, arr);
65 createCallback(view, owner);
69 STArray narr = cp->getFieldArray(sfNFTokens);
94 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
100 if (splitIter == narr.
end())
102 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
107 if (splitIter == narr.
end())
112 if (splitIter == narr.
begin())
128 splitIter = narr.
end();
151 : carr[0].getFieldH256(sfNFTokenID);
154 XRPL_ASSERT(np->key() > base.key,
"xrpl::nft::getPageForToken : valid NFT page index");
155 np->setFieldArray(sfNFTokens, narr);
156 np->setFieldH256(sfNextPageMin, cp->key());
158 if (
auto ppm = (*cp)[~sfPreviousPageMin])
160 np->setFieldH256(sfPreviousPageMin, *ppm);
162 if (
auto p3 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm)))
164 p3->setFieldH256(sfNextPageMin, np->key());
171 cp->setFieldArray(sfNFTokens, carr);
172 cp->setFieldH256(sfPreviousPageMin, np->key());
175 createCallback(view, owner);
177 return (first.key < np->key()) ? np : cp;
189 return lowBitsCmp < 0;
204 STArray& arr = page->peekFieldArray(sfNFTokens);
207 arr.
begin(), arr.
end(), [&nftokenID](
STObject const& obj) { return (obj[sfNFTokenID] == nftokenID); });
209 if (nftIter == arr.
end())
213 nftIter->setFieldVL(sfURI, *uri);
214 else if (nftIter->isFieldPresent(sfURI))
215 nftIter->makeFieldAbsent(sfURI);
225 XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID),
"xrpl::nft::insertToken : has NFT token");
239 auto arr = page->getFieldArray(sfNFTokens);
240 arr.push_back(std::move(nft));
246 page->setFieldArray(sfNFTokens, arr);
257 if (p1->key() >= p2->key())
258 Throw<std::runtime_error>(
"mergePages: pages passed in out of order!");
260 if ((*p1)[~sfNextPageMin] != p2->key())
261 Throw<std::runtime_error>(
"mergePages: next link broken!");
263 if ((*p2)[~sfPreviousPageMin] != p1->key())
264 Throw<std::runtime_error>(
"mergePages: previous link broken!");
266 auto const p1arr = p1->getFieldArray(sfNFTokens);
267 auto const p2arr = p2->getFieldArray(sfNFTokens);
276 STArray x(p1arr.size() + p2arr.size());
285 return compareTokens(a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID));
288 p2->setFieldArray(sfNFTokens, x);
294 p2->makeFieldAbsent(sfPreviousPageMin);
296 if (
auto const ppm = (*p1)[~sfPreviousPageMin])
298 auto p0 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm));
301 Throw<std::runtime_error>(
"mergePages: p0 can't be located!");
303 p0->setFieldH256(sfNextPageMin, p2->key());
306 p2->setFieldH256(sfPreviousPageMin, *ppm);
325 return removeToken(view, owner, nftokenID, std::move(page));
333 auto arr = curr->getFieldArray(sfNFTokens);
337 arr.begin(), arr.end(), [&nftokenID](
STObject const& obj) { return (obj[sfNFTokenID] == nftokenID); });
349 if (
auto const id = (*page1)[~field])
351 page2 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *
id));
354 Throw<std::runtime_error>(
355 "page " +
to_string(page1->key()) +
" has a broken " + field.getName() +
" field pointing to " +
362 auto const prev = loadPage(curr, sfPreviousPageMin);
363 auto const next = loadPage(curr, sfNextPageMin);
370 curr->setFieldArray(sfNFTokens, arr);
400 curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens);
402 if (
auto const prevLink = prev->at(~sfPreviousPageMin))
404 curr->at(sfPreviousPageMin) = *prevLink;
407 auto const newPrev = loadPage(curr, sfPreviousPageMin);
408 newPrev->at(sfNextPageMin) = curr->key();
413 curr->makeFieldAbsent(sfPreviousPageMin);
427 prev->setFieldH256(sfNextPageMin, next->key());
429 prev->makeFieldAbsent(sfNextPageMin);
438 next->setFieldH256(sfPreviousPageMin, prev->key());
440 next->makeFieldAbsent(sfPreviousPageMin);
459 view, view.
peek(
Keylet(ltNFTOKEN_PAGE, prev->key())), view.
peek(
Keylet(ltNFTOKEN_PAGE, next->key()))))
477 for (
auto const& t : page->getFieldArray(sfNFTokens))
479 if (t[sfNFTokenID] == nftokenID)
496 for (
auto const& t : page->getFieldArray(sfNFTokens))
498 if (t[sfNFTokenID] == nftokenID)
508 if (maxDeletableOffers == 0)
522 pageIndex = (*page)[~sfIndexNext];
524 auto offerIndexes = page->getFieldV256(sfIndexes);
532 for (
int i = offerIndexes.size() - 1; i >= 0; --i)
537 ++deletedOffersCount;
539 Throw<std::runtime_error>(
"Offer " +
to_string(offerIndexes[i]) +
" cannot be deleted!");
542 if (maxDeletableOffers == deletedOffersCount)
545 }
while (pageIndex.value_or(0) && maxDeletableOffers != deletedOffersCount);
547 return deletedOffersCount;
559 totalOffers += iter.page_size();
569 totalOffers += iter.page_size();
580 if (offer->getType() != ltNFTOKEN_OFFER)
583 auto const owner = (*offer)[sfOwner];
588 auto const nftokenID = (*offer)[sfNFTokenID];
592 (*offer)[sfNFTokenOfferNode],
606 bool didRepair =
false;
616 if (page->
key() == last.key)
620 bool const nextPresent = page->isFieldPresent(sfNextPageMin);
621 bool const prevPresent = page->isFieldPresent(sfPreviousPageMin);
622 if (nextPresent || prevPresent)
626 page->makeFieldAbsent(sfPreviousPageMin);
628 page->makeFieldAbsent(sfNextPageMin);
636 if (page->isFieldPresent(sfPreviousPageMin))
639 page->makeFieldAbsent(sfPreviousPageMin);
648 if (!page->isFieldPresent(sfNextPageMin) || page->getFieldH256(sfNextPageMin) != nextPage->key())
651 page->setFieldH256(sfNextPageMin, nextPage->key());
655 if (!nextPage->isFieldPresent(sfPreviousPageMin) || nextPage->getFieldH256(sfPreviousPageMin) != page->
key())
658 nextPage->setFieldH256(sfPreviousPageMin, page->
key());
662 if (nextPage->key() == last.key)
685 nextPage->peekFieldArray(sfNFTokens) = page->peekFieldArray(sfNFTokens);
687 if (
auto const prevLink = page->at(~sfPreviousPageMin))
689 nextPage->at(sfPreviousPageMin) = *prevLink;
692 auto const newPrev = view.
peek(
Keylet(ltNFTOKEN_PAGE, *prevLink));
694 Throw<std::runtime_error>(
695 "NFTokenPage directory for " +
to_string(owner) +
" cannot be repaired. Unexpected link problem.");
696 newPrev->at(sfNextPageMin) = nextPage->key();
704 XRPL_ASSERT(nextPage,
"xrpl::nft::repairNFTokenDirectoryLinks : next page is available");
705 if (nextPage->isFieldPresent(sfNextPageMin))
708 nextPage->makeFieldAbsent(sfNextPageMin);
725 if (amount.negative())
741 if (!isSellOffer && !amount)
752 if (owner && owner == acctID)
756 if (dest && dest == acctID)
785 if (nftIssuer != amount.getIssuer() && !view.
read(
keylet::line(nftIssuer, amount.issue())))
793 if (
isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
800 XRPL_ASSERT(
root,
"xrpl::nft::tokenOfferCreatePreclaim : non-null account");
802 if (
auto minter = (*
root)[~sfNFTokenMinter]; minter != acctID)
806 if (
isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
847 if (view.
rules().
enabled(fixEnforceNFTokenTrustlineV2) && !amount.native())
875 if (
auto const acct = view.
read(acctKeylet); priorBalance < view.
fees().
accountReserve((*acct)[sfOwnerCount] + 1))
897 (*sle)[sfNFTokenID] = nftokenID;
909 (*offer)[sfOwner] = acctID;
910 (*offer)[sfNFTokenID] = nftokenID;
911 (*offer)[sfAmount] = amount;
912 (*offer)[sfFlags] = sleFlags;
913 (*offer)[sfOwnerNode] = *ownerNode;
914 (*offer)[sfNFTokenOfferNode] = *offerNode;
917 (*offer)[sfExpiration] = *expiration;
920 (*offer)[sfDestination] = *dest;
935 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineAuthorized : valid to check.");
942 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineAuthorized: can't "
943 "receive IOUs from non-existent issuer: "
983 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineDeepFrozen : valid to check.");
990 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineDeepFrozen: can't "
991 "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.
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::uint32_t txFlags)
Preclaim checks shared by NFTokenCreateOffer and NFTokenMint.
constexpr std::uint16_t const flagCreateTrustLines
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)
doApply implementation 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
uint256 constexpr pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
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.
TER checkTrustlineDeepFrozen(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
TER checkTrustlineAuthorized(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
bool compareTokens(uint256 const &a, uint256 const &b)
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.
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)
TER notTooManyOffers(ReadView const &view, uint256 const &nftokenID)
Returns tesSUCCESS if NFToken has few enough offers that it can be burned.
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::uint32_t txFlags)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
static std::shared_ptr< SLE const > locatePage(ReadView const &view, AccountID const &owner, uint256 const &id)
bool repairNFTokenDirectoryLinks(ApplyView &view, AccountID const &owner)
Repairs the links in an NFTokenPage directory.
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.