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/STAccount.h>
25 #include <ripple/protocol/STArray.h>
26 #include <ripple/protocol/TxFlags.h>
27 #include <ripple/protocol/nftPageMask.h>
28 #include <functional>
29 #include <memory>
30 
31 namespace ripple {
32 
33 namespace nft {
34 
36 locatePage(ReadView const& view, AccountID owner, uint256 const& id)
37 {
38  auto const first = keylet::nftpage(keylet::nftpage_min(owner), id);
39  auto const last = keylet::nftpage_max(owner);
40 
41  // This NFT can only be found in the first page with a key that's strictly
42  // greater than `first`, so look for that, up until the maximum possible
43  // page.
44  return view.read(Keylet(
46  view.succ(first.key, last.key.next()).value_or(last.key)));
47 }
48 
50 locatePage(ApplyView& view, AccountID owner, uint256 const& id)
51 {
52  auto const first = keylet::nftpage(keylet::nftpage_min(owner), id);
53  auto const last = keylet::nftpage_max(owner);
54 
55  // This NFT can only be found in the first page with a key that's strictly
56  // greater than `first`, so look for that, up until the maximum possible
57  // page.
58  return view.peek(Keylet(
60  view.succ(first.key, last.key.next()).value_or(last.key)));
61 }
62 
65  ApplyView& view,
66  AccountID const& owner,
67  uint256 const& id,
68  std::function<void(ApplyView&, AccountID const&)> const& createCallback)
69 {
70  auto const base = keylet::nftpage_min(owner);
71  auto const first = keylet::nftpage(base, id);
72  auto const last = keylet::nftpage_max(owner);
73 
74  // This NFT can only be found in the first page with a key that's strictly
75  // greater than `first`, so look for that, up until the maximum possible
76  // page.
77  auto cp = view.peek(Keylet(
79  view.succ(first.key, last.key.next()).value_or(last.key)));
80 
81  // A suitable page doesn't exist; we'll have to create one.
82  if (!cp)
83  {
84  STArray arr;
85  cp = std::make_shared<SLE>(last);
86  cp->setFieldArray(sfNFTokens, arr);
87  view.insert(cp);
88  createCallback(view, owner);
89  return cp;
90  }
91 
92  STArray narr = cp->getFieldArray(sfNFTokens);
93 
94  // The right page still has space: we're good.
95  if (narr.size() != dirMaxTokensPerPage)
96  return cp;
97 
98  // We need to split the page in two: the first half of the items in this
99  // page will go into the new page; the rest will stay with the existing
100  // page.
101  //
102  // Note we can't always split the page exactly in half. All equivalent
103  // NFTs must be kept on the same page. So when the page contains
104  // equivalent NFTs, the split may be lopsided in order to keep equivalent
105  // NFTs on the same page.
106  STArray carr;
107  {
108  // We prefer to keep equivalent NFTs on a page boundary. That gives
109  // any additional equivalent NFTs maximum room for expansion.
110  // Round up the boundary until there's a non-equivalent entry.
111  uint256 const cmp =
112  narr[(dirMaxTokensPerPage / 2) - 1].getFieldH256(sfNFTokenID) &
114 
115  // Note that the calls to find_if_not() and (later) find_if()
116  // rely on the fact that narr is kept in sorted order.
117  auto splitIter = std::find_if_not(
118  narr.begin() + (dirMaxTokensPerPage / 2),
119  narr.end(),
120  [&cmp](STObject const& obj) {
121  return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) == cmp;
122  });
123 
124  // If we get all the way from the middle to the end with only
125  // equivalent NFTokens then check the front of the page for a
126  // place to make the split.
127  if (splitIter == narr.end())
128  splitIter = std::find_if(
129  narr.begin(), narr.end(), [&cmp](STObject const& obj) {
130  return (obj.getFieldH256(sfNFTokenID) & nft::pageMask) ==
131  cmp;
132  });
133 
134  // If splitIter == begin(), then the entire page is filled with
135  // equivalent tokens. We cannot split the page, so we cannot
136  // insert the requested token.
137  //
138  // There should be no circumstance when splitIter == end(), but if it
139  // were to happen we should bail out because something is confused.
140  if (splitIter == narr.begin() || splitIter == narr.end())
141  return nullptr;
142 
143  // Split narr at splitIter.
144  STArray newCarr(
145  std::make_move_iterator(splitIter),
146  std::make_move_iterator(narr.end()));
147  narr.erase(splitIter, narr.end());
148  std::swap(carr, newCarr);
149  }
150 
151  auto np = std::make_shared<SLE>(
152  keylet::nftpage(base, carr[0].getFieldH256(sfNFTokenID)));
153  np->setFieldArray(sfNFTokens, narr);
154  np->setFieldH256(sfNextPageMin, cp->key());
155 
156  if (auto ppm = (*cp)[~sfPreviousPageMin])
157  {
158  np->setFieldH256(sfPreviousPageMin, *ppm);
159 
160  if (auto p3 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm)))
161  {
162  p3->setFieldH256(sfNextPageMin, np->key());
163  view.update(p3);
164  }
165  }
166 
167  view.insert(np);
168 
169  cp->setFieldArray(sfNFTokens, carr);
170  cp->setFieldH256(sfPreviousPageMin, np->key());
171  view.update(cp);
172 
173  createCallback(view, owner);
174 
175  return (first.key <= np->key()) ? np : cp;
176 }
177 
178 static bool
179 compareTokens(uint256 const& a, uint256 const& b)
180 {
181  // The sort of NFTokens needs to be fully deterministic, but the sort
182  // is weird because we sort on the low 96-bits first. But if the low
183  // 96-bits are identical we still need a fully deterministic sort.
184  // So we sort on the low 96-bits first. If those are equal we sort on
185  // the whole thing.
186  if (auto const lowBitsCmp = compare(a & nft::pageMask, b & nft::pageMask);
187  lowBitsCmp != 0)
188  return lowBitsCmp < 0;
189 
190  return a < b;
191 }
192 
194 TER
196 {
197  assert(nft.isFieldPresent(sfNFTokenID));
198 
199  // First, we need to locate the page the NFT belongs to, creating it
200  // if necessary. This operation may fail if it is impossible to insert
201  // the NFT.
203  view,
204  owner,
205  nft[sfNFTokenID],
206  [](ApplyView& view, AccountID const& owner) {
208  view,
209  view.peek(keylet::account(owner)),
210  1,
211  beast::Journal{beast::Journal::getNullSink()});
212  });
213 
214  if (!page)
216 
217  {
218  auto arr = page->getFieldArray(sfNFTokens);
219  arr.push_back(std::move(nft));
220 
221  arr.sort([](STObject const& o1, STObject const& o2) {
222  return compareTokens(
224  });
225 
226  page->setFieldArray(sfNFTokens, arr);
227  }
228 
229  view.update(page);
230 
231  return tesSUCCESS;
232 }
233 
234 static bool
236  ApplyView& view,
237  std::shared_ptr<SLE> const& p1,
238  std::shared_ptr<SLE> const& p2)
239 {
240  if (p1->key() >= p2->key())
241  Throw<std::runtime_error>("mergePages: pages passed in out of order!");
242 
243  if ((*p1)[~sfNextPageMin] != p2->key())
244  Throw<std::runtime_error>("mergePages: next link broken!");
245 
246  if ((*p2)[~sfPreviousPageMin] != p1->key())
247  Throw<std::runtime_error>("mergePages: previous link broken!");
248 
249  auto const p1arr = p1->getFieldArray(sfNFTokens);
250  auto const p2arr = p2->getFieldArray(sfNFTokens);
251 
252  // Now check whether to merge the two pages; it only makes sense to do
253  // this it would mean that one of them can be deleted as a result of
254  // the merge.
255 
256  if (p1arr.size() + p2arr.size() > dirMaxTokensPerPage)
257  return false;
258 
259  STArray x(p1arr.size() + p2arr.size());
260 
261  std::merge(
262  p1arr.begin(),
263  p1arr.end(),
264  p2arr.begin(),
265  p2arr.end(),
267  [](STObject const& a, STObject const& b) {
268  return compareTokens(
269  a.getFieldH256(sfNFTokenID), b.getFieldH256(sfNFTokenID));
270  });
271 
272  p2->setFieldArray(sfNFTokens, x);
273 
274  // So, at this point we need to unlink "p1" (since we just emptied it) but
275  // we need to first relink the directory: if p1 has a previous page (p0),
276  // load it, point it to p2 and point p2 to it.
277 
278  p2->makeFieldAbsent(sfPreviousPageMin);
279 
280  if (auto const ppm = (*p1)[~sfPreviousPageMin])
281  {
282  auto p0 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm));
283 
284  if (!p0)
285  Throw<std::runtime_error>("mergePages: p0 can't be located!");
286 
287  p0->setFieldH256(sfNextPageMin, p2->key());
288  view.update(p0);
289 
290  p2->setFieldH256(sfPreviousPageMin, *ppm);
291  }
292 
293  view.update(p2);
294  view.erase(p1);
295 
296  return true;
297 }
298 
300 TER
301 removeToken(ApplyView& view, AccountID const& owner, uint256 const& nftokenID)
302 {
303  std::shared_ptr<SLE> page = locatePage(view, owner, nftokenID);
304 
305  // If the page couldn't be found, the given NFT isn't owned by this account
306  if (!page)
307  return tecNO_ENTRY;
308 
309  return removeToken(view, owner, nftokenID, std::move(page));
310 }
311 
313 TER
315  ApplyView& view,
316  AccountID const& owner,
317  uint256 const& nftokenID,
318  std::shared_ptr<SLE>&& curr)
319 {
320  // We found a page, but the given NFT may not be in it.
321  auto arr = curr->getFieldArray(sfNFTokens);
322 
323  {
324  auto x = std::find_if(
325  arr.begin(), arr.end(), [&nftokenID](STObject const& obj) {
326  return (obj[sfNFTokenID] == nftokenID);
327  });
328 
329  if (x == arr.end())
330  return tecNO_ENTRY;
331 
332  arr.erase(x);
333  }
334 
335  // Page management:
336  auto const loadPage = [&view](
337  std::shared_ptr<SLE> const& page1,
338  SF_UINT256 const& field) {
339  std::shared_ptr<SLE> page2;
340 
341  if (auto const id = (*page1)[~field])
342  {
343  page2 = view.peek(Keylet(ltNFTOKEN_PAGE, *id));
344 
345  if (!page2)
346  Throw<std::runtime_error>(
347  "page " + to_string(page1->key()) + " has a broken " +
348  field.getName() + " field pointing to " + to_string(*id));
349  }
350 
351  return page2;
352  };
353 
354  auto const prev = loadPage(curr, sfPreviousPageMin);
355  auto const next = loadPage(curr, sfNextPageMin);
356 
357  if (!arr.empty())
358  {
359  // The current page isn't empty. Update it and then try to consolidate
360  // pages. Note that this consolidation attempt may actually merge three
361  // pages into one!
362  curr->setFieldArray(sfNFTokens, arr);
363  view.update(curr);
364 
365  int cnt = 0;
366 
367  if (prev && mergePages(view, prev, curr))
368  cnt--;
369 
370  if (next && mergePages(view, curr, next))
371  cnt--;
372 
373  if (cnt != 0)
375  view,
376  view.peek(keylet::account(owner)),
377  cnt,
378  beast::Journal{beast::Journal::getNullSink()});
379 
380  return tesSUCCESS;
381  }
382 
383  // The page is empty, so we can just unlink it and then remove it.
384  if (prev)
385  {
386  // Make our previous page point to our next page:
387  if (next)
388  prev->setFieldH256(sfNextPageMin, next->key());
389  else
390  prev->makeFieldAbsent(sfNextPageMin);
391 
392  view.update(prev);
393  }
394 
395  if (next)
396  {
397  // Make our next page point to our previous page:
398  if (prev)
399  next->setFieldH256(sfPreviousPageMin, prev->key());
400  else
401  next->makeFieldAbsent(sfPreviousPageMin);
402 
403  view.update(next);
404  }
405 
406  view.erase(curr);
407 
408  int cnt = 1;
409 
410  // Since we're here, try to consolidate the previous and current pages
411  // of the page we removed (if any) into one. mergePages() _should_
412  // always return false. Since tokens are burned one at a time, there
413  // should never be a page containing one token sitting between two pages
414  // that have few enough tokens that they can be merged.
415  //
416  // But, in case that analysis is wrong, it's good to leave this code here
417  // just in case.
418  if (prev && next &&
419  mergePages(
420  view,
421  view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())),
422  view.peek(Keylet(ltNFTOKEN_PAGE, next->key()))))
423  cnt++;
424 
426  view,
427  view.peek(keylet::account(owner)),
428  -1 * cnt,
429  beast::Journal{beast::Journal::getNullSink()});
430 
431  return tesSUCCESS;
432 }
433 
436  ReadView const& view,
437  AccountID const& owner,
438  uint256 const& nftokenID)
439 {
440  std::shared_ptr<SLE const> page = locatePage(view, owner, nftokenID);
441 
442  // If the page couldn't be found, the given NFT isn't owned by this account
443  if (!page)
444  return std::nullopt;
445 
446  // We found a candidate page, but the given NFT may not be in it.
447  for (auto const& t : page->getFieldArray(sfNFTokens))
448  {
449  if (t[sfNFTokenID] == nftokenID)
450  return t;
451  }
452 
453  return std::nullopt;
454 }
455 
458  ApplyView& view,
459  AccountID const& owner,
460  uint256 const& nftokenID)
461 {
462  std::shared_ptr<SLE> page = locatePage(view, owner, nftokenID);
463 
464  // If the page couldn't be found, the given NFT isn't owned by this account
465  if (!page)
466  return std::nullopt;
467 
468  // We found a candidate page, but the given NFT may not be in it.
469  for (auto const& t : page->getFieldArray(sfNFTokens))
470  {
471  if (t[sfNFTokenID] == nftokenID)
472  // This std::optional constructor is explicit, so it is spelled out.
474  std::in_place, t, std::move(page));
475  }
476  return std::nullopt;
477 }
478 void
479 removeAllTokenOffers(ApplyView& view, Keylet const& directory)
480 {
481  view.dirDelete(directory, [&view](uint256 const& id) {
482  auto offer = view.peek(Keylet{ltNFTOKEN_OFFER, id});
483 
484  if (!offer)
485  Throw<std::runtime_error>(
486  "Offer " + to_string(id) + " not found in ledger!");
487 
488  auto const owner = (*offer)[sfOwner];
489 
490  if (!view.dirRemove(
491  keylet::ownerDir(owner),
492  (*offer)[sfOwnerNode],
493  offer->key(),
494  false))
495  Throw<std::runtime_error>(
496  "Offer " + to_string(id) + " not found in owner directory!");
497 
499  view,
500  view.peek(keylet::account(owner)),
501  -1,
502  beast::Journal{beast::Journal::getNullSink()});
503 
504  view.erase(offer);
505  });
506 }
507 
508 bool
510 {
511  if (offer->getType() != ltNFTOKEN_OFFER)
512  return false;
513 
514  auto const owner = (*offer)[sfOwner];
515 
516  if (!view.dirRemove(
517  keylet::ownerDir(owner),
518  (*offer)[sfOwnerNode],
519  offer->key(),
520  false))
521  return false;
522 
523  auto const nftokenID = (*offer)[sfNFTokenID];
524 
525  if (!view.dirRemove(
526  ((*offer)[sfFlags] & tfSellNFToken) ? keylet::nft_sells(nftokenID)
527  : keylet::nft_buys(nftokenID),
528  (*offer)[sfNFTokenOfferNode],
529  offer->key(),
530  false))
531  return false;
532 
534  view,
535  view.peek(keylet::account(owner)),
536  -1,
537  beast::Journal{beast::Journal::getNullSink()});
538 
539  view.erase(offer);
540  return true;
541 }
542 
543 } // namespace nft
544 } // namespace ripple
ripple::STArray::size
size_type size() const
Definition: STArray.h:248
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
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::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:457
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:235
std::back_inserter
T back_inserter(T... args)
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:435
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:301
ripple::sfNFTokenOfferNode
const SF_UINT64 sfNFTokenOfferNode
ripple::ApplyView
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:134
ripple::nft::compareTokens
static bool compareTokens(uint256 const &a, uint256 const &b)
Definition: NFTokenUtils.cpp:179
ripple::nft::pageMask
constexpr uint256 pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
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:75
ripple::nft::removeAllTokenOffers
void removeAllTokenOffers(ApplyView &view, Keylet const &directory)
Definition: NFTokenUtils.cpp:479
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:547
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::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:127
std::merge
T merge(T... args)
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:58
ripple::ApplyView::dirDelete
bool dirDelete(Keylet const &directory, std::function< void(uint256 const &)> const &)
Remove the specified directory, invoking the callback for every node.
Definition: ApplyView.cpp:338
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::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:192
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::sfFlags
const SF_UINT32 sfFlags
ripple::tecNO_SUITABLE_NFTOKEN_PAGE
@ tecNO_SUITABLE_NFTOKEN_PAGE
Definition: TER.h:285
ripple::sfNextPageMin
const SF_UINT256 sfNextPageMin
std::optional
ripple::sfPreviousPageMin
const SF_UINT256 sfPreviousPageMin
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::tecNO_ENTRY
@ tecNO_ENTRY
Definition: TER.h:270
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:36
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:64
ripple::nft::insertToken
TER insertToken(ApplyView &view, AccountID owner, STObject &&nft)
Insert the token in the owner's token directory.
Definition: NFTokenUtils.cpp:195
ripple::nft::deleteTokenOffer
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
Definition: NFTokenUtils.cpp:509
ripple::STArray::end
iterator end()
Definition: STArray.h:230
ripple::STObject::getFieldH256
uint256 getFieldH256(SField const &field) const
Definition: STObject.cpp:583