1#include <xrpl/ledger/helpers/NFTokenHelpers.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Slice.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/contract.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/ledger/ApplyView.h>
9#include <xrpl/ledger/ReadView.h>
10#include <xrpl/ledger/helpers/AccountRootHelpers.h>
11#include <xrpl/ledger/helpers/DirectoryHelpers.h>
12#include <xrpl/ledger/helpers/RippleStateHelpers.h>
13#include <xrpl/ledger/helpers/TokenHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/Issue.h>
18#include <xrpl/protocol/LedgerFormats.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/STArray.h>
23#include <xrpl/protocol/STLedgerEntry.h>
24#include <xrpl/protocol/SeqProxy.h>
25#include <xrpl/protocol/TER.h>
26#include <xrpl/protocol/TxFlags.h>
27#include <xrpl/protocol/UintTypes.h>
28#include <xrpl/protocol/XRPAmount.h>
29#include <xrpl/protocol/nft.h>
30#include <xrpl/protocol/nftPageMask.h>
54 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
67 Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
85 view.
peek(
Keylet(ltNFTOKEN_PAGE, view.
succ(first.key, last.key.next()).value_or(last.key)));
92 cp->setFieldArray(sfNFTokens, arr);
94 createCallback(view, owner);
98 STArray narr = cp->getFieldArray(sfNFTokens);
124 return (obj.getFieldH256(sfNFTokenID) & nft::kPageMask) == cmp;
130 if (splitIter == narr.
end())
139 if (splitIter == narr.
end())
144 if (splitIter == narr.
begin())
160 splitIter = narr.
end();
183 : carr[0].getFieldH256(sfNFTokenID);
186 XRPL_ASSERT(np->key() > base.key,
"xrpl::nft::getPageForToken : valid NFT page index");
187 np->setFieldArray(sfNFTokens, narr);
188 np->setFieldH256(sfNextPageMin, cp->key());
190 if (
auto ppm = (*cp)[~sfPreviousPageMin])
192 np->setFieldH256(sfPreviousPageMin, *ppm);
194 if (
auto p3 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm)))
196 p3->setFieldH256(sfNextPageMin, np->key());
203 cp->setFieldArray(sfNFTokens, carr);
204 cp->setFieldH256(sfPreviousPageMin, np->key());
207 createCallback(view, owner);
209 return (first.key < np->key()) ? np : cp;
221 return lowBitsCmp < 0;
240 STArray& arr = page->peekFieldArray(sfNFTokens);
243 arr, [&nftokenID](
STObject const& obj) {
return (obj[sfNFTokenID] == nftokenID); });
245 if (nftIter == arr.
end())
250 nftIter->setFieldVL(sfURI, *uri);
252 else if (nftIter->isFieldPresent(sfURI))
254 nftIter->makeFieldAbsent(sfURI);
265 XRPL_ASSERT(
nft.isFieldPresent(sfNFTokenID),
"xrpl::nft::insertToken : has NFT token");
283 auto arr = page->getFieldArray(sfNFTokens);
284 arr.pushBack(std::move(
nft));
290 page->setFieldArray(sfNFTokens, arr);
301 if (p1->key() >= p2->key())
304 if ((*p1)[~sfNextPageMin] != p2->key())
307 if ((*p2)[~sfPreviousPageMin] != p1->key())
310 auto const p1arr = p1->getFieldArray(sfNFTokens);
311 auto const p2arr = p2->getFieldArray(sfNFTokens);
320 STArray x(p1arr.size() + p2arr.size());
327 p2->setFieldArray(sfNFTokens, x);
333 p2->makeFieldAbsent(sfPreviousPageMin);
335 if (
auto const ppm = (*p1)[~sfPreviousPageMin])
337 auto p0 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *ppm));
342 p0->setFieldH256(sfNextPageMin, p2->key());
345 p2->setFieldH256(sfPreviousPageMin, *ppm);
372 auto arr = curr->getFieldArray(sfNFTokens);
376 arr, [&nftokenID](
STObject const& obj) {
return (obj[sfNFTokenID] == nftokenID); });
388 if (
auto const id = (*page1)[~field])
390 page2 = view.
peek(
Keylet(ltNFTOKEN_PAGE, *
id));
395 "page " +
to_string(page1->key()) +
" has a broken " + field.getName() +
403 auto const prev = loadPage(curr, sfPreviousPageMin);
404 auto const next = loadPage(curr, sfNextPageMin);
411 curr->setFieldArray(sfNFTokens, arr);
447 curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens);
449 if (
auto const prevLink = prev->at(~sfPreviousPageMin))
451 curr->at(sfPreviousPageMin) = *prevLink;
454 auto const newPrev = loadPage(curr, sfPreviousPageMin);
455 newPrev->at(sfNextPageMin) = curr->key();
460 curr->makeFieldAbsent(sfPreviousPageMin);
478 prev->setFieldH256(sfNextPageMin, next->key());
482 prev->makeFieldAbsent(sfNextPageMin);
493 next->setFieldH256(sfPreviousPageMin, prev->key());
497 next->makeFieldAbsent(sfPreviousPageMin);
518 view.
peek(
Keylet(ltNFTOKEN_PAGE, prev->key())),
519 view.
peek(
Keylet(ltNFTOKEN_PAGE, next->key()))))
541 for (
auto const& t : page->getFieldArray(sfNFTokens))
543 if (t[sfNFTokenID] == nftokenID)
560 for (
auto const& t : page->getFieldArray(sfNFTokens))
562 if (t[sfNFTokenID] == nftokenID)
574 if (maxDeletableOffers == 0)
588 pageIndex = (*page)[~sfIndexNext];
590 auto offerIndexes = page->getFieldV256(sfIndexes);
598 for (
int i = offerIndexes.size() - 1; i >= 0; --i)
604 ++deletedOffersCount;
609 "Offer " +
to_string(offerIndexes[i]) +
" cannot be deleted!");
613 if (maxDeletableOffers == deletedOffersCount)
616 }
while ((pageIndex.
value_or(0) != 0u) && maxDeletableOffers != deletedOffersCount);
618 return deletedOffersCount;
624 if (offer->getType() != ltNFTOKEN_OFFER)
627 auto const owner = (*offer)[sfOwner];
632 auto const nftokenID = (*offer)[sfNFTokenID];
637 (*offer)[sfNFTokenOfferNode],
652 bool didRepair =
false;
663 if (page->
key() == last.key)
667 bool const nextPresent = page->isFieldPresent(sfNextPageMin);
668 bool const prevPresent = page->isFieldPresent(sfPreviousPageMin);
669 if (nextPresent || prevPresent)
673 page->makeFieldAbsent(sfPreviousPageMin);
675 page->makeFieldAbsent(sfNextPageMin);
683 if (page->isFieldPresent(sfPreviousPageMin))
686 page->makeFieldAbsent(sfPreviousPageMin);
693 ltNFTOKEN_PAGE, view.
succ(page->
key().
next(), last.key.
next()).value_or(last.key)))))
695 if (!page->isFieldPresent(sfNextPageMin) ||
696 page->getFieldH256(sfNextPageMin) != nextPage->key())
699 page->setFieldH256(sfNextPageMin, nextPage->key());
703 if (!nextPage->isFieldPresent(sfPreviousPageMin) ||
704 nextPage->getFieldH256(sfPreviousPageMin) != page->
key())
707 nextPage->setFieldH256(sfPreviousPageMin, page->
key());
711 if (nextPage->key() == last.key)
736 nextPage->peekFieldArray(sfNFTokens) = page->peekFieldArray(sfNFTokens);
738 if (
auto const prevLink = page->at(~sfPreviousPageMin))
740 nextPage->at(sfPreviousPageMin) = *prevLink;
743 auto const newPrev = view.
peek(
Keylet(ltNFTOKEN_PAGE, *prevLink));
747 "NFTokenPage directory for " +
to_string(owner) +
748 " cannot be repaired. Unexpected link problem.");
750 newPrev->at(sfNextPageMin) = nextPage->key();
758 XRPL_ASSERT(nextPage,
"xrpl::nft::repairNFTokenDirectoryLinks : next page is available");
759 if (nextPage->isFieldPresent(sfNextPageMin))
762 nextPage->makeFieldAbsent(sfNextPageMin);
796 bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
797 if (!isSellOffer && !amount)
808 if (owner && owner == acctID)
812 if (dest && dest == acctID)
857 XRPL_ASSERT(
root,
"xrpl::nft::tokenOfferCreatePreclaim : non-null account");
859 if (
auto minter = (*
root)[~sfNFTokenMinter]; minter != acctID)
869 if ((txFlags & tfSellNFToken) == 0)
887 if (sleDst->isFlag(lsfDisallowIncomingNFTokenOffer))
900 if (sleOwner->isFlag(lsfDisallowIncomingNFTokenOffer))
933 if (
auto const acct = view.
read(acctKeylet);
942 auto const ownerNode =
948 bool const isSellOffer = (txFlags & tfSellNFToken) != 0u;
955 [&nftokenID, isSellOffer](
SLE::ref sle) {
956 (*sle)[sfFlags] = isSellOffer ? lsfNFTokenSellOffers : lsfNFTokenBuyOffers;
957 (*sle)[sfNFTokenID] = nftokenID;
966 sleFlags |= lsfSellNFToken;
969 (*offer)[sfOwner] = acctID;
970 (*offer)[sfNFTokenID] = nftokenID;
971 (*offer)[sfAmount] = amount;
972 (*offer)[sfFlags] = sleFlags;
973 (*offer)[sfOwnerNode] = *ownerNode;
974 (*offer)[sfNFTokenOfferNode] = *offerNode;
977 (*offer)[sfExpiration] = *expiration;
980 (*offer)[sfDestination] = *dest;
999 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineAuthorized : valid to check.");
1001 if (view.
rules().
enabled(fixEnforceNFTokenTrustlineV2))
1006 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineAuthorized: can't "
1007 "receive IOUs from non-existent issuer: "
1021 if (issuerAccount->isFlag(lsfRequireAuth))
1033 if (!trustLine->isFlag(
id > issue.
account ? lsfLowAuth : lsfHighAuth))
1051 XRPL_ASSERT(!
isXRP(issue.
currency),
"xrpl::nft::checkTrustlineDeepFrozen : valid to check.");
1058 JLOG(j.
debug()) <<
"xrpl::nft::checkTrustlineDeepFrozen: can't "
1059 "receive IOUs from non-existent issuer: "
1082 bool const deepFrozen =
1083 ((*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 SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void insert(SLE::ref sle)=0
Insert a new state SLE.
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
virtual void erase(SLE::ref sle)=0
Remove a peeked SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(SLE::ref)> const &describe)
Insert an entry to a directory.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
constexpr TIss const & get() 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 SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
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.
Rules controlling protocol behavior.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
constexpr TIss const & get() const
bool negative() const noexcept
bool native() const noexcept
Asset const & asset() const
AccountID const & getIssuer() const
iterator erase(iterator pos)
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const_pointer
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 ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Keylet nftokenPage(Keylet const &k, uint256 const &token)
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Keylet nftSells(uint256 const &id) noexcept
The directory of sell offers for the specified NFT.
Keylet nftBuys(uint256 const &id) noexcept
The directory of buy 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.
Keylet nftokenOffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
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.
static SLE::pointer getPageForToken(ApplyView &view, AccountID const &owner, uint256 const &id, std::function< void(ApplyView &, AccountID const &)> const &createCallback)
constexpr std::uint16_t const kFlagCreateTrustLines
static bool mergePages(ApplyView &view, SLE::ref p1, SLE::ref p2)
TER changeTokenURI(ApplyView &view, AccountID const &owner, uint256 const &nftokenID, std::optional< xrpl::Slice > const &uri)
constexpr uint256 kPageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
constexpr std::uint16_t const kFlagTransferable
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
static SLE::const_pointer locatePage(ReadView const &view, AccountID const &owner, uint256 const &id)
TER checkTrustlineDeepFrozen(ReadView const &view, AccountID const id, beast::Journal const j, Issue const &issue)
constexpr std::uint16_t const kFlagOnlyXrp
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 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.
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.
bool deleteTokenOffer(ApplyView &view, SLE::ref offer)
Deletes the given token offer.
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
bool isXRP(AccountID const &c)
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Number root(Number f, unsigned d)
std::string to_string(BaseUInt< Bits, Tag > const &a)
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
TERSubset< CanCvtToNotTEC > NotTEC
void adjustOwnerCount(ApplyView &view, SLE::ref 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 isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
constexpr std::size_t kDirMaxTokensPerPage
The maximum number of items in an NFT page.
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
@ tecNO_SUITABLE_NFTOKEN_PAGE
@ tecINSUFFICIENT_RESERVE
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
TypedField< STBitString< 256 > > SF_UINT256
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
A pair of SHAMap key and LedgerEntryType.