rippled
NFTokenUtils.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2021 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/tx/impl/details/NFTokenUtils.h>
21 #include <ripple/basics/algorithm.h>
22 #include <ripple/ledger/Directory.h>
23 #include <ripple/ledger/View.h>
24 #include <ripple/protocol/Feature.h>
25 #include <ripple/protocol/STAccount.h>
26 #include <ripple/protocol/STArray.h>
27 #include <ripple/protocol/TxFlags.h>
28 #include <ripple/protocol/nftPageMask.h>
29 #include <functional>
30 #include <memory>
31 
32 namespace ripple {
33 
34 namespace nft {
35 
37 locatePage(ReadView const& view, AccountID owner, uint256 const& id)
38 {
39  auto const first = keylet::nftpage(keylet::nftpage_min(owner), id);
40  auto const last = keylet::nftpage_max(owner);
41 
42  // This NFT can only be found in the first page with a key that's strictly
43  // greater than `first`, so look for that, up until the maximum possible
44  // page.
45  return view.read(Keylet(
47  view.succ(first.key, last.key.next()).value_or(last.key)));
48 }
49 
51 locatePage(ApplyView& view, AccountID owner, uint256 const& id)
52 {
53  auto const first = keylet::nftpage(keylet::nftpage_min(owner), id);
54  auto const last = keylet::nftpage_max(owner);
55 
56  // This NFT can only be found in the first page with a key that's strictly
57  // greater than `first`, so look for that, up until the maximum possible
58  // page.
59  return view.peek(Keylet(
61  view.succ(first.key, last.key.next()).value_or(last.key)));
62 }
63 
66  ApplyView& view,
67  AccountID const& owner,
68  uint256 const& id,
69  std::function<void(ApplyView&, AccountID const&)> const& createCallback)
70 {
71  auto const base = keylet::nftpage_min(owner);
72  auto const first = keylet::nftpage(base, id);
73  auto const last = keylet::nftpage_max(owner);
74 
75  // This NFT can only be found in the first page with a key that's strictly
76  // greater than `first`, so look for that, up until the maximum possible
77  // page.
78  auto cp = view.peek(Keylet(
80  view.succ(first.key, last.key.next()).value_or(last.key)));
81 
82  // A suitable page doesn't exist; we'll have to create one.
83  if (!cp)
84  {
85  STArray arr;
86  cp = std::make_shared<SLE>(last);
87  cp->setFieldArray(sfNFTokens, arr);
88  view.insert(cp);
89  createCallback(view, owner);
90  return cp;
91  }
92 
93  STArray narr = cp->getFieldArray(sfNFTokens);
94 
95  // The right page still has space: we're good.
96  if (narr.size() != dirMaxTokensPerPage)
97  return cp;
98 
99  // We need to split the page in two: the first half of the items in this
100  // page will go into the new page; the rest will stay with the existing
101  // page.
102  //
103  // Note we can't always split the page exactly in half. All equivalent
104  // NFTs must be kept on the same page. So when the page contains
105  // equivalent NFTs, the split may be lopsided in order to keep equivalent
106  // NFTs on the same page.
107  STArray carr;
108  {
109  // We prefer to keep equivalent NFTs on a page boundary. That gives
110  // any additional equivalent NFTs maximum room for expansion.
111  // Round up the boundary until there's a non-equivalent entry.
112  uint256 const cmp =
113  narr[(dirMaxTokensPerPage / 2) - 1].getFieldH256(sfNFTokenID) &
115 
116  // Note that the calls to find_if_not() and (later) find_if()
117  // rely on the fact that narr is kept in sorted order.
118  auto splitIter = std::find_if_not(
119  narr.begin() + (dirMaxTokensPerPage / 2),
120  narr.end(),
121  [&cmp](STObject const& obj) {
122  return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
123  });
124 
125  // If we get all the way from the middle to the end with only
126  // equivalent NFTokens then check the front of the page for a
127  // place to make the split.
128  if (splitIter == narr.end())
129  splitIter = std::find_if(
130  narr.begin(), narr.end(), [&cmp](STObject const& obj) {
131  return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) ==
132  cmp;
133  });
134 
135  // There should be no circumstance when splitIter == end(), but if it
136  // were to happen we should bail out because something is confused.
137  if (splitIter == narr.end())
138  return nullptr;
139 
140  // If splitIter == begin(), then the entire page is filled with
141  // equivalent tokens. This requires special handling.
142  if (splitIter == narr.begin())
143  {
144  // Prior to fixNFTokenDirV1 we simply stopped.
145  if (!view.rules().enabled(fixNFTokenDirV1))
146  return nullptr;
147  else
148  {
149  // This would be an ideal place for the spaceship operator...
150  int const relation = compare(id & nft::pageMask, cmp);
151  if (relation == 0)
152  // If the passed in id belongs exactly on this (full) page
153  // this account simply cannot store the NFT.
154  return nullptr;
155 
156  else if (relation > 0)
157  // We need to leave the entire contents of this page in
158  // narr so carr stays empty. The new NFT will be
159  // inserted in carr. This keeps the NFTs that must be
160  // together all on their own page.
161  splitIter = narr.end();
162 
163  // If neither of those conditions apply then put all of
164  // narr into carr and produce an empty narr where the new NFT
165  // will be inserted. Leave the split at narr.begin().
166  }
167  }
168 
169  // Split narr at splitIter.
170  STArray newCarr(
171  std::make_move_iterator(splitIter),
172  std::make_move_iterator(narr.end()));
173  narr.erase(splitIter, narr.end());
174  std::swap(carr, newCarr);
175  }
176 
177  // Determine the ID for the page index. This decision is conditional on
178  // fixNFTokenDirV1 being enabled. But the condition for the decision
179  // is not possible unless fixNFTokenDirV1 is enabled.
180  //
181  // Note that we use uint256::next() because there's a subtlety in the way
182  // NFT pages are structured. The low 96-bits of NFT ID must be strictly
183  // less than the low 96-bits of the enclosing page's index. In order to
184  // accommodate that requirement we use an index one higher than the
185  // largest NFT in the page.
186  uint256 const tokenIDForNewPage = narr.size() == dirMaxTokensPerPage
187  ? narr[dirMaxTokensPerPage - 1].getFieldH256(sfNFTokenID).next()
188  : carr[0].getFieldH256(sfNFTokenID);
189 
190  auto np = std::make_shared<SLE>(keylet::nftpage(base, tokenIDForNewPage));
191  np->setFieldArray(sfNFTokens, narr);
192  np->setFieldH256(sfNextPageMin, cp->key());
193 
194  if (auto ppm = (*cp)[~sfPreviousPageMin])
195  {
196  np->setFieldH256(sfPreviousPageMin, *ppm);
197 
198  if (auto p3 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm)))
199  {
200  p3->setFieldH256(sfNextPageMin, np->key());
201  view.update(p3);
202  }
203  }
204 
205  view.insert(np);
206 
207  cp->setFieldArray(sfNFTokens, carr);
208  cp->setFieldH256(sfPreviousPageMin, np->key());
209  view.update(cp);
210 
211  createCallback(view, owner);
212 
213  // fixNFTokenDirV1 corrects a bug in the initial implementation that
214  // would put an NFT in the wrong page. The problem was caused by an
215  // off-by-one subtlety that the NFT can only be stored in the first page
216  // with a key that's strictly greater than `first`
217  if (!view.rules().enabled(fixNFTokenDirV1))
218  return (first.key <= np->key()) ? np : cp;
219 
220  return (first.key < np->key()) ? np : cp;
221 }
222 
223 bool
224 compareTokens(uint256 const& a, uint256 const& b)
225 {
226  // The sort of NFTokens needs to be fully deterministic, but the sort
227  // is weird because we sort on the low 96-bits first. But if the low
228  // 96-bits are identical we still need a fully deterministic sort.
229  // So we sort on the low 96-bits first. If those are equal we sort on
230  // the whole thing.
231  if (auto const lowBitsCmp = compare(a & nft::pageMask, b & nft::pageMask);
232  lowBitsCmp != 0)
233  return lowBitsCmp < 0;
234 
235  return a < b;
236 }
237 
239 TER
241 {
242  assert(nft.isFieldPresent(sfNFTokenID));
243 
244  // First, we need to locate the page the NFT belongs to, creating it
245  // if necessary. This operation may fail if it is impossible to insert
246  // the NFT.
248  view,
249  owner,
250  nft[sfNFTokenID],
251  [](ApplyView& view, AccountID const& owner) {
253  view,
254  view.peek(keylet::account(owner)),
255  1,
256  beast::Journal{beast::Journal::getNullSink()});
257  });
258 
259  if (!page)
261 
262  {
263  auto arr = page->getFieldArray(sfNFTokens);
264  arr.push_back(std::move(nft));
265 
266  arr.sort([](STObject const& o1, STObject const& o2) {
267  return compareTokens(
269  });
270 
271  page->setFieldArray(sfNFTokens, arr);
272  }
273 
274  view.update(page);
275 
276  return tesSUCCESS;
277 }
278 
279 static bool
281  ApplyView& view,
282  std::shared_ptr<SLE> const& p1,
283  std::shared_ptr<SLE> const& p2)
284 {
285  if (p1->key() >= p2->key())
286  Throw<std::runtime_error>("mergePages: pages passed in out of order!");
287 
288  if ((*p1)[~sfNextPageMin] != p2->key())
289  Throw<std::runtime_error>("mergePages: next link broken!");
290 
291  if ((*p2)[~sfPreviousPageMin] != p1->key())
292  Throw<std::runtime_error>("mergePages: previous link broken!");
293 
294  auto const p1arr = p1->getFieldArray(sfNFTokens);
295  auto const p2arr = p2->getFieldArray(sfNFTokens);
296 
297  // Now check whether to merge the two pages; it only makes sense to do
298  // this it would mean that one of them can be deleted as a result of
299  // the merge.
300 
301  if (p1arr.size() + p2arr.size() > dirMaxTokensPerPage)
302  return false;
303 
304  STArray x(p1arr.size() + p2arr.size());
305 
306  std::merge(
307  p1arr.begin(),
308  p1arr.end(),
309  p2arr.begin(),
310  p2arr.end(),
312  [](STObject const& a, STObject const& b) {
313  return compareTokens(
314  a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID));
315  });
316 
317  p2->setFieldArray(sfNFTokens, x);
318 
319  // So, at this point we need to unlink "p1" (since we just emptied it) but
320  // we need to first relink the directory: if p1 has a previous page (p0),
321  // load it, point it to p2 and point p2 to it.
322 
323  p2->makeFieldAbsent(sfPreviousPageMin);
324 
325  if (auto const ppm = (*p1)[~sfPreviousPageMin])
326  {
327  auto p0 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm));
328 
329  if (!p0)
330  Throw<std::runtime_error>("mergePages: p0 can't be located!");
331 
332  p0->setFieldH256(sfNextPageMin, p2->key());
333  view.update(p0);
334 
335  p2->setFieldH256(sfPreviousPageMin, *ppm);
336  }
337 
338  view.update(p2);
339  view.erase(p1);
340 
341  return true;
342 }
343 
345 TER
346 removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID)
347 {
348  std::shared_ptr<SLE> page = locatePage(view, owner, nftokenID);
349 
350  // If the page couldn't be found, the given NFT isn't owned by this account
351  if (!page)
352  return tecNO_ENTRY;
353 
354  return removeToken(view, owner, nftokenID, std::move(page));
355 }
356 
358 TER
360  ApplyView& view,
361  AccountID const& owner,
362  uint256 const& nftokenID,
363  std::shared_ptr<SLE>&& curr)
364 {
365  // We found a page, but the given NFT may not be in it.
366  auto arr = curr->getFieldArray(sfNFTokens);
367 
368  {
369  auto x = std::find_if(
370  arr.begin(), arr.end(), [&nftokenID](STObject const& obj) {
371  return (obj[sfNFTokenID] == nftokenID);
372  });
373 
374  if (x == arr.end())
375  return tecNO_ENTRY;
376 
377  arr.erase(x);
378  }
379 
380  // Page management:
381  auto const loadPage = [&view](
382  std::shared_ptr<SLE> const& page1,
383  SF_UINT256 const& field) {
384  std::shared_ptr<SLE> page2;
385 
386  if (auto const id = (*page1)[~field])
387  {
388  page2 = view.peek(Keylet(ltNFTOKEN_PAGE, *id));
389 
390  if (!page2)
391  Throw<std::runtime_error>(
392  "page " + to_string(page1->key()) + " has a broken " +
393  field.getName() + " field pointing to " + to_string(*id));
394  }
395 
396  return page2;
397  };
398 
399  auto const prev = loadPage(curr, sfPreviousPageMin);
400  auto const next = loadPage(curr, sfNextPageMin);
401 
402  if (!arr.empty())
403  {
404  // The current page isn't empty. Update it and then try to consolidate
405  // pages. Note that this consolidation attempt may actually merge three
406  // pages into one!
407  curr->setFieldArray(sfNFTokens, arr);
408  view.update(curr);
409 
410  int cnt = 0;
411 
412  if (prev && mergePages(view, prev, curr))
413  cnt--;
414 
415  if (next && mergePages(view, curr, next))
416  cnt--;
417 
418  if (cnt != 0)
420  view,
421  view.peek(keylet::account(owner)),
422  cnt,
423  beast::Journal{beast::Journal::getNullSink()});
424 
425  return tesSUCCESS;
426  }
427 
428  // The page is empty, so we can just unlink it and then remove it.
429  if (prev)
430  {
431  // Make our previous page point to our next page:
432  if (next)
433  prev->setFieldH256(sfNextPageMin, next->key());
434  else
435  prev->makeFieldAbsent(sfNextPageMin);
436 
437  view.update(prev);
438  }
439 
440  if (next)
441  {
442  // Make our next page point to our previous page:
443  if (prev)
444  next->setFieldH256(sfPreviousPageMin, prev->key());
445  else
446  next->makeFieldAbsent(sfPreviousPageMin);
447 
448  view.update(next);
449  }
450 
451  view.erase(curr);
452 
453  int cnt = 1;
454 
455  // Since we're here, try to consolidate the previous and current pages
456  // of the page we removed (if any) into one. mergePages() _should_
457  // always return false. Since tokens are burned one at a time, there
458  // should never be a page containing one token sitting between two pages
459  // that have few enough tokens that they can be merged.
460  //
461  // But, in case that analysis is wrong, it's good to leave this code here
462  // just in case.
463  if (prev && next &&
464  mergePages(
465  view,
466  view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
467  view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
468  cnt++;
469 
471  view,
472  view.peek(keylet::account(owner)),
473  -1 * cnt,
474  beast::Journal{beast::Journal::getNullSink()});
475 
476  return tesSUCCESS;
477 }
478 
481  ReadView const& view,
482  AccountID const& owner,
483  uint256 const& nftokenID)
484 {
485  std::shared_ptr<SLE const> page = locatePage(view, owner, nftokenID);
486 
487  // If the page couldn't be found, the given NFT isn't owned by this account
488  if (!page)
489  return std::nullopt;
490 
491  // We found a candidate page, but the given NFT may not be in it.
492  for (auto const& t : page->getFieldArray(sfNFTokens))
493  {
494  if (t[sfNFTokenID] == nftokenID)
495  return t;
496  }
497 
498  return std::nullopt;
499 }
500 
503  ApplyView& view,
504  AccountID const& owner,
505  uint256 const& nftokenID)
506 {
507  std::shared_ptr<SLE> page = locatePage(view, owner, nftokenID);
508 
509  // If the page couldn't be found, the given NFT isn't owned by this account
510  if (!page)
511  return std::nullopt;
512 
513  // We found a candidate page, but the given NFT may not be in it.
514  for (auto const& t : page->getFieldArray(sfNFTokens))
515  {
516  if (t[sfNFTokenID] == nftokenID)
517  // This std::optional constructor is explicit, so it is spelled out.
519  std::in_place, t, std::move(page));
520  }
521  return std::nullopt;
522 }
523 
526  ApplyView& view,
527  Keylet const& directory,
528  std::size_t maxDeletableOffers)
529 {
530  if (maxDeletableOffers == 0)
531  return 0;
532 
533  std::optional<std::uint64_t> pageIndex{0};
534  std::size_t deletedOffersCount = 0;
535 
536  do
537  {
538  auto const page = view.peek(keylet::page(directory, *pageIndex));
539  if (!page)
540  break;
541 
542  // We get the index of the next page in case the current
543  // page is deleted after all of its entries have been removed
544  pageIndex = (*page)[~sfIndexNext];
545 
546  auto offerIndexes = page->getFieldV256(sfIndexes);
547 
548  // We reverse-iterate the offer directory page to delete all entries.
549  // Deleting an entry in a NFTokenOffer directory page won't cause
550  // entries from other pages to move to the current, so, it is safe to
551  // delete entries one by one in the page. It is required to iterate
552  // backwards to handle iterator invalidation for vector, as we are
553  // deleting during iteration.
554  for (int i = offerIndexes.size() - 1; i >= 0; --i)
555  {
556  if (auto const offer = view.peek(keylet::nftoffer(offerIndexes[i])))
557  {
558  if (deleteTokenOffer(view, offer))
559  ++deletedOffersCount;
560  else
561  Throw<std::runtime_error>(
562  "Offer " + to_string(offerIndexes[i]) +
563  " cannot be deleted!");
564  }
565 
566  if (maxDeletableOffers == deletedOffersCount)
567  break;
568  }
569  } while (pageIndex.value_or(0) && maxDeletableOffers != deletedOffersCount);
570 
571  return deletedOffersCount;
572 }
573 
574 TER
575 notTooManyOffers(ReadView const& view, uint256 const& nftokenID)
576 {
577  std::size_t totalOffers = 0;
578 
579  {
580  Dir buys(view, keylet::nft_buys(nftokenID));
581  for (auto iter = buys.begin(); iter != buys.end(); iter.next_page())
582  {
583  totalOffers += iter.page_size();
584  if (totalOffers > maxDeletableTokenOfferEntries)
585  return tefTOO_BIG;
586  }
587  }
588 
589  {
590  Dir sells(view, keylet::nft_sells(nftokenID));
591  for (auto iter = sells.begin(); iter != sells.end(); iter.next_page())
592  {
593  totalOffers += iter.page_size();
594  if (totalOffers > maxDeletableTokenOfferEntries)
595  return tefTOO_BIG;
596  }
597  }
598  return tesSUCCESS;
599 }
600 
601 bool
603 {
604  if (offer->getType() != ltNFTOKEN_OFFER)
605  return false;
606 
607  auto const owner = (*offer)[sfOwner];
608 
609  if (!view.dirRemove(
610  keylet::ownerDir(owner),
611  (*offer)[sfOwnerNode],
612  offer->key(),
613  false))
614  return false;
615 
616  auto const nftokenID = (*offer)[sfNFTokenID];
617 
618  if (!view.dirRemove(
619  ((*offer)[sfFlags] & tfSellNFToken) ? keylet::nft_sells(nftokenID)
620  : keylet::nft_buys(nftokenID),
621  (*offer)[sfNFTokenOfferNode],
622  offer->key(),
623  false))
624  return false;
625 
627  view,
628  view.peek(keylet::account(owner)),
629  -1,
630  beast::Journal{beast::Journal::getNullSink()});
631 
632  view.erase(offer);
633  return true;
634 }
635 
636 } // namespace nft
637 } // namespace ripple
ripple::STArray::size
size_type size() const
Definition: STArray.h:248
ripple::sfIndexNext
const SF_UINT64 sfIndexNext
ripple::keylet::ownerDir
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:303
std::in_place
T in_place
ripple::Keylet
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
ripple::Dir::begin
const_iterator begin() const
Definition: Directory.cpp:34
ripple::Rules::enabled
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:81
std::shared_ptr
STL class.
ripple::sfOwnerNode
const SF_UINT64 sfOwnerNode
ripple::TypedField
A field with a type known at compile time.
Definition: SField.h:271
ripple::maxDeletableTokenOfferEntries
constexpr std::size_t maxDeletableTokenOfferEntries
The maximum number of offers in an offer directory for NFT to be burnable.
Definition: Protocol.h:70
ripple::ApplyView::peek
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
ripple::sfNFTokenID
const SF_UINT256 sfNFTokenID
functional
ripple::nft::findTokenAndPage
std::optional< TokenAndPage > findTokenAndPage(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Definition: NFTokenUtils.cpp:502
ripple::nft::removeTokenOffersWithLimit
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.
Definition: NFTokenUtils.cpp:525
ripple::sfOwner
const SF_ACCOUNT sfOwner
ripple::ApplyView::erase
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
std::find_if_not
T find_if_not(T... args)
ripple::nft::mergePages
static bool mergePages(ApplyView &view, std::shared_ptr< SLE > const &p1, std::shared_ptr< SLE > const &p2)
Definition: NFTokenUtils.cpp:280
ripple::nft::notTooManyOffers
TER notTooManyOffers(ReadView const &view, uint256 const &nftokenID)
Returns tesSUCCESS if NFToken has few enough offers that it can be burned.
Definition: NFTokenUtils.cpp:575
std::back_inserter
T back_inserter(T... args)
ripple::keylet::nftoffer
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:355
ripple::dirMaxTokensPerPage
constexpr std::size_t dirMaxTokensPerPage
The maximum number of items in an NFT page.
Definition: Protocol.h:61
ripple::ApplyView::update
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
ripple::nft::findToken
std::optional< STObject > findToken(ReadView const &view, AccountID const &owner, uint256 const &nftokenID)
Finds the specified token in the owner's token directory.
Definition: NFTokenUtils.cpp:480
std::function
ripple::nft::removeToken
TER removeToken(ApplyView &view, AccountID const &owner, uint256 const &nftokenID)
Remove the token from the owner's token directory.
Definition: NFTokenUtils.cpp:346
ripple::sfNFTokenOfferNode
const SF_UINT64 sfNFTokenOfferNode
ripple::nft::compareTokens
bool compareTokens(uint256 const &a, uint256 const &b)
Definition: NFTokenUtils.cpp:224
ripple::ApplyView
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:134
ripple::nft::pageMask
constexpr uint256 pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
ripple::sfIndexes
const SF_VECTOR256 sfIndexes
ripple::ApplyView::dirRemove
bool dirRemove(Keylet const &directory, std::uint64_t page, uint256 const &key, bool keepRoot)
Remove an entry from a directory.
Definition: ApplyView.cpp:189
ripple::Keylet::key
uint256 key
Definition: Keylet.h:40
ripple::base_uint
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:81
ripple::keylet::nftpage_min
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:332
ripple::adjustOwnerCount
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition: View.cpp:713
ripple::keylet::nftpage
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition: Indexes.cpp:348
ripple::keylet::account
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
ripple::compare
int compare(base_uint< Bits, Tag > const &a, base_uint< Bits, Tag > const &b)
Definition: base_uint.h:553
ripple::ltNFTOKEN_OFFER
@ ltNFTOKEN_OFFER
A ledger object which identifies an offer to buy or sell an NFT.
Definition: LedgerFormats.h:162
ripple::TERSubset< CanCvtToTER >
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:309
ripple::STArray
Definition: STArray.h:28
ripple::keylet::nft_sells
Keylet nft_sells(uint256 const &id) noexcept
The directory of sell offers for the specified NFT.
Definition: Indexes.cpp:368
ripple::keylet::nftpage_max
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:340
ripple::tfSellNFToken
constexpr const std::uint32_t tfSellNFToken
Definition: TxFlags.h:152
std::merge
T merge(T... args)
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::keylet::nft_buys
Keylet nft_buys(uint256 const &id) noexcept
The directory of buy offers for the specified NFT.
Definition: Indexes.cpp:362
ripple::ReadView::succ
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.
ripple::fixNFTokenDirV1
const uint256 fixNFTokenDirV1
ripple::ReadView::read
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
memory
std::swap
T swap(T... args)
ripple::STArray::begin
iterator begin()
Definition: STArray.h:224
ripple::sfNFTokens
const SField sfNFTokens
ripple::STObject
Definition: STObject.h:51
ripple::ReadView
A view into a ledger.
Definition: ReadView.h:125
ripple::ltNFTOKEN_PAGE
@ ltNFTOKEN_PAGE
A ledger object which contains a list of NFTs.
Definition: LedgerFormats.h:156
ripple::ApplyView::insert
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::ReadView::rules
virtual Rules const & rules() const =0
Returns the tx processing rules.
ripple::sfFlags
const SF_UINT32 sfFlags
ripple::tefTOO_BIG
@ tefTOO_BIG
Definition: TER.h:163
ripple::tecNO_SUITABLE_NFTOKEN_PAGE
@ tecNO_SUITABLE_NFTOKEN_PAGE
Definition: TER.h:285
ripple::Dir
Definition: Directory.h:28
ripple::sfNextPageMin
const SF_UINT256 sfNextPageMin
std::optional
ripple::sfPreviousPageMin
const SF_UINT256 sfPreviousPageMin
std::size_t
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::STArray::erase
iterator erase(iterator pos)
Definition: STArray.h:290
ripple::Dir::const_iterator::next_page
const_iterator & next_page()
Definition: Directory.cpp:100
ripple::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:270
ripple::Dir::end
const_iterator end() const
Definition: Directory.cpp:52
std::make_move_iterator
T make_move_iterator(T... args)
ripple::nft::locatePage
static std::shared_ptr< SLE const > locatePage(ReadView const &view, AccountID owner, uint256 const &id)
Definition: NFTokenUtils.cpp:37
ripple::tesSUCCESS
@ tesSUCCESS
Definition: TER.h:219
ripple::nft::getPageForToken
static std::shared_ptr< SLE > getPageForToken(ApplyView &view, AccountID const &owner, uint256 const &id, std::function< void(ApplyView &, AccountID const &)> const &createCallback)
Definition: NFTokenUtils.cpp:65
ripple::nft::insertToken
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Definition: NFTokenUtils.cpp:240
ripple::nft::deleteTokenOffer
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
Definition: NFTokenUtils.cpp:602
ripple::STArray::end
iterator end()
Definition: STArray.h:230
ripple::STObject::getFieldH256
uint256 getFieldH256(SField const &field) const
Definition: STObject.cpp:583