1#include <xrpl/basics/algorithm.h>
2#include <xrpl/ledger/Dir.h>
3#include <xrpl/ledger/View.h>
4#include <xrpl/ledger/helpers/AccountRootHelpers.h>
5#include <xrpl/ledger/helpers/DirectoryHelpers.h>
6#include <xrpl/ledger/helpers/RippleStateHelpers.h>
7#include <xrpl/ledger/helpers/TokenHelpers.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/STArray.h>
10#include <xrpl/protocol/TxFlags.h>
11#include <xrpl/protocol/nftPageMask.h>
12#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
31 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
44 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
62 view.
peek(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
69 cp->setFieldArray(sfNFTokens, arr);
71 createCallback(view, owner);
75 STArray narr = cp->getFieldArray(sfNFTokens);
101 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
107 if (splitIter == narr.
end())
110 return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
116 if (splitIter == narr.
end())
121 if (splitIter == narr.
begin())
137 splitIter = narr.
end();
160 : carr[0].getFieldH256(sfNFTokenID);
163 XRPL_ASSERT(np->key() > base.key,
"xrpl::nft::getPageForToken : valid NFT page index");
164 np->setFieldArray(sfNFTokens, narr);
165 np->setFieldH256(sfNextPageMin, cp->key());
167 if (
auto ppm = (*cp)[~sfPreviousPageMin])
169 np->setFieldH256(sfPreviousPageMin, *ppm);
171 if (
auto p3 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm)))
173 p3->setFieldH256(sfNextPageMin, np->key());
180 cp->setFieldArray(sfNFTokens, carr);
181 cp->setFieldH256(sfPreviousPageMin, np->key());
184 createCallback(view, owner);
186 return (first.key < np->key()) ? np : cp;
198 return lowBitsCmp < 0;
217 STArray& arr = page->peekFieldArray(sfNFTokens);
220 return (obj[sfNFTokenID] == nftokenID);
223 if (nftIter == arr.
end())
228 nftIter->setFieldVL(sfURI, *uri);
230 else if (nftIter->isFieldPresent(sfURI))
232 nftIter->makeFieldAbsent(sfURI);
243 XRPL_ASSERT(nft.isFieldPresent(sfNFTokenID),
"xrpl::nft::insertToken : has NFT token");
261 auto arr = page->getFieldArray(sfNFTokens);
262 arr.push_back(std::move(nft));
268 page->setFieldArray(sfNFTokens, arr);
279 if (p1->key() >= p2->key())
280 Throw<std::runtime_error>(
"mergePages: pages passed in out of order!");
282 if ((*p1)[~sfNextPageMin] != p2->key())
283 Throw<std::runtime_error>(
"mergePages: next link broken!");
285 if ((*p2)[~sfPreviousPageMin] != p1->key())
286 Throw<std::runtime_error>(
"mergePages: previous link broken!");
288 auto const p1arr = p1->getFieldArray(sfNFTokens);
289 auto const p2arr = p2->getFieldArray(sfNFTokens);
298 STArray x(p1arr.size() + p2arr.size());
307 return compareTokens(a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID));
310 p2->setFieldArray(sfNFTokens, x);
316 p2->makeFieldAbsent(sfPreviousPageMin);
318 if (
auto const ppm = (*p1)[~sfPreviousPageMin])
320 auto p0 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm));
323 Throw<std::runtime_error>(
"mergePages: p0 can't be located!");
325 p0->setFieldH256(sfNextPageMin, p2->key());
328 p2->setFieldH256(sfPreviousPageMin, *ppm);
359 auto arr = curr->getFieldArray(sfNFTokens);
363 return (obj[sfNFTokenID] == nftokenID);
376 if (
auto const id = (*page1)[~field])
378 page2 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *
id));
382 Throw<std::runtime_error>(
383 "page " +
to_string(page1->key()) +
" has a broken " + field.getName() +
391 auto const prev = loadPage(curr, sfPreviousPageMin);
392 auto const next = loadPage(curr, sfNextPageMin);
399 curr->setFieldArray(sfNFTokens, arr);
435 curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens);
437 if (
auto const prevLink = prev->at(~sfPreviousPageMin))
439 curr->at(sfPreviousPageMin) = *prevLink;
442 auto const newPrev = loadPage(curr, sfPreviousPageMin);
443 newPrev->at(sfNextPageMin) = curr->key();
448 curr->makeFieldAbsent(sfPreviousPageMin);
466 prev->setFieldH256(sfNextPageMin, next->key());
470 prev->makeFieldAbsent(sfNextPageMin);
481 next->setFieldH256(sfPreviousPageMin, prev->key());
485 next->makeFieldAbsent(sfPreviousPageMin);
506 view.
peek(
Keylet(ltNFTOKEN_PAGE, prev->key())),
507 view.
peek(
Keylet(ltNFTOKEN_PAGE, next->key()))))
529 for (
auto const& t : page->getFieldArray(sfNFTokens))
531 if (t[sfNFTokenID] == nftokenID)
548 for (
auto const& t : page->getFieldArray(sfNFTokens))
550 if (t[sfNFTokenID] == nftokenID)
562 if (maxDeletableOffers == 0)
576 pageIndex = (*page)[~sfIndexNext];
578 auto offerIndexes = page->getFieldV256(sfIndexes);
586 for (
int i = offerIndexes.size() - 1; i >= 0; --i)
592 ++deletedOffersCount;
596 Throw<std::runtime_error>(
597 "Offer " +
to_string(offerIndexes[i]) +
" cannot be deleted!");
601 if (maxDeletableOffers == deletedOffersCount)
604 }
while ((pageIndex.value_or(0) != 0u) && maxDeletableOffers != deletedOffersCount);
606 return deletedOffersCount;
618 totalOffers += iter.page_size();
628 totalOffers += iter.page_size();
639 if (offer->getType() != ltNFTOKEN_OFFER)
642 auto const owner = (*offer)[sfOwner];
647 auto const nftokenID = (*offer)[sfNFTokenID];
652 (*offer)[sfNFTokenOfferNode],
667 bool didRepair =
false;
678 if (page->
key() == last.key)
682 bool const nextPresent = page->isFieldPresent(sfNextPageMin);
683 bool const prevPresent = page->isFieldPresent(sfPreviousPageMin);
684 if (nextPresent || prevPresent)
688 page->makeFieldAbsent(sfPreviousPageMin);
690 page->makeFieldAbsent(sfNextPageMin);
698 if (page->isFieldPresent(sfPreviousPageMin))
701 page->makeFieldAbsent(sfPreviousPageMin);
708 ltNFTOKEN_PAGE, view.
succ(page->
key().
next(), last.key.
next()).value_or(last.key)))))
710 if (!page->isFieldPresent(sfNextPageMin) ||
711 page->getFieldH256(sfNextPageMin) != nextPage->key())
714 page->setFieldH256(sfNextPageMin, nextPage->key());
718 if (!nextPage->isFieldPresent(sfPreviousPageMin) ||
719 nextPage->getFieldH256(sfPreviousPageMin) != page->
key())
722 nextPage->setFieldH256(sfPreviousPageMin, page->
key());
726 if (nextPage->key() == last.key)
751 nextPage->peekFieldArray(sfNFTokens) = page->peekFieldArray(sfNFTokens);
753 if (
auto const prevLink = page->at(~sfPreviousPageMin))
755 nextPage->at(sfPreviousPageMin) = *prevLink;
758 auto const newPrev = view.
peek(
Keylet(ltNFTOKEN_PAGE, *prevLink));
761 Throw<std::runtime_error>(
762 "NFTokenPage directory for " +
to_string(owner) +
763 " cannot be repaired. Unexpected link problem.");
765 newPrev->at(sfNextPageMin) = nextPage->key();
773 XRPL_ASSERT(nextPage,
"xrpl::nft::repairNFTokenDirectoryLinks : next page is available");
774 if (nextPage->isFieldPresent(sfNextPageMin))
777 nextPage->makeFieldAbsent(sfNextPageMin);
794 if (amount.negative())
811 bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
812 if (!isSellOffer && !amount)
823 if (owner && owner == acctID)
827 if (dest && dest == acctID)
856 if (nftIssuer != amount.getIssuer() &&
865 if (
isFrozen(view, nftIssuer, amount.getCurrency(), amount.getIssuer()))
872 XRPL_ASSERT(
root,
"xrpl::nft::tokenOfferCreatePreclaim : non-null account");
874 if (
auto minter = (*
root)[~sfNFTokenMinter]; minter != acctID)
878 if (
isFrozen(view, acctID, amount.getCurrency(), amount.getIssuer()))
884 if ((txFlags & tfSellNFToken) == 0)
902 if ((sleDst->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
915 if ((sleOwner->getFlags() & lsfDisallowIncomingNFTokenOffer) != 0u)
919 if (view.
rules().
enabled(fixEnforceNFTokenTrustlineV2) && !amount.native())
948 if (
auto const acct = view.
read(acctKeylet);
957 auto const ownerNode =
963 bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
971 (*sle)[sfFlags] = isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers;
972 (*sle)[sfNFTokenID] = nftokenID;
981 sleFlags |= lsfSellNFToken;
984 (*offer)[sfOwner] = acctID;
985 (*offer)[sfNFTokenID] = nftokenID;
986 (*offer)[sfAmount] = amount;
987 (*offer)[sfFlags] = sleFlags;
988 (*offer)[sfOwnerNode] = *ownerNode;
989 (*offer)[sfNFTokenOfferNode] = *offerNode;
992 (*offer)[sfExpiration] = *expiration;
995 (*offer)[sfDestination] = *dest;
1014 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineAuthorized : valid to check.");
1016 if (view.
rules().
enabled(fixEnforceNFTokenTrustlineV2))
1021 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineAuthorized: can't "
1022 "receive IOUs from non-existent issuer: "
1036 if (issuerAccount->isFlag(lsfRequireAuth))
1048 if (!trustLine->isFlag(
id > issue.
account ? lsfLowAuth : lsfHighAuth))
1066 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineDeepFrozen : valid to check.");
1073 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineDeepFrozen: can't "
1074 "receive IOUs from non-existent issuer: "
1097 bool const deepFrozen =
1098 ((*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze)) != 0u;
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 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=tfSellNFToken)
Preclaim checks shared by NFTokenCreateOffer and NFTokenMint.
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
constexpr std::uint16_t const flagCreateTrustLines
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"))
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.
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=tfSellNFToken)
doApply implementation shared by NFTokenCreateOffer and NFTokenMint
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)
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=tfSellNFToken)
Preflight checks shared by NFTokenCreateOffer and NFTokenMint.
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)
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, int depth=0)
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
bool isTesSuccess(TER x) noexcept
@ tecNO_SUITABLE_NFTOKEN_PAGE
@ tecINSUFFICIENT_RESERVE
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.