From 0e7fa14c4510a47a58a5ace483da5d3a795a0b9a Mon Sep 17 00:00:00 2001 From: intelliot Date: Mon, 20 Mar 2023 21:51:54 +0000 Subject: [PATCH] deploy: 305c9a8d61b919f8be18ff5345ccd7b050c64ddd --- DeleteAccount_8cpp_source.html | 333 +- DeleteAccount_8h_source.html | 2 +- Feature_8cpp_source.html | 114 +- Feature_8h_source.html | 12 +- LedgerFormats_8cpp_source.html | 448 +- LedgerFormats_8h_source.html | 2 +- NFTokenBurn__test_8cpp_source.html | 818 +- NFTokenDir__test_8cpp_source.html | 2812 ++-- NFTokenMint_8cpp_source.html | 182 +- NFToken__test_8cpp_source.html | 11936 ++++++++-------- SField_8cpp_source.html | 532 +- SField_8h_source.html | 384 +- STArray_8cpp_source.html | 2 +- STInteger_8cpp_source.html | 2 +- STLedgerEntry_8cpp_source.html | 2 +- STObject_8cpp_source.html | 2 +- STObject__test_8cpp_source.html | 2 +- STParsedJSON_8cpp_source.html | 4 +- classripple_1_1DeleteAccount.html | 2 +- classripple_1_1LedgerFormats.html | 2 +- classripple_1_1NFTokenBurn__test.html | 6 +- classripple_1_1NFTokenDir__test.html | 10 +- classripple_1_1NFToken__test-members.html | 35 +- classripple_1_1NFToken__test.html | 86 +- classripple_1_1SField.html | 10 +- ..._1_1test_1_1jtx_1_1token_1_1brokerFee.html | 8 +- ..._1test_1_1jtx_1_1token_1_1destination.html | 8 +- ...1_1test_1_1jtx_1_1token_1_1expiration.html | 8 +- ...pple_1_1test_1_1jtx_1_1token_1_1owner.html | 8 +- ..._1_1test_1_1jtx_1_1token_1_1rootIndex.html | 8 +- functions_func_n.html | 6 +- functions_func_o.html | 16 +- functions_func_p.html | 6 +- functions_func_r.html | 2 +- functions_func_s.html | 6 +- functions_func_t.html | 45 +- functions_func_v.html | 6 +- functions_func_w.html | 4 +- functions_n.html | 6 +- functions_o.html | 12 +- functions_p.html | 4 +- functions_q.html | 8 +- functions_r.html | 4 +- functions_s.html | 16 +- functions_t.html | 53 +- functions_v.html | 20 +- functions_w.html | 10 +- namespacemembers_c.html | 6 +- namespacemembers_f.html | 13 +- namespacemembers_func_c.html | 6 +- namespacemembers_func_g.html | 2 +- namespacemembers_func_i.html | 6 +- namespacemembers_func_r.html | 4 +- namespacemembers_g.html | 2 +- namespacemembers_i.html | 6 +- namespacemembers_r.html | 2 +- namespacemembers_s.html | 15 +- namespacemembers_t.html | 6 +- namespacemembers_vars_f.html | 3 + namespacemembers_vars_s.html | 3 + namespaceripple.html | 516 +- namespaceripple_1_1detail.html | 4 +- namespaceripple_1_1test_1_1jtx_1_1token.html | 36 +- search/all_10.js | 1246 +- search/all_11.js | 96 +- search/all_12.js | 1340 +- search/all_13.js | 2875 ++-- search/all_14.js | 3285 ++--- search/all_15.js | 708 +- search/all_16.js | 502 +- search/all_17.js | 472 +- search/all_18.js | 144 +- search/all_19.js | 14 +- search/all_1a.js | 16 +- search/all_1b.js | 710 +- search/all_3.js | 2 +- search/all_5.js | 2 +- search/all_6.js | 377 +- search/all_7.js | 1302 +- search/all_8.js | 392 +- search/all_9.js | 1578 +- search/all_a.js | 226 +- search/all_b.js | 112 +- search/all_c.js | 932 +- search/all_d.js | 1942 +-- search/all_e.js | 658 +- search/all_f.js | 674 +- search/classes_0.js | 4 +- search/classes_1.js | 396 +- search/classes_10.js | 268 +- search/classes_11.js | 22 +- search/classes_12.js | 256 +- search/classes_13.js | 664 +- search/classes_14.js | 302 +- search/classes_15.js | 408 +- search/classes_16.js | 254 +- search/classes_17.js | 144 +- search/classes_18.js | 36 +- search/classes_19.js | 4 +- search/classes_1a.js | 10 +- search/classes_2.js | 228 +- search/classes_3.js | 426 +- search/classes_4.js | 202 +- search/classes_5.js | 182 +- search/classes_6.js | 154 +- search/classes_7.js | 56 +- search/classes_8.js | 80 +- search/classes_9.js | 410 +- search/classes_a.js | 46 +- search/classes_b.js | 32 +- search/classes_c.js | 214 +- search/classes_d.js | 274 +- search/classes_e.js | 170 +- search/classes_f.js | 174 +- search/enums_0.js | 12 +- search/enums_1.js | 20 +- search/enums_10.js | 30 +- search/enums_11.js | 34 +- search/enums_12.js | 8 +- search/enums_13.js | 6 +- search/enums_14.js | 6 +- search/enums_2.js | 10 +- search/enums_3.js | 10 +- search/enums_4.js | 6 +- search/enums_5.js | 4 +- search/enums_6.js | 4 +- search/enums_7.js | 4 +- search/enums_8.js | 4 +- search/enums_9.js | 14 +- search/enums_a.js | 4 +- search/enums_b.js | 6 +- search/enums_c.js | 6 +- search/enums_d.js | 18 +- search/enums_e.js | 2 +- search/enums_f.js | 10 +- search/enumvalues_0.js | 44 +- search/enumvalues_1.js | 46 +- search/enumvalues_10.js | 4 +- search/enumvalues_11.js | 172 +- search/enumvalues_12.js | 176 +- search/enumvalues_13.js | 450 +- search/enumvalues_14.js | 28 +- search/enumvalues_15.js | 12 +- search/enumvalues_16.js | 18 +- search/enumvalues_17.js | 18 +- search/enumvalues_18.js | 2 +- search/enumvalues_2.js | 50 +- search/enumvalues_3.js | 52 +- search/enumvalues_4.js | 26 +- search/enumvalues_5.js | 36 +- search/enumvalues_6.js | 42 +- search/enumvalues_7.js | 24 +- search/enumvalues_8.js | 32 +- search/enumvalues_9.js | 96 +- search/enumvalues_a.js | 28 +- search/enumvalues_b.js | 164 +- search/enumvalues_c.js | 46 +- search/enumvalues_d.js | 66 +- search/enumvalues_e.js | 38 +- search/enumvalues_f.js | 42 +- search/files_0.js | 8 +- search/files_1.js | 4 +- search/files_10.js | 8 +- search/files_2.js | 68 +- search/files_3.js | 2 +- search/files_4.js | 4 +- search/files_5.js | 10 +- search/files_6.js | 14 +- search/files_7.js | 6 +- search/files_8.js | 8 +- search/files_9.js | 4 +- search/files_a.js | 4 +- search/files_b.js | 2 +- search/files_c.js | 8 +- search/files_d.js | 26 +- search/files_e.js | 10 +- search/files_f.js | 6 +- search/functions_0.js | 2 +- search/functions_1.js | 740 +- search/functions_10.js | 750 +- search/functions_11.js | 48 +- search/functions_12.js | 608 +- search/functions_13.js | 1342 +- search/functions_14.js | 2253 +-- search/functions_15.js | 242 +- search/functions_16.js | 174 +- search/functions_17.js | 298 +- search/functions_18.js | 82 +- search/functions_19.js | 2 +- search/functions_1a.js | 10 +- search/functions_1b.js | 710 +- search/functions_2.js | 302 +- search/functions_3.js | 924 +- search/functions_4.js | 738 +- search/functions_5.js | 300 +- search/functions_6.js | 534 +- search/functions_7.js | 1190 +- search/functions_8.js | 186 +- search/functions_9.js | 844 +- search/functions_a.js | 48 +- search/functions_b.js | 34 +- search/functions_c.js | 356 +- search/functions_d.js | 608 +- search/functions_e.js | 324 +- search/functions_f.js | 430 +- search/groups_0.js | 2 +- search/namespaces_0.js | 24 +- search/namespaces_1.js | 4 +- search/namespaces_2.js | 2 +- search/namespaces_3.js | 80 +- search/namespaces_4.js | 24 +- search/pages_0.js | 4 +- search/pages_1.js | 10 +- search/pages_2.js | 4 +- search/pages_3.js | 4 +- search/pages_4.js | 6 +- search/pages_5.js | 4 +- search/pages_6.js | 4 +- search/pages_7.js | 2 +- search/pages_8.js | 4 +- search/pages_9.js | 6 +- search/pages_a.js | 16 +- search/pages_b.js | 8 +- search/pages_c.js | 2 +- search/pages_d.js | 2 +- search/related_0.js | 8 +- search/related_1.js | 16 +- search/related_2.js | 4 +- search/related_3.js | 6 +- search/related_4.js | 2 +- search/related_5.js | 2 +- search/related_6.js | 18 +- search/related_7.js | 14 +- search/related_8.js | 34 +- search/related_9.js | 6 +- search/related_a.js | 10 +- search/related_b.js | 12 +- search/related_c.js | 4 +- search/related_d.js | 6 +- search/typedefs_0.js | 50 +- search/typedefs_1.js | 28 +- search/typedefs_10.js | 2 +- search/typedefs_11.js | 54 +- search/typedefs_12.js | 154 +- search/typedefs_13.js | 80 +- search/typedefs_14.js | 16 +- search/typedefs_15.js | 14 +- search/typedefs_16.js | 10 +- search/typedefs_17.js | 2 +- search/typedefs_18.js | 2 +- search/typedefs_2.js | 62 +- search/typedefs_3.js | 28 +- search/typedefs_4.js | 36 +- search/typedefs_5.js | 20 +- search/typedefs_6.js | 2 +- search/typedefs_7.js | 38 +- search/typedefs_8.js | 52 +- search/typedefs_9.js | 6 +- search/typedefs_a.js | 14 +- search/typedefs_b.js | 52 +- search/typedefs_c.js | 58 +- search/typedefs_d.js | 22 +- search/typedefs_e.js | 20 +- search/typedefs_f.js | 56 +- search/variables_0.js | 328 +- search/variables_1.js | 148 +- search/variables_10.js | 42 +- search/variables_11.js | 330 +- search/variables_12.js | 927 +- search/variables_13.js | 382 +- search/variables_14.js | 70 +- search/variables_15.js | 124 +- search/variables_16.js | 96 +- search/variables_17.js | 18 +- search/variables_18.js | 4 +- search/variables_19.js | 2 +- search/variables_2.js | 336 +- search/variables_3.js | 234 +- search/variables_4.js | 118 +- search/variables_5.js | 371 +- search/variables_6.js | 54 +- search/variables_7.js | 106 +- search/variables_8.js | 384 +- search/variables_9.js | 50 +- search/variables_a.js | 26 +- search/variables_b.js | 288 +- search/variables_c.js | 1122 +- search/variables_d.js | 200 +- search/variables_e.js | 98 +- search/variables_f.js | 312 +- structripple_1_1TypedField.html | 6 +- token_8cpp_source.html | 353 +- token_8h_source.html | 303 +- 293 files changed, 34388 insertions(+), 33532 deletions(-) diff --git a/DeleteAccount_8cpp_source.html b/DeleteAccount_8cpp_source.html index 89a4b15519..f738fc4e16 100644 --- a/DeleteAccount_8cpp_source.html +++ b/DeleteAccount_8cpp_source.html @@ -285,170 +285,188 @@ $(function() {
214  if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
215  return tecTOO_SOON;
216 
-
217  // Verify that the account does not own any objects that would prevent
-
218  // the account from being deleted.
-
219  Keylet const ownerDirKeylet{keylet::ownerDir(account)};
-
220  if (dirIsEmpty(ctx.view, ownerDirKeylet))
-
221  return tesSUCCESS;
-
222 
-
223  std::shared_ptr<SLE const> sleDirNode{};
-
224  unsigned int uDirEntry{0};
-
225  uint256 dirEntry{beast::zero};
-
226 
-
227  // Account has no directory at all. This _should_ have been caught
-
228  // by the dirIsEmpty() check earlier, but it's okay to catch it here.
-
229  if (!cdirFirst(
-
230  ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
-
231  return tesSUCCESS;
-
232 
-
233  std::int32_t deletableDirEntryCount{0};
-
234  do
-
235  {
-
236  // Make sure any directory node types that we find are the kind
-
237  // we can delete.
-
238  auto sleItem = ctx.view.read(keylet::child(dirEntry));
-
239  if (!sleItem)
-
240  {
-
241  // Directory node has an invalid index. Bail out.
-
242  JLOG(ctx.j.fatal())
-
243  << "DeleteAccount: directory node in ledger " << ctx.view.seq()
-
244  << " has index to object that is missing: "
-
245  << to_string(dirEntry);
-
246  return tefBAD_LEDGER;
-
247  }
-
248 
-
249  LedgerEntryType const nodeType{
-
250  safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
-
251 
-
252  if (!nonObligationDeleter(nodeType))
-
253  return tecHAS_OBLIGATIONS;
-
254 
-
255  // We found a deletable directory entry. Count it. If we find too
-
256  // many deletable directory entries then bail out.
-
257  if (++deletableDirEntryCount > maxDeletableDirEntries)
-
258  return tefTOO_BIG;
-
259 
-
260  } while (cdirNext(
-
261  ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
-
262 
-
263  return tesSUCCESS;
-
264 }
+
217  // When fixNFTokenRemint is enabled, we don't allow an account to be
+
218  // deleted if <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
+
219  // current ledger. This is to prevent having duplicate NFTokenIDs after
+
220  // account re-creation.
+
221  //
+
222  // Without this restriction, duplicate NFTokenIDs can be reproduced when
+
223  // authorized minting is involved. Because when the minter mints a NFToken,
+
224  // the issuer's sequence does not change. So when the issuer re-creates
+
225  // their account and mints a NFToken, it is possible that the
+
226  // NFTokenSequence of this NFToken is the same as the one that the
+
227  // authorized minter minted in a previous ledger.
+
228  if (ctx.view.rules().enabled(fixNFTokenRemint) &&
+
229  ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
+
230  (*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
+
231  ctx.view.seq()))
+
232  return tecTOO_SOON;
+
233 
+
234  // Verify that the account does not own any objects that would prevent
+
235  // the account from being deleted.
+
236  Keylet const ownerDirKeylet{keylet::ownerDir(account)};
+
237  if (dirIsEmpty(ctx.view, ownerDirKeylet))
+
238  return tesSUCCESS;
+
239 
+
240  std::shared_ptr<SLE const> sleDirNode{};
+
241  unsigned int uDirEntry{0};
+
242  uint256 dirEntry{beast::zero};
+
243 
+
244  // Account has no directory at all. This _should_ have been caught
+
245  // by the dirIsEmpty() check earlier, but it's okay to catch it here.
+
246  if (!cdirFirst(
+
247  ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
+
248  return tesSUCCESS;
+
249 
+
250  std::int32_t deletableDirEntryCount{0};
+
251  do
+
252  {
+
253  // Make sure any directory node types that we find are the kind
+
254  // we can delete.
+
255  auto sleItem = ctx.view.read(keylet::child(dirEntry));
+
256  if (!sleItem)
+
257  {
+
258  // Directory node has an invalid index. Bail out.
+
259  JLOG(ctx.j.fatal())
+
260  << "DeleteAccount: directory node in ledger " << ctx.view.seq()
+
261  << " has index to object that is missing: "
+
262  << to_string(dirEntry);
+
263  return tefBAD_LEDGER;
+
264  }
265 
-
266 TER
-
267 DeleteAccount::doApply()
-
268 {
-
269  auto src = view().peek(keylet::account(account_));
-
270  assert(src);
+
266  LedgerEntryType const nodeType{
+
267  safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
+
268 
+
269  if (!nonObligationDeleter(nodeType))
+
270  return tecHAS_OBLIGATIONS;
271 
-
272  auto dst = view().peek(keylet::account(ctx_.tx[sfDestination]));
-
273  assert(dst);
-
274 
-
275  if (!src || !dst)
-
276  return tefBAD_LEDGER;
-
277 
-
278  // Delete all of the entries in the account directory.
-
279  Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
-
280  std::shared_ptr<SLE> sleDirNode{};
-
281  unsigned int uDirEntry{0};
-
282  uint256 dirEntry{beast::zero};
-
283 
-
284  if (view().exists(ownerDirKeylet) &&
-
285  dirFirst(view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
-
286  {
-
287  do
-
288  {
-
289  // Choose the right way to delete each directory node.
-
290  auto sleItem = view().peek(keylet::child(dirEntry));
-
291  if (!sleItem)
-
292  {
-
293  // Directory node has an invalid index. Bail out.
-
294  JLOG(j_.fatal())
-
295  << "DeleteAccount: Directory node in ledger "
-
296  << view().seq() << " has index to object that is missing: "
-
297  << to_string(dirEntry);
-
298  return tefBAD_LEDGER;
-
299  }
+
272  // We found a deletable directory entry. Count it. If we find too
+
273  // many deletable directory entries then bail out.
+
274  if (++deletableDirEntryCount > maxDeletableDirEntries)
+
275  return tefTOO_BIG;
+
276 
+
277  } while (cdirNext(
+
278  ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
+
279 
+
280  return tesSUCCESS;
+
281 }
+
282 
+
283 TER
+
284 DeleteAccount::doApply()
+
285 {
+
286  auto src = view().peek(keylet::account(account_));
+
287  assert(src);
+
288 
+
289  auto dst = view().peek(keylet::account(ctx_.tx[sfDestination]));
+
290  assert(dst);
+
291 
+
292  if (!src || !dst)
+
293  return tefBAD_LEDGER;
+
294 
+
295  // Delete all of the entries in the account directory.
+
296  Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
+
297  std::shared_ptr<SLE> sleDirNode{};
+
298  unsigned int uDirEntry{0};
+
299  uint256 dirEntry{beast::zero};
300 
-
301  LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
-
302  sleItem->getFieldU16(sfLedgerEntryType))};
-
303 
-
304  if (auto deleter = nonObligationDeleter(nodeType))
-
305  {
-
306  TER const result{
-
307  deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
-
308 
-
309  if (!isTesSuccess(result))
-
310  return result;
-
311  }
-
312  else
-
313  {
-
314  assert(!"Undeletable entry should be found in preclaim.");
-
315  JLOG(j_.error())
-
316  << "DeleteAccount undeletable item not found in preclaim.";
-
317  return tecHAS_OBLIGATIONS;
-
318  }
-
319 
-
320  // dirFirst() and dirNext() are like iterators with exposed
-
321  // internal state. We'll take advantage of that exposed state
-
322  // to solve a common C++ problem: iterator invalidation while
-
323  // deleting elements from a container.
-
324  //
-
325  // We have just deleted one directory entry, which means our
-
326  // "iterator state" is invalid.
-
327  //
-
328  // 1. During the process of getting an entry from the
-
329  // directory uDirEntry was incremented from 0 to 1.
-
330  //
-
331  // 2. We then deleted the entry at index 0, which means the
-
332  // entry that was at 1 has now moved to 0.
-
333  //
-
334  // 3. So we verify that uDirEntry is indeed 1. Then we jam it
-
335  // back to zero to "un-invalidate" the iterator.
-
336  assert(uDirEntry == 1);
-
337  if (uDirEntry != 1)
-
338  {
-
339  JLOG(j_.error())
-
340  << "DeleteAccount iterator re-validation failed.";
-
341  return tefBAD_LEDGER;
-
342  }
-
343  uDirEntry = 0;
-
344 
-
345  } while (dirNext(
-
346  view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
-
347  }
-
348 
-
349  // Transfer any XRP remaining after the fee is paid to the destination:
-
350  (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
-
351  (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
-
352  ctx_.deliver(mSourceBalance);
-
353 
-
354  assert((*src)[sfBalance] == XRPAmount(0));
-
355 
-
356  // If there's still an owner directory associated with the source account
-
357  // delete it.
-
358  if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
-
359  {
-
360  JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
-
361  << toBase58(account_);
-
362  return tecHAS_OBLIGATIONS;
-
363  }
-
364 
-
365  // Re-arm the password change fee if we can and need to.
-
366  if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
-
367  dst->clearFlag(lsfPasswordSpent);
-
368 
-
369  view().update(dst);
-
370  view().erase(src);
-
371 
-
372  return tesSUCCESS;
-
373 }
-
374 
-
375 } // namespace ripple
+
301  if (view().exists(ownerDirKeylet) &&
+
302  dirFirst(view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
+
303  {
+
304  do
+
305  {
+
306  // Choose the right way to delete each directory node.
+
307  auto sleItem = view().peek(keylet::child(dirEntry));
+
308  if (!sleItem)
+
309  {
+
310  // Directory node has an invalid index. Bail out.
+
311  JLOG(j_.fatal())
+
312  << "DeleteAccount: Directory node in ledger "
+
313  << view().seq() << " has index to object that is missing: "
+
314  << to_string(dirEntry);
+
315  return tefBAD_LEDGER;
+
316  }
+
317 
+
318  LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
+
319  sleItem->getFieldU16(sfLedgerEntryType))};
+
320 
+
321  if (auto deleter = nonObligationDeleter(nodeType))
+
322  {
+
323  TER const result{
+
324  deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
+
325 
+
326  if (!isTesSuccess(result))
+
327  return result;
+
328  }
+
329  else
+
330  {
+
331  assert(!"Undeletable entry should be found in preclaim.");
+
332  JLOG(j_.error())
+
333  << "DeleteAccount undeletable item not found in preclaim.";
+
334  return tecHAS_OBLIGATIONS;
+
335  }
+
336 
+
337  // dirFirst() and dirNext() are like iterators with exposed
+
338  // internal state. We'll take advantage of that exposed state
+
339  // to solve a common C++ problem: iterator invalidation while
+
340  // deleting elements from a container.
+
341  //
+
342  // We have just deleted one directory entry, which means our
+
343  // "iterator state" is invalid.
+
344  //
+
345  // 1. During the process of getting an entry from the
+
346  // directory uDirEntry was incremented from 0 to 1.
+
347  //
+
348  // 2. We then deleted the entry at index 0, which means the
+
349  // entry that was at 1 has now moved to 0.
+
350  //
+
351  // 3. So we verify that uDirEntry is indeed 1. Then we jam it
+
352  // back to zero to "un-invalidate" the iterator.
+
353  assert(uDirEntry == 1);
+
354  if (uDirEntry != 1)
+
355  {
+
356  JLOG(j_.error())
+
357  << "DeleteAccount iterator re-validation failed.";
+
358  return tefBAD_LEDGER;
+
359  }
+
360  uDirEntry = 0;
+
361 
+
362  } while (dirNext(
+
363  view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
+
364  }
+
365 
+
366  // Transfer any XRP remaining after the fee is paid to the destination:
+
367  (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
+
368  (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
+
369  ctx_.deliver(mSourceBalance);
+
370 
+
371  assert((*src)[sfBalance] == XRPAmount(0));
+
372 
+
373  // If there's still an owner directory associated with the source account
+
374  // delete it.
+
375  if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
+
376  {
+
377  JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
+
378  << toBase58(account_);
+
379  return tecHAS_OBLIGATIONS;
+
380  }
+
381 
+
382  // Re-arm the password change fee if we can and need to.
+
383  if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
+
384  dst->clearFlag(lsfPasswordSpent);
+
385 
+
386  view().update(dst);
+
387  view().erase(src);
+
388 
+
389  return tesSUCCESS;
+
390 }
+
391 
+
392 } // namespace ripple
Stream fatal() const
Definition: Journal.h:339
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:303
Definition: Application.h:115
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition: View.cpp:145
+
const SF_UINT32 sfFirstNFTokenSequence
bool dirNext(ApplyView &view, uint256 const &root, std::shared_ptr< SLE > &page, unsigned int &index, uint256 &entry)
Definition: View.cpp:123
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:109
@ ltTICKET
A ledger object which describes a ticket.
Definition: LedgerFormats.h:80
@@ -473,7 +491,7 @@ $(function() {
static NotTEC preflight(PreflightContext const &ctx)
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:139
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
-
TER doApply() override
+
TER doApply() override
base_uint next() const
Definition: base_uint.h:448
static TER preclaim(PreclaimContext const &ctx)
@ temDST_IS_SRC
Definition: TER.h:103
@@ -482,6 +500,7 @@ $(function() {
@ lsfDepositAuth
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:134
Application & app
Definition: ApplyContext.h:47
+
const uint256 fixNFTokenRemint
base_uint< 256 > uint256
Definition: base_uint.h:550
const uint256 featureDeletableAccounts
XRPAmount increment
Definition: ReadView.h:53
diff --git a/DeleteAccount_8h_source.html b/DeleteAccount_8h_source.html index 715feb312e..9a62661497 100644 --- a/DeleteAccount_8h_source.html +++ b/DeleteAccount_8h_source.html @@ -126,7 +126,7 @@ $(function() {
@ Blocker
Definition: Transactor.h:101
Definition: Transactor.h:85
static NotTEC preflight(PreflightContext const &ctx)
-
TER doApply() override
+
TER doApply() override
Definition: DeleteAccount.h:29
static TER preclaim(PreclaimContext const &ctx)
static constexpr ConsequencesFactoryType ConsequencesFactory
Definition: DeleteAccount.h:32
diff --git a/Feature_8cpp_source.html b/Feature_8cpp_source.html index 6d006e6db9..3f8552a31f 100644 --- a/Feature_8cpp_source.html +++ b/Feature_8cpp_source.html @@ -502,54 +502,55 @@ $(function() {
454 REGISTER_FEATURE(XRPFees, Supported::yes, DefaultVote::no);
455 REGISTER_FIX (fixUniversalNumber, Supported::yes, DefaultVote::no);
456 REGISTER_FIX (fixNonFungibleTokensV1_2, Supported::yes, DefaultVote::no);
-
457 
-
458 // The following amendments have been active for at least two years. Their
-
459 // pre-amendment code has been removed and the identifiers are deprecated.
-
460 // All known amendments and amendments that may appear in a validated
-
461 // ledger must be registered either here or above with the "active" amendments
-
462 [[deprecated("The referenced amendment has been retired"), maybe_unused]]
-
463 uint256 const
-
464  retiredMultiSign = retireFeature("MultiSign"),
-
465  retiredTrustSetAuth = retireFeature("TrustSetAuth"),
-
466  retiredFeeEscalation = retireFeature("FeeEscalation"),
-
467  retiredPayChan = retireFeature("PayChan"),
-
468  retiredCryptoConditions = retireFeature("CryptoConditions"),
-
469  retiredTickSize = retireFeature("TickSize"),
-
470  retiredFix1368 = retireFeature("fix1368"),
-
471  retiredEscrow = retireFeature("Escrow"),
-
472  retiredFix1373 = retireFeature("fix1373"),
-
473  retiredEnforceInvariants = retireFeature("EnforceInvariants"),
-
474  retiredSortedDirectories = retireFeature("SortedDirectories"),
-
475  retiredFix1201 = retireFeature("fix1201"),
-
476  retiredFix1512 = retireFeature("fix1512"),
-
477  retiredFix1523 = retireFeature("fix1523"),
-
478  retiredFix1528 = retireFeature("fix1528");
-
479 
-
480 // clang-format on
-
481 
-
482 #undef REGISTER_FIX
-
483 #pragma pop_macro("REGISTER_FIX")
-
484 
-
485 #undef REGISTER_FEATURE
-
486 #pragma pop_macro("REGISTER_FEATURE")
-
487 
-
488 // All of the features should now be registered, since variables in a cpp file
-
489 // are initialized from top to bottom.
-
490 //
-
491 // Use initialization of one final static variable to set
-
492 // featureCollections::readOnly.
-
493 [[maybe_unused]] static const bool readOnlySet =
-
494  featureCollections.registrationIsDone();
-
495 
-
496 } // namespace ripple
+
457 REGISTER_FIX (fixNFTokenRemint, Supported::yes, DefaultVote::no);
+
458 
+
459 // The following amendments have been active for at least two years. Their
+
460 // pre-amendment code has been removed and the identifiers are deprecated.
+
461 // All known amendments and amendments that may appear in a validated
+
462 // ledger must be registered either here or above with the "active" amendments
+
463 [[deprecated("The referenced amendment has been retired"), maybe_unused]]
+
464 uint256 const
+
465  retiredMultiSign = retireFeature("MultiSign"),
+
466  retiredTrustSetAuth = retireFeature("TrustSetAuth"),
+
467  retiredFeeEscalation = retireFeature("FeeEscalation"),
+
468  retiredPayChan = retireFeature("PayChan"),
+
469  retiredCryptoConditions = retireFeature("CryptoConditions"),
+
470  retiredTickSize = retireFeature("TickSize"),
+
471  retiredFix1368 = retireFeature("fix1368"),
+
472  retiredEscrow = retireFeature("Escrow"),
+
473  retiredFix1373 = retireFeature("fix1373"),
+
474  retiredEnforceInvariants = retireFeature("EnforceInvariants"),
+
475  retiredSortedDirectories = retireFeature("SortedDirectories"),
+
476  retiredFix1201 = retireFeature("fix1201"),
+
477  retiredFix1512 = retireFeature("fix1512"),
+
478  retiredFix1523 = retireFeature("fix1523"),
+
479  retiredFix1528 = retireFeature("fix1528");
+
480 
+
481 // clang-format on
+
482 
+
483 #undef REGISTER_FIX
+
484 #pragma pop_macro("REGISTER_FIX")
+
485 
+
486 #undef REGISTER_FEATURE
+
487 #pragma pop_macro("REGISTER_FEATURE")
+
488 
+
489 // All of the features should now be registered, since variables in a cpp file
+
490 // are initialized from top to bottom.
+
491 //
+
492 // Use initialization of one final static variable to set
+
493 // featureCollections::readOnly.
+
494 [[maybe_unused]] static const bool readOnlySet =
+
495  featureCollections.registrationIsDone();
+
496 
+
497 } // namespace ripple
const uint256 fixRemoveNFTokenAutoTrustLine
const uint256 fixQualityUpperBound
const uint256 fixNFTokenNegOffer
STL class.
-
const uint256 retiredFix1528
Definition: Feature.cpp:478
-
const uint256 retiredSortedDirectories
Definition: Feature.cpp:474
+
const uint256 retiredFix1528
Definition: Feature.cpp:479
+
const uint256 retiredSortedDirectories
Definition: Feature.cpp:475
const uint256 fix1515
static constexpr std::size_t numFeatures
Definition: Feature.h:77
std::size_t hash_value(ripple::uint256 const &feature)
Definition: Feature.cpp:35
@@ -558,26 +559,27 @@ $(function() {
const uint256 fix1781
REGISTER_FEATURE(OwnerPaysFee, Supported::no, DefaultVote::no)
void check(bool condition, std::string const &message)
Definition: json/Writer.h:252
-
static const bool readOnlySet
Definition: Feature.cpp:493
-
const uint256 retiredPayChan
Definition: Feature.cpp:467
+
static const bool readOnlySet
Definition: Feature.cpp:494
+
const uint256 retiredPayChan
Definition: Feature.cpp:468
Definition: IPAddress.h:103
-
const uint256 retiredFix1368
Definition: Feature.cpp:470
-
const uint256 retiredEnforceInvariants
Definition: Feature.cpp:473
-
const uint256 retiredMultiSign
Definition: Feature.cpp:464
+
const uint256 retiredFix1368
Definition: Feature.cpp:471
+
const uint256 retiredEnforceInvariants
Definition: Feature.cpp:474
+
const uint256 retiredMultiSign
Definition: Feature.cpp:465
+
const uint256 fixNFTokenRemint
base_uint< 256 > uint256
Definition: base_uint.h:550
-
const uint256 retiredTickSize
Definition: Feature.cpp:469
+
const uint256 retiredTickSize
Definition: Feature.cpp:470
std::size_t numUpVotedAmendments()
Amendments that this server will vote for by default.
Definition: Feature.cpp:333
std::size_t numDownVotedAmendments()
Amendments that this server won't vote for by default.
Definition: Feature.cpp:326
std::map< std::string, DefaultVote > const & supportedAmendments()
Amendments that this server supports and the default voting behavior.
Definition: Feature.cpp:319
-
const uint256 retiredEscrow
Definition: Feature.cpp:471
+
const uint256 retiredEscrow
Definition: Feature.cpp:472
@ yes
const uint256 fix1513
const uint256 fixCheckThreading
-
const uint256 retiredFix1373
Definition: Feature.cpp:472
+
const uint256 retiredFix1373
Definition: Feature.cpp:473
uint256 registerFeature(std::string const &name, Supported support, DefaultVote vote)
Definition: Feature.cpp:347
const uint256 fixAmendmentMajorityCalc
-
const uint256 retiredTrustSetAuth
Definition: Feature.cpp:465
+
const uint256 retiredTrustSetAuth
Definition: Feature.cpp:466
const uint256 fixTakerDryOfferRemoval
const uint256 fix1623
size_t featureToBitsetIndex(uint256 const &f)
Definition: Feature.cpp:368
@@ -585,13 +587,13 @@ $(function() {
const uint256 fixMasterKeyAsRegularKey
REGISTER_FIX(fix1513, Supported::yes, DefaultVote::yes)
-
const uint256 retiredCryptoConditions
Definition: Feature.cpp:468
+
const uint256 retiredCryptoConditions
Definition: Feature.cpp:469
STL class.
const uint256 fixNFTokenDirV1
const uint256 fixRmSmallIncreasedQOffers
const uint256 fixTrustLinesToSelf
const uint256 fix1543
-
const uint256 retiredFeeEscalation
Definition: Feature.cpp:466
+
const uint256 retiredFeeEscalation
Definition: Feature.cpp:467
const uint256 fix1578
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
void LogicError(std::string const &how) noexcept
Called when faulty logic causes a broken invariant.
Definition: contract.cpp:48
@@ -599,14 +601,14 @@ $(function() {
const uint256 fixUniversalNumber
uint256 bitsetIndexToFeature(size_t i)
Definition: Feature.cpp:374
DefaultVote
Definition: Feature.h:69
-
const uint256 retiredFix1523
Definition: Feature.cpp:477
-
const uint256 retiredFix1201
Definition: Feature.cpp:475
+
const uint256 retiredFix1523
Definition: Feature.cpp:478
+
const uint256 retiredFix1201
Definition: Feature.cpp:476
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
uint256 retireFeature(std::string const &name)
Definition: Feature.cpp:355
const uint256 fixNonFungibleTokensV1_2
-
const uint256 retiredFix1512
Definition: Feature.cpp:476
+
const uint256 retiredFix1512
Definition: Feature.cpp:477
const uint256 fix1571
std::optional< uint256 > getRegisteredFeature(std::string const &name)
Definition: Feature.cpp:341
bool registrationIsDone()
Tell FeatureCollections when registration is complete.
Definition: Feature.cpp:362
diff --git a/Feature_8h_source.html b/Feature_8h_source.html index 222783ad1e..7526ad07d3 100644 --- a/Feature_8h_source.html +++ b/Feature_8h_source.html @@ -108,7 +108,7 @@ $(function() {
74 // Feature.cpp. Because it's only used to reserve storage, and determine how
75 // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
76 // the actual number of amendments. A LogicError on startup will verify this.
-
77 static constexpr std::size_t numFeatures = 57;
+
77 static constexpr std::size_t numFeatures = 58;
78 
82 std::map<std::string, DefaultVote> const&
83 supportedAmendments();
@@ -367,10 +367,11 @@ $(function() {
344 extern uint256 const featureXRPFees;
345 extern uint256 const fixUniversalNumber;
346 extern uint256 const fixNonFungibleTokensV1_2;
-
347 
-
348 } // namespace ripple
-
349 
-
350 #endif
+
347 extern uint256 const fixNFTokenRemint;
+
348 
+
349 } // namespace ripple
+
350 
+
351 #endif
FeatureBitset & operator|=(FeatureBitset const &rhs)
Definition: Feature.h:207
const uint256 fixRemoveNFTokenAutoTrustLine
@@ -396,6 +397,7 @@ $(function() {
friend FeatureBitset operator^(uint256 const &lhs, FeatureBitset const &rhs)
Definition: Feature.h:271
T reset(T... args)
friend FeatureBitset operator-(uint256 const &lhs, FeatureBitset const &rhs)
Definition: Feature.h:290
+
const uint256 fixNFTokenRemint
base_uint< 256 > uint256
Definition: base_uint.h:550
friend FeatureBitset operator|(uint256 const &lhs, FeatureBitset const &rhs)
Definition: Feature.h:252
const uint256 featureDeletableAccounts
diff --git a/LedgerFormats_8cpp_source.html b/LedgerFormats_8cpp_source.html index c43ee8f55c..0acc4299d6 100644 --- a/LedgerFormats_8cpp_source.html +++ b/LedgerFormats_8cpp_source.html @@ -126,234 +126,236 @@ $(function() {
55  {sfNFTokenMinter, soeOPTIONAL},
56  {sfMintedNFTokens, soeDEFAULT},
57  {sfBurnedNFTokens, soeDEFAULT},
-
58  },
-
59  commonFields);
-
60 
-
61  add(jss::DirectoryNode,
-
62  ltDIR_NODE,
-
63  {
-
64  {sfOwner, soeOPTIONAL}, // for owner directories
-
65  {sfTakerPaysCurrency, soeOPTIONAL}, // order book directories
-
66  {sfTakerPaysIssuer, soeOPTIONAL}, // order book directories
-
67  {sfTakerGetsCurrency, soeOPTIONAL}, // order book directories
-
68  {sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
-
69  {sfExchangeRate, soeOPTIONAL}, // order book directories
-
70  {sfIndexes, soeREQUIRED},
-
71  {sfRootIndex, soeREQUIRED},
-
72  {sfIndexNext, soeOPTIONAL},
-
73  {sfIndexPrevious, soeOPTIONAL},
-
74  {sfNFTokenID, soeOPTIONAL},
-
75  },
-
76  commonFields);
-
77 
-
78  add(jss::Offer,
-
79  ltOFFER,
-
80  {
-
81  {sfAccount, soeREQUIRED},
-
82  {sfSequence, soeREQUIRED},
-
83  {sfTakerPays, soeREQUIRED},
-
84  {sfTakerGets, soeREQUIRED},
-
85  {sfBookDirectory, soeREQUIRED},
-
86  {sfBookNode, soeREQUIRED},
-
87  {sfOwnerNode, soeREQUIRED},
-
88  {sfPreviousTxnID, soeREQUIRED},
-
89  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
90  {sfExpiration, soeOPTIONAL},
-
91  },
-
92  commonFields);
-
93 
-
94  add(jss::RippleState,
-
95  ltRIPPLE_STATE,
-
96  {
-
97  {sfBalance, soeREQUIRED},
-
98  {sfLowLimit, soeREQUIRED},
-
99  {sfHighLimit, soeREQUIRED},
-
100  {sfPreviousTxnID, soeREQUIRED},
-
101  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
102  {sfLowNode, soeOPTIONAL},
-
103  {sfLowQualityIn, soeOPTIONAL},
-
104  {sfLowQualityOut, soeOPTIONAL},
-
105  {sfHighNode, soeOPTIONAL},
-
106  {sfHighQualityIn, soeOPTIONAL},
-
107  {sfHighQualityOut, soeOPTIONAL},
-
108  },
-
109  commonFields);
-
110 
-
111  add(jss::Escrow,
-
112  ltESCROW,
-
113  {
-
114  {sfAccount, soeREQUIRED},
-
115  {sfDestination, soeREQUIRED},
-
116  {sfAmount, soeREQUIRED},
-
117  {sfCondition, soeOPTIONAL},
-
118  {sfCancelAfter, soeOPTIONAL},
-
119  {sfFinishAfter, soeOPTIONAL},
-
120  {sfSourceTag, soeOPTIONAL},
-
121  {sfDestinationTag, soeOPTIONAL},
-
122  {sfOwnerNode, soeREQUIRED},
-
123  {sfPreviousTxnID, soeREQUIRED},
-
124  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
125  {sfDestinationNode, soeOPTIONAL},
-
126  },
-
127  commonFields);
-
128 
-
129  add(jss::LedgerHashes,
-
130  ltLEDGER_HASHES,
-
131  {
-
132  {sfFirstLedgerSequence, soeOPTIONAL},
-
133  {sfLastLedgerSequence, soeOPTIONAL},
-
134  {sfHashes, soeREQUIRED},
-
135  },
-
136  commonFields);
-
137 
-
138  add(jss::Amendments,
-
139  ltAMENDMENTS,
-
140  {
-
141  {sfAmendments, soeOPTIONAL}, // Enabled
-
142  {sfMajorities, soeOPTIONAL},
-
143  },
-
144  commonFields);
-
145 
-
146  add(jss::FeeSettings,
-
147  ltFEE_SETTINGS,
-
148  {
-
149  // Old version uses raw numbers
-
150  {sfBaseFee, soeOPTIONAL},
-
151  {sfReferenceFeeUnits, soeOPTIONAL},
-
152  {sfReserveBase, soeOPTIONAL},
-
153  {sfReserveIncrement, soeOPTIONAL},
-
154  // New version uses Amounts
-
155  {sfBaseFeeDrops, soeOPTIONAL},
-
156  {sfReserveBaseDrops, soeOPTIONAL},
-
157  {sfReserveIncrementDrops, soeOPTIONAL},
-
158  },
-
159  commonFields);
-
160 
-
161  add(jss::Ticket,
-
162  ltTICKET,
-
163  {
-
164  {sfAccount, soeREQUIRED},
-
165  {sfOwnerNode, soeREQUIRED},
-
166  {sfTicketSequence, soeREQUIRED},
-
167  {sfPreviousTxnID, soeREQUIRED},
-
168  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
169  },
-
170  commonFields);
-
171 
-
172  // All fields are soeREQUIRED because there is always a
-
173  // SignerEntries. If there are no SignerEntries the node is deleted.
-
174  add(jss::SignerList,
-
175  ltSIGNER_LIST,
-
176  {
-
177  {sfOwnerNode, soeREQUIRED},
-
178  {sfSignerQuorum, soeREQUIRED},
-
179  {sfSignerEntries, soeREQUIRED},
-
180  {sfSignerListID, soeREQUIRED},
-
181  {sfPreviousTxnID, soeREQUIRED},
-
182  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
183  },
-
184  commonFields);
-
185 
-
186  add(jss::PayChannel,
-
187  ltPAYCHAN,
-
188  {
-
189  {sfAccount, soeREQUIRED},
-
190  {sfDestination, soeREQUIRED},
-
191  {sfAmount, soeREQUIRED},
-
192  {sfBalance, soeREQUIRED},
-
193  {sfPublicKey, soeREQUIRED},
-
194  {sfSettleDelay, soeREQUIRED},
-
195  {sfExpiration, soeOPTIONAL},
-
196  {sfCancelAfter, soeOPTIONAL},
-
197  {sfSourceTag, soeOPTIONAL},
-
198  {sfDestinationTag, soeOPTIONAL},
-
199  {sfOwnerNode, soeREQUIRED},
-
200  {sfPreviousTxnID, soeREQUIRED},
-
201  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
202  {sfDestinationNode, soeOPTIONAL},
-
203  },
-
204  commonFields);
-
205 
-
206  add(jss::Check,
-
207  ltCHECK,
-
208  {
-
209  {sfAccount, soeREQUIRED},
-
210  {sfDestination, soeREQUIRED},
-
211  {sfSendMax, soeREQUIRED},
-
212  {sfSequence, soeREQUIRED},
-
213  {sfOwnerNode, soeREQUIRED},
-
214  {sfDestinationNode, soeREQUIRED},
-
215  {sfExpiration, soeOPTIONAL},
-
216  {sfInvoiceID, soeOPTIONAL},
-
217  {sfSourceTag, soeOPTIONAL},
-
218  {sfDestinationTag, soeOPTIONAL},
-
219  {sfPreviousTxnID, soeREQUIRED},
-
220  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
221  },
-
222  commonFields);
-
223 
-
224  add(jss::DepositPreauth,
-
225  ltDEPOSIT_PREAUTH,
-
226  {
-
227  {sfAccount, soeREQUIRED},
-
228  {sfAuthorize, soeREQUIRED},
-
229  {sfOwnerNode, soeREQUIRED},
-
230  {sfPreviousTxnID, soeREQUIRED},
-
231  {sfPreviousTxnLgrSeq, soeREQUIRED},
-
232  },
-
233  commonFields);
-
234 
-
235  add(jss::NegativeUNL,
-
236  ltNEGATIVE_UNL,
-
237  {
-
238  {sfDisabledValidators, soeOPTIONAL},
-
239  {sfValidatorToDisable, soeOPTIONAL},
-
240  {sfValidatorToReEnable, soeOPTIONAL},
-
241  },
-
242  commonFields);
-
243 
-
244  add(jss::NFTokenPage,
-
245  ltNFTOKEN_PAGE,
-
246  {
-
247  {sfPreviousPageMin, soeOPTIONAL},
-
248  {sfNextPageMin, soeOPTIONAL},
-
249  {sfNFTokens, soeREQUIRED},
-
250  {sfPreviousTxnID, soeREQUIRED},
-
251  {sfPreviousTxnLgrSeq, soeREQUIRED}
-
252  },
-
253  commonFields);
-
254 
-
255  add(jss::NFTokenOffer,
-
256  ltNFTOKEN_OFFER,
-
257  {
-
258  {sfOwner, soeREQUIRED},
-
259  {sfNFTokenID, soeREQUIRED},
-
260  {sfAmount, soeREQUIRED},
-
261  {sfOwnerNode, soeREQUIRED},
-
262  {sfNFTokenOfferNode, soeREQUIRED},
-
263  {sfDestination, soeOPTIONAL},
-
264  {sfExpiration, soeOPTIONAL},
-
265  {sfPreviousTxnID, soeREQUIRED},
-
266  {sfPreviousTxnLgrSeq, soeREQUIRED}
-
267  },
-
268  commonFields);
-
269  // clang-format on
-
270 }
-
271 
-
272 LedgerFormats const&
-
273 LedgerFormats::getInstance()
-
274 {
-
275  static LedgerFormats instance;
-
276  return instance;
-
277 }
-
278 
-
279 } // namespace ripple
+
58  {sfFirstNFTokenSequence, soeOPTIONAL},
+
59  },
+
60  commonFields);
+
61 
+
62  add(jss::DirectoryNode,
+
63  ltDIR_NODE,
+
64  {
+
65  {sfOwner, soeOPTIONAL}, // for owner directories
+
66  {sfTakerPaysCurrency, soeOPTIONAL}, // order book directories
+
67  {sfTakerPaysIssuer, soeOPTIONAL}, // order book directories
+
68  {sfTakerGetsCurrency, soeOPTIONAL}, // order book directories
+
69  {sfTakerGetsIssuer, soeOPTIONAL}, // order book directories
+
70  {sfExchangeRate, soeOPTIONAL}, // order book directories
+
71  {sfIndexes, soeREQUIRED},
+
72  {sfRootIndex, soeREQUIRED},
+
73  {sfIndexNext, soeOPTIONAL},
+
74  {sfIndexPrevious, soeOPTIONAL},
+
75  {sfNFTokenID, soeOPTIONAL},
+
76  },
+
77  commonFields);
+
78 
+
79  add(jss::Offer,
+
80  ltOFFER,
+
81  {
+
82  {sfAccount, soeREQUIRED},
+
83  {sfSequence, soeREQUIRED},
+
84  {sfTakerPays, soeREQUIRED},
+
85  {sfTakerGets, soeREQUIRED},
+
86  {sfBookDirectory, soeREQUIRED},
+
87  {sfBookNode, soeREQUIRED},
+
88  {sfOwnerNode, soeREQUIRED},
+
89  {sfPreviousTxnID, soeREQUIRED},
+
90  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
91  {sfExpiration, soeOPTIONAL},
+
92  },
+
93  commonFields);
+
94 
+
95  add(jss::RippleState,
+
96  ltRIPPLE_STATE,
+
97  {
+
98  {sfBalance, soeREQUIRED},
+
99  {sfLowLimit, soeREQUIRED},
+
100  {sfHighLimit, soeREQUIRED},
+
101  {sfPreviousTxnID, soeREQUIRED},
+
102  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
103  {sfLowNode, soeOPTIONAL},
+
104  {sfLowQualityIn, soeOPTIONAL},
+
105  {sfLowQualityOut, soeOPTIONAL},
+
106  {sfHighNode, soeOPTIONAL},
+
107  {sfHighQualityIn, soeOPTIONAL},
+
108  {sfHighQualityOut, soeOPTIONAL},
+
109  },
+
110  commonFields);
+
111 
+
112  add(jss::Escrow,
+
113  ltESCROW,
+
114  {
+
115  {sfAccount, soeREQUIRED},
+
116  {sfDestination, soeREQUIRED},
+
117  {sfAmount, soeREQUIRED},
+
118  {sfCondition, soeOPTIONAL},
+
119  {sfCancelAfter, soeOPTIONAL},
+
120  {sfFinishAfter, soeOPTIONAL},
+
121  {sfSourceTag, soeOPTIONAL},
+
122  {sfDestinationTag, soeOPTIONAL},
+
123  {sfOwnerNode, soeREQUIRED},
+
124  {sfPreviousTxnID, soeREQUIRED},
+
125  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
126  {sfDestinationNode, soeOPTIONAL},
+
127  },
+
128  commonFields);
+
129 
+
130  add(jss::LedgerHashes,
+
131  ltLEDGER_HASHES,
+
132  {
+
133  {sfFirstLedgerSequence, soeOPTIONAL},
+
134  {sfLastLedgerSequence, soeOPTIONAL},
+
135  {sfHashes, soeREQUIRED},
+
136  },
+
137  commonFields);
+
138 
+
139  add(jss::Amendments,
+
140  ltAMENDMENTS,
+
141  {
+
142  {sfAmendments, soeOPTIONAL}, // Enabled
+
143  {sfMajorities, soeOPTIONAL},
+
144  },
+
145  commonFields);
+
146 
+
147  add(jss::FeeSettings,
+
148  ltFEE_SETTINGS,
+
149  {
+
150  // Old version uses raw numbers
+
151  {sfBaseFee, soeOPTIONAL},
+
152  {sfReferenceFeeUnits, soeOPTIONAL},
+
153  {sfReserveBase, soeOPTIONAL},
+
154  {sfReserveIncrement, soeOPTIONAL},
+
155  // New version uses Amounts
+
156  {sfBaseFeeDrops, soeOPTIONAL},
+
157  {sfReserveBaseDrops, soeOPTIONAL},
+
158  {sfReserveIncrementDrops, soeOPTIONAL},
+
159  },
+
160  commonFields);
+
161 
+
162  add(jss::Ticket,
+
163  ltTICKET,
+
164  {
+
165  {sfAccount, soeREQUIRED},
+
166  {sfOwnerNode, soeREQUIRED},
+
167  {sfTicketSequence, soeREQUIRED},
+
168  {sfPreviousTxnID, soeREQUIRED},
+
169  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
170  },
+
171  commonFields);
+
172 
+
173  // All fields are soeREQUIRED because there is always a
+
174  // SignerEntries. If there are no SignerEntries the node is deleted.
+
175  add(jss::SignerList,
+
176  ltSIGNER_LIST,
+
177  {
+
178  {sfOwnerNode, soeREQUIRED},
+
179  {sfSignerQuorum, soeREQUIRED},
+
180  {sfSignerEntries, soeREQUIRED},
+
181  {sfSignerListID, soeREQUIRED},
+
182  {sfPreviousTxnID, soeREQUIRED},
+
183  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
184  },
+
185  commonFields);
+
186 
+
187  add(jss::PayChannel,
+
188  ltPAYCHAN,
+
189  {
+
190  {sfAccount, soeREQUIRED},
+
191  {sfDestination, soeREQUIRED},
+
192  {sfAmount, soeREQUIRED},
+
193  {sfBalance, soeREQUIRED},
+
194  {sfPublicKey, soeREQUIRED},
+
195  {sfSettleDelay, soeREQUIRED},
+
196  {sfExpiration, soeOPTIONAL},
+
197  {sfCancelAfter, soeOPTIONAL},
+
198  {sfSourceTag, soeOPTIONAL},
+
199  {sfDestinationTag, soeOPTIONAL},
+
200  {sfOwnerNode, soeREQUIRED},
+
201  {sfPreviousTxnID, soeREQUIRED},
+
202  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
203  {sfDestinationNode, soeOPTIONAL},
+
204  },
+
205  commonFields);
+
206 
+
207  add(jss::Check,
+
208  ltCHECK,
+
209  {
+
210  {sfAccount, soeREQUIRED},
+
211  {sfDestination, soeREQUIRED},
+
212  {sfSendMax, soeREQUIRED},
+
213  {sfSequence, soeREQUIRED},
+
214  {sfOwnerNode, soeREQUIRED},
+
215  {sfDestinationNode, soeREQUIRED},
+
216  {sfExpiration, soeOPTIONAL},
+
217  {sfInvoiceID, soeOPTIONAL},
+
218  {sfSourceTag, soeOPTIONAL},
+
219  {sfDestinationTag, soeOPTIONAL},
+
220  {sfPreviousTxnID, soeREQUIRED},
+
221  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
222  },
+
223  commonFields);
+
224 
+
225  add(jss::DepositPreauth,
+
226  ltDEPOSIT_PREAUTH,
+
227  {
+
228  {sfAccount, soeREQUIRED},
+
229  {sfAuthorize, soeREQUIRED},
+
230  {sfOwnerNode, soeREQUIRED},
+
231  {sfPreviousTxnID, soeREQUIRED},
+
232  {sfPreviousTxnLgrSeq, soeREQUIRED},
+
233  },
+
234  commonFields);
+
235 
+
236  add(jss::NegativeUNL,
+
237  ltNEGATIVE_UNL,
+
238  {
+
239  {sfDisabledValidators, soeOPTIONAL},
+
240  {sfValidatorToDisable, soeOPTIONAL},
+
241  {sfValidatorToReEnable, soeOPTIONAL},
+
242  },
+
243  commonFields);
+
244 
+
245  add(jss::NFTokenPage,
+
246  ltNFTOKEN_PAGE,
+
247  {
+
248  {sfPreviousPageMin, soeOPTIONAL},
+
249  {sfNextPageMin, soeOPTIONAL},
+
250  {sfNFTokens, soeREQUIRED},
+
251  {sfPreviousTxnID, soeREQUIRED},
+
252  {sfPreviousTxnLgrSeq, soeREQUIRED}
+
253  },
+
254  commonFields);
+
255 
+
256  add(jss::NFTokenOffer,
+
257  ltNFTOKEN_OFFER,
+
258  {
+
259  {sfOwner, soeREQUIRED},
+
260  {sfNFTokenID, soeREQUIRED},
+
261  {sfAmount, soeREQUIRED},
+
262  {sfOwnerNode, soeREQUIRED},
+
263  {sfNFTokenOfferNode, soeREQUIRED},
+
264  {sfDestination, soeOPTIONAL},
+
265  {sfExpiration, soeOPTIONAL},
+
266  {sfPreviousTxnID, soeREQUIRED},
+
267  {sfPreviousTxnLgrSeq, soeREQUIRED}
+
268  },
+
269  commonFields);
+
270  // clang-format on
+
271 }
+
272 
+
273 LedgerFormats const&
+
274 LedgerFormats::getInstance()
+
275 {
+
276  static LedgerFormats instance;
+
277  return instance;
+
278 }
+
279 
+
280 } // namespace ripple
const SF_UINT64 sfIndexNext
const SF_UINT32 sfSignerListID
const SF_UINT32 sfHighQualityIn
const SF_UINT32 sfPreviousTxnLgrSeq
const SF_UINT32 sfOwnerCount
+
const SF_UINT32 sfFirstNFTokenSequence
const SF_UINT32 sfSourceTag
const SF_UINT256 sfRootIndex
@ ltTICKET
A ledger object which describes a ticket.
Definition: LedgerFormats.h:80
@@ -417,7 +419,7 @@ $(function() {
const SF_UINT64 sfExchangeRate
const SF_AMOUNT sfReserveIncrementDrops
const SF_AMOUNT sfReserveBaseDrops
-
static LedgerFormats const & getInstance()
+
static LedgerFormats const & getInstance()
const SF_UINT128 sfEmailHash
const SField sfNFTokens
const SField sfSignerEntries
diff --git a/LedgerFormats_8h_source.html b/LedgerFormats_8h_source.html index 02fc575201..62788578a3 100644 --- a/LedgerFormats_8h_source.html +++ b/LedgerFormats_8h_source.html @@ -244,7 +244,7 @@ $(function() {
@ lsfPassive
@ ltNICKNAME
A legacy, deprecated type.
-
static LedgerFormats const & getInstance()
+
static LedgerFormats const & getInstance()
@ lsfRequireDestTag
@ lsfHighNoRipple
@ lsfHighFreeze
diff --git a/NFTokenBurn__test_8cpp_source.html b/NFTokenBurn__test_8cpp_source.html index bb65887c4c..5f497de15c 100644 --- a/NFTokenBurn__test_8cpp_source.html +++ b/NFTokenBurn__test_8cpp_source.html @@ -451,424 +451,435 @@ $(function() {
380  auto internalTaxon = [&env](
381  Account const& acct,
382  std::uint32_t taxon) -> std::uint32_t {
-
383  std::uint32_t const tokenSeq = {
-
384  env.le(acct)->at(~sfMintedNFTokens).value_or(0)};
-
385  return toUInt32(
-
386  nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon)));
-
387  };
-
388 
-
389  for (std::uint32_t i = 0; i < 96; ++i)
-
390  {
-
391  // In order to fill the pages we use the taxon to break them
-
392  // into groups of 16 entries. By having the internal
-
393  // representation of the taxon go...
-
394  // 0, 3, 2, 5, 4, 7...
-
395  // in sets of 16 NFTs we can get each page to be fully
-
396  // populated.
-
397  std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
-
398  uint32_t const extTaxon = internalTaxon(alice, intTaxon);
-
399  nfts.push_back(token::getNextID(env, alice, extTaxon));
-
400  env(token::mint(alice, extTaxon));
-
401  env.close();
-
402  }
-
403 
-
404  // Sort the NFTs so they are listed in storage order, not
-
405  // creation order.
-
406  std::sort(nfts.begin(), nfts.end());
-
407 
-
408  // Verify that the ledger does indeed contain exactly three pages
-
409  // of NFTs with 32 entries in each page.
-
410  Json::Value jvParams;
-
411  jvParams[jss::ledger_index] = "current";
-
412  jvParams[jss::binary] = false;
-
413  {
-
414  Json::Value jrr = env.rpc(
-
415  "json",
-
416  "ledger_data",
-
417  boost::lexical_cast<std::string>(jvParams));
-
418 
-
419  Json::Value& state = jrr[jss::result][jss::state];
-
420 
-
421  int pageCount = 0;
-
422  for (Json::UInt i = 0; i < state.size(); ++i)
-
423  {
-
424  if (state[i].isMember(sfNFTokens.jsonName) &&
-
425  state[i][sfNFTokens.jsonName].isArray())
-
426  {
-
427  BEAST_EXPECT(
-
428  state[i][sfNFTokens.jsonName].size() == 32);
-
429  ++pageCount;
-
430  }
-
431  }
-
432  // If this check fails then the internal NFT directory logic
-
433  // has changed.
-
434  BEAST_EXPECT(pageCount == 3);
-
435  }
-
436  };
-
437 
-
438  // Generate three packed pages. Then burn the tokens in order from
-
439  // first to last. This exercises specific cases where coalescing
-
440  // pages is not possible.
-
441  std::vector<uint256> nfts;
-
442  genPackedTokens(nfts);
-
443  BEAST_EXPECT(nftCount(env, alice) == 96);
-
444  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
383  std::uint32_t tokenSeq =
+
384  env.le(acct)->at(~sfMintedNFTokens).value_or(0);
+
385 
+
386  // If fixNFTokenRemint amendment is on, we must
+
387  // add FirstNFTokenSequence.
+
388  if (env.current()->rules().enabled(fixNFTokenRemint))
+
389  tokenSeq += env.le(acct)
+
390  ->at(~sfFirstNFTokenSequence)
+
391  .value_or(env.seq(acct));
+
392 
+
393  return toUInt32(
+
394  nft::cipheredTaxon(tokenSeq, nft::toTaxon(taxon)));
+
395  };
+
396 
+
397  for (std::uint32_t i = 0; i < 96; ++i)
+
398  {
+
399  // In order to fill the pages we use the taxon to break them
+
400  // into groups of 16 entries. By having the internal
+
401  // representation of the taxon go...
+
402  // 0, 3, 2, 5, 4, 7...
+
403  // in sets of 16 NFTs we can get each page to be fully
+
404  // populated.
+
405  std::uint32_t const intTaxon = (i / 16) + (i & 0b10000 ? 2 : 0);
+
406  uint32_t const extTaxon = internalTaxon(alice, intTaxon);
+
407  nfts.push_back(token::getNextID(env, alice, extTaxon));
+
408  env(token::mint(alice, extTaxon));
+
409  env.close();
+
410  }
+
411 
+
412  // Sort the NFTs so they are listed in storage order, not
+
413  // creation order.
+
414  std::sort(nfts.begin(), nfts.end());
+
415 
+
416  // Verify that the ledger does indeed contain exactly three pages
+
417  // of NFTs with 32 entries in each page.
+
418  Json::Value jvParams;
+
419  jvParams[jss::ledger_index] = "current";
+
420  jvParams[jss::binary] = false;
+
421  {
+
422  Json::Value jrr = env.rpc(
+
423  "json",
+
424  "ledger_data",
+
425  boost::lexical_cast<std::string>(jvParams));
+
426 
+
427  Json::Value& state = jrr[jss::result][jss::state];
+
428 
+
429  int pageCount = 0;
+
430  for (Json::UInt i = 0; i < state.size(); ++i)
+
431  {
+
432  if (state[i].isMember(sfNFTokens.jsonName) &&
+
433  state[i][sfNFTokens.jsonName].isArray())
+
434  {
+
435  BEAST_EXPECT(
+
436  state[i][sfNFTokens.jsonName].size() == 32);
+
437  ++pageCount;
+
438  }
+
439  }
+
440  // If this check fails then the internal NFT directory logic
+
441  // has changed.
+
442  BEAST_EXPECT(pageCount == 3);
+
443  }
+
444  };
445 
-
446  for (uint256 const& nft : nfts)
-
447  {
-
448  env(token::burn(alice, {nft}));
-
449  env.close();
-
450  }
-
451  BEAST_EXPECT(nftCount(env, alice) == 0);
-
452  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
446  // Generate three packed pages. Then burn the tokens in order from
+
447  // first to last. This exercises specific cases where coalescing
+
448  // pages is not possible.
+
449  std::vector<uint256> nfts;
+
450  genPackedTokens(nfts);
+
451  BEAST_EXPECT(nftCount(env, alice) == 96);
+
452  BEAST_EXPECT(ownerCount(env, alice) == 3);
453 
-
454  // A lambda verifies that the ledger no longer contains any NFT pages.
-
455  auto checkNoTokenPages = [this, &env]() {
-
456  Json::Value jvParams;
-
457  jvParams[jss::ledger_index] = "current";
-
458  jvParams[jss::binary] = false;
-
459  {
-
460  Json::Value jrr = env.rpc(
-
461  "json",
-
462  "ledger_data",
-
463  boost::lexical_cast<std::string>(jvParams));
-
464 
-
465  Json::Value& state = jrr[jss::result][jss::state];
-
466 
-
467  for (Json::UInt i = 0; i < state.size(); ++i)
-
468  {
-
469  BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
-
470  }
-
471  }
-
472  };
-
473  checkNoTokenPages();
+
454  for (uint256 const& nft : nfts)
+
455  {
+
456  env(token::burn(alice, {nft}));
+
457  env.close();
+
458  }
+
459  BEAST_EXPECT(nftCount(env, alice) == 0);
+
460  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
461 
+
462  // A lambda verifies that the ledger no longer contains any NFT pages.
+
463  auto checkNoTokenPages = [this, &env]() {
+
464  Json::Value jvParams;
+
465  jvParams[jss::ledger_index] = "current";
+
466  jvParams[jss::binary] = false;
+
467  {
+
468  Json::Value jrr = env.rpc(
+
469  "json",
+
470  "ledger_data",
+
471  boost::lexical_cast<std::string>(jvParams));
+
472 
+
473  Json::Value& state = jrr[jss::result][jss::state];
474 
-
475  // Generate three packed pages. Then burn the tokens in order from
-
476  // last to first. This exercises different specific cases where
-
477  // coalescing pages is not possible.
-
478  genPackedTokens(nfts);
-
479  BEAST_EXPECT(nftCount(env, alice) == 96);
-
480  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
481 
-
482  std::reverse(nfts.begin(), nfts.end());
-
483  for (uint256 const& nft : nfts)
-
484  {
-
485  env(token::burn(alice, {nft}));
-
486  env.close();
-
487  }
-
488  BEAST_EXPECT(nftCount(env, alice) == 0);
-
489  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
490  checkNoTokenPages();
-
491 
-
492  // Generate three packed pages. Then burn all tokens in the middle
-
493  // page. This exercises the case where a page is removed between
-
494  // two fully populated pages.
-
495  genPackedTokens(nfts);
-
496  BEAST_EXPECT(nftCount(env, alice) == 96);
-
497  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
498 
-
499  for (std::size_t i = 32; i < 64; ++i)
-
500  {
-
501  env(token::burn(alice, nfts[i]));
-
502  env.close();
-
503  }
-
504  nfts.erase(nfts.begin() + 32, nfts.begin() + 64);
-
505  BEAST_EXPECT(nftCount(env, alice) == 64);
-
506  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
507 
-
508  // Burn the remaining nfts.
-
509  for (uint256 const& nft : nfts)
-
510  {
-
511  env(token::burn(alice, {nft}));
-
512  env.close();
-
513  }
-
514  BEAST_EXPECT(nftCount(env, alice) == 0);
-
515  checkNoTokenPages();
-
516  }
-
517 
-
518  void
-
519  testBurnTooManyOffers(FeatureBitset features)
-
520  {
-
521  // Look at the case where too many offers prevents burning a token.
-
522  testcase("Burn too many offers");
-
523 
-
524  using namespace test::jtx;
+
475  for (Json::UInt i = 0; i < state.size(); ++i)
+
476  {
+
477  BEAST_EXPECT(!state[i].isMember(sfNFTokens.jsonName));
+
478  }
+
479  }
+
480  };
+
481  checkNoTokenPages();
+
482 
+
483  // Generate three packed pages. Then burn the tokens in order from
+
484  // last to first. This exercises different specific cases where
+
485  // coalescing pages is not possible.
+
486  genPackedTokens(nfts);
+
487  BEAST_EXPECT(nftCount(env, alice) == 96);
+
488  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
489 
+
490  std::reverse(nfts.begin(), nfts.end());
+
491  for (uint256 const& nft : nfts)
+
492  {
+
493  env(token::burn(alice, {nft}));
+
494  env.close();
+
495  }
+
496  BEAST_EXPECT(nftCount(env, alice) == 0);
+
497  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
498  checkNoTokenPages();
+
499 
+
500  // Generate three packed pages. Then burn all tokens in the middle
+
501  // page. This exercises the case where a page is removed between
+
502  // two fully populated pages.
+
503  genPackedTokens(nfts);
+
504  BEAST_EXPECT(nftCount(env, alice) == 96);
+
505  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
506 
+
507  for (std::size_t i = 32; i < 64; ++i)
+
508  {
+
509  env(token::burn(alice, nfts[i]));
+
510  env.close();
+
511  }
+
512  nfts.erase(nfts.begin() + 32, nfts.begin() + 64);
+
513  BEAST_EXPECT(nftCount(env, alice) == 64);
+
514  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
515 
+
516  // Burn the remaining nfts.
+
517  for (uint256 const& nft : nfts)
+
518  {
+
519  env(token::burn(alice, {nft}));
+
520  env.close();
+
521  }
+
522  BEAST_EXPECT(nftCount(env, alice) == 0);
+
523  checkNoTokenPages();
+
524  }
525 
-
526  // Test what happens if a NFT is unburnable when there are
-
527  // more than 500 offers, before fixNonFungibleTokensV1_2 goes live
-
528  if (!features[fixNonFungibleTokensV1_2])
-
529  {
-
530  Env env{*this, features};
+
526  void
+
527  testBurnTooManyOffers(FeatureBitset features)
+
528  {
+
529  // Look at the case where too many offers prevents burning a token.
+
530  testcase("Burn too many offers");
531 
-
532  Account const alice("alice");
-
533  Account const becky("becky");
-
534  env.fund(XRP(1000), alice, becky);
-
535  env.close();
-
536 
-
537  // We structure the test to try and maximize the metadata produced.
-
538  // This verifies that we don't create too much metadata during a
-
539  // maximal burn operation.
-
540  //
-
541  // 1. alice mints an nft with a full-sized URI.
-
542  // 2. We create 500 new accounts, each of which creates an offer
-
543  // for alice's nft.
-
544  // 3. becky creates one more offer for alice's NFT
-
545  // 4. Attempt to burn the nft which fails because there are too
-
546  // many offers.
-
547  // 5. Cancel becky's offer and the nft should become burnable.
-
548  uint256 const nftokenID =
-
549  token::getNextID(env, alice, 0, tfTransferable);
-
550  env(token::mint(alice, 0),
-
551  token::uri(std::string(maxTokenURILength, 'u')),
-
552  txflags(tfTransferable));
-
553  env.close();
-
554 
-
555  std::vector<uint256> offerIndexes;
-
556  offerIndexes.reserve(maxTokenOfferCancelCount);
-
557  for (std::uint32_t i = 0; i < maxTokenOfferCancelCount; ++i)
-
558  {
-
559  Account const acct(std::string("acct") + std::to_string(i));
-
560  env.fund(XRP(1000), acct);
-
561  env.close();
+
532  using namespace test::jtx;
+
533 
+
534  // Test what happens if a NFT is unburnable when there are
+
535  // more than 500 offers, before fixNonFungibleTokensV1_2 goes live
+
536  if (!features[fixNonFungibleTokensV1_2])
+
537  {
+
538  Env env{*this, features};
+
539 
+
540  Account const alice("alice");
+
541  Account const becky("becky");
+
542  env.fund(XRP(1000), alice, becky);
+
543  env.close();
+
544 
+
545  // We structure the test to try and maximize the metadata produced.
+
546  // This verifies that we don't create too much metadata during a
+
547  // maximal burn operation.
+
548  //
+
549  // 1. alice mints an nft with a full-sized URI.
+
550  // 2. We create 500 new accounts, each of which creates an offer
+
551  // for alice's nft.
+
552  // 3. becky creates one more offer for alice's NFT
+
553  // 4. Attempt to burn the nft which fails because there are too
+
554  // many offers.
+
555  // 5. Cancel becky's offer and the nft should become burnable.
+
556  uint256 const nftokenID =
+
557  token::getNextID(env, alice, 0, tfTransferable);
+
558  env(token::mint(alice, 0),
+
559  token::uri(std::string(maxTokenURILength, 'u')),
+
560  txflags(tfTransferable));
+
561  env.close();
562 
-
563  offerIndexes.push_back(
-
564  keylet::nftoffer(acct, env.seq(acct)).key);
-
565  env(token::createOffer(acct, nftokenID, drops(1)),
-
566  token::owner(alice));
-
567  env.close();
-
568  }
-
569 
-
570  // Verify all offers are present in the ledger.
-
571  for (uint256 const& offerIndex : offerIndexes)
-
572  {
-
573  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
574  }
-
575 
-
576  // Create one too many offers.
-
577  uint256 const beckyOfferIndex =
-
578  keylet::nftoffer(becky, env.seq(becky)).key;
-
579  env(token::createOffer(becky, nftokenID, drops(1)),
-
580  token::owner(alice));
-
581 
-
582  // Attempt to burn the nft which should fail.
-
583  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
-
584 
-
585  // Close enough ledgers that the burn transaction is no longer
-
586  // retried.
-
587  for (int i = 0; i < 10; ++i)
-
588  env.close();
+
563  std::vector<uint256> offerIndexes;
+
564  offerIndexes.reserve(maxTokenOfferCancelCount);
+
565  for (std::uint32_t i = 0; i < maxTokenOfferCancelCount; ++i)
+
566  {
+
567  Account const acct(std::string("acct") + std::to_string(i));
+
568  env.fund(XRP(1000), acct);
+
569  env.close();
+
570 
+
571  offerIndexes.push_back(
+
572  keylet::nftoffer(acct, env.seq(acct)).key);
+
573  env(token::createOffer(acct, nftokenID, drops(1)),
+
574  token::owner(alice));
+
575  env.close();
+
576  }
+
577 
+
578  // Verify all offers are present in the ledger.
+
579  for (uint256 const& offerIndex : offerIndexes)
+
580  {
+
581  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
582  }
+
583 
+
584  // Create one too many offers.
+
585  uint256 const beckyOfferIndex =
+
586  keylet::nftoffer(becky, env.seq(becky)).key;
+
587  env(token::createOffer(becky, nftokenID, drops(1)),
+
588  token::owner(alice));
589 
-
590  // Cancel becky's offer, but alice adds a sell offer. The token
-
591  // should still not be burnable.
-
592  env(token::cancelOffer(becky, {beckyOfferIndex}));
-
593  env.close();
-
594 
-
595  uint256 const aliceOfferIndex =
-
596  keylet::nftoffer(alice, env.seq(alice)).key;
-
597  env(token::createOffer(alice, nftokenID, drops(1)),
-
598  txflags(tfSellNFToken));
-
599  env.close();
-
600 
-
601  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
-
602  env.close();
-
603 
-
604  // Cancel alice's sell offer. Now the token should be burnable.
-
605  env(token::cancelOffer(alice, {aliceOfferIndex}));
-
606  env.close();
-
607 
-
608  env(token::burn(alice, nftokenID));
-
609  env.close();
-
610 
-
611  // Burning the token should remove all the offers from the ledger.
-
612  for (uint256 const& offerIndex : offerIndexes)
-
613  {
-
614  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
-
615  }
-
616 
-
617  // Both alice and becky should have ownerCounts of zero.
-
618  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
619  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
620  }
-
621 
-
622  // Test that up to 499 buy/sell offers will be removed when NFT is
-
623  // burned after fixNonFungibleTokensV1_2 is enabled. This is to test
-
624  // that we can successfully remove all offers if the number of offers is
-
625  // less than 500.
-
626  if (features[fixNonFungibleTokensV1_2])
-
627  {
-
628  Env env{*this, features};
+
590  // Attempt to burn the nft which should fail.
+
591  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
+
592 
+
593  // Close enough ledgers that the burn transaction is no longer
+
594  // retried.
+
595  for (int i = 0; i < 10; ++i)
+
596  env.close();
+
597 
+
598  // Cancel becky's offer, but alice adds a sell offer. The token
+
599  // should still not be burnable.
+
600  env(token::cancelOffer(becky, {beckyOfferIndex}));
+
601  env.close();
+
602 
+
603  uint256 const aliceOfferIndex =
+
604  keylet::nftoffer(alice, env.seq(alice)).key;
+
605  env(token::createOffer(alice, nftokenID, drops(1)),
+
606  txflags(tfSellNFToken));
+
607  env.close();
+
608 
+
609  env(token::burn(alice, nftokenID), ter(tefTOO_BIG));
+
610  env.close();
+
611 
+
612  // Cancel alice's sell offer. Now the token should be burnable.
+
613  env(token::cancelOffer(alice, {aliceOfferIndex}));
+
614  env.close();
+
615 
+
616  env(token::burn(alice, nftokenID));
+
617  env.close();
+
618 
+
619  // Burning the token should remove all the offers from the ledger.
+
620  for (uint256 const& offerIndex : offerIndexes)
+
621  {
+
622  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
623  }
+
624 
+
625  // Both alice and becky should have ownerCounts of zero.
+
626  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
627  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
628  }
629 
-
630  Account const alice("alice");
-
631  Account const becky("becky");
-
632  env.fund(XRP(100000), alice, becky);
-
633  env.close();
-
634 
-
635  // alice creates 498 sell offers and becky creates 1 buy offers.
-
636  // When the token is burned, 498 sell offers and 1 buy offer are
-
637  // removed. In total, 499 offers are removed
-
638  std::vector<uint256> offerIndexes;
-
639  auto const nftokenID = createNftAndOffers(
-
640  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 2);
-
641 
-
642  // Verify all sell offers are present in the ledger.
-
643  for (uint256 const& offerIndex : offerIndexes)
-
644  {
-
645  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
646  }
-
647 
-
648  // Becky creates a buy offer
-
649  uint256 const beckyOfferIndex =
-
650  keylet::nftoffer(becky, env.seq(becky)).key;
-
651  env(token::createOffer(becky, nftokenID, drops(1)),
-
652  token::owner(alice));
-
653  env.close();
-
654 
-
655  // Burn the token
-
656  env(token::burn(alice, nftokenID));
-
657  env.close();
-
658 
-
659  // Burning the token should remove all 498 sell offers
-
660  // that alice created
-
661  for (uint256 const& offerIndex : offerIndexes)
-
662  {
-
663  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
-
664  }
-
665 
-
666  // Burning the token should also remove the one buy offer
-
667  // that becky created
-
668  BEAST_EXPECT(!env.le(keylet::nftoffer(beckyOfferIndex)));
-
669 
-
670  // alice and becky should have ownerCounts of zero
-
671  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
672  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
673  }
-
674 
-
675  // Test that up to 500 buy offers are removed when NFT is burned
-
676  // after fixNonFungibleTokensV1_2 is enabled
-
677  if (features[fixNonFungibleTokensV1_2])
-
678  {
-
679  Env env{*this, features};
-
680 
-
681  Account const alice("alice");
-
682  Account const becky("becky");
-
683  env.fund(XRP(100000), alice, becky);
-
684  env.close();
-
685 
-
686  // alice creates 501 sell offers for the token
-
687  // After we burn the token, 500 of the sell offers should be
-
688  // removed, and one is left over
-
689  std::vector<uint256> offerIndexes;
-
690  auto const nftokenID = createNftAndOffers(
-
691  env, alice, offerIndexes, maxDeletableTokenOfferEntries + 1);
-
692 
-
693  // Verify all sell offers are present in the ledger.
-
694  for (uint256 const& offerIndex : offerIndexes)
-
695  {
-
696  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
697  }
-
698 
-
699  // Burn the token
-
700  env(token::burn(alice, nftokenID));
-
701  env.close();
-
702 
-
703  uint32_t offerDeletedCount = 0;
-
704  // Count the number of sell offers that have been deleted
-
705  for (uint256 const& offerIndex : offerIndexes)
-
706  {
-
707  if (!env.le(keylet::nftoffer(offerIndex)))
-
708  offerDeletedCount++;
-
709  }
+
630  // Test that up to 499 buy/sell offers will be removed when NFT is
+
631  // burned after fixNonFungibleTokensV1_2 is enabled. This is to test
+
632  // that we can successfully remove all offers if the number of offers is
+
633  // less than 500.
+
634  if (features[fixNonFungibleTokensV1_2])
+
635  {
+
636  Env env{*this, features};
+
637 
+
638  Account const alice("alice");
+
639  Account const becky("becky");
+
640  env.fund(XRP(100000), alice, becky);
+
641  env.close();
+
642 
+
643  // alice creates 498 sell offers and becky creates 1 buy offers.
+
644  // When the token is burned, 498 sell offers and 1 buy offer are
+
645  // removed. In total, 499 offers are removed
+
646  std::vector<uint256> offerIndexes;
+
647  auto const nftokenID = createNftAndOffers(
+
648  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 2);
+
649 
+
650  // Verify all sell offers are present in the ledger.
+
651  for (uint256 const& offerIndex : offerIndexes)
+
652  {
+
653  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
654  }
+
655 
+
656  // Becky creates a buy offer
+
657  uint256 const beckyOfferIndex =
+
658  keylet::nftoffer(becky, env.seq(becky)).key;
+
659  env(token::createOffer(becky, nftokenID, drops(1)),
+
660  token::owner(alice));
+
661  env.close();
+
662 
+
663  // Burn the token
+
664  env(token::burn(alice, nftokenID));
+
665  env.close();
+
666 
+
667  // Burning the token should remove all 498 sell offers
+
668  // that alice created
+
669  for (uint256 const& offerIndex : offerIndexes)
+
670  {
+
671  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
672  }
+
673 
+
674  // Burning the token should also remove the one buy offer
+
675  // that becky created
+
676  BEAST_EXPECT(!env.le(keylet::nftoffer(beckyOfferIndex)));
+
677 
+
678  // alice and becky should have ownerCounts of zero
+
679  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
680  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
681  }
+
682 
+
683  // Test that up to 500 buy offers are removed when NFT is burned
+
684  // after fixNonFungibleTokensV1_2 is enabled
+
685  if (features[fixNonFungibleTokensV1_2])
+
686  {
+
687  Env env{*this, features};
+
688 
+
689  Account const alice("alice");
+
690  Account const becky("becky");
+
691  env.fund(XRP(100000), alice, becky);
+
692  env.close();
+
693 
+
694  // alice creates 501 sell offers for the token
+
695  // After we burn the token, 500 of the sell offers should be
+
696  // removed, and one is left over
+
697  std::vector<uint256> offerIndexes;
+
698  auto const nftokenID = createNftAndOffers(
+
699  env, alice, offerIndexes, maxDeletableTokenOfferEntries + 1);
+
700 
+
701  // Verify all sell offers are present in the ledger.
+
702  for (uint256 const& offerIndex : offerIndexes)
+
703  {
+
704  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
705  }
+
706 
+
707  // Burn the token
+
708  env(token::burn(alice, nftokenID));
+
709  env.close();
710 
-
711  BEAST_EXPECT(offerIndexes.size() == maxTokenOfferCancelCount + 1);
-
712 
-
713  // 500 sell offers should be removed
-
714  BEAST_EXPECT(offerDeletedCount == maxTokenOfferCancelCount);
-
715 
-
716  // alice should have ownerCounts of one for the orphaned sell offer
-
717  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
718  }
-
719 
-
720  // Test that up to 500 buy/sell offers are removed when NFT is burned
-
721  // after fixNonFungibleTokensV1_2 is enabled
-
722  if (features[fixNonFungibleTokensV1_2])
-
723  {
-
724  Env env{*this, features};
-
725 
-
726  Account const alice("alice");
-
727  Account const becky("becky");
-
728  env.fund(XRP(100000), alice, becky);
-
729  env.close();
-
730 
-
731  // alice creates 499 sell offers and becky creates 2 buy offers.
-
732  // When the token is burned, 499 sell offers and 1 buy offer
-
733  // are removed.
-
734  // In total, 500 offers are removed
-
735  std::vector<uint256> offerIndexes;
-
736  auto const nftokenID = createNftAndOffers(
-
737  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 1);
+
711  uint32_t offerDeletedCount = 0;
+
712  // Count the number of sell offers that have been deleted
+
713  for (uint256 const& offerIndex : offerIndexes)
+
714  {
+
715  if (!env.le(keylet::nftoffer(offerIndex)))
+
716  offerDeletedCount++;
+
717  }
+
718 
+
719  BEAST_EXPECT(offerIndexes.size() == maxTokenOfferCancelCount + 1);
+
720 
+
721  // 500 sell offers should be removed
+
722  BEAST_EXPECT(offerDeletedCount == maxTokenOfferCancelCount);
+
723 
+
724  // alice should have ownerCounts of one for the orphaned sell offer
+
725  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
726  }
+
727 
+
728  // Test that up to 500 buy/sell offers are removed when NFT is burned
+
729  // after fixNonFungibleTokensV1_2 is enabled
+
730  if (features[fixNonFungibleTokensV1_2])
+
731  {
+
732  Env env{*this, features};
+
733 
+
734  Account const alice("alice");
+
735  Account const becky("becky");
+
736  env.fund(XRP(100000), alice, becky);
+
737  env.close();
738 
-
739  // Verify all sell offers are present in the ledger.
-
740  for (uint256 const& offerIndex : offerIndexes)
-
741  {
-
742  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
743  }
-
744 
-
745  // becky creates 2 buy offers
-
746  env(token::createOffer(becky, nftokenID, drops(1)),
-
747  token::owner(alice));
-
748  env.close();
-
749  env(token::createOffer(becky, nftokenID, drops(1)),
-
750  token::owner(alice));
-
751  env.close();
+
739  // alice creates 499 sell offers and becky creates 2 buy offers.
+
740  // When the token is burned, 499 sell offers and 1 buy offer
+
741  // are removed.
+
742  // In total, 500 offers are removed
+
743  std::vector<uint256> offerIndexes;
+
744  auto const nftokenID = createNftAndOffers(
+
745  env, alice, offerIndexes, maxDeletableTokenOfferEntries - 1);
+
746 
+
747  // Verify all sell offers are present in the ledger.
+
748  for (uint256 const& offerIndex : offerIndexes)
+
749  {
+
750  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
751  }
752 
-
753  // Burn the token
-
754  env(token::burn(alice, nftokenID));
-
755  env.close();
-
756 
-
757  // Burning the token should remove all 499 sell offers from the
-
758  // ledger.
-
759  for (uint256 const& offerIndex : offerIndexes)
-
760  {
-
761  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
-
762  }
-
763 
-
764  // alice should have ownerCount of zero because all her
-
765  // sell offers have been deleted
-
766  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
767 
-
768  // becky has ownerCount of one due to an orphaned buy offer
-
769  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
770  }
-
771  }
-
772 
-
773  void
-
774  testWithFeats(FeatureBitset features)
-
775  {
-
776  testBurnRandom(features);
-
777  testBurnSequential(features);
-
778  testBurnTooManyOffers(features);
+
753  // becky creates 2 buy offers
+
754  env(token::createOffer(becky, nftokenID, drops(1)),
+
755  token::owner(alice));
+
756  env.close();
+
757  env(token::createOffer(becky, nftokenID, drops(1)),
+
758  token::owner(alice));
+
759  env.close();
+
760 
+
761  // Burn the token
+
762  env(token::burn(alice, nftokenID));
+
763  env.close();
+
764 
+
765  // Burning the token should remove all 499 sell offers from the
+
766  // ledger.
+
767  for (uint256 const& offerIndex : offerIndexes)
+
768  {
+
769  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
770  }
+
771 
+
772  // alice should have ownerCount of zero because all her
+
773  // sell offers have been deleted
+
774  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
775 
+
776  // becky has ownerCount of one due to an orphaned buy offer
+
777  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
778  }
779  }
780 
-
781 public:
-
782  void
-
783  run() override
-
784  {
-
785  using namespace test::jtx;
-
786  FeatureBitset const all{supported_amendments()};
-
787  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
+
781  void
+
782  testWithFeats(FeatureBitset features)
+
783  {
+
784  testBurnRandom(features);
+
785  testBurnSequential(features);
+
786  testBurnTooManyOffers(features);
+
787  }
788 
-
789  testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTDir);
-
790  testWithFeats(all - fixNonFungibleTokensV1_2);
-
791  testWithFeats(all);
-
792  }
-
793 };
-
794 
-
795 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn, tx, ripple, 3);
+
789 public:
+
790  void
+
791  run() override
+
792  {
+
793  using namespace test::jtx;
+
794  FeatureBitset const all{supported_amendments()};
+
795  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
796 
-
797 } // namespace ripple
+
797  testWithFeats(
+
798  all - fixNonFungibleTokensV1_2 - fixNFTDir - fixNFTokenRemint);
+
799  testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTokenRemint);
+
800  testWithFeats(all - fixNFTokenRemint);
+
801  testWithFeats(all);
+
802  }
+
803 };
+
804 
+
805 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBurn, tx, ripple, 3);
+
806 
+
807 } // namespace ripple
constexpr std::uint16_t maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:81
const SF_UINT32 sfOwnerCount
+
const SF_UINT32 sfFirstNFTokenSequence
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
STL class.
@@ -892,6 +903,7 @@ $(function() {
const Json::StaticString jsonName
Definition: SField.h:136
T sort(T... args)
uint256 createNftAndOffers(test::jtx::Env &env, test::jtx::Account const &owner, std::vector< uint256 > &offerIndexes, size_t const tokenCancelCount)
+
const uint256 fixNFTokenRemint
T push_back(T... args)
@@ -918,14 +930,14 @@ $(function() {
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Immutable cryptographic account descriptor.
Definition: Account.h:37
-
void testWithFeats(FeatureBitset features)
+
void testWithFeats(FeatureBitset features)
const uint256 fixNonFungibleTokensV1_2
Taxon cipheredTaxon(std::uint32_t tokenSeq, Taxon taxon)
Definition: NFTokenUtils.h:144
static std::uint32_t nftCount(test::jtx::Env &env, test::jtx::Account const &acct)
-
void testBurnTooManyOffers(FeatureBitset features)
+
void testBurnTooManyOffers(FeatureBitset features)
A transaction testing environment.
Definition: Env.h:116
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
-
void run() override
+
void run() override
Represents a JSON value.
Definition: json_value.h:145
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
diff --git a/NFTokenDir__test_8cpp_source.html b/NFTokenDir__test_8cpp_source.html index f9ffc387d1..b949c86462 100644 --- a/NFTokenDir__test_8cpp_source.html +++ b/NFTokenDir__test_8cpp_source.html @@ -261,1416 +261,1441 @@ $(function() {
190  Account const& account = accounts.emplace_back(
191  Account::base58Seed, std::string(seed));
192  env.fund(XRP(10000), account);
-
193  env.close();
-
194  }
-
195 
-
196  // All of the accounts create one NFT and and offer that NFT to
-
197  // buyer.
-
198  std::vector<uint256> nftIDs;
-
199  std::vector<uint256> offers;
-
200  offers.reserve(accounts.size());
-
201  for (Account const& account : accounts)
-
202  {
-
203  // Mint the NFT.
-
204  uint256 const& nftID = nftIDs.emplace_back(
-
205  token::getNextID(env, account, 0, tfTransferable));
-
206  env(token::mint(account, 0), txflags(tfTransferable));
-
207  env.close();
-
208 
-
209  // Create an offer to give the NFT to buyer for free.
-
210  offers.emplace_back(
-
211  keylet::nftoffer(account, env.seq(account)).key);
-
212  env(token::createOffer(account, nftID, XRP(0)),
-
213  token::destination(buyer),
-
214  txflags((tfSellNFToken)));
-
215  }
-
216  env.close();
-
217 
-
218  // buyer accepts all of the offers.
-
219  for (uint256 const& offer : offers)
-
220  {
-
221  env(token::acceptSellOffer(buyer, offer));
-
222  env.close();
-
223  }
-
224 
-
225  // This can be a good time to look at the NFT pages.
-
226  // printNFTPages(env, noisy);
-
227 
-
228  // Verify that all NFTs are owned by buyer and findable in the
-
229  // ledger by having buyer create sell offers for all of their
-
230  // NFTs. Attempting to sell an offer that the ledger can't find
-
231  // generates a non-tesSUCCESS error code.
-
232  for (uint256 const& nftID : nftIDs)
-
233  {
-
234  uint256 const offerID =
-
235  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
236  env(token::createOffer(buyer, nftID, XRP(100)),
-
237  txflags(tfSellNFToken));
-
238  env.close();
-
239 
-
240  env(token::cancelOffer(buyer, {offerID}));
-
241  }
-
242 
-
243  // Verify that all the NFTs are owned by buyer.
-
244  Json::Value buyerNFTs = [&env, &buyer]() {
-
245  Json::Value params;
-
246  params[jss::account] = buyer.human();
-
247  params[jss::type] = "state";
-
248  return env.rpc("json", "account_nfts", to_string(params));
-
249  }();
-
250 
-
251  BEAST_EXPECT(
-
252  buyerNFTs[jss::result][jss::account_nfts].size() ==
-
253  nftIDs.size());
-
254  for (Json::Value const& ownedNFT :
-
255  buyerNFTs[jss::result][jss::account_nfts])
-
256  {
-
257  uint256 ownedID;
-
258  BEAST_EXPECT(ownedID.parseHex(
-
259  ownedNFT[sfNFTokenID.jsonName].asString()));
-
260  auto const foundIter =
-
261  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
-
262 
-
263  // Assuming we find the NFT, erase it so we know it's been
-
264  // found and can't be found again.
-
265  if (BEAST_EXPECT(foundIter != nftIDs.end()))
-
266  nftIDs.erase(foundIter);
-
267  }
+
193 
+
194  // Do not close the ledger inside the loop. If
+
195  // fixNFTokenRemint is enabled and accounts are initialized
+
196  // at different ledgers, they will have different account
+
197  // sequences. That would cause the accounts to have
+
198  // different NFTokenID sequence numbers.
+
199  }
+
200  env.close();
+
201 
+
202  // All of the accounts create one NFT and and offer that NFT to
+
203  // buyer.
+
204  std::vector<uint256> nftIDs;
+
205  std::vector<uint256> offers;
+
206  offers.reserve(accounts.size());
+
207  for (Account const& account : accounts)
+
208  {
+
209  // Mint the NFT.
+
210  uint256 const& nftID = nftIDs.emplace_back(
+
211  token::getNextID(env, account, 0, tfTransferable));
+
212  env(token::mint(account, 0), txflags(tfTransferable));
+
213  env.close();
+
214 
+
215  // Create an offer to give the NFT to buyer for free.
+
216  offers.emplace_back(
+
217  keylet::nftoffer(account, env.seq(account)).key);
+
218  env(token::createOffer(account, nftID, XRP(0)),
+
219  token::destination(buyer),
+
220  txflags((tfSellNFToken)));
+
221  }
+
222  env.close();
+
223 
+
224  // buyer accepts all of the offers.
+
225  for (uint256 const& offer : offers)
+
226  {
+
227  env(token::acceptSellOffer(buyer, offer));
+
228  env.close();
+
229  }
+
230 
+
231  // This can be a good time to look at the NFT pages.
+
232  // printNFTPages(env, noisy);
+
233 
+
234  // Verify that all NFTs are owned by buyer and findable in the
+
235  // ledger by having buyer create sell offers for all of their
+
236  // NFTs. Attempting to sell an offer that the ledger can't find
+
237  // generates a non-tesSUCCESS error code.
+
238  for (uint256 const& nftID : nftIDs)
+
239  {
+
240  uint256 const offerID =
+
241  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
242  env(token::createOffer(buyer, nftID, XRP(100)),
+
243  txflags(tfSellNFToken));
+
244  env.close();
+
245 
+
246  env(token::cancelOffer(buyer, {offerID}));
+
247  }
+
248 
+
249  // Verify that all the NFTs are owned by buyer.
+
250  Json::Value buyerNFTs = [&env, &buyer]() {
+
251  Json::Value params;
+
252  params[jss::account] = buyer.human();
+
253  params[jss::type] = "state";
+
254  return env.rpc("json", "account_nfts", to_string(params));
+
255  }();
+
256 
+
257  BEAST_EXPECT(
+
258  buyerNFTs[jss::result][jss::account_nfts].size() ==
+
259  nftIDs.size());
+
260  for (Json::Value const& ownedNFT :
+
261  buyerNFTs[jss::result][jss::account_nfts])
+
262  {
+
263  uint256 ownedID;
+
264  BEAST_EXPECT(ownedID.parseHex(
+
265  ownedNFT[sfNFTokenID.jsonName].asString()));
+
266  auto const foundIter =
+
267  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
268 
-
269  // All NFTs should now be accounted for, so nftIDs should be
-
270  // empty.
-
271  BEAST_EXPECT(nftIDs.empty());
-
272  };
-
273 
-
274  // These seeds cause a lopsided split where the new NFT is added
-
275  // to the upper page.
-
276  static std::initializer_list<std::string_view const> const
-
277  splitAndAddToHi{
-
278  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
-
279  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
-
280  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
-
281  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
-
282  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
-
283  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
-
284 
-
285  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
-
286  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
-
287  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
-
288  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
-
289  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
-
290  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
-
291 
-
292  // These eight need to be kept together by the implementation.
-
293  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
-
294  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
-
295  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
-
296  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
-
297  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
-
298  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
-
299  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
-
300  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
-
301 
-
302  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
-
303  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
-
304  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
-
305  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
-
306  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
-
307  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
-
308 
-
309  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
-
310  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
-
311  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
-
312  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
-
313  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
-
314  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
-
315 
-
316  // Adding this NFT splits the page. It is added to the upper
-
317  // page.
-
318  "sp6JS7f14BuwFY8Mw6ut1hFrqWoY5", // 32. 0x503b6ba9
-
319  };
-
320 
-
321  // These seeds cause a lopsided split where the new NFT is added
-
322  // to the lower page.
-
323  static std::initializer_list<std::string_view const> const
-
324  splitAndAddToLo{
-
325  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
-
326  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
-
327  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
-
328  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
-
329  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
-
330  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
-
331 
-
332  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
-
333  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
-
334  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
-
335  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
-
336  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
-
337  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
-
338 
-
339  // These eight need to be kept together by the implementation.
-
340  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
-
341  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
-
342  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
-
343  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
-
344  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
-
345  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
-
346  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
-
347  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
-
348 
-
349  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
-
350  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
-
351  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
-
352  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
-
353  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
-
354  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
-
355 
-
356  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
-
357  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
-
358  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
-
359  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
-
360  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
-
361  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
-
362 
-
363  // Adding this NFT splits the page. It is added to the lower
-
364  // page.
-
365  "sp6JS7f14BuwFY8Mw6xCigaMwC6Dp", // 32. 0x309b67ed
-
366  };
-
367 
-
368  // Run the test cases.
-
369  exerciseLopsided(splitAndAddToHi);
-
370  exerciseLopsided(splitAndAddToLo);
-
371  }
-
372 
-
373  void
-
374  testFixNFTokenDirV1(FeatureBitset features)
-
375  {
-
376  // Exercise a fix for an off-by-one in the creation of an NFTokenPage
-
377  // index.
-
378  testcase("fixNFTokenDirV1");
-
379 
-
380  using namespace test::jtx;
-
381 
-
382  // When a single NFT page exceeds 32 entries, the code is inclined
-
383  // to split that page into two equal pieces. The new page is lower
-
384  // than the original. There was an off-by-one in the selection of
-
385  // the index for the new page. This test recreates the problem.
-
386 
-
387  // Lambda that exercises the split.
-
388  auto exerciseFixNFTokenDirV1 =
-
389  [this,
-
390  &features](std::initializer_list<std::string_view const> seeds) {
-
391  Env env{
-
392  *this,
-
393  envconfig(),
-
394  features,
-
395  nullptr,
-
396  beast::severities::kDisabled};
-
397 
-
398  // Eventually all of the NFTokens will be owned by buyer.
-
399  Account const buyer{"buyer"};
-
400  env.fund(XRP(10000), buyer);
-
401  env.close();
-
402 
-
403  // Create accounts for all of the seeds and fund those accounts.
-
404  std::vector<Account> accounts;
-
405  accounts.reserve(seeds.size());
-
406  for (std::string_view const& seed : seeds)
-
407  {
-
408  Account const& account = accounts.emplace_back(
-
409  Account::base58Seed, std::string(seed));
-
410  env.fund(XRP(10000), account);
-
411  env.close();
-
412  }
-
413 
-
414  // All of the accounts create one NFT and and offer that NFT to
-
415  // buyer.
-
416  std::vector<uint256> nftIDs;
-
417  std::vector<uint256> offers;
-
418  offers.reserve(accounts.size());
-
419  for (Account const& account : accounts)
-
420  {
-
421  // Mint the NFT.
-
422  uint256 const& nftID = nftIDs.emplace_back(
-
423  token::getNextID(env, account, 0, tfTransferable));
-
424  env(token::mint(account, 0), txflags(tfTransferable));
-
425  env.close();
-
426 
-
427  // Create an offer to give the NFT to buyer for free.
-
428  offers.emplace_back(
-
429  keylet::nftoffer(account, env.seq(account)).key);
-
430  env(token::createOffer(account, nftID, XRP(0)),
-
431  token::destination(buyer),
-
432  txflags((tfSellNFToken)));
-
433  }
-
434  env.close();
-
435 
-
436  // buyer accepts all of the but the last. The last offer
-
437  // causes the page to split.
-
438  for (std::size_t i = 0; i < offers.size() - 1; ++i)
-
439  {
-
440  env(token::acceptSellOffer(buyer, offers[i]));
-
441  env.close();
-
442  }
-
443 
-
444  // Here is the last offer. Without the fix accepting this
-
445  // offer causes tecINVARIANT_FAILED. With the fix the offer
-
446  // accept succeeds.
-
447  if (!features[fixNFTokenDirV1])
-
448  {
-
449  env(token::acceptSellOffer(buyer, offers.back()),
-
450  ter(tecINVARIANT_FAILED));
-
451  env.close();
-
452  return;
-
453  }
-
454  env(token::acceptSellOffer(buyer, offers.back()));
-
455  env.close();
-
456 
-
457  // This can be a good time to look at the NFT pages.
-
458  // printNFTPages(env, noisy);
-
459 
-
460  // Verify that all NFTs are owned by buyer and findable in the
-
461  // ledger by having buyer create sell offers for all of their
-
462  // NFTs. Attempting to sell an offer that the ledger can't find
-
463  // generates a non-tesSUCCESS error code.
-
464  for (uint256 const& nftID : nftIDs)
-
465  {
-
466  uint256 const offerID =
-
467  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
468  env(token::createOffer(buyer, nftID, XRP(100)),
-
469  txflags(tfSellNFToken));
-
470  env.close();
+
269  // Assuming we find the NFT, erase it so we know it's been
+
270  // found and can't be found again.
+
271  if (BEAST_EXPECT(foundIter != nftIDs.end()))
+
272  nftIDs.erase(foundIter);
+
273  }
+
274 
+
275  // All NFTs should now be accounted for, so nftIDs should be
+
276  // empty.
+
277  BEAST_EXPECT(nftIDs.empty());
+
278  };
+
279 
+
280  // These seeds cause a lopsided split where the new NFT is added
+
281  // to the upper page.
+
282  static std::initializer_list<std::string_view const> const
+
283  splitAndAddToHi{
+
284  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
+
285  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
+
286  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
+
287  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
+
288  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
+
289  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
+
290 
+
291  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
+
292  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
+
293  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
+
294  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
+
295  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
+
296  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
+
297 
+
298  // These eight need to be kept together by the implementation.
+
299  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
+
300  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
+
301  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
+
302  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
+
303  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
+
304  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
+
305  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
+
306  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
+
307 
+
308  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
+
309  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
+
310  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
+
311  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
+
312  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
+
313  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
+
314 
+
315  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
+
316  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
+
317  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
+
318  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
+
319  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
+
320  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
+
321 
+
322  // Adding this NFT splits the page. It is added to the upper
+
323  // page.
+
324  "sp6JS7f14BuwFY8Mw6ut1hFrqWoY5", // 32. 0x503b6ba9
+
325  };
+
326 
+
327  // These seeds cause a lopsided split where the new NFT is added
+
328  // to the lower page.
+
329  static std::initializer_list<std::string_view const> const
+
330  splitAndAddToLo{
+
331  "sp6JS7f14BuwFY8Mw5p3b8jjQBBTK", // 0. 0x1d2932ea
+
332  "sp6JS7f14BuwFY8Mw6F7X3EiGKazu", // 1. 0x1d2932ea
+
333  "sp6JS7f14BuwFY8Mw6FxjntJJfKXq", // 2. 0x1d2932ea
+
334  "sp6JS7f14BuwFY8Mw6eSF1ydEozJg", // 3. 0x1d2932ea
+
335  "sp6JS7f14BuwFY8Mw6koPB91um2ej", // 4. 0x1d2932ea
+
336  "sp6JS7f14BuwFY8Mw6m6D64iwquSe", // 5. 0x1d2932ea
+
337 
+
338  "sp6JS7f14BuwFY8Mw5rC43sN4adC2", // 6. 0x208dbc24
+
339  "sp6JS7f14BuwFY8Mw65L9DDQqgebz", // 7. 0x208dbc24
+
340  "sp6JS7f14BuwFY8Mw65nKvU8pPQNn", // 8. 0x208dbc24
+
341  "sp6JS7f14BuwFY8Mw6bxZLyTrdipw", // 9. 0x208dbc24
+
342  "sp6JS7f14BuwFY8Mw6d5abucntSoX", // 10. 0x208dbc24
+
343  "sp6JS7f14BuwFY8Mw6qXK5awrRRP8", // 11. 0x208dbc24
+
344 
+
345  // These eight need to be kept together by the implementation.
+
346  "sp6JS7f14BuwFY8Mw66EBtMxoMcCa", // 12. 0x309b67ed
+
347  "sp6JS7f14BuwFY8Mw66dGfE9jVfGv", // 13. 0x309b67ed
+
348  "sp6JS7f14BuwFY8Mw6APdZa7PH566", // 14. 0x309b67ed
+
349  "sp6JS7f14BuwFY8Mw6C3QX5CZyET5", // 15. 0x309b67ed
+
350  "sp6JS7f14BuwFY8Mw6CSysFf8GvaR", // 16. 0x309b67ed
+
351  "sp6JS7f14BuwFY8Mw6c7QSDmoAeRV", // 17. 0x309b67ed
+
352  "sp6JS7f14BuwFY8Mw6mvonveaZhW7", // 18. 0x309b67ed
+
353  "sp6JS7f14BuwFY8Mw6vtHHG7dYcXi", // 19. 0x309b67ed
+
354 
+
355  "sp6JS7f14BuwFY8Mw66yppUNxESaw", // 20. 0x40d4b96f
+
356  "sp6JS7f14BuwFY8Mw6ATYQvobXiDT", // 21. 0x40d4b96f
+
357  "sp6JS7f14BuwFY8Mw6bis8D1Wa9Uy", // 22. 0x40d4b96f
+
358  "sp6JS7f14BuwFY8Mw6cTiGCWA8Wfa", // 23. 0x40d4b96f
+
359  "sp6JS7f14BuwFY8Mw6eAy2fpXmyYf", // 24. 0x40d4b96f
+
360  "sp6JS7f14BuwFY8Mw6icn58TRs8YG", // 25. 0x40d4b96f
+
361 
+
362  "sp6JS7f14BuwFY8Mw68tj2eQEWoJt", // 26. 0x503b6ba9
+
363  "sp6JS7f14BuwFY8Mw6AjnAinNnMHT", // 27. 0x503b6ba9
+
364  "sp6JS7f14BuwFY8Mw6CKDUwB4LrhL", // 28. 0x503b6ba9
+
365  "sp6JS7f14BuwFY8Mw6d2yPszEFA6J", // 29. 0x503b6ba9
+
366  "sp6JS7f14BuwFY8Mw6jcBQBH3PfnB", // 30. 0x503b6ba9
+
367  "sp6JS7f14BuwFY8Mw6qxx19KSnN1w", // 31. 0x503b6ba9
+
368 
+
369  // Adding this NFT splits the page. It is added to the lower
+
370  // page.
+
371  "sp6JS7f14BuwFY8Mw6xCigaMwC6Dp", // 32. 0x309b67ed
+
372  };
+
373 
+
374  // Run the test cases.
+
375  exerciseLopsided(splitAndAddToHi);
+
376  exerciseLopsided(splitAndAddToLo);
+
377  }
+
378 
+
379  void
+
380  testFixNFTokenDirV1(FeatureBitset features)
+
381  {
+
382  // Exercise a fix for an off-by-one in the creation of an NFTokenPage
+
383  // index.
+
384  testcase("fixNFTokenDirV1");
+
385 
+
386  using namespace test::jtx;
+
387 
+
388  // When a single NFT page exceeds 32 entries, the code is inclined
+
389  // to split that page into two equal pieces. The new page is lower
+
390  // than the original. There was an off-by-one in the selection of
+
391  // the index for the new page. This test recreates the problem.
+
392 
+
393  // Lambda that exercises the split.
+
394  auto exerciseFixNFTokenDirV1 =
+
395  [this,
+
396  &features](std::initializer_list<std::string_view const> seeds) {
+
397  Env env{
+
398  *this,
+
399  envconfig(),
+
400  features,
+
401  nullptr,
+
402  beast::severities::kDisabled};
+
403 
+
404  // Eventually all of the NFTokens will be owned by buyer.
+
405  Account const buyer{"buyer"};
+
406  env.fund(XRP(10000), buyer);
+
407  env.close();
+
408 
+
409  // Create accounts for all of the seeds and fund those accounts.
+
410  std::vector<Account> accounts;
+
411  accounts.reserve(seeds.size());
+
412  for (std::string_view const& seed : seeds)
+
413  {
+
414  Account const& account = accounts.emplace_back(
+
415  Account::base58Seed, std::string(seed));
+
416  env.fund(XRP(10000), account);
+
417 
+
418  // Do not close the ledger inside the loop. If
+
419  // fixNFTokenRemint is enabled and accounts are initialized
+
420  // at different ledgers, they will have different account
+
421  // sequences. That would cause the accounts to have
+
422  // different NFTokenID sequence numbers.
+
423  }
+
424  env.close();
+
425 
+
426  // All of the accounts create one NFT and and offer that NFT to
+
427  // buyer.
+
428  std::vector<uint256> nftIDs;
+
429  std::vector<uint256> offers;
+
430  offers.reserve(accounts.size());
+
431  for (Account const& account : accounts)
+
432  {
+
433  // Mint the NFT.
+
434  uint256 const& nftID = nftIDs.emplace_back(
+
435  token::getNextID(env, account, 0, tfTransferable));
+
436  env(token::mint(account, 0), txflags(tfTransferable));
+
437  env.close();
+
438 
+
439  // Create an offer to give the NFT to buyer for free.
+
440  offers.emplace_back(
+
441  keylet::nftoffer(account, env.seq(account)).key);
+
442  env(token::createOffer(account, nftID, XRP(0)),
+
443  token::destination(buyer),
+
444  txflags((tfSellNFToken)));
+
445  }
+
446  env.close();
+
447 
+
448  // buyer accepts all of the but the last. The last offer
+
449  // causes the page to split.
+
450  for (std::size_t i = 0; i < offers.size() - 1; ++i)
+
451  {
+
452  env(token::acceptSellOffer(buyer, offers[i]));
+
453  env.close();
+
454  }
+
455 
+
456  // Here is the last offer. Without the fix accepting this
+
457  // offer causes tecINVARIANT_FAILED. With the fix the offer
+
458  // accept succeeds.
+
459  if (!features[fixNFTokenDirV1])
+
460  {
+
461  env(token::acceptSellOffer(buyer, offers.back()),
+
462  ter(tecINVARIANT_FAILED));
+
463  env.close();
+
464  return;
+
465  }
+
466  env(token::acceptSellOffer(buyer, offers.back()));
+
467  env.close();
+
468 
+
469  // This can be a good time to look at the NFT pages.
+
470  // printNFTPages(env, noisy);
471 
-
472  env(token::cancelOffer(buyer, {offerID}));
-
473  }
-
474 
-
475  // Verify that all the NFTs are owned by buyer.
-
476  Json::Value buyerNFTs = [&env, &buyer]() {
-
477  Json::Value params;
-
478  params[jss::account] = buyer.human();
-
479  params[jss::type] = "state";
-
480  return env.rpc("json", "account_nfts", to_string(params));
-
481  }();
-
482 
-
483  BEAST_EXPECT(
-
484  buyerNFTs[jss::result][jss::account_nfts].size() ==
-
485  nftIDs.size());
-
486  for (Json::Value const& ownedNFT :
-
487  buyerNFTs[jss::result][jss::account_nfts])
-
488  {
-
489  uint256 ownedID;
-
490  BEAST_EXPECT(ownedID.parseHex(
-
491  ownedNFT[sfNFTokenID.jsonName].asString()));
-
492  auto const foundIter =
-
493  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
+
472  // Verify that all NFTs are owned by buyer and findable in the
+
473  // ledger by having buyer create sell offers for all of their
+
474  // NFTs. Attempting to sell an offer that the ledger can't find
+
475  // generates a non-tesSUCCESS error code.
+
476  for (uint256 const& nftID : nftIDs)
+
477  {
+
478  uint256 const offerID =
+
479  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
480  env(token::createOffer(buyer, nftID, XRP(100)),
+
481  txflags(tfSellNFToken));
+
482  env.close();
+
483 
+
484  env(token::cancelOffer(buyer, {offerID}));
+
485  }
+
486 
+
487  // Verify that all the NFTs are owned by buyer.
+
488  Json::Value buyerNFTs = [&env, &buyer]() {
+
489  Json::Value params;
+
490  params[jss::account] = buyer.human();
+
491  params[jss::type] = "state";
+
492  return env.rpc("json", "account_nfts", to_string(params));
+
493  }();
494 
-
495  // Assuming we find the NFT, erase it so we know it's been
-
496  // found and can't be found again.
-
497  if (BEAST_EXPECT(foundIter != nftIDs.end()))
-
498  nftIDs.erase(foundIter);
-
499  }
-
500 
-
501  // All NFTs should now be accounted for, so nftIDs should be
-
502  // empty.
-
503  BEAST_EXPECT(nftIDs.empty());
-
504  };
-
505 
-
506  // These seeds fill the last 17 entries of the initial page with
-
507  // equivalent NFTs. The split should keep these together.
-
508  static std::initializer_list<std::string_view const> const seventeenHi{
-
509  // These 16 need to be kept together by the implementation.
-
510  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
-
511  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
-
512  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
-
513  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
-
514  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
-
515  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
-
516  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
-
517  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
-
518  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
-
519  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
-
520  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
-
521  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
-
522  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
-
523  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
-
524  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
-
525  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
-
526 
-
527  // These 17 need to be kept together by the implementation.
-
528  "sp6JS7f14BuwFY8MwjJCwYr9zSfAv", // 16. 0xabb11898
-
529  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
-
530  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
-
531  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
-
532  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
-
533  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
-
534  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
-
535  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
-
536  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
-
537  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
-
538  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
-
539  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
-
540  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
-
541  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
-
542  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
-
543  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
-
544  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
-
545  };
-
546 
-
547  // These seeds fill the first entries of the initial page with
-
548  // equivalent NFTs. The split should keep these together.
-
549  static std::initializer_list<std::string_view const> const seventeenLo{
-
550  // These 17 need to be kept together by the implementation.
-
551  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
-
552  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
-
553  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
-
554  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
-
555  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
-
556  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
-
557  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
-
558  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
-
559  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
-
560  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
-
561  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
-
562  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
-
563  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
-
564  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
-
565  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
-
566  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
-
567  "sp6JS7f14BuwFY8Mwj6TYekeeyukh", // 16. 0x399187e9
-
568 
-
569  // These 16 need to be kept together by the implementation.
-
570  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
-
571  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
-
572  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
-
573  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
-
574  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
-
575  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
-
576  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
-
577  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
-
578  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
-
579  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
-
580  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
-
581  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
-
582  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
-
583  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
-
584  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
-
585  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
-
586  };
-
587 
-
588  // Run the test cases.
-
589  exerciseFixNFTokenDirV1(seventeenHi);
-
590  exerciseFixNFTokenDirV1(seventeenLo);
-
591  }
-
592 
-
593  void
-
594  testTooManyEquivalent(FeatureBitset features)
-
595  {
-
596  // Exercise the case where 33 NFTs with identical sort
-
597  // characteristics are owned by the same account.
-
598  testcase("NFToken too many same");
+
495  BEAST_EXPECT(
+
496  buyerNFTs[jss::result][jss::account_nfts].size() ==
+
497  nftIDs.size());
+
498  for (Json::Value const& ownedNFT :
+
499  buyerNFTs[jss::result][jss::account_nfts])
+
500  {
+
501  uint256 ownedID;
+
502  BEAST_EXPECT(ownedID.parseHex(
+
503  ownedNFT[sfNFTokenID.jsonName].asString()));
+
504  auto const foundIter =
+
505  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
+
506 
+
507  // Assuming we find the NFT, erase it so we know it's been
+
508  // found and can't be found again.
+
509  if (BEAST_EXPECT(foundIter != nftIDs.end()))
+
510  nftIDs.erase(foundIter);
+
511  }
+
512 
+
513  // All NFTs should now be accounted for, so nftIDs should be
+
514  // empty.
+
515  BEAST_EXPECT(nftIDs.empty());
+
516  };
+
517 
+
518  // These seeds fill the last 17 entries of the initial page with
+
519  // equivalent NFTs. The split should keep these together.
+
520  static std::initializer_list<std::string_view const> const seventeenHi{
+
521  // These 16 need to be kept together by the implementation.
+
522  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
+
523  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
+
524  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
+
525  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
+
526  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
+
527  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
+
528  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
+
529  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
+
530  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
+
531  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
+
532  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
+
533  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
+
534  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
+
535  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
+
536  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
+
537  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
+
538 
+
539  // These 17 need to be kept together by the implementation.
+
540  "sp6JS7f14BuwFY8MwjJCwYr9zSfAv", // 16. 0xabb11898
+
541  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
+
542  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
+
543  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
+
544  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
+
545  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
+
546  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
+
547  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
+
548  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
+
549  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
+
550  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
+
551  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
+
552  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
+
553  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
+
554  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
+
555  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
+
556  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
+
557  };
+
558 
+
559  // These seeds fill the first entries of the initial page with
+
560  // equivalent NFTs. The split should keep these together.
+
561  static std::initializer_list<std::string_view const> const seventeenLo{
+
562  // These 17 need to be kept together by the implementation.
+
563  "sp6JS7f14BuwFY8Mw5EYu5z86hKDL", // 0. 0x399187e9
+
564  "sp6JS7f14BuwFY8Mw5PUAMwc5ygd7", // 1. 0x399187e9
+
565  "sp6JS7f14BuwFY8Mw5R3xUBcLSeTs", // 2. 0x399187e9
+
566  "sp6JS7f14BuwFY8Mw5W6oS5sdC3oF", // 3. 0x399187e9
+
567  "sp6JS7f14BuwFY8Mw5pYc3D9iuLcw", // 4. 0x399187e9
+
568  "sp6JS7f14BuwFY8Mw5pfGVnhcdp3b", // 5. 0x399187e9
+
569  "sp6JS7f14BuwFY8Mw6jS6RdEqXqrN", // 6. 0x399187e9
+
570  "sp6JS7f14BuwFY8Mw6krt6AKbvRXW", // 7. 0x399187e9
+
571  "sp6JS7f14BuwFY8Mw6mnVBQq7cAN2", // 8. 0x399187e9
+
572  "sp6JS7f14BuwFY8Mw8ECJxPjmkufQ", // 9. 0x399187e9
+
573  "sp6JS7f14BuwFY8Mw8asgzcceGWYm", // 10. 0x399187e9
+
574  "sp6JS7f14BuwFY8MwF6J3FXnPCgL8", // 11. 0x399187e9
+
575  "sp6JS7f14BuwFY8MwFEud2w5czv5q", // 12. 0x399187e9
+
576  "sp6JS7f14BuwFY8MwFNxKVqJnx8P5", // 13. 0x399187e9
+
577  "sp6JS7f14BuwFY8MwFnTCXg3eRidL", // 14. 0x399187e9
+
578  "sp6JS7f14BuwFY8Mwj47hv1vrDge6", // 15. 0x399187e9
+
579  "sp6JS7f14BuwFY8Mwj6TYekeeyukh", // 16. 0x399187e9
+
580 
+
581  // These 16 need to be kept together by the implementation.
+
582  "sp6JS7f14BuwFY8MwjYa5yLkgCLuT", // 17. 0xabb11898
+
583  "sp6JS7f14BuwFY8MwjenxuJ3TH2Bc", // 18. 0xabb11898
+
584  "sp6JS7f14BuwFY8MwjriN7Ui11NzB", // 19. 0xabb11898
+
585  "sp6JS7f14BuwFY8Mwk3AuoJNSEo34", // 20. 0xabb11898
+
586  "sp6JS7f14BuwFY8MwkT36hnRv8hTo", // 21. 0xabb11898
+
587  "sp6JS7f14BuwFY8MwkTQixEXfi1Cr", // 22. 0xabb11898
+
588  "sp6JS7f14BuwFY8MwkYJaZM1yTJBF", // 23. 0xabb11898
+
589  "sp6JS7f14BuwFY8Mwkc4k1uo85qp2", // 24. 0xabb11898
+
590  "sp6JS7f14BuwFY8Mwkf7cFhF1uuxx", // 25. 0xabb11898
+
591  "sp6JS7f14BuwFY8MwmCK2un99wb4e", // 26. 0xabb11898
+
592  "sp6JS7f14BuwFY8MwmETztNHYu2Bx", // 27. 0xabb11898
+
593  "sp6JS7f14BuwFY8MwmJws9UwRASfR", // 28. 0xabb11898
+
594  "sp6JS7f14BuwFY8MwoH5PQkGK8tEb", // 29. 0xabb11898
+
595  "sp6JS7f14BuwFY8MwoVXtP2yCzjJV", // 30. 0xabb11898
+
596  "sp6JS7f14BuwFY8MwobxRXA9vsTeX", // 31. 0xabb11898
+
597  "sp6JS7f14BuwFY8Mwos3pc5Gb3ihU", // 32. 0xabb11898
+
598  };
599 
-
600  using namespace test::jtx;
-
601 
-
602  Env env{*this, features};
-
603 
-
604  // Eventually all of the NFTokens will be owned by buyer.
-
605  Account const buyer{"buyer"};
-
606  env.fund(XRP(10000), buyer);
-
607  env.close();
-
608 
-
609  // Here are 33 seeds that produce identical low 32-bits in their
-
610  // corresponding AccountIDs.
-
611  static std::initializer_list<std::string_view const> const seeds{
-
612  "sp6JS7f14BuwFY8Mw5FnqmbciPvH6", // 0. 0x9a8ebed3
-
613  "sp6JS7f14BuwFY8Mw5MBGbyMSsXLp", // 1. 0x9a8ebed3
-
614  "sp6JS7f14BuwFY8Mw5S4PnDyBdKKm", // 2. 0x9a8ebed3
-
615  "sp6JS7f14BuwFY8Mw6kcXpM2enE35", // 3. 0x9a8ebed3
-
616  "sp6JS7f14BuwFY8Mw6tuuSMMwyJ44", // 4. 0x9a8ebed3
-
617  "sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt", // 5. 0x9a8ebed3
-
618  "sp6JS7f14BuwFY8Mw8WwdgWkCHhEx", // 6. 0x9a8ebed3
-
619  "sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ", // 7. 0x9a8ebed3
-
620  "sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ", // 8. 0x9a8ebed3
-
621  "sp6JS7f14BuwFY8Mw8fdSwLCZWDFd", // 9. 0x9a8ebed3
-
622  "sp6JS7f14BuwFY8Mw8zuF6Fg65i1E", // 10. 0x9a8ebed3
-
623  "sp6JS7f14BuwFY8MwF2k7bihVfqes", // 11. 0x9a8ebed3
-
624  "sp6JS7f14BuwFY8MwF6X24WXGn557", // 12. 0x9a8ebed3
-
625  "sp6JS7f14BuwFY8MwFMpn7strjekg", // 13. 0x9a8ebed3
-
626  "sp6JS7f14BuwFY8MwFSdy9sYVrwJs", // 14. 0x9a8ebed3
-
627  "sp6JS7f14BuwFY8MwFdMcLy9UkrXn", // 15. 0x9a8ebed3
-
628  "sp6JS7f14BuwFY8MwFdbwFm1AAboa", // 16. 0x9a8ebed3
-
629  "sp6JS7f14BuwFY8MwFdr5AhKThVtU", // 17. 0x9a8ebed3
-
630  "sp6JS7f14BuwFY8MwjFc3Q9YatvAw", // 18. 0x9a8ebed3
-
631  "sp6JS7f14BuwFY8MwjRXcNs1ozEXn", // 19. 0x9a8ebed3
-
632  "sp6JS7f14BuwFY8MwkQGUKL7v1FBt", // 20. 0x9a8ebed3
-
633  "sp6JS7f14BuwFY8Mwkamsoxx1wECt", // 21. 0x9a8ebed3
-
634  "sp6JS7f14BuwFY8Mwm3hus1dG6U8y", // 22. 0x9a8ebed3
-
635  "sp6JS7f14BuwFY8Mwm589M8vMRpXF", // 23. 0x9a8ebed3
-
636  "sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3", // 24. 0x9a8ebed3
-
637  "sp6JS7f14BuwFY8MwmRfy8fer4QbL", // 25. 0x9a8ebed3
-
638  "sp6JS7f14BuwFY8MwmkkFx1HtgWRx", // 26. 0x9a8ebed3
-
639  "sp6JS7f14BuwFY8MwmwP9JFdKa4PS", // 27. 0x9a8ebed3
-
640  "sp6JS7f14BuwFY8MwoXWJLB3ciHfo", // 28. 0x9a8ebed3
-
641  "sp6JS7f14BuwFY8MwoYc1gTtT2mWL", // 29. 0x9a8ebed3
-
642  "sp6JS7f14BuwFY8MwogXtHH7FNVoo", // 30. 0x9a8ebed3
-
643  "sp6JS7f14BuwFY8MwoqYoA9P8gf3r", // 31. 0x9a8ebed3
-
644  "sp6JS7f14BuwFY8MwoujwMJofGnsA", // 32. 0x9a8ebed3
-
645  };
-
646 
-
647  // Create accounts for all of the seeds and fund those accounts.
-
648  std::vector<Account> accounts;
-
649  accounts.reserve(seeds.size());
-
650  for (std::string_view const& seed : seeds)
-
651  {
-
652  Account const& account =
-
653  accounts.emplace_back(Account::base58Seed, std::string(seed));
-
654  env.fund(XRP(10000), account);
-
655  env.close();
-
656  }
-
657 
-
658  // All of the accounts create one NFT and and offer that NFT to buyer.
-
659  std::vector<uint256> nftIDs;
-
660  std::vector<uint256> offers;
-
661  offers.reserve(accounts.size());
-
662  for (Account const& account : accounts)
+
600  // Run the test cases.
+
601  exerciseFixNFTokenDirV1(seventeenHi);
+
602  exerciseFixNFTokenDirV1(seventeenLo);
+
603  }
+
604 
+
605  void
+
606  testTooManyEquivalent(FeatureBitset features)
+
607  {
+
608  // Exercise the case where 33 NFTs with identical sort
+
609  // characteristics are owned by the same account.
+
610  testcase("NFToken too many same");
+
611 
+
612  using namespace test::jtx;
+
613 
+
614  Env env{*this, features};
+
615 
+
616  // Eventually all of the NFTokens will be owned by buyer.
+
617  Account const buyer{"buyer"};
+
618  env.fund(XRP(10000), buyer);
+
619  env.close();
+
620 
+
621  // Here are 33 seeds that produce identical low 32-bits in their
+
622  // corresponding AccountIDs.
+
623  static std::initializer_list<std::string_view const> const seeds{
+
624  "sp6JS7f14BuwFY8Mw5FnqmbciPvH6", // 0. 0x9a8ebed3
+
625  "sp6JS7f14BuwFY8Mw5MBGbyMSsXLp", // 1. 0x9a8ebed3
+
626  "sp6JS7f14BuwFY8Mw5S4PnDyBdKKm", // 2. 0x9a8ebed3
+
627  "sp6JS7f14BuwFY8Mw6kcXpM2enE35", // 3. 0x9a8ebed3
+
628  "sp6JS7f14BuwFY8Mw6tuuSMMwyJ44", // 4. 0x9a8ebed3
+
629  "sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt", // 5. 0x9a8ebed3
+
630  "sp6JS7f14BuwFY8Mw8WwdgWkCHhEx", // 6. 0x9a8ebed3
+
631  "sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ", // 7. 0x9a8ebed3
+
632  "sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ", // 8. 0x9a8ebed3
+
633  "sp6JS7f14BuwFY8Mw8fdSwLCZWDFd", // 9. 0x9a8ebed3
+
634  "sp6JS7f14BuwFY8Mw8zuF6Fg65i1E", // 10. 0x9a8ebed3
+
635  "sp6JS7f14BuwFY8MwF2k7bihVfqes", // 11. 0x9a8ebed3
+
636  "sp6JS7f14BuwFY8MwF6X24WXGn557", // 12. 0x9a8ebed3
+
637  "sp6JS7f14BuwFY8MwFMpn7strjekg", // 13. 0x9a8ebed3
+
638  "sp6JS7f14BuwFY8MwFSdy9sYVrwJs", // 14. 0x9a8ebed3
+
639  "sp6JS7f14BuwFY8MwFdMcLy9UkrXn", // 15. 0x9a8ebed3
+
640  "sp6JS7f14BuwFY8MwFdbwFm1AAboa", // 16. 0x9a8ebed3
+
641  "sp6JS7f14BuwFY8MwFdr5AhKThVtU", // 17. 0x9a8ebed3
+
642  "sp6JS7f14BuwFY8MwjFc3Q9YatvAw", // 18. 0x9a8ebed3
+
643  "sp6JS7f14BuwFY8MwjRXcNs1ozEXn", // 19. 0x9a8ebed3
+
644  "sp6JS7f14BuwFY8MwkQGUKL7v1FBt", // 20. 0x9a8ebed3
+
645  "sp6JS7f14BuwFY8Mwkamsoxx1wECt", // 21. 0x9a8ebed3
+
646  "sp6JS7f14BuwFY8Mwm3hus1dG6U8y", // 22. 0x9a8ebed3
+
647  "sp6JS7f14BuwFY8Mwm589M8vMRpXF", // 23. 0x9a8ebed3
+
648  "sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3", // 24. 0x9a8ebed3
+
649  "sp6JS7f14BuwFY8MwmRfy8fer4QbL", // 25. 0x9a8ebed3
+
650  "sp6JS7f14BuwFY8MwmkkFx1HtgWRx", // 26. 0x9a8ebed3
+
651  "sp6JS7f14BuwFY8MwmwP9JFdKa4PS", // 27. 0x9a8ebed3
+
652  "sp6JS7f14BuwFY8MwoXWJLB3ciHfo", // 28. 0x9a8ebed3
+
653  "sp6JS7f14BuwFY8MwoYc1gTtT2mWL", // 29. 0x9a8ebed3
+
654  "sp6JS7f14BuwFY8MwogXtHH7FNVoo", // 30. 0x9a8ebed3
+
655  "sp6JS7f14BuwFY8MwoqYoA9P8gf3r", // 31. 0x9a8ebed3
+
656  "sp6JS7f14BuwFY8MwoujwMJofGnsA", // 32. 0x9a8ebed3
+
657  };
+
658 
+
659  // Create accounts for all of the seeds and fund those accounts.
+
660  std::vector<Account> accounts;
+
661  accounts.reserve(seeds.size());
+
662  for (std::string_view const& seed : seeds)
663  {
-
664  // Mint the NFT.
-
665  uint256 const& nftID = nftIDs.emplace_back(
-
666  token::getNextID(env, account, 0, tfTransferable));
-
667  env(token::mint(account, 0), txflags(tfTransferable));
-
668  env.close();
-
669 
-
670  // Create an offer to give the NFT to buyer for free.
-
671  offers.emplace_back(
-
672  keylet::nftoffer(account, env.seq(account)).key);
-
673  env(token::createOffer(account, nftID, XRP(0)),
-
674  token::destination(buyer),
-
675  txflags((tfSellNFToken)));
-
676  }
-
677  env.close();
-
678 
-
679  // Verify that the low 96 bits of all generated NFTs is identical.
-
680  uint256 const expectLowBits = nftIDs.front() & nft::pageMask;
-
681  for (uint256 const& nftID : nftIDs)
-
682  {
-
683  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
-
684  }
-
685 
-
686  // Remove one NFT and offer from the vectors. This offer is the one
-
687  // that will overflow the page.
-
688  nftIDs.pop_back();
-
689  uint256 const offerForPageOverflow = offers.back();
-
690  offers.pop_back();
-
691 
-
692  // buyer accepts all of the offers but one.
-
693  for (uint256 const& offer : offers)
-
694  {
-
695  env(token::acceptSellOffer(buyer, offer));
-
696  env.close();
-
697  }
-
698 
-
699  // buyer accepts the last offer which causes a page overflow.
-
700  env(token::acceptSellOffer(buyer, offerForPageOverflow),
-
701  ter(tecNO_SUITABLE_NFTOKEN_PAGE));
-
702 
-
703  // Verify that all expected NFTs are owned by buyer and findable in
-
704  // the ledger by having buyer create sell offers for all of their NFTs.
-
705  // Attempting to sell an offer that the ledger can't find generates
-
706  // a non-tesSUCCESS error code.
-
707  for (uint256 const& nftID : nftIDs)
-
708  {
-
709  uint256 const offerID = keylet::nftoffer(buyer, env.seq(buyer)).key;
-
710  env(token::createOffer(buyer, nftID, XRP(100)),
-
711  txflags(tfSellNFToken));
-
712  env.close();
-
713 
-
714  env(token::cancelOffer(buyer, {offerID}));
+
664  Account const& account =
+
665  accounts.emplace_back(Account::base58Seed, std::string(seed));
+
666  env.fund(XRP(10000), account);
+
667 
+
668  // Do not close the ledger inside the loop. If
+
669  // fixNFTokenRemint is enabled and accounts are initialized
+
670  // at different ledgers, they will have different account
+
671  // sequences. That would cause the accounts to have
+
672  // different NFTokenID sequence numbers.
+
673  }
+
674  env.close();
+
675 
+
676  // All of the accounts create one NFT and and offer that NFT to buyer.
+
677  std::vector<uint256> nftIDs;
+
678  std::vector<uint256> offers;
+
679  offers.reserve(accounts.size());
+
680  for (Account const& account : accounts)
+
681  {
+
682  // Mint the NFT.
+
683  uint256 const& nftID = nftIDs.emplace_back(
+
684  token::getNextID(env, account, 0, tfTransferable));
+
685  env(token::mint(account, 0), txflags(tfTransferable));
+
686  env.close();
+
687 
+
688  // Create an offer to give the NFT to buyer for free.
+
689  offers.emplace_back(
+
690  keylet::nftoffer(account, env.seq(account)).key);
+
691  env(token::createOffer(account, nftID, XRP(0)),
+
692  token::destination(buyer),
+
693  txflags((tfSellNFToken)));
+
694  }
+
695  env.close();
+
696 
+
697  // Verify that the low 96 bits of all generated NFTs is identical.
+
698  uint256 const expectLowBits = nftIDs.front() & nft::pageMask;
+
699  for (uint256 const& nftID : nftIDs)
+
700  {
+
701  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
+
702  }
+
703 
+
704  // Remove one NFT and offer from the vectors. This offer is the one
+
705  // that will overflow the page.
+
706  nftIDs.pop_back();
+
707  uint256 const offerForPageOverflow = offers.back();
+
708  offers.pop_back();
+
709 
+
710  // buyer accepts all of the offers but one.
+
711  for (uint256 const& offer : offers)
+
712  {
+
713  env(token::acceptSellOffer(buyer, offer));
+
714  env.close();
715  }
716 
-
717  // Verify that all the NFTs are owned by buyer.
-
718  Json::Value buyerNFTs = [&env, &buyer]() {
-
719  Json::Value params;
-
720  params[jss::account] = buyer.human();
-
721  params[jss::type] = "state";
-
722  return env.rpc("json", "account_nfts", to_string(params));
-
723  }();
-
724 
-
725  BEAST_EXPECT(
-
726  buyerNFTs[jss::result][jss::account_nfts].size() == nftIDs.size());
-
727  for (Json::Value const& ownedNFT :
-
728  buyerNFTs[jss::result][jss::account_nfts])
-
729  {
-
730  uint256 ownedID;
-
731  BEAST_EXPECT(
-
732  ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString()));
-
733  auto const foundIter =
-
734  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
-
735 
-
736  // Assuming we find the NFT, erase it so we know it's been found
-
737  // and can't be found again.
-
738  if (BEAST_EXPECT(foundIter != nftIDs.end()))
-
739  nftIDs.erase(foundIter);
-
740  }
-
741 
-
742  // All NFTs should now be accounted for, so nftIDs should be empty.
-
743  BEAST_EXPECT(nftIDs.empty());
-
744 
-
745  // Show that Without fixNFTokenDirV1 no more NFTs can be added to
-
746  // buyer. Also show that fixNFTokenDirV1 fixes the problem.
-
747  TER const expect = features[fixNFTokenDirV1]
-
748  ? static_cast<TER>(tesSUCCESS)
-
749  : static_cast<TER>(tecNO_SUITABLE_NFTOKEN_PAGE);
-
750  env(token::mint(buyer, 0), txflags(tfTransferable), ter(expect));
-
751  env.close();
-
752  }
+
717  // buyer accepts the last offer which causes a page overflow.
+
718  env(token::acceptSellOffer(buyer, offerForPageOverflow),
+
719  ter(tecNO_SUITABLE_NFTOKEN_PAGE));
+
720 
+
721  // Verify that all expected NFTs are owned by buyer and findable in
+
722  // the ledger by having buyer create sell offers for all of their NFTs.
+
723  // Attempting to sell an offer that the ledger can't find generates
+
724  // a non-tesSUCCESS error code.
+
725  for (uint256 const& nftID : nftIDs)
+
726  {
+
727  uint256 const offerID = keylet::nftoffer(buyer, env.seq(buyer)).key;
+
728  env(token::createOffer(buyer, nftID, XRP(100)),
+
729  txflags(tfSellNFToken));
+
730  env.close();
+
731 
+
732  env(token::cancelOffer(buyer, {offerID}));
+
733  }
+
734 
+
735  // Verify that all the NFTs are owned by buyer.
+
736  Json::Value buyerNFTs = [&env, &buyer]() {
+
737  Json::Value params;
+
738  params[jss::account] = buyer.human();
+
739  params[jss::type] = "state";
+
740  return env.rpc("json", "account_nfts", to_string(params));
+
741  }();
+
742 
+
743  BEAST_EXPECT(
+
744  buyerNFTs[jss::result][jss::account_nfts].size() == nftIDs.size());
+
745  for (Json::Value const& ownedNFT :
+
746  buyerNFTs[jss::result][jss::account_nfts])
+
747  {
+
748  uint256 ownedID;
+
749  BEAST_EXPECT(
+
750  ownedID.parseHex(ownedNFT[sfNFTokenID.jsonName].asString()));
+
751  auto const foundIter =
+
752  std::find(nftIDs.begin(), nftIDs.end(), ownedID);
753 
-
754  void
-
755  testConsecutivePacking(FeatureBitset features)
-
756  {
-
757  // We'll make a worst case scenario for NFT packing:
-
758  //
-
759  // 1. 33 accounts with identical low-32 bits mint 7 consecutive NFTs.
-
760  // 2. The taxon is manipulated to always be stored as zero.
-
761  // 3. A single account buys all 7x32 of the 33 NFTs.
-
762  //
-
763  // All of the NFTs should be acquired by the buyer.
-
764  //
-
765  // Lastly, none of the remaining NFTs should be acquirable by the
-
766  // buyer. They would cause page overflow.
-
767 
-
768  // This test collapses in a heap if fixNFTokenDirV1 is not enabled.
-
769  // If it is enabled just return so we skip the test.
-
770  if (!features[fixNFTokenDirV1])
-
771  return;
-
772 
-
773  testcase("NFToken consecutive packing");
-
774 
-
775  using namespace test::jtx;
-
776 
-
777  Env env{*this, features};
-
778 
-
779  // Eventually all of the NFTokens will be owned by buyer.
-
780  Account const buyer{"buyer"};
-
781  env.fund(XRP(10000), buyer);
-
782  env.close();
-
783 
-
784  // Here are 33 seeds that produce identical low 32-bits in their
-
785  // corresponding AccountIDs.
-
786  static std::initializer_list<std::string_view const> const seeds{
-
787  "sp6JS7f14BuwFY8Mw56vZeiBuhePx", // 0. 0x115d0525
-
788  "sp6JS7f14BuwFY8Mw5BodF9tGuTUe", // 1. 0x115d0525
-
789  "sp6JS7f14BuwFY8Mw5EnhC1cg84J7", // 2. 0x115d0525
-
790  "sp6JS7f14BuwFY8Mw5P913Cunr2BK", // 3. 0x115d0525
-
791  "sp6JS7f14BuwFY8Mw5Pru7eLo1XzT", // 4. 0x115d0525
-
792  "sp6JS7f14BuwFY8Mw61SLUC8UX2m8", // 5. 0x115d0525
-
793  "sp6JS7f14BuwFY8Mw6AsBF9TpeMpq", // 6. 0x115d0525
-
794  "sp6JS7f14BuwFY8Mw84XqrBZkU2vE", // 7. 0x115d0525
-
795  "sp6JS7f14BuwFY8Mw89oSU6dBk3KB", // 8. 0x115d0525
-
796  "sp6JS7f14BuwFY8Mw89qUKCyDmyzj", // 9. 0x115d0525
-
797  "sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm", // 10. 0x115d0525
-
798  "sp6JS7f14BuwFY8Mw8LtW3VqrqMks", // 11. 0x115d0525
-
799  "sp6JS7f14BuwFY8Mw8ZrAkJc2sHew", // 12. 0x115d0525
-
800  "sp6JS7f14BuwFY8Mw8jpkYSNrD3ah", // 13. 0x115d0525
-
801  "sp6JS7f14BuwFY8MwF2mshd786m3V", // 14. 0x115d0525
-
802  "sp6JS7f14BuwFY8MwFHfXq9x5NbPY", // 15. 0x115d0525
-
803  "sp6JS7f14BuwFY8MwFrjWq5LAB8NT", // 16. 0x115d0525
-
804  "sp6JS7f14BuwFY8Mwj4asgSh6hQZd", // 17. 0x115d0525
-
805  "sp6JS7f14BuwFY8Mwj7ipFfqBSRrE", // 18. 0x115d0525
-
806  "sp6JS7f14BuwFY8MwjHqtcvGav8uW", // 19. 0x115d0525
-
807  "sp6JS7f14BuwFY8MwjLp4sk5fmzki", // 20. 0x115d0525
-
808  "sp6JS7f14BuwFY8MwjioHuYb3Ytkx", // 21. 0x115d0525
-
809  "sp6JS7f14BuwFY8MwkRjHPXWi7fGN", // 22. 0x115d0525
-
810  "sp6JS7f14BuwFY8MwkdVdPV3LjNN1", // 23. 0x115d0525
-
811  "sp6JS7f14BuwFY8MwkxUtVY5AXZFk", // 24. 0x115d0525
-
812  "sp6JS7f14BuwFY8Mwm4jQzdfTbY9F", // 25. 0x115d0525
-
813  "sp6JS7f14BuwFY8MwmCucYAqNp4iF", // 26. 0x115d0525
-
814  "sp6JS7f14BuwFY8Mwo2bgdFtxBzpF", // 27. 0x115d0525
-
815  "sp6JS7f14BuwFY8MwoGwD7v4U6qBh", // 28. 0x115d0525
-
816  "sp6JS7f14BuwFY8MwoUczqFADMoXi", // 29. 0x115d0525
-
817  "sp6JS7f14BuwFY8MwoY1xZeGd3gAr", // 30. 0x115d0525
-
818  "sp6JS7f14BuwFY8MwomVCbfkv4kYZ", // 31. 0x115d0525
-
819  "sp6JS7f14BuwFY8MwoqbrPSr4z13F", // 32. 0x115d0525
-
820  };
-
821 
-
822  // Create accounts for all of the seeds and fund those accounts.
-
823  std::vector<Account> accounts;
-
824  accounts.reserve(seeds.size());
-
825  for (std::string_view const& seed : seeds)
-
826  {
-
827  Account const& account =
-
828  accounts.emplace_back(Account::base58Seed, std::string(seed));
-
829  env.fund(XRP(10000), account);
-
830  env.close();
-
831  }
-
832 
-
833  // All of the accounts create seven consecutive NFTs and and offer
-
834  // those NFTs to buyer.
-
835  std::array<std::vector<uint256>, 7> nftIDsByPage;
-
836  for (auto& vec : nftIDsByPage)
-
837  vec.reserve(accounts.size());
-
838  std::array<std::vector<uint256>, 7> offers;
-
839  for (auto& vec : offers)
-
840  vec.reserve(accounts.size());
-
841  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
-
842  {
-
843  for (Account const& account : accounts)
-
844  {
-
845  // Mint the NFT. Tweak the taxon so zero is always stored.
-
846  std::uint32_t taxon =
-
847  toUInt32(nft::cipheredTaxon(i, nft::toTaxon(0)));
+
754  // Assuming we find the NFT, erase it so we know it's been found
+
755  // and can't be found again.
+
756  if (BEAST_EXPECT(foundIter != nftIDs.end()))
+
757  nftIDs.erase(foundIter);
+
758  }
+
759 
+
760  // All NFTs should now be accounted for, so nftIDs should be empty.
+
761  BEAST_EXPECT(nftIDs.empty());
+
762 
+
763  // Show that Without fixNFTokenDirV1 no more NFTs can be added to
+
764  // buyer. Also show that fixNFTokenDirV1 fixes the problem.
+
765  TER const expect = features[fixNFTokenDirV1]
+
766  ? static_cast<TER>(tesSUCCESS)
+
767  : static_cast<TER>(tecNO_SUITABLE_NFTOKEN_PAGE);
+
768  env(token::mint(buyer, 0), txflags(tfTransferable), ter(expect));
+
769  env.close();
+
770  }
+
771 
+
772  void
+
773  testConsecutivePacking(FeatureBitset features)
+
774  {
+
775  // We'll make a worst case scenario for NFT packing:
+
776  //
+
777  // 1. 33 accounts with identical low-32 bits mint 7 consecutive NFTs.
+
778  // 2. The taxon is manipulated to always be stored as zero.
+
779  // 3. A single account buys all 7x32 of the 33 NFTs.
+
780  //
+
781  // All of the NFTs should be acquired by the buyer.
+
782  //
+
783  // Lastly, none of the remaining NFTs should be acquirable by the
+
784  // buyer. They would cause page overflow.
+
785 
+
786  // This test collapses in a heap if fixNFTokenDirV1 is not enabled.
+
787  // If it is enabled just return so we skip the test.
+
788  if (!features[fixNFTokenDirV1])
+
789  return;
+
790 
+
791  testcase("NFToken consecutive packing");
+
792 
+
793  using namespace test::jtx;
+
794 
+
795  Env env{*this, features};
+
796 
+
797  // Eventually all of the NFTokens will be owned by buyer.
+
798  Account const buyer{"buyer"};
+
799  env.fund(XRP(10000), buyer);
+
800  env.close();
+
801 
+
802  // Here are 33 seeds that produce identical low 32-bits in their
+
803  // corresponding AccountIDs.
+
804  static std::initializer_list<std::string_view const> const seeds{
+
805  "sp6JS7f14BuwFY8Mw56vZeiBuhePx", // 0. 0x115d0525
+
806  "sp6JS7f14BuwFY8Mw5BodF9tGuTUe", // 1. 0x115d0525
+
807  "sp6JS7f14BuwFY8Mw5EnhC1cg84J7", // 2. 0x115d0525
+
808  "sp6JS7f14BuwFY8Mw5P913Cunr2BK", // 3. 0x115d0525
+
809  "sp6JS7f14BuwFY8Mw5Pru7eLo1XzT", // 4. 0x115d0525
+
810  "sp6JS7f14BuwFY8Mw61SLUC8UX2m8", // 5. 0x115d0525
+
811  "sp6JS7f14BuwFY8Mw6AsBF9TpeMpq", // 6. 0x115d0525
+
812  "sp6JS7f14BuwFY8Mw84XqrBZkU2vE", // 7. 0x115d0525
+
813  "sp6JS7f14BuwFY8Mw89oSU6dBk3KB", // 8. 0x115d0525
+
814  "sp6JS7f14BuwFY8Mw89qUKCyDmyzj", // 9. 0x115d0525
+
815  "sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm", // 10. 0x115d0525
+
816  "sp6JS7f14BuwFY8Mw8LtW3VqrqMks", // 11. 0x115d0525
+
817  "sp6JS7f14BuwFY8Mw8ZrAkJc2sHew", // 12. 0x115d0525
+
818  "sp6JS7f14BuwFY8Mw8jpkYSNrD3ah", // 13. 0x115d0525
+
819  "sp6JS7f14BuwFY8MwF2mshd786m3V", // 14. 0x115d0525
+
820  "sp6JS7f14BuwFY8MwFHfXq9x5NbPY", // 15. 0x115d0525
+
821  "sp6JS7f14BuwFY8MwFrjWq5LAB8NT", // 16. 0x115d0525
+
822  "sp6JS7f14BuwFY8Mwj4asgSh6hQZd", // 17. 0x115d0525
+
823  "sp6JS7f14BuwFY8Mwj7ipFfqBSRrE", // 18. 0x115d0525
+
824  "sp6JS7f14BuwFY8MwjHqtcvGav8uW", // 19. 0x115d0525
+
825  "sp6JS7f14BuwFY8MwjLp4sk5fmzki", // 20. 0x115d0525
+
826  "sp6JS7f14BuwFY8MwjioHuYb3Ytkx", // 21. 0x115d0525
+
827  "sp6JS7f14BuwFY8MwkRjHPXWi7fGN", // 22. 0x115d0525
+
828  "sp6JS7f14BuwFY8MwkdVdPV3LjNN1", // 23. 0x115d0525
+
829  "sp6JS7f14BuwFY8MwkxUtVY5AXZFk", // 24. 0x115d0525
+
830  "sp6JS7f14BuwFY8Mwm4jQzdfTbY9F", // 25. 0x115d0525
+
831  "sp6JS7f14BuwFY8MwmCucYAqNp4iF", // 26. 0x115d0525
+
832  "sp6JS7f14BuwFY8Mwo2bgdFtxBzpF", // 27. 0x115d0525
+
833  "sp6JS7f14BuwFY8MwoGwD7v4U6qBh", // 28. 0x115d0525
+
834  "sp6JS7f14BuwFY8MwoUczqFADMoXi", // 29. 0x115d0525
+
835  "sp6JS7f14BuwFY8MwoY1xZeGd3gAr", // 30. 0x115d0525
+
836  "sp6JS7f14BuwFY8MwomVCbfkv4kYZ", // 31. 0x115d0525
+
837  "sp6JS7f14BuwFY8MwoqbrPSr4z13F", // 32. 0x115d0525
+
838  };
+
839 
+
840  // Create accounts for all of the seeds and fund those accounts.
+
841  std::vector<Account> accounts;
+
842  accounts.reserve(seeds.size());
+
843  for (std::string_view const& seed : seeds)
+
844  {
+
845  Account const& account =
+
846  accounts.emplace_back(Account::base58Seed, std::string(seed));
+
847  env.fund(XRP(10000), account);
848 
-
849  uint256 const& nftID = nftIDsByPage[i].emplace_back(
-
850  token::getNextID(env, account, taxon, tfTransferable));
-
851  env(token::mint(account, taxon), txflags(tfTransferable));
-
852  env.close();
-
853 
-
854  // Create an offer to give the NFT to buyer for free.
-
855  offers[i].emplace_back(
-
856  keylet::nftoffer(account, env.seq(account)).key);
-
857  env(token::createOffer(account, nftID, XRP(0)),
-
858  token::destination(buyer),
-
859  txflags((tfSellNFToken)));
-
860  }
-
861  }
-
862  env.close();
-
863 
-
864  // Verify that the low 96 bits of all generated NFTs of the same
-
865  // sequence is identical.
-
866  for (auto const& vec : nftIDsByPage)
-
867  {
-
868  uint256 const expectLowBits = vec.front() & nft::pageMask;
-
869  for (uint256 const& nftID : vec)
-
870  {
-
871  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
-
872  }
-
873  }
-
874 
-
875  // Remove one NFT and offer from each of the vectors. These offers
-
876  // are the ones that will overflow the page.
-
877  std::vector<uint256> overflowNFTs;
-
878  overflowNFTs.reserve(nftIDsByPage.size());
-
879  std::vector<uint256> overflowOffers;
-
880  overflowOffers.reserve(nftIDsByPage.size());
-
881 
-
882  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
-
883  {
-
884  overflowNFTs.push_back(nftIDsByPage[i].back());
-
885  nftIDsByPage[i].pop_back();
-
886  BEAST_EXPECT(nftIDsByPage[i].size() == seeds.size() - 1);
+
849  // Do not close the ledger inside the loop. If
+
850  // fixNFTokenRemint is enabled and accounts are initialized
+
851  // at different ledgers, they will have different account
+
852  // sequences. That would cause the accounts to have
+
853  // different NFTokenID sequence numbers.
+
854  }
+
855  env.close();
+
856 
+
857  // All of the accounts create seven consecutive NFTs and and offer
+
858  // those NFTs to buyer.
+
859  std::array<std::vector<uint256>, 7> nftIDsByPage;
+
860  for (auto& vec : nftIDsByPage)
+
861  vec.reserve(accounts.size());
+
862  std::array<std::vector<uint256>, 7> offers;
+
863  for (auto& vec : offers)
+
864  vec.reserve(accounts.size());
+
865  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
+
866  {
+
867  for (Account const& account : accounts)
+
868  {
+
869  // Mint the NFT. Tweak the taxon so zero is always stored.
+
870  std::uint32_t taxon =
+
871  toUInt32(nft::cipheredTaxon(i, nft::toTaxon(0)));
+
872 
+
873  uint256 const& nftID = nftIDsByPage[i].emplace_back(
+
874  token::getNextID(env, account, taxon, tfTransferable));
+
875  env(token::mint(account, taxon), txflags(tfTransferable));
+
876  env.close();
+
877 
+
878  // Create an offer to give the NFT to buyer for free.
+
879  offers[i].emplace_back(
+
880  keylet::nftoffer(account, env.seq(account)).key);
+
881  env(token::createOffer(account, nftID, XRP(0)),
+
882  token::destination(buyer),
+
883  txflags((tfSellNFToken)));
+
884  }
+
885  }
+
886  env.close();
887 
-
888  overflowOffers.push_back(offers[i].back());
-
889  offers[i].pop_back();
-
890  BEAST_EXPECT(offers[i].size() == seeds.size() - 1);
-
891  }
-
892 
-
893  // buyer accepts all of the offers that won't cause an overflow.
-
894  // Fill the center and outsides first to exercise different boundary
-
895  // cases.
-
896  for (int i : std::initializer_list<int>{3, 6, 0, 1, 2, 5, 4})
-
897  {
-
898  for (uint256 const& offer : offers[i])
-
899  {
-
900  env(token::acceptSellOffer(buyer, offer));
-
901  env.close();
-
902  }
-
903  }
-
904 
-
905  // buyer accepts the seven offers that would cause page overflows if
-
906  // the transaction succeeded.
-
907  for (uint256 const& offer : overflowOffers)
-
908  {
-
909  env(token::acceptSellOffer(buyer, offer),
-
910  ter(tecNO_SUITABLE_NFTOKEN_PAGE));
-
911  env.close();
-
912  }
-
913 
-
914  // Verify that all expected NFTs are owned by buyer and findable in
-
915  // the ledger by having buyer create sell offers for all of their NFTs.
-
916  // Attempting to sell an offer that the ledger can't find generates
-
917  // a non-tesSUCCESS error code.
-
918  for (auto const& vec : nftIDsByPage)
-
919  {
-
920  for (uint256 const& nftID : vec)
-
921  {
-
922  env(token::createOffer(buyer, nftID, XRP(100)),
-
923  txflags(tfSellNFToken));
-
924  env.close();
-
925  }
-
926  }
-
927 
-
928  // See what the account_objects command does with "nft_offer".
-
929  {
-
930  Json::Value ownedNftOffers(Json::arrayValue);
-
931  std::string marker;
-
932  do
-
933  {
-
934  Json::Value buyerOffers = [&env, &buyer, &marker]() {
-
935  Json::Value params;
-
936  params[jss::account] = buyer.human();
-
937  params[jss::type] = jss::nft_offer;
-
938 
-
939  if (!marker.empty())
-
940  params[jss::marker] = marker;
-
941  return env.rpc(
-
942  "json", "account_objects", to_string(params));
-
943  }();
-
944 
-
945  marker.clear();
-
946  if (buyerOffers.isMember(jss::result))
-
947  {
-
948  Json::Value& result = buyerOffers[jss::result];
-
949 
-
950  if (result.isMember(jss::marker))
-
951  marker = result[jss::marker].asString();
-
952 
-
953  if (result.isMember(jss::account_objects))
-
954  {
-
955  Json::Value& someOffers = result[jss::account_objects];
-
956  for (std::size_t i = 0; i < someOffers.size(); ++i)
-
957  ownedNftOffers.append(someOffers[i]);
-
958  }
-
959  }
-
960  } while (!marker.empty());
-
961 
-
962  // Verify there are as many offers are there are NFTs.
-
963  {
-
964  std::size_t totalOwnedNFTs = 0;
-
965  for (auto const& vec : nftIDsByPage)
-
966  totalOwnedNFTs += vec.size();
-
967  BEAST_EXPECT(ownedNftOffers.size() == totalOwnedNFTs);
-
968  }
-
969 
-
970  // Cancel all the offers.
-
971  {
-
972  std::vector<uint256> cancelOffers;
-
973  cancelOffers.reserve(ownedNftOffers.size());
-
974 
-
975  for (auto const& offer : ownedNftOffers)
-
976  {
-
977  if (offer.isMember(jss::index))
+
888  // Verify that the low 96 bits of all generated NFTs of the same
+
889  // sequence is identical.
+
890  for (auto const& vec : nftIDsByPage)
+
891  {
+
892  uint256 const expectLowBits = vec.front() & nft::pageMask;
+
893  for (uint256 const& nftID : vec)
+
894  {
+
895  BEAST_EXPECT(expectLowBits == (nftID & nft::pageMask));
+
896  }
+
897  }
+
898 
+
899  // Remove one NFT and offer from each of the vectors. These offers
+
900  // are the ones that will overflow the page.
+
901  std::vector<uint256> overflowNFTs;
+
902  overflowNFTs.reserve(nftIDsByPage.size());
+
903  std::vector<uint256> overflowOffers;
+
904  overflowOffers.reserve(nftIDsByPage.size());
+
905 
+
906  for (std::size_t i = 0; i < nftIDsByPage.size(); ++i)
+
907  {
+
908  overflowNFTs.push_back(nftIDsByPage[i].back());
+
909  nftIDsByPage[i].pop_back();
+
910  BEAST_EXPECT(nftIDsByPage[i].size() == seeds.size() - 1);
+
911 
+
912  overflowOffers.push_back(offers[i].back());
+
913  offers[i].pop_back();
+
914  BEAST_EXPECT(offers[i].size() == seeds.size() - 1);
+
915  }
+
916 
+
917  // buyer accepts all of the offers that won't cause an overflow.
+
918  // Fill the center and outsides first to exercise different boundary
+
919  // cases.
+
920  for (int i : std::initializer_list<int>{3, 6, 0, 1, 2, 5, 4})
+
921  {
+
922  for (uint256 const& offer : offers[i])
+
923  {
+
924  env(token::acceptSellOffer(buyer, offer));
+
925  env.close();
+
926  }
+
927  }
+
928 
+
929  // buyer accepts the seven offers that would cause page overflows if
+
930  // the transaction succeeded.
+
931  for (uint256 const& offer : overflowOffers)
+
932  {
+
933  env(token::acceptSellOffer(buyer, offer),
+
934  ter(tecNO_SUITABLE_NFTOKEN_PAGE));
+
935  env.close();
+
936  }
+
937 
+
938  // Verify that all expected NFTs are owned by buyer and findable in
+
939  // the ledger by having buyer create sell offers for all of their NFTs.
+
940  // Attempting to sell an offer that the ledger can't find generates
+
941  // a non-tesSUCCESS error code.
+
942  for (auto const& vec : nftIDsByPage)
+
943  {
+
944  for (uint256 const& nftID : vec)
+
945  {
+
946  env(token::createOffer(buyer, nftID, XRP(100)),
+
947  txflags(tfSellNFToken));
+
948  env.close();
+
949  }
+
950  }
+
951 
+
952  // See what the account_objects command does with "nft_offer".
+
953  {
+
954  Json::Value ownedNftOffers(Json::arrayValue);
+
955  std::string marker;
+
956  do
+
957  {
+
958  Json::Value buyerOffers = [&env, &buyer, &marker]() {
+
959  Json::Value params;
+
960  params[jss::account] = buyer.human();
+
961  params[jss::type] = jss::nft_offer;
+
962 
+
963  if (!marker.empty())
+
964  params[jss::marker] = marker;
+
965  return env.rpc(
+
966  "json", "account_objects", to_string(params));
+
967  }();
+
968 
+
969  marker.clear();
+
970  if (buyerOffers.isMember(jss::result))
+
971  {
+
972  Json::Value& result = buyerOffers[jss::result];
+
973 
+
974  if (result.isMember(jss::marker))
+
975  marker = result[jss::marker].asString();
+
976 
+
977  if (result.isMember(jss::account_objects))
978  {
-
979  uint256 offerIndex;
-
980  if (offerIndex.parseHex(offer[jss::index].asString()))
-
981  cancelOffers.push_back(offerIndex);
+
979  Json::Value& someOffers = result[jss::account_objects];
+
980  for (std::size_t i = 0; i < someOffers.size(); ++i)
+
981  ownedNftOffers.append(someOffers[i]);
982  }
983  }
-
984  env(token::cancelOffer(buyer, cancelOffers));
-
985  env.close();
-
986  }
-
987 
-
988  // account_objects should no longer return any "nft_offer"s.
-
989  Json::Value remainingOffers = [&env, &buyer]() {
-
990  Json::Value params;
-
991  params[jss::account] = buyer.human();
-
992  params[jss::type] = jss::nft_offer;
+
984  } while (!marker.empty());
+
985 
+
986  // Verify there are as many offers are there are NFTs.
+
987  {
+
988  std::size_t totalOwnedNFTs = 0;
+
989  for (auto const& vec : nftIDsByPage)
+
990  totalOwnedNFTs += vec.size();
+
991  BEAST_EXPECT(ownedNftOffers.size() == totalOwnedNFTs);
+
992  }
993 
-
994  return env.rpc("json", "account_objects", to_string(params));
-
995  }();
-
996  BEAST_EXPECT(
-
997  remainingOffers.isMember(jss::result) &&
-
998  remainingOffers[jss::result].isMember(jss::account_objects) &&
-
999  remainingOffers[jss::result][jss::account_objects].size() == 0);
-
1000  }
-
1001 
-
1002  // Verify that the ledger reports all of the NFTs owned by buyer.
-
1003  // Use the account_nfts rpc call to get the values.
-
1004  Json::Value ownedNFTs(Json::arrayValue);
-
1005  std::string marker;
-
1006  do
-
1007  {
-
1008  Json::Value buyerNFTs = [&env, &buyer, &marker]() {
-
1009  Json::Value params;
-
1010  params[jss::account] = buyer.human();
-
1011  params[jss::type] = "state";
-
1012 
-
1013  if (!marker.empty())
-
1014  params[jss::marker] = marker;
-
1015  return env.rpc("json", "account_nfts", to_string(params));
-
1016  }();
+
994  // Cancel all the offers.
+
995  {
+
996  std::vector<uint256> cancelOffers;
+
997  cancelOffers.reserve(ownedNftOffers.size());
+
998 
+
999  for (auto const& offer : ownedNftOffers)
+
1000  {
+
1001  if (offer.isMember(jss::index))
+
1002  {
+
1003  uint256 offerIndex;
+
1004  if (offerIndex.parseHex(offer[jss::index].asString()))
+
1005  cancelOffers.push_back(offerIndex);
+
1006  }
+
1007  }
+
1008  env(token::cancelOffer(buyer, cancelOffers));
+
1009  env.close();
+
1010  }
+
1011 
+
1012  // account_objects should no longer return any "nft_offer"s.
+
1013  Json::Value remainingOffers = [&env, &buyer]() {
+
1014  Json::Value params;
+
1015  params[jss::account] = buyer.human();
+
1016  params[jss::type] = jss::nft_offer;
1017 
-
1018  marker.clear();
-
1019  if (buyerNFTs.isMember(jss::result))
-
1020  {
-
1021  Json::Value& result = buyerNFTs[jss::result];
-
1022 
-
1023  if (result.isMember(jss::marker))
-
1024  marker = result[jss::marker].asString();
+
1018  return env.rpc("json", "account_objects", to_string(params));
+
1019  }();
+
1020  BEAST_EXPECT(
+
1021  remainingOffers.isMember(jss::result) &&
+
1022  remainingOffers[jss::result].isMember(jss::account_objects) &&
+
1023  remainingOffers[jss::result][jss::account_objects].size() == 0);
+
1024  }
1025 
-
1026  if (result.isMember(jss::account_nfts))
-
1027  {
-
1028  Json::Value& someNFTs = result[jss::account_nfts];
-
1029  for (std::size_t i = 0; i < someNFTs.size(); ++i)
-
1030  ownedNFTs.append(someNFTs[i]);
-
1031  }
-
1032  }
-
1033  } while (!marker.empty());
-
1034 
-
1035  // Copy all of the nftIDs into a set to make validation easier.
-
1036  std::set<uint256> allNftIDs;
-
1037  for (auto& vec : nftIDsByPage)
-
1038  allNftIDs.insert(vec.begin(), vec.end());
-
1039 
-
1040  BEAST_EXPECT(ownedNFTs.size() == allNftIDs.size());
+
1026  // Verify that the ledger reports all of the NFTs owned by buyer.
+
1027  // Use the account_nfts rpc call to get the values.
+
1028  Json::Value ownedNFTs(Json::arrayValue);
+
1029  std::string marker;
+
1030  do
+
1031  {
+
1032  Json::Value buyerNFTs = [&env, &buyer, &marker]() {
+
1033  Json::Value params;
+
1034  params[jss::account] = buyer.human();
+
1035  params[jss::type] = "state";
+
1036 
+
1037  if (!marker.empty())
+
1038  params[jss::marker] = marker;
+
1039  return env.rpc("json", "account_nfts", to_string(params));
+
1040  }();
1041 
-
1042  for (Json::Value const& ownedNFT : ownedNFTs)
-
1043  {
-
1044  if (ownedNFT.isMember(sfNFTokenID.jsonName))
-
1045  {
-
1046  uint256 ownedID;
-
1047  BEAST_EXPECT(ownedID.parseHex(
-
1048  ownedNFT[sfNFTokenID.jsonName].asString()));
-
1049  auto const foundIter = allNftIDs.find(ownedID);
-
1050 
-
1051  // Assuming we find the NFT, erase it so we know it's been found
-
1052  // and can't be found again.
-
1053  if (BEAST_EXPECT(foundIter != allNftIDs.end()))
-
1054  allNftIDs.erase(foundIter);
-
1055  }
-
1056  }
-
1057 
-
1058  // All NFTs should now be accounted for, so allNftIDs should be empty.
-
1059  BEAST_EXPECT(allNftIDs.empty());
-
1060  }
-
1061 
-
1062  void
-
1063  testWithFeats(FeatureBitset features)
-
1064  {
-
1065  testConsecutiveNFTs(features);
-
1066  testLopsidedSplits(features);
-
1067  testFixNFTokenDirV1(features);
-
1068  testTooManyEquivalent(features);
-
1069  testConsecutivePacking(features);
-
1070  }
-
1071 
-
1072 public:
-
1073  void
-
1074  run() override
-
1075  {
-
1076  using namespace test::jtx;
-
1077  FeatureBitset const all{supported_amendments()};
-
1078  FeatureBitset const fixNFTDir{
-
1079  fixNFTokenDirV1, featureNonFungibleTokensV1_1};
-
1080 
-
1081  testWithFeats(all - fixNFTDir);
-
1082  testWithFeats(all);
-
1083  }
-
1084 };
+
1042  marker.clear();
+
1043  if (buyerNFTs.isMember(jss::result))
+
1044  {
+
1045  Json::Value& result = buyerNFTs[jss::result];
+
1046 
+
1047  if (result.isMember(jss::marker))
+
1048  marker = result[jss::marker].asString();
+
1049 
+
1050  if (result.isMember(jss::account_nfts))
+
1051  {
+
1052  Json::Value& someNFTs = result[jss::account_nfts];
+
1053  for (std::size_t i = 0; i < someNFTs.size(); ++i)
+
1054  ownedNFTs.append(someNFTs[i]);
+
1055  }
+
1056  }
+
1057  } while (!marker.empty());
+
1058 
+
1059  // Copy all of the nftIDs into a set to make validation easier.
+
1060  std::set<uint256> allNftIDs;
+
1061  for (auto& vec : nftIDsByPage)
+
1062  allNftIDs.insert(vec.begin(), vec.end());
+
1063 
+
1064  BEAST_EXPECT(ownedNFTs.size() == allNftIDs.size());
+
1065 
+
1066  for (Json::Value const& ownedNFT : ownedNFTs)
+
1067  {
+
1068  if (ownedNFT.isMember(sfNFTokenID.jsonName))
+
1069  {
+
1070  uint256 ownedID;
+
1071  BEAST_EXPECT(ownedID.parseHex(
+
1072  ownedNFT[sfNFTokenID.jsonName].asString()));
+
1073  auto const foundIter = allNftIDs.find(ownedID);
+
1074 
+
1075  // Assuming we find the NFT, erase it so we know it's been found
+
1076  // and can't be found again.
+
1077  if (BEAST_EXPECT(foundIter != allNftIDs.end()))
+
1078  allNftIDs.erase(foundIter);
+
1079  }
+
1080  }
+
1081 
+
1082  // All NFTs should now be accounted for, so allNftIDs should be empty.
+
1083  BEAST_EXPECT(allNftIDs.empty());
+
1084  }
1085 
-
1086 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDir, tx, ripple, 1);
-
1087 
-
1088 } // namespace ripple
-
1089 
-
1090 // Seed that produces an account with the low-32 bits == 0xFFFFFFFF in
-
1091 // case it is needed for future testing:
-
1092 //
-
1093 // sp6JS7f14BuwFY8MwFe95Vpi9Znjs
-
1094 //
+
1086  void
+
1087  testWithFeats(FeatureBitset features)
+
1088  {
+
1089  testConsecutiveNFTs(features);
+
1090  testLopsidedSplits(features);
+
1091  testFixNFTokenDirV1(features);
+
1092  testTooManyEquivalent(features);
+
1093  testConsecutivePacking(features);
+
1094  }
1095 
-
1096 // Sets of related accounts.
-
1097 //
-
1098 // Identifying the seeds of accounts that generate account IDs with the
-
1099 // same low 32 bits takes a while. However several sets of accounts with
-
1100 // that relationship have been located. In case these sets of accounts are
-
1101 // needed for future testing scenarios they are recorded below.
-
1102 #if 0
-
1103 34 account seeds that produce account IDs with low 32-bits 0x399187e9:
-
1104  sp6JS7f14BuwFY8Mw5EYu5z86hKDL
-
1105  sp6JS7f14BuwFY8Mw5PUAMwc5ygd7
-
1106  sp6JS7f14BuwFY8Mw5R3xUBcLSeTs
-
1107  sp6JS7f14BuwFY8Mw5W6oS5sdC3oF
-
1108  sp6JS7f14BuwFY8Mw5pYc3D9iuLcw
-
1109  sp6JS7f14BuwFY8Mw5pfGVnhcdp3b
-
1110  sp6JS7f14BuwFY8Mw6jS6RdEqXqrN
-
1111  sp6JS7f14BuwFY8Mw6krt6AKbvRXW
-
1112  sp6JS7f14BuwFY8Mw6mnVBQq7cAN2
-
1113  sp6JS7f14BuwFY8Mw8ECJxPjmkufQ
-
1114  sp6JS7f14BuwFY8Mw8asgzcceGWYm
-
1115  sp6JS7f14BuwFY8MwF6J3FXnPCgL8
-
1116  sp6JS7f14BuwFY8MwFEud2w5czv5q
-
1117  sp6JS7f14BuwFY8MwFNxKVqJnx8P5
-
1118  sp6JS7f14BuwFY8MwFnTCXg3eRidL
-
1119  sp6JS7f14BuwFY8Mwj47hv1vrDge6
-
1120  sp6JS7f14BuwFY8Mwj6TYekeeyukh
-
1121  sp6JS7f14BuwFY8MwjFjsRDerz7jb
-
1122  sp6JS7f14BuwFY8Mwjrj9mHTLBrcX
-
1123  sp6JS7f14BuwFY8MwkKcJi3zMzAea
-
1124  sp6JS7f14BuwFY8MwkYTDdnYRm9z4
-
1125  sp6JS7f14BuwFY8Mwkq8ei4D8uPNd
-
1126  sp6JS7f14BuwFY8Mwm2pFruxbnJRd
-
1127  sp6JS7f14BuwFY8MwmJV2ZnAjpC2g
-
1128  sp6JS7f14BuwFY8MwmTFMPHQHfVYF
-
1129  sp6JS7f14BuwFY8MwmkG2jXEgqiud
-
1130  sp6JS7f14BuwFY8Mwms3xEh5tMDTw
-
1131  sp6JS7f14BuwFY8MwmtipW4D8giZ9
-
1132  sp6JS7f14BuwFY8MwoRQBZm4KUUeE
-
1133  sp6JS7f14BuwFY8MwoVey94QpXcrc
-
1134  sp6JS7f14BuwFY8MwoZiuUoUTo3VG
-
1135  sp6JS7f14BuwFY8MwonFFDLT4bHAZ
-
1136  sp6JS7f14BuwFY8MwooGphD4hefBQ
-
1137  sp6JS7f14BuwFY8MwoxDp3dmX6q5N
-
1138 
-
1139 34 account seeds that produce account IDs with low 32-bits 0x473f2c9a:
-
1140  sp6JS7f14BuwFY8Mw53ktgqmv5Bmz
-
1141  sp6JS7f14BuwFY8Mw5KPb2Kz7APFX
-
1142  sp6JS7f14BuwFY8Mw5Xx4A6HRTPEE
-
1143  sp6JS7f14BuwFY8Mw5y6qZFNAo358
-
1144  sp6JS7f14BuwFY8Mw6kdaBg1QrZfn
-
1145  sp6JS7f14BuwFY8Mw8QmTfLMAZ5K1
-
1146  sp6JS7f14BuwFY8Mw8cbRRVcCEELr
-
1147  sp6JS7f14BuwFY8Mw8gQvJebmxvDG
-
1148  sp6JS7f14BuwFY8Mw8qPQurwu3P7Y
-
1149  sp6JS7f14BuwFY8MwFS4PEVKmuPy5
-
1150  sp6JS7f14BuwFY8MwFUQM1rAsQ8tS
-
1151  sp6JS7f14BuwFY8MwjJBZCkuwsRnM
-
1152  sp6JS7f14BuwFY8MwjTdS8vZhX5E9
-
1153  sp6JS7f14BuwFY8MwjhSmWCbNhd25
-
1154  sp6JS7f14BuwFY8MwjwkpqwZsDBw9
-
1155  sp6JS7f14BuwFY8MwjyET4p6eqd5J
-
1156  sp6JS7f14BuwFY8MwkMNAe4JhnG7E
-
1157  sp6JS7f14BuwFY8MwkRRpnT93UWWS
-
1158  sp6JS7f14BuwFY8MwkY9CvB22RvUe
-
1159  sp6JS7f14BuwFY8Mwkhw9VxXqmTr7
-
1160  sp6JS7f14BuwFY8MwkmgaTat7eFa7
-
1161  sp6JS7f14BuwFY8Mwkq5SxGGv1oLH
-
1162  sp6JS7f14BuwFY8MwmCBM5p5bTg6y
-
1163  sp6JS7f14BuwFY8MwmmmXaVah64dB
-
1164  sp6JS7f14BuwFY8Mwo7R7Cn614v9V
-
1165  sp6JS7f14BuwFY8MwoCAG1na7GR2M
-
1166  sp6JS7f14BuwFY8MwoDuPvJS4gG7C
-
1167  sp6JS7f14BuwFY8MwoMMowSyPQLfy
-
1168  sp6JS7f14BuwFY8MwoRqDiwTNsTBm
-
1169  sp6JS7f14BuwFY8MwoWbBWtjpB7pg
-
1170  sp6JS7f14BuwFY8Mwoi1AEeELGecF
-
1171  sp6JS7f14BuwFY8MwopGP6Lo5byuj
-
1172  sp6JS7f14BuwFY8MwoufkXGHp2VW8
-
1173  sp6JS7f14BuwFY8MwowGeagFQY32k
-
1174 
-
1175 34 account seeds that produce account IDs with low 32-bits 0x4d59f0d1:
-
1176  sp6JS7f14BuwFY8Mw5CsNgH64zxK7
-
1177  sp6JS7f14BuwFY8Mw5Dg4wi2E344h
-
1178  sp6JS7f14BuwFY8Mw5ErV949Zh2PX
-
1179  sp6JS7f14BuwFY8Mw5p4nsQvEUE1s
-
1180  sp6JS7f14BuwFY8Mw8LGnkbaP68Gn
-
1181  sp6JS7f14BuwFY8Mw8aq6RCBc3iHo
-
1182  sp6JS7f14BuwFY8Mw8bkWaGoKYT6e
-
1183  sp6JS7f14BuwFY8Mw8qrCuXnzAXVj
-
1184  sp6JS7f14BuwFY8MwFDKcPAHPHJTm
-
1185  sp6JS7f14BuwFY8MwFUXJs4unfgNu
-
1186  sp6JS7f14BuwFY8MwFj9Yv5LjshD9
-
1187  sp6JS7f14BuwFY8Mwj3H73nmq5UaC
-
1188  sp6JS7f14BuwFY8MwjHSYShis1Yhk
-
1189  sp6JS7f14BuwFY8MwjpfE1HVo8UP1
-
1190  sp6JS7f14BuwFY8Mwk6JE1SXUuiNc
-
1191  sp6JS7f14BuwFY8MwkASgxEjEnFmU
-
1192  sp6JS7f14BuwFY8MwkGNY8kg7R6RK
-
1193  sp6JS7f14BuwFY8MwkHinNZ8SYBQu
-
1194  sp6JS7f14BuwFY8MwkXLCW1hbhGya
-
1195  sp6JS7f14BuwFY8MwkZ7mWrYK9YtU
-
1196  sp6JS7f14BuwFY8MwkdFSqNB5DbKL
-
1197  sp6JS7f14BuwFY8Mwm3jdBaCAx8H6
-
1198  sp6JS7f14BuwFY8Mwm3rk5hEwDRtY
-
1199  sp6JS7f14BuwFY8Mwm77a2ULuwxu4
-
1200  sp6JS7f14BuwFY8MwmJpY7braKLaN
-
1201  sp6JS7f14BuwFY8MwmKHQjG4XiZ6g
-
1202  sp6JS7f14BuwFY8Mwmmv8Y3wyUDzs
-
1203  sp6JS7f14BuwFY8MwmucFe1WgqtwG
-
1204  sp6JS7f14BuwFY8Mwo1EjdU1bznZR
-
1205  sp6JS7f14BuwFY8MwoJiqankkU5uR
-
1206  sp6JS7f14BuwFY8MwoLnvQ6zdqbKw
-
1207  sp6JS7f14BuwFY8MwoUGeJ319eu48
-
1208  sp6JS7f14BuwFY8MwoYf135tQjHP4
-
1209  sp6JS7f14BuwFY8MwogeF6M6SAyid
-
1210 
-
1211 34 account seeds that produce account IDs with low 32-bits 0xabb11898:
-
1212  sp6JS7f14BuwFY8Mw5DgiYaNVSb1G
-
1213  sp6JS7f14BuwFY8Mw5k6e94TMvuox
-
1214  sp6JS7f14BuwFY8Mw5tTSN7KzYxiT
-
1215  sp6JS7f14BuwFY8Mw61XV6m33utif
-
1216  sp6JS7f14BuwFY8Mw87jKfrjiENCb
-
1217  sp6JS7f14BuwFY8Mw8AFtxxFiRtJG
-
1218  sp6JS7f14BuwFY8Mw8cosAVExzbeE
-
1219  sp6JS7f14BuwFY8Mw8fmkQ63zE8WQ
-
1220  sp6JS7f14BuwFY8Mw8iYSsxNbDN6D
-
1221  sp6JS7f14BuwFY8Mw8wTZdGRJyyM1
-
1222  sp6JS7f14BuwFY8Mw8z7xEh3qBGr7
-
1223  sp6JS7f14BuwFY8MwFL5gpKQWZj7g
-
1224  sp6JS7f14BuwFY8MwFPeZchXQnRZ5
-
1225  sp6JS7f14BuwFY8MwFSPxWSJVoU29
-
1226  sp6JS7f14BuwFY8MwFYyVkqX8kvRm
-
1227  sp6JS7f14BuwFY8MwFcbVikUEwJvk
-
1228  sp6JS7f14BuwFY8MwjF7NcZk1NctK
-
1229  sp6JS7f14BuwFY8MwjJCwYr9zSfAv
-
1230  sp6JS7f14BuwFY8MwjYa5yLkgCLuT
-
1231  sp6JS7f14BuwFY8MwjenxuJ3TH2Bc
-
1232  sp6JS7f14BuwFY8MwjriN7Ui11NzB
-
1233  sp6JS7f14BuwFY8Mwk3AuoJNSEo34
-
1234  sp6JS7f14BuwFY8MwkT36hnRv8hTo
-
1235  sp6JS7f14BuwFY8MwkTQixEXfi1Cr
-
1236  sp6JS7f14BuwFY8MwkYJaZM1yTJBF
-
1237  sp6JS7f14BuwFY8Mwkc4k1uo85qp2
-
1238  sp6JS7f14BuwFY8Mwkf7cFhF1uuxx
-
1239  sp6JS7f14BuwFY8MwmCK2un99wb4e
-
1240  sp6JS7f14BuwFY8MwmETztNHYu2Bx
-
1241  sp6JS7f14BuwFY8MwmJws9UwRASfR
-
1242  sp6JS7f14BuwFY8MwoH5PQkGK8tEb
-
1243  sp6JS7f14BuwFY8MwoVXtP2yCzjJV
-
1244  sp6JS7f14BuwFY8MwobxRXA9vsTeX
-
1245  sp6JS7f14BuwFY8Mwos3pc5Gb3ihU
-
1246 
-
1247 34 account seeds that produce account IDs with low 32-bits 0xce627322:
-
1248  sp6JS7f14BuwFY8Mw5Ck6i83pGNh3
-
1249  sp6JS7f14BuwFY8Mw5FKuwTxjAdH1
-
1250  sp6JS7f14BuwFY8Mw5FVKkEn6TkLH
-
1251  sp6JS7f14BuwFY8Mw5NbQwLwHDd5v
-
1252  sp6JS7f14BuwFY8Mw5X1dbz3msZaZ
-
1253  sp6JS7f14BuwFY8Mw6qv6qaXNeP74
-
1254  sp6JS7f14BuwFY8Mw81SXagUeutCw
-
1255  sp6JS7f14BuwFY8Mw84Ph7Qa8kwwk
-
1256  sp6JS7f14BuwFY8Mw8Hp4gFyU3Qko
-
1257  sp6JS7f14BuwFY8Mw8Kt8bAKredSx
-
1258  sp6JS7f14BuwFY8Mw8XHK3VKRQ7v7
-
1259  sp6JS7f14BuwFY8Mw8eGyWxZGHY6v
-
1260  sp6JS7f14BuwFY8Mw8iU5CLyHVcD2
-
1261  sp6JS7f14BuwFY8Mw8u3Zr26Ar914
-
1262  sp6JS7f14BuwFY8MwF2Kcdxtjzjv8
-
1263  sp6JS7f14BuwFY8MwFLmPWb6rbxNg
-
1264  sp6JS7f14BuwFY8MwFUu8s7UVuxuJ
-
1265  sp6JS7f14BuwFY8MwFYBaatwHxAJ8
-
1266  sp6JS7f14BuwFY8Mwjg6hFkeHwoqG
-
1267  sp6JS7f14BuwFY8MwjjycJojy2ufk
-
1268  sp6JS7f14BuwFY8MwkEWoxcSKGPXv
-
1269  sp6JS7f14BuwFY8MwkMe7wLkEUsQT
-
1270  sp6JS7f14BuwFY8MwkvyKLaPUc4FS
-
1271  sp6JS7f14BuwFY8Mwm8doqXPKZmVQ
-
1272  sp6JS7f14BuwFY8Mwm9r3No8yQ8Tx
-
1273  sp6JS7f14BuwFY8Mwm9w6dks68W9B
-
1274  sp6JS7f14BuwFY8MwmMPrv9sCdbpS
-
1275  sp6JS7f14BuwFY8MwmPAvs3fcQNja
-
1276  sp6JS7f14BuwFY8MwmS5jasapfcnJ
-
1277  sp6JS7f14BuwFY8MwmU2L3qJEhnuA
-
1278  sp6JS7f14BuwFY8MwoAQYmiBnW7fM
-
1279  sp6JS7f14BuwFY8MwoBkkkXrPmkKF
-
1280  sp6JS7f14BuwFY8MwonfmxPo6tkvC
-
1281  sp6JS7f14BuwFY8MwouZFwhiNcYq6
-
1282 
-
1283 34 account seeds that produce account IDs with low 32-bits 0xe29643e8:
-
1284  sp6JS7f14BuwFY8Mw5EfAavcXAh2k
-
1285  sp6JS7f14BuwFY8Mw5LhFjLkFSCVF
-
1286  sp6JS7f14BuwFY8Mw5bRfEv5HgdBh
-
1287  sp6JS7f14BuwFY8Mw5d6sPcKzypKN
-
1288  sp6JS7f14BuwFY8Mw5rcqDtk1fACP
-
1289  sp6JS7f14BuwFY8Mw5xkxRq1Notzv
-
1290  sp6JS7f14BuwFY8Mw66fbkdw5WYmt
-
1291  sp6JS7f14BuwFY8Mw6diEG8sZ7Fx7
-
1292  sp6JS7f14BuwFY8Mw6v2r1QhG7xc1
-
1293  sp6JS7f14BuwFY8Mw6zP6DHCTx2Fd
-
1294  sp6JS7f14BuwFY8Mw8B3n39JKuFkk
-
1295  sp6JS7f14BuwFY8Mw8FmBvqYw7uqn
-
1296  sp6JS7f14BuwFY8Mw8KEaftb1eRwu
-
1297  sp6JS7f14BuwFY8Mw8WJ1qKkegj9N
-
1298  sp6JS7f14BuwFY8Mw8r8cAZEkq2BS
-
1299  sp6JS7f14BuwFY8MwFKPxxwF65gZh
-
1300  sp6JS7f14BuwFY8MwFKhaF8APcN5H
-
1301  sp6JS7f14BuwFY8MwFN2buJn4BgYC
-
1302  sp6JS7f14BuwFY8MwFUTe175MjP3x
-
1303  sp6JS7f14BuwFY8MwFZhmRDb53NNb
-
1304  sp6JS7f14BuwFY8MwFa2Azn5nU2WS
-
1305  sp6JS7f14BuwFY8MwjNNt91hwgkn7
-
1306  sp6JS7f14BuwFY8MwjdiYt6ChACe7
-
1307  sp6JS7f14BuwFY8Mwk5qFVQ48Mmr9
-
1308  sp6JS7f14BuwFY8MwkGvCj7pNf1zG
-
1309  sp6JS7f14BuwFY8MwkY9UcN2D2Fzs
-
1310  sp6JS7f14BuwFY8MwkpGvSk9G9RyT
-
1311  sp6JS7f14BuwFY8MwmGQ7nJf1eEzV
-
1312  sp6JS7f14BuwFY8MwmQLjGsYdyAmV
-
1313  sp6JS7f14BuwFY8MwmZ8usztKvikT
-
1314  sp6JS7f14BuwFY8MwobyMLC2hQdFR
-
1315  sp6JS7f14BuwFY8MwoiRtwUecZeJ5
-
1316  sp6JS7f14BuwFY8MwojHjKsUzj1KJ
-
1317  sp6JS7f14BuwFY8Mwop29anGAjidU
-
1318 
-
1319 33 account seeds that produce account IDs with low 32-bits 0x115d0525:
-
1320  sp6JS7f14BuwFY8Mw56vZeiBuhePx
-
1321  sp6JS7f14BuwFY8Mw5BodF9tGuTUe
-
1322  sp6JS7f14BuwFY8Mw5EnhC1cg84J7
-
1323  sp6JS7f14BuwFY8Mw5P913Cunr2BK
-
1324  sp6JS7f14BuwFY8Mw5Pru7eLo1XzT
-
1325  sp6JS7f14BuwFY8Mw61SLUC8UX2m8
-
1326  sp6JS7f14BuwFY8Mw6AsBF9TpeMpq
-
1327  sp6JS7f14BuwFY8Mw84XqrBZkU2vE
-
1328  sp6JS7f14BuwFY8Mw89oSU6dBk3KB
-
1329  sp6JS7f14BuwFY8Mw89qUKCyDmyzj
-
1330  sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm
-
1331  sp6JS7f14BuwFY8Mw8LtW3VqrqMks
-
1332  sp6JS7f14BuwFY8Mw8ZrAkJc2sHew
-
1333  sp6JS7f14BuwFY8Mw8jpkYSNrD3ah
-
1334  sp6JS7f14BuwFY8MwF2mshd786m3V
-
1335  sp6JS7f14BuwFY8MwFHfXq9x5NbPY
-
1336  sp6JS7f14BuwFY8MwFrjWq5LAB8NT
-
1337  sp6JS7f14BuwFY8Mwj4asgSh6hQZd
-
1338  sp6JS7f14BuwFY8Mwj7ipFfqBSRrE
-
1339  sp6JS7f14BuwFY8MwjHqtcvGav8uW
-
1340  sp6JS7f14BuwFY8MwjLp4sk5fmzki
-
1341  sp6JS7f14BuwFY8MwjioHuYb3Ytkx
-
1342  sp6JS7f14BuwFY8MwkRjHPXWi7fGN
-
1343  sp6JS7f14BuwFY8MwkdVdPV3LjNN1
-
1344  sp6JS7f14BuwFY8MwkxUtVY5AXZFk
-
1345  sp6JS7f14BuwFY8Mwm4jQzdfTbY9F
-
1346  sp6JS7f14BuwFY8MwmCucYAqNp4iF
-
1347  sp6JS7f14BuwFY8Mwo2bgdFtxBzpF
-
1348  sp6JS7f14BuwFY8MwoGwD7v4U6qBh
-
1349  sp6JS7f14BuwFY8MwoUczqFADMoXi
-
1350  sp6JS7f14BuwFY8MwoY1xZeGd3gAr
-
1351  sp6JS7f14BuwFY8MwomVCbfkv4kYZ
-
1352  sp6JS7f14BuwFY8MwoqbrPSr4z13F
-
1353 
-
1354 33 account seeds that produce account IDs with low 32-bits 0x304033aa:
-
1355  sp6JS7f14BuwFY8Mw5DaUP9agF5e1
-
1356  sp6JS7f14BuwFY8Mw5ohbtmPN4yGN
-
1357  sp6JS7f14BuwFY8Mw5rRsA5fcoTAQ
-
1358  sp6JS7f14BuwFY8Mw6zpYHMY3m6KT
-
1359  sp6JS7f14BuwFY8Mw86BzQq4sTnoW
-
1360  sp6JS7f14BuwFY8Mw8CCpnfvmGdV7
-
1361  sp6JS7f14BuwFY8Mw8DRjUDaBcFco
-
1362  sp6JS7f14BuwFY8Mw8cL7GPo3zZN7
-
1363  sp6JS7f14BuwFY8Mw8y6aeYVtH6qt
-
1364  sp6JS7f14BuwFY8MwFZR3PtVTCdUH
-
1365  sp6JS7f14BuwFY8MwFcdcdbgz7m3s
-
1366  sp6JS7f14BuwFY8MwjdnJDiUxEBRR
-
1367  sp6JS7f14BuwFY8MwjhxWgSntqrFe
-
1368  sp6JS7f14BuwFY8MwjrSHEhZ8CUM1
-
1369  sp6JS7f14BuwFY8MwjzkEeSTc9ZYf
-
1370  sp6JS7f14BuwFY8MwkBZSk9JhaeCB
-
1371  sp6JS7f14BuwFY8MwkGfwNY4i2iiU
-
1372  sp6JS7f14BuwFY8MwknjtZd2oU2Ff
-
1373  sp6JS7f14BuwFY8Mwkszsqd3ok9NE
-
1374  sp6JS7f14BuwFY8Mwm58A81MAMvgZ
-
1375  sp6JS7f14BuwFY8MwmiPTWysuDJCH
-
1376  sp6JS7f14BuwFY8MwmxhiNeLfD76r
-
1377  sp6JS7f14BuwFY8Mwo7SPdkwpGrFH
-
1378  sp6JS7f14BuwFY8MwoANq4F1Sj3qH
-
1379  sp6JS7f14BuwFY8MwoVjcHufAkd6L
-
1380  sp6JS7f14BuwFY8MwoVxHBXdaxzhm
-
1381  sp6JS7f14BuwFY8MwoZ2oTjBNfLpm
-
1382  sp6JS7f14BuwFY8Mwoc9swzyotFVD
-
1383  sp6JS7f14BuwFY8MwogMqVRwVEcQ9
-
1384  sp6JS7f14BuwFY8MwohMm7WxwnFqH
-
1385  sp6JS7f14BuwFY8MwopUcpZHuF8BH
-
1386  sp6JS7f14BuwFY8Mwor6rW6SS7tiB
-
1387  sp6JS7f14BuwFY8MwoxyaqYz4Ngsb
-
1388 
-
1389 33 account seeds that produce account IDs with low 32-bits 0x42d4e09c:
-
1390  sp6JS7f14BuwFY8Mw58NSZH9EaUxQ
-
1391  sp6JS7f14BuwFY8Mw5JByk1pgPpL7
-
1392  sp6JS7f14BuwFY8Mw5YrJJuXnkHVB
-
1393  sp6JS7f14BuwFY8Mw5kZe2ZzNSnKR
-
1394  sp6JS7f14BuwFY8Mw6eXHTsbwi1U7
-
1395  sp6JS7f14BuwFY8Mw6gqN7HHDDKSh
-
1396  sp6JS7f14BuwFY8Mw6zw8L1sSSR53
-
1397  sp6JS7f14BuwFY8Mw8E4WqSKKbksy
-
1398  sp6JS7f14BuwFY8MwF3V9gemqJtND
-
1399  sp6JS7f14BuwFY8Mwj4j46LHWZuY6
-
1400  sp6JS7f14BuwFY8MwjF5i8vh4Ezjy
-
1401  sp6JS7f14BuwFY8MwjJZpEKgMpUAt
-
1402  sp6JS7f14BuwFY8MwjWL7LfnzNUuh
-
1403  sp6JS7f14BuwFY8Mwk7Y1csGuqAhX
-
1404  sp6JS7f14BuwFY8MwkB1HVH17hN5W
-
1405  sp6JS7f14BuwFY8MwkBntH7BZZupu
-
1406  sp6JS7f14BuwFY8MwkEy4rMbNHG9P
-
1407  sp6JS7f14BuwFY8MwkKz4LYesZeiN
-
1408  sp6JS7f14BuwFY8MwkUrXyo9gMDPM
-
1409  sp6JS7f14BuwFY8MwkV2hySsxej1G
-
1410  sp6JS7f14BuwFY8MwkozhTVN12F9C
-
1411  sp6JS7f14BuwFY8MwkpkzGB3sFJw5
-
1412  sp6JS7f14BuwFY8Mwks3zDZLGrhdn
-
1413  sp6JS7f14BuwFY8MwktG1KCS7L2wW
-
1414  sp6JS7f14BuwFY8Mwm1jVFsafwcYx
-
1415  sp6JS7f14BuwFY8Mwm8hmrU6g5Wd6
-
1416  sp6JS7f14BuwFY8MwmFvstfRF7e2f
-
1417  sp6JS7f14BuwFY8MwmeRohi6m5fs8
-
1418  sp6JS7f14BuwFY8MwmmU96RHUaRZL
-
1419  sp6JS7f14BuwFY8MwoDFzteYqaUh4
-
1420  sp6JS7f14BuwFY8MwoPkTf5tDykPF
-
1421  sp6JS7f14BuwFY8MwoSbMaDtiMoDN
-
1422  sp6JS7f14BuwFY8MwoVL1vY1CysjR
-
1423 
-
1424 33 account seeds that produce account IDs with low 32-bits 0x9a8ebed3:
-
1425  sp6JS7f14BuwFY8Mw5FnqmbciPvH6
-
1426  sp6JS7f14BuwFY8Mw5MBGbyMSsXLp
-
1427  sp6JS7f14BuwFY8Mw5S4PnDyBdKKm
-
1428  sp6JS7f14BuwFY8Mw6kcXpM2enE35
-
1429  sp6JS7f14BuwFY8Mw6tuuSMMwyJ44
-
1430  sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt
-
1431  sp6JS7f14BuwFY8Mw8WwdgWkCHhEx
-
1432  sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ
-
1433  sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ
-
1434  sp6JS7f14BuwFY8Mw8fdSwLCZWDFd
-
1435  sp6JS7f14BuwFY8Mw8zuF6Fg65i1E
-
1436  sp6JS7f14BuwFY8MwF2k7bihVfqes
-
1437  sp6JS7f14BuwFY8MwF6X24WXGn557
-
1438  sp6JS7f14BuwFY8MwFMpn7strjekg
-
1439  sp6JS7f14BuwFY8MwFSdy9sYVrwJs
-
1440  sp6JS7f14BuwFY8MwFdMcLy9UkrXn
-
1441  sp6JS7f14BuwFY8MwFdbwFm1AAboa
-
1442  sp6JS7f14BuwFY8MwFdr5AhKThVtU
-
1443  sp6JS7f14BuwFY8MwjFc3Q9YatvAw
-
1444  sp6JS7f14BuwFY8MwjRXcNs1ozEXn
-
1445  sp6JS7f14BuwFY8MwkQGUKL7v1FBt
-
1446  sp6JS7f14BuwFY8Mwkamsoxx1wECt
-
1447  sp6JS7f14BuwFY8Mwm3hus1dG6U8y
-
1448  sp6JS7f14BuwFY8Mwm589M8vMRpXF
-
1449  sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3
-
1450  sp6JS7f14BuwFY8MwmRfy8fer4QbL
-
1451  sp6JS7f14BuwFY8MwmkkFx1HtgWRx
-
1452  sp6JS7f14BuwFY8MwmwP9JFdKa4PS
-
1453  sp6JS7f14BuwFY8MwoXWJLB3ciHfo
-
1454  sp6JS7f14BuwFY8MwoYc1gTtT2mWL
-
1455  sp6JS7f14BuwFY8MwogXtHH7FNVoo
-
1456  sp6JS7f14BuwFY8MwoqYoA9P8gf3r
-
1457  sp6JS7f14BuwFY8MwoujwMJofGnsA
-
1458 
-
1459 33 account seeds that produce account IDs with low 32-bits 0xa1dcea4a:
-
1460  sp6JS7f14BuwFY8Mw5Ccov2N36QTy
-
1461  sp6JS7f14BuwFY8Mw5CuSemVb5p7w
-
1462  sp6JS7f14BuwFY8Mw5Ep8wpsTfpSz
-
1463  sp6JS7f14BuwFY8Mw5WtutJc2H45M
-
1464  sp6JS7f14BuwFY8Mw6vsDeaSKeUJZ
-
1465  sp6JS7f14BuwFY8Mw83t5BPWUAzzF
-
1466  sp6JS7f14BuwFY8Mw8FYGnK35mgkV
-
1467  sp6JS7f14BuwFY8Mw8huo1x5pfKKJ
-
1468  sp6JS7f14BuwFY8Mw8mPStxfMDrZa
-
1469  sp6JS7f14BuwFY8Mw8yC3A7aQJytK
-
1470  sp6JS7f14BuwFY8MwFCWCDmo9o3t8
-
1471  sp6JS7f14BuwFY8MwFjapa4gKxPhR
-
1472  sp6JS7f14BuwFY8Mwj8CWtG29uw71
-
1473  sp6JS7f14BuwFY8MwjHyU5KpEMLVT
-
1474  sp6JS7f14BuwFY8MwjMZSN7LZuWD8
-
1475  sp6JS7f14BuwFY8Mwja2TXJNBhKHU
-
1476  sp6JS7f14BuwFY8Mwjf3xNTopHKTF
-
1477  sp6JS7f14BuwFY8Mwjn5RAhedPeuM
-
1478  sp6JS7f14BuwFY8MwkJdr4d6QoE8K
-
1479  sp6JS7f14BuwFY8MwkmBryo3SUoLm
-
1480  sp6JS7f14BuwFY8MwkrPdsc4tR8yw
-
1481  sp6JS7f14BuwFY8Mwkttjcw2a65Fi
-
1482  sp6JS7f14BuwFY8Mwm19n3rSaNx5S
-
1483  sp6JS7f14BuwFY8Mwm3ryr4Xp2aQX
-
1484  sp6JS7f14BuwFY8MwmBnDmgnJLB6B
-
1485  sp6JS7f14BuwFY8MwmHgPjzrYjthq
-
1486  sp6JS7f14BuwFY8MwmeV55DAnWKdd
-
1487  sp6JS7f14BuwFY8Mwo49hK6BGrauT
-
1488  sp6JS7f14BuwFY8Mwo56vfKY9aoWu
-
1489  sp6JS7f14BuwFY8MwoU7tTTXLQTrh
-
1490  sp6JS7f14BuwFY8MwoXpogSF2KaZB
-
1491  sp6JS7f14BuwFY8MwoY9JYQAR16pc
-
1492  sp6JS7f14BuwFY8MwoozLzKNAEXKM
-
1493 
-
1494 33 account seeds that produce account IDs with low 32-bits 0xbd2116db:
-
1495  sp6JS7f14BuwFY8Mw5GrpkmPuA3Bw
-
1496  sp6JS7f14BuwFY8Mw5r1sLoQJZDc6
-
1497  sp6JS7f14BuwFY8Mw68zzRmezLdd6
-
1498  sp6JS7f14BuwFY8Mw6jDSyaiF1mRp
-
1499  sp6JS7f14BuwFY8Mw813wU9u5D6Uh
-
1500  sp6JS7f14BuwFY8Mw8BBvpf2JFGoJ
-
1501  sp6JS7f14BuwFY8Mw8F7zXxAiT263
-
1502  sp6JS7f14BuwFY8Mw8XG7WuVGHP2N
-
1503  sp6JS7f14BuwFY8Mw8eyWrcz91cz6
-
1504  sp6JS7f14BuwFY8Mw8yNVKFVYyk9u
-
1505  sp6JS7f14BuwFY8MwF2oA6ePqvZWP
-
1506  sp6JS7f14BuwFY8MwF9VkcSNh3keq
-
1507  sp6JS7f14BuwFY8MwFYsMWajgEf2j
-
1508  sp6JS7f14BuwFY8Mwj3Gu43jYoJ4n
-
1509  sp6JS7f14BuwFY8MwjJ5iRmYDHrW4
-
1510  sp6JS7f14BuwFY8MwjaUSSga93CiM
-
1511  sp6JS7f14BuwFY8MwjxgLh2FY4Lvt
-
1512  sp6JS7f14BuwFY8Mwk9hQdNZUgmTB
-
1513  sp6JS7f14BuwFY8MwkcMXqtFp1sMx
-
1514  sp6JS7f14BuwFY8MwkzZCDc56jsUB
-
1515  sp6JS7f14BuwFY8Mwm5Zz7fP24Qym
-
1516  sp6JS7f14BuwFY8MwmDWqizXSoJRG
-
1517  sp6JS7f14BuwFY8MwmKHmkNYdMqqi
-
1518  sp6JS7f14BuwFY8MwmRfAWHxWpGNK
-
1519  sp6JS7f14BuwFY8MwmjCdXwyhphZ1
-
1520  sp6JS7f14BuwFY8MwmmukDAm1w6FL
-
1521  sp6JS7f14BuwFY8Mwmmz2SzaR9TRH
-
1522  sp6JS7f14BuwFY8Mwmz2z5mKHXzfn
-
1523  sp6JS7f14BuwFY8Mwo2xNe5629r5k
-
1524  sp6JS7f14BuwFY8MwoKy8tZxZrfJw
-
1525  sp6JS7f14BuwFY8MwoLyQ9aMsq8Dm
-
1526  sp6JS7f14BuwFY8MwoqqYkewuyZck
-
1527  sp6JS7f14BuwFY8MwouvvhREVp6Pp
-
1528 
-
1529 33 account seeds that produce account IDs with low 32-bits 0xd80df065:
-
1530  sp6JS7f14BuwFY8Mw5B7ERyhAfgHA
-
1531  sp6JS7f14BuwFY8Mw5VuW3cF7bm2v
-
1532  sp6JS7f14BuwFY8Mw5py3t1j7YbFT
-
1533  sp6JS7f14BuwFY8Mw5qc84SzB6RHr
-
1534  sp6JS7f14BuwFY8Mw5vGHW1G1hAy8
-
1535  sp6JS7f14BuwFY8Mw6gVa8TYukws6
-
1536  sp6JS7f14BuwFY8Mw8K9w1RoUAv1w
-
1537  sp6JS7f14BuwFY8Mw8KvKtB7787CA
-
1538  sp6JS7f14BuwFY8Mw8Y7WhRbuFzRq
-
1539  sp6JS7f14BuwFY8Mw8cipw7inRmMn
-
1540  sp6JS7f14BuwFY8MwFM5fAUNLNB13
-
1541  sp6JS7f14BuwFY8MwFSe1zAsht3X3
-
1542  sp6JS7f14BuwFY8MwFYNdigqQuHZM
-
1543  sp6JS7f14BuwFY8MwjWkejj7V4V5Q
-
1544  sp6JS7f14BuwFY8Mwjd2JGpsjvynq
-
1545  sp6JS7f14BuwFY8Mwjg1xkducn751
-
1546  sp6JS7f14BuwFY8Mwjsp6LnaJvL1W
-
1547  sp6JS7f14BuwFY8MwjvSbLc9593yH
-
1548  sp6JS7f14BuwFY8Mwjw2h5wx7U6vZ
-
1549  sp6JS7f14BuwFY8MwjxKUjtRsmPLH
-
1550  sp6JS7f14BuwFY8Mwk1Yy8ginDfqv
-
1551  sp6JS7f14BuwFY8Mwk2HrWhWwZP12
-
1552  sp6JS7f14BuwFY8Mwk4SsqiexvpWs
-
1553  sp6JS7f14BuwFY8Mwk66zCs5ACpE6
-
1554  sp6JS7f14BuwFY8MwkCwx6vY97Nwh
-
1555  sp6JS7f14BuwFY8MwknrbjnhTTWU8
-
1556  sp6JS7f14BuwFY8MwkokDy2ShRzQx
-
1557  sp6JS7f14BuwFY8Mwm3BxnRPNxsuu
-
1558  sp6JS7f14BuwFY8MwmY9EWdQQsFVr
-
1559  sp6JS7f14BuwFY8MwmYTWjrDhmk8S
-
1560  sp6JS7f14BuwFY8Mwo9skXt9Y5BVS
-
1561  sp6JS7f14BuwFY8MwoZYKZybJ1Crp
-
1562  sp6JS7f14BuwFY8MwoyXqkhySfSmF
-
1563 
-
1564 33 account seeds that produce account IDs with low 32-bits 0xe2e44294:
-
1565  sp6JS7f14BuwFY8Mw53dmvTgNtBwi
-
1566  sp6JS7f14BuwFY8Mw5Wrxsqn6WrXW
-
1567  sp6JS7f14BuwFY8Mw5fGDT31RCXgC
-
1568  sp6JS7f14BuwFY8Mw5nKRkubwrLWM
-
1569  sp6JS7f14BuwFY8Mw5nXMajwKjriB
-
1570  sp6JS7f14BuwFY8Mw5xZybggrC9NG
-
1571  sp6JS7f14BuwFY8Mw5xea8f6dBMV5
-
1572  sp6JS7f14BuwFY8Mw5zDGofAHy5Lb
-
1573  sp6JS7f14BuwFY8Mw6eado41rQNVG
-
1574  sp6JS7f14BuwFY8Mw6yqKXQsQJPuU
-
1575  sp6JS7f14BuwFY8Mw83MSN4FDzSGH
-
1576  sp6JS7f14BuwFY8Mw8B3pUbzQqHe2
-
1577  sp6JS7f14BuwFY8Mw8WwRLnhBRvfk
-
1578  sp6JS7f14BuwFY8Mw8hDBpKbpJwJX
-
1579  sp6JS7f14BuwFY8Mw8jggRSZACe7M
-
1580  sp6JS7f14BuwFY8Mw8mJRpU3qWbwC
-
1581  sp6JS7f14BuwFY8MwFDnVozykN21u
-
1582  sp6JS7f14BuwFY8MwFGGRGY9fctgv
-
1583  sp6JS7f14BuwFY8MwjKznfChH9DQb
-
1584  sp6JS7f14BuwFY8MwjbC5GvngRCk6
-
1585  sp6JS7f14BuwFY8Mwk3Lb7FPe1629
-
1586  sp6JS7f14BuwFY8MwkCeS41BwVrBD
-
1587  sp6JS7f14BuwFY8MwkDnnvRyuWJ7d
-
1588  sp6JS7f14BuwFY8MwkbkRNnzDEFpf
-
1589  sp6JS7f14BuwFY8MwkiNhaVhGNk6v
-
1590  sp6JS7f14BuwFY8Mwm1X4UJXRZx3p
-
1591  sp6JS7f14BuwFY8Mwm7da9q5vfq7J
-
1592  sp6JS7f14BuwFY8MwmPLqfBPrHw5H
-
1593  sp6JS7f14BuwFY8MwmbJpxvVjEwm2
-
1594  sp6JS7f14BuwFY8MwoAVeA7ka37cD
-
1595  sp6JS7f14BuwFY8MwoTFFTAwFKmVM
-
1596  sp6JS7f14BuwFY8MwoYsne51VpDE3
-
1597  sp6JS7f14BuwFY8MwohLVnU1VTk5h
-
1598 
-
1599 #endif // 0
+
1096 public:
+
1097  void
+
1098  run() override
+
1099  {
+
1100  using namespace test::jtx;
+
1101  FeatureBitset const all{supported_amendments()};
+
1102  FeatureBitset const fixNFTDir{
+
1103  fixNFTokenDirV1, featureNonFungibleTokensV1_1};
+
1104 
+
1105  testWithFeats(all - fixNFTDir - fixNFTokenRemint);
+
1106  testWithFeats(all - fixNFTokenRemint);
+
1107  testWithFeats(all);
+
1108  }
+
1109 };
+
1110 
+
1111 BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDir, tx, ripple, 1);
+
1112 
+
1113 } // namespace ripple
+
1114 
+
1115 // Seed that produces an account with the low-32 bits == 0xFFFFFFFF in
+
1116 // case it is needed for future testing:
+
1117 //
+
1118 // sp6JS7f14BuwFY8MwFe95Vpi9Znjs
+
1119 //
+
1120 
+
1121 // Sets of related accounts.
+
1122 //
+
1123 // Identifying the seeds of accounts that generate account IDs with the
+
1124 // same low 32 bits takes a while. However several sets of accounts with
+
1125 // that relationship have been located. In case these sets of accounts are
+
1126 // needed for future testing scenarios they are recorded below.
+
1127 #if 0
+
1128 34 account seeds that produce account IDs with low 32-bits 0x399187e9:
+
1129  sp6JS7f14BuwFY8Mw5EYu5z86hKDL
+
1130  sp6JS7f14BuwFY8Mw5PUAMwc5ygd7
+
1131  sp6JS7f14BuwFY8Mw5R3xUBcLSeTs
+
1132  sp6JS7f14BuwFY8Mw5W6oS5sdC3oF
+
1133  sp6JS7f14BuwFY8Mw5pYc3D9iuLcw
+
1134  sp6JS7f14BuwFY8Mw5pfGVnhcdp3b
+
1135  sp6JS7f14BuwFY8Mw6jS6RdEqXqrN
+
1136  sp6JS7f14BuwFY8Mw6krt6AKbvRXW
+
1137  sp6JS7f14BuwFY8Mw6mnVBQq7cAN2
+
1138  sp6JS7f14BuwFY8Mw8ECJxPjmkufQ
+
1139  sp6JS7f14BuwFY8Mw8asgzcceGWYm
+
1140  sp6JS7f14BuwFY8MwF6J3FXnPCgL8
+
1141  sp6JS7f14BuwFY8MwFEud2w5czv5q
+
1142  sp6JS7f14BuwFY8MwFNxKVqJnx8P5
+
1143  sp6JS7f14BuwFY8MwFnTCXg3eRidL
+
1144  sp6JS7f14BuwFY8Mwj47hv1vrDge6
+
1145  sp6JS7f14BuwFY8Mwj6TYekeeyukh
+
1146  sp6JS7f14BuwFY8MwjFjsRDerz7jb
+
1147  sp6JS7f14BuwFY8Mwjrj9mHTLBrcX
+
1148  sp6JS7f14BuwFY8MwkKcJi3zMzAea
+
1149  sp6JS7f14BuwFY8MwkYTDdnYRm9z4
+
1150  sp6JS7f14BuwFY8Mwkq8ei4D8uPNd
+
1151  sp6JS7f14BuwFY8Mwm2pFruxbnJRd
+
1152  sp6JS7f14BuwFY8MwmJV2ZnAjpC2g
+
1153  sp6JS7f14BuwFY8MwmTFMPHQHfVYF
+
1154  sp6JS7f14BuwFY8MwmkG2jXEgqiud
+
1155  sp6JS7f14BuwFY8Mwms3xEh5tMDTw
+
1156  sp6JS7f14BuwFY8MwmtipW4D8giZ9
+
1157  sp6JS7f14BuwFY8MwoRQBZm4KUUeE
+
1158  sp6JS7f14BuwFY8MwoVey94QpXcrc
+
1159  sp6JS7f14BuwFY8MwoZiuUoUTo3VG
+
1160  sp6JS7f14BuwFY8MwonFFDLT4bHAZ
+
1161  sp6JS7f14BuwFY8MwooGphD4hefBQ
+
1162  sp6JS7f14BuwFY8MwoxDp3dmX6q5N
+
1163 
+
1164 34 account seeds that produce account IDs with low 32-bits 0x473f2c9a:
+
1165  sp6JS7f14BuwFY8Mw53ktgqmv5Bmz
+
1166  sp6JS7f14BuwFY8Mw5KPb2Kz7APFX
+
1167  sp6JS7f14BuwFY8Mw5Xx4A6HRTPEE
+
1168  sp6JS7f14BuwFY8Mw5y6qZFNAo358
+
1169  sp6JS7f14BuwFY8Mw6kdaBg1QrZfn
+
1170  sp6JS7f14BuwFY8Mw8QmTfLMAZ5K1
+
1171  sp6JS7f14BuwFY8Mw8cbRRVcCEELr
+
1172  sp6JS7f14BuwFY8Mw8gQvJebmxvDG
+
1173  sp6JS7f14BuwFY8Mw8qPQurwu3P7Y
+
1174  sp6JS7f14BuwFY8MwFS4PEVKmuPy5
+
1175  sp6JS7f14BuwFY8MwFUQM1rAsQ8tS
+
1176  sp6JS7f14BuwFY8MwjJBZCkuwsRnM
+
1177  sp6JS7f14BuwFY8MwjTdS8vZhX5E9
+
1178  sp6JS7f14BuwFY8MwjhSmWCbNhd25
+
1179  sp6JS7f14BuwFY8MwjwkpqwZsDBw9
+
1180  sp6JS7f14BuwFY8MwjyET4p6eqd5J
+
1181  sp6JS7f14BuwFY8MwkMNAe4JhnG7E
+
1182  sp6JS7f14BuwFY8MwkRRpnT93UWWS
+
1183  sp6JS7f14BuwFY8MwkY9CvB22RvUe
+
1184  sp6JS7f14BuwFY8Mwkhw9VxXqmTr7
+
1185  sp6JS7f14BuwFY8MwkmgaTat7eFa7
+
1186  sp6JS7f14BuwFY8Mwkq5SxGGv1oLH
+
1187  sp6JS7f14BuwFY8MwmCBM5p5bTg6y
+
1188  sp6JS7f14BuwFY8MwmmmXaVah64dB
+
1189  sp6JS7f14BuwFY8Mwo7R7Cn614v9V
+
1190  sp6JS7f14BuwFY8MwoCAG1na7GR2M
+
1191  sp6JS7f14BuwFY8MwoDuPvJS4gG7C
+
1192  sp6JS7f14BuwFY8MwoMMowSyPQLfy
+
1193  sp6JS7f14BuwFY8MwoRqDiwTNsTBm
+
1194  sp6JS7f14BuwFY8MwoWbBWtjpB7pg
+
1195  sp6JS7f14BuwFY8Mwoi1AEeELGecF
+
1196  sp6JS7f14BuwFY8MwopGP6Lo5byuj
+
1197  sp6JS7f14BuwFY8MwoufkXGHp2VW8
+
1198  sp6JS7f14BuwFY8MwowGeagFQY32k
+
1199 
+
1200 34 account seeds that produce account IDs with low 32-bits 0x4d59f0d1:
+
1201  sp6JS7f14BuwFY8Mw5CsNgH64zxK7
+
1202  sp6JS7f14BuwFY8Mw5Dg4wi2E344h
+
1203  sp6JS7f14BuwFY8Mw5ErV949Zh2PX
+
1204  sp6JS7f14BuwFY8Mw5p4nsQvEUE1s
+
1205  sp6JS7f14BuwFY8Mw8LGnkbaP68Gn
+
1206  sp6JS7f14BuwFY8Mw8aq6RCBc3iHo
+
1207  sp6JS7f14BuwFY8Mw8bkWaGoKYT6e
+
1208  sp6JS7f14BuwFY8Mw8qrCuXnzAXVj
+
1209  sp6JS7f14BuwFY8MwFDKcPAHPHJTm
+
1210  sp6JS7f14BuwFY8MwFUXJs4unfgNu
+
1211  sp6JS7f14BuwFY8MwFj9Yv5LjshD9
+
1212  sp6JS7f14BuwFY8Mwj3H73nmq5UaC
+
1213  sp6JS7f14BuwFY8MwjHSYShis1Yhk
+
1214  sp6JS7f14BuwFY8MwjpfE1HVo8UP1
+
1215  sp6JS7f14BuwFY8Mwk6JE1SXUuiNc
+
1216  sp6JS7f14BuwFY8MwkASgxEjEnFmU
+
1217  sp6JS7f14BuwFY8MwkGNY8kg7R6RK
+
1218  sp6JS7f14BuwFY8MwkHinNZ8SYBQu
+
1219  sp6JS7f14BuwFY8MwkXLCW1hbhGya
+
1220  sp6JS7f14BuwFY8MwkZ7mWrYK9YtU
+
1221  sp6JS7f14BuwFY8MwkdFSqNB5DbKL
+
1222  sp6JS7f14BuwFY8Mwm3jdBaCAx8H6
+
1223  sp6JS7f14BuwFY8Mwm3rk5hEwDRtY
+
1224  sp6JS7f14BuwFY8Mwm77a2ULuwxu4
+
1225  sp6JS7f14BuwFY8MwmJpY7braKLaN
+
1226  sp6JS7f14BuwFY8MwmKHQjG4XiZ6g
+
1227  sp6JS7f14BuwFY8Mwmmv8Y3wyUDzs
+
1228  sp6JS7f14BuwFY8MwmucFe1WgqtwG
+
1229  sp6JS7f14BuwFY8Mwo1EjdU1bznZR
+
1230  sp6JS7f14BuwFY8MwoJiqankkU5uR
+
1231  sp6JS7f14BuwFY8MwoLnvQ6zdqbKw
+
1232  sp6JS7f14BuwFY8MwoUGeJ319eu48
+
1233  sp6JS7f14BuwFY8MwoYf135tQjHP4
+
1234  sp6JS7f14BuwFY8MwogeF6M6SAyid
+
1235 
+
1236 34 account seeds that produce account IDs with low 32-bits 0xabb11898:
+
1237  sp6JS7f14BuwFY8Mw5DgiYaNVSb1G
+
1238  sp6JS7f14BuwFY8Mw5k6e94TMvuox
+
1239  sp6JS7f14BuwFY8Mw5tTSN7KzYxiT
+
1240  sp6JS7f14BuwFY8Mw61XV6m33utif
+
1241  sp6JS7f14BuwFY8Mw87jKfrjiENCb
+
1242  sp6JS7f14BuwFY8Mw8AFtxxFiRtJG
+
1243  sp6JS7f14BuwFY8Mw8cosAVExzbeE
+
1244  sp6JS7f14BuwFY8Mw8fmkQ63zE8WQ
+
1245  sp6JS7f14BuwFY8Mw8iYSsxNbDN6D
+
1246  sp6JS7f14BuwFY8Mw8wTZdGRJyyM1
+
1247  sp6JS7f14BuwFY8Mw8z7xEh3qBGr7
+
1248  sp6JS7f14BuwFY8MwFL5gpKQWZj7g
+
1249  sp6JS7f14BuwFY8MwFPeZchXQnRZ5
+
1250  sp6JS7f14BuwFY8MwFSPxWSJVoU29
+
1251  sp6JS7f14BuwFY8MwFYyVkqX8kvRm
+
1252  sp6JS7f14BuwFY8MwFcbVikUEwJvk
+
1253  sp6JS7f14BuwFY8MwjF7NcZk1NctK
+
1254  sp6JS7f14BuwFY8MwjJCwYr9zSfAv
+
1255  sp6JS7f14BuwFY8MwjYa5yLkgCLuT
+
1256  sp6JS7f14BuwFY8MwjenxuJ3TH2Bc
+
1257  sp6JS7f14BuwFY8MwjriN7Ui11NzB
+
1258  sp6JS7f14BuwFY8Mwk3AuoJNSEo34
+
1259  sp6JS7f14BuwFY8MwkT36hnRv8hTo
+
1260  sp6JS7f14BuwFY8MwkTQixEXfi1Cr
+
1261  sp6JS7f14BuwFY8MwkYJaZM1yTJBF
+
1262  sp6JS7f14BuwFY8Mwkc4k1uo85qp2
+
1263  sp6JS7f14BuwFY8Mwkf7cFhF1uuxx
+
1264  sp6JS7f14BuwFY8MwmCK2un99wb4e
+
1265  sp6JS7f14BuwFY8MwmETztNHYu2Bx
+
1266  sp6JS7f14BuwFY8MwmJws9UwRASfR
+
1267  sp6JS7f14BuwFY8MwoH5PQkGK8tEb
+
1268  sp6JS7f14BuwFY8MwoVXtP2yCzjJV
+
1269  sp6JS7f14BuwFY8MwobxRXA9vsTeX
+
1270  sp6JS7f14BuwFY8Mwos3pc5Gb3ihU
+
1271 
+
1272 34 account seeds that produce account IDs with low 32-bits 0xce627322:
+
1273  sp6JS7f14BuwFY8Mw5Ck6i83pGNh3
+
1274  sp6JS7f14BuwFY8Mw5FKuwTxjAdH1
+
1275  sp6JS7f14BuwFY8Mw5FVKkEn6TkLH
+
1276  sp6JS7f14BuwFY8Mw5NbQwLwHDd5v
+
1277  sp6JS7f14BuwFY8Mw5X1dbz3msZaZ
+
1278  sp6JS7f14BuwFY8Mw6qv6qaXNeP74
+
1279  sp6JS7f14BuwFY8Mw81SXagUeutCw
+
1280  sp6JS7f14BuwFY8Mw84Ph7Qa8kwwk
+
1281  sp6JS7f14BuwFY8Mw8Hp4gFyU3Qko
+
1282  sp6JS7f14BuwFY8Mw8Kt8bAKredSx
+
1283  sp6JS7f14BuwFY8Mw8XHK3VKRQ7v7
+
1284  sp6JS7f14BuwFY8Mw8eGyWxZGHY6v
+
1285  sp6JS7f14BuwFY8Mw8iU5CLyHVcD2
+
1286  sp6JS7f14BuwFY8Mw8u3Zr26Ar914
+
1287  sp6JS7f14BuwFY8MwF2Kcdxtjzjv8
+
1288  sp6JS7f14BuwFY8MwFLmPWb6rbxNg
+
1289  sp6JS7f14BuwFY8MwFUu8s7UVuxuJ
+
1290  sp6JS7f14BuwFY8MwFYBaatwHxAJ8
+
1291  sp6JS7f14BuwFY8Mwjg6hFkeHwoqG
+
1292  sp6JS7f14BuwFY8MwjjycJojy2ufk
+
1293  sp6JS7f14BuwFY8MwkEWoxcSKGPXv
+
1294  sp6JS7f14BuwFY8MwkMe7wLkEUsQT
+
1295  sp6JS7f14BuwFY8MwkvyKLaPUc4FS
+
1296  sp6JS7f14BuwFY8Mwm8doqXPKZmVQ
+
1297  sp6JS7f14BuwFY8Mwm9r3No8yQ8Tx
+
1298  sp6JS7f14BuwFY8Mwm9w6dks68W9B
+
1299  sp6JS7f14BuwFY8MwmMPrv9sCdbpS
+
1300  sp6JS7f14BuwFY8MwmPAvs3fcQNja
+
1301  sp6JS7f14BuwFY8MwmS5jasapfcnJ
+
1302  sp6JS7f14BuwFY8MwmU2L3qJEhnuA
+
1303  sp6JS7f14BuwFY8MwoAQYmiBnW7fM
+
1304  sp6JS7f14BuwFY8MwoBkkkXrPmkKF
+
1305  sp6JS7f14BuwFY8MwonfmxPo6tkvC
+
1306  sp6JS7f14BuwFY8MwouZFwhiNcYq6
+
1307 
+
1308 34 account seeds that produce account IDs with low 32-bits 0xe29643e8:
+
1309  sp6JS7f14BuwFY8Mw5EfAavcXAh2k
+
1310  sp6JS7f14BuwFY8Mw5LhFjLkFSCVF
+
1311  sp6JS7f14BuwFY8Mw5bRfEv5HgdBh
+
1312  sp6JS7f14BuwFY8Mw5d6sPcKzypKN
+
1313  sp6JS7f14BuwFY8Mw5rcqDtk1fACP
+
1314  sp6JS7f14BuwFY8Mw5xkxRq1Notzv
+
1315  sp6JS7f14BuwFY8Mw66fbkdw5WYmt
+
1316  sp6JS7f14BuwFY8Mw6diEG8sZ7Fx7
+
1317  sp6JS7f14BuwFY8Mw6v2r1QhG7xc1
+
1318  sp6JS7f14BuwFY8Mw6zP6DHCTx2Fd
+
1319  sp6JS7f14BuwFY8Mw8B3n39JKuFkk
+
1320  sp6JS7f14BuwFY8Mw8FmBvqYw7uqn
+
1321  sp6JS7f14BuwFY8Mw8KEaftb1eRwu
+
1322  sp6JS7f14BuwFY8Mw8WJ1qKkegj9N
+
1323  sp6JS7f14BuwFY8Mw8r8cAZEkq2BS
+
1324  sp6JS7f14BuwFY8MwFKPxxwF65gZh
+
1325  sp6JS7f14BuwFY8MwFKhaF8APcN5H
+
1326  sp6JS7f14BuwFY8MwFN2buJn4BgYC
+
1327  sp6JS7f14BuwFY8MwFUTe175MjP3x
+
1328  sp6JS7f14BuwFY8MwFZhmRDb53NNb
+
1329  sp6JS7f14BuwFY8MwFa2Azn5nU2WS
+
1330  sp6JS7f14BuwFY8MwjNNt91hwgkn7
+
1331  sp6JS7f14BuwFY8MwjdiYt6ChACe7
+
1332  sp6JS7f14BuwFY8Mwk5qFVQ48Mmr9
+
1333  sp6JS7f14BuwFY8MwkGvCj7pNf1zG
+
1334  sp6JS7f14BuwFY8MwkY9UcN2D2Fzs
+
1335  sp6JS7f14BuwFY8MwkpGvSk9G9RyT
+
1336  sp6JS7f14BuwFY8MwmGQ7nJf1eEzV
+
1337  sp6JS7f14BuwFY8MwmQLjGsYdyAmV
+
1338  sp6JS7f14BuwFY8MwmZ8usztKvikT
+
1339  sp6JS7f14BuwFY8MwobyMLC2hQdFR
+
1340  sp6JS7f14BuwFY8MwoiRtwUecZeJ5
+
1341  sp6JS7f14BuwFY8MwojHjKsUzj1KJ
+
1342  sp6JS7f14BuwFY8Mwop29anGAjidU
+
1343 
+
1344 33 account seeds that produce account IDs with low 32-bits 0x115d0525:
+
1345  sp6JS7f14BuwFY8Mw56vZeiBuhePx
+
1346  sp6JS7f14BuwFY8Mw5BodF9tGuTUe
+
1347  sp6JS7f14BuwFY8Mw5EnhC1cg84J7
+
1348  sp6JS7f14BuwFY8Mw5P913Cunr2BK
+
1349  sp6JS7f14BuwFY8Mw5Pru7eLo1XzT
+
1350  sp6JS7f14BuwFY8Mw61SLUC8UX2m8
+
1351  sp6JS7f14BuwFY8Mw6AsBF9TpeMpq
+
1352  sp6JS7f14BuwFY8Mw84XqrBZkU2vE
+
1353  sp6JS7f14BuwFY8Mw89oSU6dBk3KB
+
1354  sp6JS7f14BuwFY8Mw89qUKCyDmyzj
+
1355  sp6JS7f14BuwFY8Mw8GfqQ9VRZ8tm
+
1356  sp6JS7f14BuwFY8Mw8LtW3VqrqMks
+
1357  sp6JS7f14BuwFY8Mw8ZrAkJc2sHew
+
1358  sp6JS7f14BuwFY8Mw8jpkYSNrD3ah
+
1359  sp6JS7f14BuwFY8MwF2mshd786m3V
+
1360  sp6JS7f14BuwFY8MwFHfXq9x5NbPY
+
1361  sp6JS7f14BuwFY8MwFrjWq5LAB8NT
+
1362  sp6JS7f14BuwFY8Mwj4asgSh6hQZd
+
1363  sp6JS7f14BuwFY8Mwj7ipFfqBSRrE
+
1364  sp6JS7f14BuwFY8MwjHqtcvGav8uW
+
1365  sp6JS7f14BuwFY8MwjLp4sk5fmzki
+
1366  sp6JS7f14BuwFY8MwjioHuYb3Ytkx
+
1367  sp6JS7f14BuwFY8MwkRjHPXWi7fGN
+
1368  sp6JS7f14BuwFY8MwkdVdPV3LjNN1
+
1369  sp6JS7f14BuwFY8MwkxUtVY5AXZFk
+
1370  sp6JS7f14BuwFY8Mwm4jQzdfTbY9F
+
1371  sp6JS7f14BuwFY8MwmCucYAqNp4iF
+
1372  sp6JS7f14BuwFY8Mwo2bgdFtxBzpF
+
1373  sp6JS7f14BuwFY8MwoGwD7v4U6qBh
+
1374  sp6JS7f14BuwFY8MwoUczqFADMoXi
+
1375  sp6JS7f14BuwFY8MwoY1xZeGd3gAr
+
1376  sp6JS7f14BuwFY8MwomVCbfkv4kYZ
+
1377  sp6JS7f14BuwFY8MwoqbrPSr4z13F
+
1378 
+
1379 33 account seeds that produce account IDs with low 32-bits 0x304033aa:
+
1380  sp6JS7f14BuwFY8Mw5DaUP9agF5e1
+
1381  sp6JS7f14BuwFY8Mw5ohbtmPN4yGN
+
1382  sp6JS7f14BuwFY8Mw5rRsA5fcoTAQ
+
1383  sp6JS7f14BuwFY8Mw6zpYHMY3m6KT
+
1384  sp6JS7f14BuwFY8Mw86BzQq4sTnoW
+
1385  sp6JS7f14BuwFY8Mw8CCpnfvmGdV7
+
1386  sp6JS7f14BuwFY8Mw8DRjUDaBcFco
+
1387  sp6JS7f14BuwFY8Mw8cL7GPo3zZN7
+
1388  sp6JS7f14BuwFY8Mw8y6aeYVtH6qt
+
1389  sp6JS7f14BuwFY8MwFZR3PtVTCdUH
+
1390  sp6JS7f14BuwFY8MwFcdcdbgz7m3s
+
1391  sp6JS7f14BuwFY8MwjdnJDiUxEBRR
+
1392  sp6JS7f14BuwFY8MwjhxWgSntqrFe
+
1393  sp6JS7f14BuwFY8MwjrSHEhZ8CUM1
+
1394  sp6JS7f14BuwFY8MwjzkEeSTc9ZYf
+
1395  sp6JS7f14BuwFY8MwkBZSk9JhaeCB
+
1396  sp6JS7f14BuwFY8MwkGfwNY4i2iiU
+
1397  sp6JS7f14BuwFY8MwknjtZd2oU2Ff
+
1398  sp6JS7f14BuwFY8Mwkszsqd3ok9NE
+
1399  sp6JS7f14BuwFY8Mwm58A81MAMvgZ
+
1400  sp6JS7f14BuwFY8MwmiPTWysuDJCH
+
1401  sp6JS7f14BuwFY8MwmxhiNeLfD76r
+
1402  sp6JS7f14BuwFY8Mwo7SPdkwpGrFH
+
1403  sp6JS7f14BuwFY8MwoANq4F1Sj3qH
+
1404  sp6JS7f14BuwFY8MwoVjcHufAkd6L
+
1405  sp6JS7f14BuwFY8MwoVxHBXdaxzhm
+
1406  sp6JS7f14BuwFY8MwoZ2oTjBNfLpm
+
1407  sp6JS7f14BuwFY8Mwoc9swzyotFVD
+
1408  sp6JS7f14BuwFY8MwogMqVRwVEcQ9
+
1409  sp6JS7f14BuwFY8MwohMm7WxwnFqH
+
1410  sp6JS7f14BuwFY8MwopUcpZHuF8BH
+
1411  sp6JS7f14BuwFY8Mwor6rW6SS7tiB
+
1412  sp6JS7f14BuwFY8MwoxyaqYz4Ngsb
+
1413 
+
1414 33 account seeds that produce account IDs with low 32-bits 0x42d4e09c:
+
1415  sp6JS7f14BuwFY8Mw58NSZH9EaUxQ
+
1416  sp6JS7f14BuwFY8Mw5JByk1pgPpL7
+
1417  sp6JS7f14BuwFY8Mw5YrJJuXnkHVB
+
1418  sp6JS7f14BuwFY8Mw5kZe2ZzNSnKR
+
1419  sp6JS7f14BuwFY8Mw6eXHTsbwi1U7
+
1420  sp6JS7f14BuwFY8Mw6gqN7HHDDKSh
+
1421  sp6JS7f14BuwFY8Mw6zw8L1sSSR53
+
1422  sp6JS7f14BuwFY8Mw8E4WqSKKbksy
+
1423  sp6JS7f14BuwFY8MwF3V9gemqJtND
+
1424  sp6JS7f14BuwFY8Mwj4j46LHWZuY6
+
1425  sp6JS7f14BuwFY8MwjF5i8vh4Ezjy
+
1426  sp6JS7f14BuwFY8MwjJZpEKgMpUAt
+
1427  sp6JS7f14BuwFY8MwjWL7LfnzNUuh
+
1428  sp6JS7f14BuwFY8Mwk7Y1csGuqAhX
+
1429  sp6JS7f14BuwFY8MwkB1HVH17hN5W
+
1430  sp6JS7f14BuwFY8MwkBntH7BZZupu
+
1431  sp6JS7f14BuwFY8MwkEy4rMbNHG9P
+
1432  sp6JS7f14BuwFY8MwkKz4LYesZeiN
+
1433  sp6JS7f14BuwFY8MwkUrXyo9gMDPM
+
1434  sp6JS7f14BuwFY8MwkV2hySsxej1G
+
1435  sp6JS7f14BuwFY8MwkozhTVN12F9C
+
1436  sp6JS7f14BuwFY8MwkpkzGB3sFJw5
+
1437  sp6JS7f14BuwFY8Mwks3zDZLGrhdn
+
1438  sp6JS7f14BuwFY8MwktG1KCS7L2wW
+
1439  sp6JS7f14BuwFY8Mwm1jVFsafwcYx
+
1440  sp6JS7f14BuwFY8Mwm8hmrU6g5Wd6
+
1441  sp6JS7f14BuwFY8MwmFvstfRF7e2f
+
1442  sp6JS7f14BuwFY8MwmeRohi6m5fs8
+
1443  sp6JS7f14BuwFY8MwmmU96RHUaRZL
+
1444  sp6JS7f14BuwFY8MwoDFzteYqaUh4
+
1445  sp6JS7f14BuwFY8MwoPkTf5tDykPF
+
1446  sp6JS7f14BuwFY8MwoSbMaDtiMoDN
+
1447  sp6JS7f14BuwFY8MwoVL1vY1CysjR
+
1448 
+
1449 33 account seeds that produce account IDs with low 32-bits 0x9a8ebed3:
+
1450  sp6JS7f14BuwFY8Mw5FnqmbciPvH6
+
1451  sp6JS7f14BuwFY8Mw5MBGbyMSsXLp
+
1452  sp6JS7f14BuwFY8Mw5S4PnDyBdKKm
+
1453  sp6JS7f14BuwFY8Mw6kcXpM2enE35
+
1454  sp6JS7f14BuwFY8Mw6tuuSMMwyJ44
+
1455  sp6JS7f14BuwFY8Mw8E8JWLQ1P8pt
+
1456  sp6JS7f14BuwFY8Mw8WwdgWkCHhEx
+
1457  sp6JS7f14BuwFY8Mw8XDUYvU6oGhQ
+
1458  sp6JS7f14BuwFY8Mw8ceVGL4M1zLQ
+
1459  sp6JS7f14BuwFY8Mw8fdSwLCZWDFd
+
1460  sp6JS7f14BuwFY8Mw8zuF6Fg65i1E
+
1461  sp6JS7f14BuwFY8MwF2k7bihVfqes
+
1462  sp6JS7f14BuwFY8MwF6X24WXGn557
+
1463  sp6JS7f14BuwFY8MwFMpn7strjekg
+
1464  sp6JS7f14BuwFY8MwFSdy9sYVrwJs
+
1465  sp6JS7f14BuwFY8MwFdMcLy9UkrXn
+
1466  sp6JS7f14BuwFY8MwFdbwFm1AAboa
+
1467  sp6JS7f14BuwFY8MwFdr5AhKThVtU
+
1468  sp6JS7f14BuwFY8MwjFc3Q9YatvAw
+
1469  sp6JS7f14BuwFY8MwjRXcNs1ozEXn
+
1470  sp6JS7f14BuwFY8MwkQGUKL7v1FBt
+
1471  sp6JS7f14BuwFY8Mwkamsoxx1wECt
+
1472  sp6JS7f14BuwFY8Mwm3hus1dG6U8y
+
1473  sp6JS7f14BuwFY8Mwm589M8vMRpXF
+
1474  sp6JS7f14BuwFY8MwmJTRJ4Fqz1A3
+
1475  sp6JS7f14BuwFY8MwmRfy8fer4QbL
+
1476  sp6JS7f14BuwFY8MwmkkFx1HtgWRx
+
1477  sp6JS7f14BuwFY8MwmwP9JFdKa4PS
+
1478  sp6JS7f14BuwFY8MwoXWJLB3ciHfo
+
1479  sp6JS7f14BuwFY8MwoYc1gTtT2mWL
+
1480  sp6JS7f14BuwFY8MwogXtHH7FNVoo
+
1481  sp6JS7f14BuwFY8MwoqYoA9P8gf3r
+
1482  sp6JS7f14BuwFY8MwoujwMJofGnsA
+
1483 
+
1484 33 account seeds that produce account IDs with low 32-bits 0xa1dcea4a:
+
1485  sp6JS7f14BuwFY8Mw5Ccov2N36QTy
+
1486  sp6JS7f14BuwFY8Mw5CuSemVb5p7w
+
1487  sp6JS7f14BuwFY8Mw5Ep8wpsTfpSz
+
1488  sp6JS7f14BuwFY8Mw5WtutJc2H45M
+
1489  sp6JS7f14BuwFY8Mw6vsDeaSKeUJZ
+
1490  sp6JS7f14BuwFY8Mw83t5BPWUAzzF
+
1491  sp6JS7f14BuwFY8Mw8FYGnK35mgkV
+
1492  sp6JS7f14BuwFY8Mw8huo1x5pfKKJ
+
1493  sp6JS7f14BuwFY8Mw8mPStxfMDrZa
+
1494  sp6JS7f14BuwFY8Mw8yC3A7aQJytK
+
1495  sp6JS7f14BuwFY8MwFCWCDmo9o3t8
+
1496  sp6JS7f14BuwFY8MwFjapa4gKxPhR
+
1497  sp6JS7f14BuwFY8Mwj8CWtG29uw71
+
1498  sp6JS7f14BuwFY8MwjHyU5KpEMLVT
+
1499  sp6JS7f14BuwFY8MwjMZSN7LZuWD8
+
1500  sp6JS7f14BuwFY8Mwja2TXJNBhKHU
+
1501  sp6JS7f14BuwFY8Mwjf3xNTopHKTF
+
1502  sp6JS7f14BuwFY8Mwjn5RAhedPeuM
+
1503  sp6JS7f14BuwFY8MwkJdr4d6QoE8K
+
1504  sp6JS7f14BuwFY8MwkmBryo3SUoLm
+
1505  sp6JS7f14BuwFY8MwkrPdsc4tR8yw
+
1506  sp6JS7f14BuwFY8Mwkttjcw2a65Fi
+
1507  sp6JS7f14BuwFY8Mwm19n3rSaNx5S
+
1508  sp6JS7f14BuwFY8Mwm3ryr4Xp2aQX
+
1509  sp6JS7f14BuwFY8MwmBnDmgnJLB6B
+
1510  sp6JS7f14BuwFY8MwmHgPjzrYjthq
+
1511  sp6JS7f14BuwFY8MwmeV55DAnWKdd
+
1512  sp6JS7f14BuwFY8Mwo49hK6BGrauT
+
1513  sp6JS7f14BuwFY8Mwo56vfKY9aoWu
+
1514  sp6JS7f14BuwFY8MwoU7tTTXLQTrh
+
1515  sp6JS7f14BuwFY8MwoXpogSF2KaZB
+
1516  sp6JS7f14BuwFY8MwoY9JYQAR16pc
+
1517  sp6JS7f14BuwFY8MwoozLzKNAEXKM
+
1518 
+
1519 33 account seeds that produce account IDs with low 32-bits 0xbd2116db:
+
1520  sp6JS7f14BuwFY8Mw5GrpkmPuA3Bw
+
1521  sp6JS7f14BuwFY8Mw5r1sLoQJZDc6
+
1522  sp6JS7f14BuwFY8Mw68zzRmezLdd6
+
1523  sp6JS7f14BuwFY8Mw6jDSyaiF1mRp
+
1524  sp6JS7f14BuwFY8Mw813wU9u5D6Uh
+
1525  sp6JS7f14BuwFY8Mw8BBvpf2JFGoJ
+
1526  sp6JS7f14BuwFY8Mw8F7zXxAiT263
+
1527  sp6JS7f14BuwFY8Mw8XG7WuVGHP2N
+
1528  sp6JS7f14BuwFY8Mw8eyWrcz91cz6
+
1529  sp6JS7f14BuwFY8Mw8yNVKFVYyk9u
+
1530  sp6JS7f14BuwFY8MwF2oA6ePqvZWP
+
1531  sp6JS7f14BuwFY8MwF9VkcSNh3keq
+
1532  sp6JS7f14BuwFY8MwFYsMWajgEf2j
+
1533  sp6JS7f14BuwFY8Mwj3Gu43jYoJ4n
+
1534  sp6JS7f14BuwFY8MwjJ5iRmYDHrW4
+
1535  sp6JS7f14BuwFY8MwjaUSSga93CiM
+
1536  sp6JS7f14BuwFY8MwjxgLh2FY4Lvt
+
1537  sp6JS7f14BuwFY8Mwk9hQdNZUgmTB
+
1538  sp6JS7f14BuwFY8MwkcMXqtFp1sMx
+
1539  sp6JS7f14BuwFY8MwkzZCDc56jsUB
+
1540  sp6JS7f14BuwFY8Mwm5Zz7fP24Qym
+
1541  sp6JS7f14BuwFY8MwmDWqizXSoJRG
+
1542  sp6JS7f14BuwFY8MwmKHmkNYdMqqi
+
1543  sp6JS7f14BuwFY8MwmRfAWHxWpGNK
+
1544  sp6JS7f14BuwFY8MwmjCdXwyhphZ1
+
1545  sp6JS7f14BuwFY8MwmmukDAm1w6FL
+
1546  sp6JS7f14BuwFY8Mwmmz2SzaR9TRH
+
1547  sp6JS7f14BuwFY8Mwmz2z5mKHXzfn
+
1548  sp6JS7f14BuwFY8Mwo2xNe5629r5k
+
1549  sp6JS7f14BuwFY8MwoKy8tZxZrfJw
+
1550  sp6JS7f14BuwFY8MwoLyQ9aMsq8Dm
+
1551  sp6JS7f14BuwFY8MwoqqYkewuyZck
+
1552  sp6JS7f14BuwFY8MwouvvhREVp6Pp
+
1553 
+
1554 33 account seeds that produce account IDs with low 32-bits 0xd80df065:
+
1555  sp6JS7f14BuwFY8Mw5B7ERyhAfgHA
+
1556  sp6JS7f14BuwFY8Mw5VuW3cF7bm2v
+
1557  sp6JS7f14BuwFY8Mw5py3t1j7YbFT
+
1558  sp6JS7f14BuwFY8Mw5qc84SzB6RHr
+
1559  sp6JS7f14BuwFY8Mw5vGHW1G1hAy8
+
1560  sp6JS7f14BuwFY8Mw6gVa8TYukws6
+
1561  sp6JS7f14BuwFY8Mw8K9w1RoUAv1w
+
1562  sp6JS7f14BuwFY8Mw8KvKtB7787CA
+
1563  sp6JS7f14BuwFY8Mw8Y7WhRbuFzRq
+
1564  sp6JS7f14BuwFY8Mw8cipw7inRmMn
+
1565  sp6JS7f14BuwFY8MwFM5fAUNLNB13
+
1566  sp6JS7f14BuwFY8MwFSe1zAsht3X3
+
1567  sp6JS7f14BuwFY8MwFYNdigqQuHZM
+
1568  sp6JS7f14BuwFY8MwjWkejj7V4V5Q
+
1569  sp6JS7f14BuwFY8Mwjd2JGpsjvynq
+
1570  sp6JS7f14BuwFY8Mwjg1xkducn751
+
1571  sp6JS7f14BuwFY8Mwjsp6LnaJvL1W
+
1572  sp6JS7f14BuwFY8MwjvSbLc9593yH
+
1573  sp6JS7f14BuwFY8Mwjw2h5wx7U6vZ
+
1574  sp6JS7f14BuwFY8MwjxKUjtRsmPLH
+
1575  sp6JS7f14BuwFY8Mwk1Yy8ginDfqv
+
1576  sp6JS7f14BuwFY8Mwk2HrWhWwZP12
+
1577  sp6JS7f14BuwFY8Mwk4SsqiexvpWs
+
1578  sp6JS7f14BuwFY8Mwk66zCs5ACpE6
+
1579  sp6JS7f14BuwFY8MwkCwx6vY97Nwh
+
1580  sp6JS7f14BuwFY8MwknrbjnhTTWU8
+
1581  sp6JS7f14BuwFY8MwkokDy2ShRzQx
+
1582  sp6JS7f14BuwFY8Mwm3BxnRPNxsuu
+
1583  sp6JS7f14BuwFY8MwmY9EWdQQsFVr
+
1584  sp6JS7f14BuwFY8MwmYTWjrDhmk8S
+
1585  sp6JS7f14BuwFY8Mwo9skXt9Y5BVS
+
1586  sp6JS7f14BuwFY8MwoZYKZybJ1Crp
+
1587  sp6JS7f14BuwFY8MwoyXqkhySfSmF
+
1588 
+
1589 33 account seeds that produce account IDs with low 32-bits 0xe2e44294:
+
1590  sp6JS7f14BuwFY8Mw53dmvTgNtBwi
+
1591  sp6JS7f14BuwFY8Mw5Wrxsqn6WrXW
+
1592  sp6JS7f14BuwFY8Mw5fGDT31RCXgC
+
1593  sp6JS7f14BuwFY8Mw5nKRkubwrLWM
+
1594  sp6JS7f14BuwFY8Mw5nXMajwKjriB
+
1595  sp6JS7f14BuwFY8Mw5xZybggrC9NG
+
1596  sp6JS7f14BuwFY8Mw5xea8f6dBMV5
+
1597  sp6JS7f14BuwFY8Mw5zDGofAHy5Lb
+
1598  sp6JS7f14BuwFY8Mw6eado41rQNVG
+
1599  sp6JS7f14BuwFY8Mw6yqKXQsQJPuU
+
1600  sp6JS7f14BuwFY8Mw83MSN4FDzSGH
+
1601  sp6JS7f14BuwFY8Mw8B3pUbzQqHe2
+
1602  sp6JS7f14BuwFY8Mw8WwRLnhBRvfk
+
1603  sp6JS7f14BuwFY8Mw8hDBpKbpJwJX
+
1604  sp6JS7f14BuwFY8Mw8jggRSZACe7M
+
1605  sp6JS7f14BuwFY8Mw8mJRpU3qWbwC
+
1606  sp6JS7f14BuwFY8MwFDnVozykN21u
+
1607  sp6JS7f14BuwFY8MwFGGRGY9fctgv
+
1608  sp6JS7f14BuwFY8MwjKznfChH9DQb
+
1609  sp6JS7f14BuwFY8MwjbC5GvngRCk6
+
1610  sp6JS7f14BuwFY8Mwk3Lb7FPe1629
+
1611  sp6JS7f14BuwFY8MwkCeS41BwVrBD
+
1612  sp6JS7f14BuwFY8MwkDnnvRyuWJ7d
+
1613  sp6JS7f14BuwFY8MwkbkRNnzDEFpf
+
1614  sp6JS7f14BuwFY8MwkiNhaVhGNk6v
+
1615  sp6JS7f14BuwFY8Mwm1X4UJXRZx3p
+
1616  sp6JS7f14BuwFY8Mwm7da9q5vfq7J
+
1617  sp6JS7f14BuwFY8MwmPLqfBPrHw5H
+
1618  sp6JS7f14BuwFY8MwmbJpxvVjEwm2
+
1619  sp6JS7f14BuwFY8MwoAVeA7ka37cD
+
1620  sp6JS7f14BuwFY8MwoTFFTAwFKmVM
+
1621  sp6JS7f14BuwFY8MwoYsne51VpDE3
+
1622  sp6JS7f14BuwFY8MwohLVnU1VTk5h
+
1623 
+
1624 #endif // 0
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
-
void testWithFeats(FeatureBitset features)
+
void testWithFeats(FeatureBitset features)
@ tecINVARIANT_FAILED
Definition: TER.h:277
STL class.
@ kDisabled
Definition: Journal.h:41
@@ -1684,7 +1709,7 @@ $(function() {
T find(T... args)
T size(T... args)
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:355
-
void testTooManyEquivalent(FeatureBitset features)
+
void testTooManyEquivalent(FeatureBitset features)
T reverse(T... args)
std::string toStyledString() const
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
@@ -1693,14 +1718,15 @@ $(function() {
T front(T... args)
T clear(T... args)
constexpr uint256 pageMask(std::string_view("0000000000000000000000000000000000000000ffffffffffffffffffffffff"))
+
const uint256 fixNFTokenRemint
T push_back(T... args)
void testLopsidedSplits(FeatureBitset features)
uint256 key
Definition: Keylet.h:40
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
-
void testFixNFTokenDirV1(FeatureBitset features)
-
void run() override
+
void testFixNFTokenDirV1(FeatureBitset features)
+
void run() override
void printNFTPages(test::jtx::Env &env, Volume vol)
STL class.
@@ -1714,7 +1740,7 @@ $(function() {
@ noisy
bool isArray() const
const SField sfNFTokens
-
void testConsecutivePacking(FeatureBitset features)
+
void testConsecutivePacking(FeatureBitset features)
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
T endl(T... args)
diff --git a/NFTokenMint_8cpp_source.html b/NFTokenMint_8cpp_source.html index 005c46bf8e..9527c493ef 100644 --- a/NFTokenMint_8cpp_source.html +++ b/NFTokenMint_8cpp_source.html @@ -231,76 +231,128 @@ $(function() {
160  // Should not happen. Checked in preclaim.
161  return Unexpected(tecNO_ISSUER);
162 
-
163  // Get the unique sequence number for this token:
-
164  std::uint32_t const tokenSeq = (*root)[~sfMintedNFTokens].value_or(0);
-
165  {
-
166  std::uint32_t const nextTokenSeq = tokenSeq + 1;
-
167  if (nextTokenSeq < tokenSeq)
-
168  return Unexpected(tecMAX_SEQUENCE_REACHED);
-
169 
-
170  (*root)[sfMintedNFTokens] = nextTokenSeq;
-
171  }
-
172  ctx_.view().update(root);
-
173  return tokenSeq;
-
174  }();
-
175 
-
176  if (!tokenSeq.has_value())
-
177  return (tokenSeq.error());
+
163  if (!ctx_.view().rules().enabled(fixNFTokenRemint))
+
164  {
+
165  // Get the unique sequence number for this token:
+
166  std::uint32_t const tokenSeq =
+
167  (*root)[~sfMintedNFTokens].value_or(0);
+
168  {
+
169  std::uint32_t const nextTokenSeq = tokenSeq + 1;
+
170  if (nextTokenSeq < tokenSeq)
+
171  return Unexpected(tecMAX_SEQUENCE_REACHED);
+
172 
+
173  (*root)[sfMintedNFTokens] = nextTokenSeq;
+
174  }
+
175  ctx_.view().update(root);
+
176  return tokenSeq;
+
177  }
178 
-
179  std::uint32_t const ownerCountBefore =
-
180  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
-
181 
-
182  // Assemble the new NFToken.
-
183  SOTemplate const* nfTokenTemplate =
-
184  InnerObjectFormats::getInstance().findSOTemplateBySField(sfNFToken);
-
185 
-
186  if (nfTokenTemplate == nullptr)
-
187  // Should never happen.
-
188  return tecINTERNAL;
-
189 
-
190  STObject newToken(
-
191  *nfTokenTemplate,
-
192  sfNFToken,
-
193  [this, &issuer, &tokenSeq](STObject& object) {
-
194  object.setFieldH256(
-
195  sfNFTokenID,
-
196  createNFTokenID(
-
197  static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
-
198  ctx_.tx[~sfTransferFee].value_or(0),
-
199  issuer,
-
200  nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
-
201  tokenSeq.value()));
-
202 
-
203  if (auto const uri = ctx_.tx[~sfURI])
-
204  object.setFieldVL(sfURI, *uri);
-
205  });
+
179  // With fixNFTokenRemint amendment enabled:
+
180  //
+
181  // If the issuer hasn't minted an NFToken before we must add a
+
182  // FirstNFTokenSequence field to the issuer's AccountRoot. The
+
183  // value of the FirstNFTokenSequence must equal the issuer's
+
184  // current account sequence.
+
185  //
+
186  // There are three situations:
+
187  // o If the first token is being minted by the issuer and
+
188  // * If the transaction consumes a Sequence number, then the
+
189  // Sequence has been pre-incremented by the time we get here in
+
190  // doApply. We must decrement the value in the Sequence field.
+
191  // * Otherwise the transaction uses a Ticket so the Sequence has
+
192  // not been pre-incremented. We use the Sequence value as is.
+
193  // o The first token is being minted by an authorized minter. In
+
194  // this case the issuer's Sequence field has been left untouched.
+
195  // We use the issuer's Sequence value as is.
+
196  if (!root->isFieldPresent(sfFirstNFTokenSequence))
+
197  {
+
198  std::uint32_t const acctSeq = root->at(sfSequence);
+
199 
+
200  root->at(sfFirstNFTokenSequence) =
+
201  ctx_.tx.isFieldPresent(sfIssuer) ||
+
202  ctx_.tx.getSeqProxy().isTicket()
+
203  ? acctSeq
+
204  : acctSeq - 1;
+
205  }
206 
-
207  if (TER const ret =
-
208  nft::insertToken(ctx_.view(), account_, std::move(newToken));
-
209  ret != tesSUCCESS)
-
210  return ret;
-
211 
-
212  // Only check the reserve if the owner count actually changed. This
-
213  // allows NFTs to be added to the page (and burn fees) without
-
214  // requiring the reserve to be met each time. The reserve is
-
215  // only managed when a new NFT page is added.
-
216  if (auto const ownerCountAfter =
-
217  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
-
218  ownerCountAfter > ownerCountBefore)
-
219  {
-
220  if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
-
221  mPriorBalance < reserve)
-
222  return tecINSUFFICIENT_RESERVE;
-
223  }
-
224  return tesSUCCESS;
-
225 }
+
207  std::uint32_t const mintedNftCnt =
+
208  (*root)[~sfMintedNFTokens].value_or(0u);
+
209 
+
210  (*root)[sfMintedNFTokens] = mintedNftCnt + 1u;
+
211  if ((*root)[sfMintedNFTokens] == 0u)
+
212  return Unexpected(tecMAX_SEQUENCE_REACHED);
+
213 
+
214  // Get the unique sequence number of this token by
+
215  // sfFirstNFTokenSequence + sfMintedNFTokens
+
216  std::uint32_t const offset = (*root)[sfFirstNFTokenSequence];
+
217  std::uint32_t const tokenSeq = offset + mintedNftCnt;
+
218 
+
219  // Check for more overflow cases
+
220  if (tokenSeq + 1u == 0u || tokenSeq < offset)
+
221  return Unexpected(tecMAX_SEQUENCE_REACHED);
+
222 
+
223  ctx_.view().update(root);
+
224  return tokenSeq;
+
225  }();
226 
-
227 } // namespace ripple
+
227  if (!tokenSeq.has_value())
+
228  return (tokenSeq.error());
+
229 
+
230  std::uint32_t const ownerCountBefore =
+
231  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
+
232 
+
233  // Assemble the new NFToken.
+
234  SOTemplate const* nfTokenTemplate =
+
235  InnerObjectFormats::getInstance().findSOTemplateBySField(sfNFToken);
+
236 
+
237  if (nfTokenTemplate == nullptr)
+
238  // Should never happen.
+
239  return tecINTERNAL;
+
240 
+
241  STObject newToken(
+
242  *nfTokenTemplate,
+
243  sfNFToken,
+
244  [this, &issuer, &tokenSeq](STObject& object) {
+
245  object.setFieldH256(
+
246  sfNFTokenID,
+
247  createNFTokenID(
+
248  static_cast<std::uint16_t>(ctx_.tx.getFlags() & 0x0000FFFF),
+
249  ctx_.tx[~sfTransferFee].value_or(0),
+
250  issuer,
+
251  nft::toTaxon(ctx_.tx[sfNFTokenTaxon]),
+
252  tokenSeq.value()));
+
253 
+
254  if (auto const uri = ctx_.tx[~sfURI])
+
255  object.setFieldVL(sfURI, *uri);
+
256  });
+
257 
+
258  if (TER const ret =
+
259  nft::insertToken(ctx_.view(), account_, std::move(newToken));
+
260  ret != tesSUCCESS)
+
261  return ret;
+
262 
+
263  // Only check the reserve if the owner count actually changed. This
+
264  // allows NFTs to be added to the page (and burn fees) without
+
265  // requiring the reserve to be met each time. The reserve is
+
266  // only managed when a new NFT page is added.
+
267  if (auto const ownerCountAfter =
+
268  view().read(keylet::account(account_))->getFieldU32(sfOwnerCount);
+
269  ownerCountAfter > ownerCountBefore)
+
270  {
+
271  if (auto const reserve = view().fees().accountReserve(ownerCountAfter);
+
272  mPriorBalance < reserve)
+
273  return tecINSUFFICIENT_RESERVE;
+
274  }
+
275  return tesSUCCESS;
+
276 }
+
277 
+
278 } // namespace ripple
static TER preclaim(PreclaimContext const &ctx)
constexpr std::uint16_t maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:81
const SF_UINT32 sfOwnerCount
const uint256 fixRemoveNFTokenAutoTrustLine
+
const SF_UINT32 sfFirstNFTokenSequence
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:109
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:81
@@ -310,16 +362,20 @@ $(function() {
bool isTesSuccess(TER x)
Definition: TER.h:594
SOTemplate const * findSOTemplateBySField(SField const &sField) const
TER doApply() override
+
const SF_UINT32 sfSequence
constexpr const std::uint32_t tfNFTokenMintOldMask
Definition: TxFlags.h:145
Unexpected(E(&)[N]) -> Unexpected< E const * >
const SF_UINT32 sfMintedNFTokens
T distance(T... args)
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
+
SeqProxy getSeqProxy() const
Definition: STTx.cpp:183
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
pointer data()
Definition: base_uint.h:122
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:57
const SF_UINT16 sfTransferFee
constexpr static std::size_t size()
Definition: base_uint.h:519
+
const uint256 fixNFTokenRemint
+
constexpr bool isTicket() const
Definition: SeqProxy.h:94
Integers of any length that is a multiple of 32-bits.
Definition: base_uint.h:82
static NotTEC preflight(PreflightContext const &ctx)
Definition: NFTokenMint.cpp:35
@ temINVALID_FLAG
Definition: TER.h:106
@@ -347,6 +403,8 @@ $(function() {
ApplyView & view()
Definition: Transactor.h:107
@ temDISABLED
Definition: TER.h:109
const SF_ACCOUNT sfIssuer
+
virtual Rules const & rules() const =0
Returns the tx processing rules.
+
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:428
static base_uint fromVoid(void const *data)
Definition: base_uint.h:312
@ tecNO_ISSUER
Definition: TER.h:263
XRPAmount mPriorBalance
Definition: Transactor.h:92
diff --git a/NFToken__test_8cpp_source.html b/NFToken__test_8cpp_source.html index 351c6e68e8..89ef98c4eb 100644 --- a/NFToken__test_8cpp_source.html +++ b/NFToken__test_8cpp_source.html @@ -286,8 +286,8 @@ $(function() {
215  Account const minter{"minter"};
216 
217  // Fund alice and minter enough to exist, but not enough to meet
-
218  // the reserve for creating their first NFT. Account reserve for unit
-
219  // tests is 200 XRP, not 20.
+
218  // the reserve for creating their first NFT. Account reserve for
+
219  // unit tests is 200 XRP, not 20.
220  env.fund(XRP(200), alice, minter);
221  env.close();
222  BEAST_EXPECT(env.balance(alice) == XRP(200));
@@ -295,5602 +295,5602 @@ $(function() {
224  BEAST_EXPECT(ownerCount(env, alice) == 0);
225  BEAST_EXPECT(ownerCount(env, minter) == 0);
226 
-
227  // alice does not have enough XRP to cover the reserve for an NFT page.
-
228  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
-
229  env.close();
-
230  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
231  BEAST_EXPECT(mintedCount(env, alice) == 0);
-
232  BEAST_EXPECT(burnedCount(env, alice) == 0);
-
233 
-
234  // Pay alice almost enough to make the reserve for an NFT page.
-
235  env(pay(env.master, alice, XRP(50) + drops(9)));
-
236  env.close();
-
237 
-
238  // A lambda that checks alice's ownerCount, mintedCount, and
-
239  // burnedCount all in one fell swoop.
-
240  auto checkAliceOwnerMintedBurned = [&env, this, &alice](
-
241  std::uint32_t owners,
-
242  std::uint32_t minted,
-
243  std::uint32_t burned,
-
244  int line) {
-
245  auto oneCheck =
-
246  [line, this](
-
247  char const* type, std::uint32_t found, std::uint32_t exp) {
-
248  if (found == exp)
-
249  pass();
-
250  else
-
251  {
-
252  std::stringstream ss;
-
253  ss << "Wrong " << type << " count. Found: " << found
-
254  << "; Expected: " << exp;
-
255  fail(ss.str(), __FILE__, line);
-
256  }
-
257  };
-
258  oneCheck("owner", ownerCount(env, alice), owners);
-
259  oneCheck("minted", mintedCount(env, alice), minted);
-
260  oneCheck("burned", burnedCount(env, alice), burned);
-
261  };
-
262 
-
263  // alice still does not have enough XRP for the reserve of an NFT page.
-
264  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
-
265  env.close();
-
266  checkAliceOwnerMintedBurned(0, 0, 0, __LINE__);
-
267 
-
268  // Pay alice enough to make the reserve for an NFT page.
-
269  env(pay(env.master, alice, drops(11)));
-
270  env.close();
-
271 
-
272  // Now alice can mint an NFT.
-
273  env(token::mint(alice));
-
274  env.close();
-
275  checkAliceOwnerMintedBurned(1, 1, 0, __LINE__);
-
276 
-
277  // Alice should be able to mint an additional 31 NFTs without
-
278  // any additional reserve requirements.
-
279  for (int i = 1; i < 32; ++i)
-
280  {
-
281  env(token::mint(alice));
-
282  checkAliceOwnerMintedBurned(1, i + 1, 0, __LINE__);
-
283  }
-
284 
-
285  // That NFT page is full. Creating an additional NFT page requires
-
286  // additional reserve.
-
287  env(token::mint(alice), ter(tecINSUFFICIENT_RESERVE));
-
288  env.close();
-
289  checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
-
290 
-
291  // Pay alice almost enough to make the reserve for an NFT page.
-
292  env(pay(env.master, alice, XRP(50) + drops(329)));
-
293  env.close();
-
294 
-
295  // alice still does not have enough XRP for the reserve of an NFT page.
-
296  env(token::mint(alice), ter(tecINSUFFICIENT_RESERVE));
-
297  env.close();
-
298  checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
-
299 
-
300  // Pay alice enough to make the reserve for an NFT page.
-
301  env(pay(env.master, alice, drops(11)));
-
302  env.close();
-
303 
-
304  // Now alice can mint an NFT.
-
305  env(token::mint(alice));
-
306  env.close();
-
307  checkAliceOwnerMintedBurned(2, 33, 0, __LINE__);
-
308 
-
309  // alice burns the NFTs she created: check that pages consolidate
-
310  std::uint32_t seq = 0;
+
227  // alice does not have enough XRP to cover the reserve for an NFT
+
228  // page.
+
229  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
+
230  env.close();
+
231  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
232  BEAST_EXPECT(mintedCount(env, alice) == 0);
+
233  BEAST_EXPECT(burnedCount(env, alice) == 0);
+
234 
+
235  // Pay alice almost enough to make the reserve for an NFT page.
+
236  env(pay(env.master, alice, XRP(50) + drops(9)));
+
237  env.close();
+
238 
+
239  // A lambda that checks alice's ownerCount, mintedCount, and
+
240  // burnedCount all in one fell swoop.
+
241  auto checkAliceOwnerMintedBurned = [&env, this, &alice](
+
242  std::uint32_t owners,
+
243  std::uint32_t minted,
+
244  std::uint32_t burned,
+
245  int line) {
+
246  auto oneCheck =
+
247  [line, this](
+
248  char const* type, std::uint32_t found, std::uint32_t exp) {
+
249  if (found == exp)
+
250  pass();
+
251  else
+
252  {
+
253  std::stringstream ss;
+
254  ss << "Wrong " << type << " count. Found: " << found
+
255  << "; Expected: " << exp;
+
256  fail(ss.str(), __FILE__, line);
+
257  }
+
258  };
+
259  oneCheck("owner", ownerCount(env, alice), owners);
+
260  oneCheck("minted", mintedCount(env, alice), minted);
+
261  oneCheck("burned", burnedCount(env, alice), burned);
+
262  };
+
263 
+
264  // alice still does not have enough XRP for the reserve of an NFT
+
265  // page.
+
266  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
+
267  env.close();
+
268  checkAliceOwnerMintedBurned(0, 0, 0, __LINE__);
+
269 
+
270  // Pay alice enough to make the reserve for an NFT page.
+
271  env(pay(env.master, alice, drops(11)));
+
272  env.close();
+
273 
+
274  // Now alice can mint an NFT.
+
275  env(token::mint(alice));
+
276  env.close();
+
277  checkAliceOwnerMintedBurned(1, 1, 0, __LINE__);
+
278 
+
279  // Alice should be able to mint an additional 31 NFTs without
+
280  // any additional reserve requirements.
+
281  for (int i = 1; i < 32; ++i)
+
282  {
+
283  env(token::mint(alice));
+
284  checkAliceOwnerMintedBurned(1, i + 1, 0, __LINE__);
+
285  }
+
286 
+
287  // That NFT page is full. Creating an additional NFT page requires
+
288  // additional reserve.
+
289  env(token::mint(alice), ter(tecINSUFFICIENT_RESERVE));
+
290  env.close();
+
291  checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
+
292 
+
293  // Pay alice almost enough to make the reserve for an NFT page.
+
294  env(pay(env.master, alice, XRP(50) + drops(329)));
+
295  env.close();
+
296 
+
297  // alice still does not have enough XRP for the reserve of an NFT
+
298  // page.
+
299  env(token::mint(alice), ter(tecINSUFFICIENT_RESERVE));
+
300  env.close();
+
301  checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
+
302 
+
303  // Pay alice enough to make the reserve for an NFT page.
+
304  env(pay(env.master, alice, drops(11)));
+
305  env.close();
+
306 
+
307  // Now alice can mint an NFT.
+
308  env(token::mint(alice));
+
309  env.close();
+
310  checkAliceOwnerMintedBurned(2, 33, 0, __LINE__);
311 
-
312  while (seq < 33)
-
313  {
-
314  env(token::burn(alice, token::getID(alice, 0, seq++)));
-
315  env.close();
-
316  checkAliceOwnerMintedBurned((33 - seq) ? 1 : 0, 33, seq, __LINE__);
-
317  }
-
318 
-
319  // alice burns a non-existent NFT.
-
320  env(token::burn(alice, token::getID(alice, 197, 5)), ter(tecNO_ENTRY));
-
321  env.close();
-
322  checkAliceOwnerMintedBurned(0, 33, 33, __LINE__);
-
323 
-
324  // That was fun! Now let's see what happens when we let someone else
-
325  // mint NFTs on alice's behalf. alice gives permission to minter.
-
326  env(token::setMinter(alice, minter));
-
327  env.close();
-
328  BEAST_EXPECT(
-
329  env.le(alice)->getAccountID(sfNFTokenMinter) == minter.id());
-
330 
-
331  // A lambda that checks minter's and alice's ownerCount,
-
332  // mintedCount, and burnedCount all in one fell swoop.
-
333  auto checkMintersOwnerMintedBurned = [&env, this, &alice, &minter](
-
334  std::uint32_t aliceOwners,
-
335  std::uint32_t aliceMinted,
-
336  std::uint32_t aliceBurned,
-
337  std::uint32_t minterOwners,
-
338  std::uint32_t minterMinted,
-
339  std::uint32_t minterBurned,
-
340  int line) {
-
341  auto oneCheck = [this](
-
342  char const* type,
-
343  std::uint32_t found,
-
344  std::uint32_t exp,
-
345  int line) {
-
346  if (found == exp)
-
347  pass();
-
348  else
-
349  {
-
350  std::stringstream ss;
-
351  ss << "Wrong " << type << " count. Found: " << found
-
352  << "; Expected: " << exp;
-
353  fail(ss.str(), __FILE__, line);
-
354  }
-
355  };
-
356  oneCheck("alice owner", ownerCount(env, alice), aliceOwners, line);
-
357  oneCheck(
-
358  "alice minted", mintedCount(env, alice), aliceMinted, line);
-
359  oneCheck(
-
360  "alice burned", burnedCount(env, alice), aliceBurned, line);
-
361  oneCheck(
-
362  "minter owner", ownerCount(env, minter), minterOwners, line);
-
363  oneCheck(
-
364  "minter minted", mintedCount(env, minter), minterMinted, line);
-
365  oneCheck(
-
366  "minter burned", burnedCount(env, minter), minterBurned, line);
-
367  };
-
368 
-
369  std::uint32_t nftSeq = 33;
-
370 
-
371  // Pay minter almost enough to make the reserve for an NFT page.
-
372  env(pay(env.master, minter, XRP(50) - drops(1)));
-
373  env.close();
-
374  checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
+
312  // alice burns the NFTs she created: check that pages consolidate
+
313  std::uint32_t seq = 0;
+
314 
+
315  while (seq < 33)
+
316  {
+
317  env(token::burn(alice, token::getID(env, alice, 0, seq++)));
+
318  env.close();
+
319  checkAliceOwnerMintedBurned((33 - seq) ? 1 : 0, 33, seq, __LINE__);
+
320  }
+
321 
+
322  // alice burns a non-existent NFT.
+
323  env(token::burn(alice, token::getID(env, alice, 197, 5)),
+
324  ter(tecNO_ENTRY));
+
325  env.close();
+
326  checkAliceOwnerMintedBurned(0, 33, 33, __LINE__);
+
327 
+
328  // That was fun! Now let's see what happens when we let someone
+
329  // else mint NFTs on alice's behalf. alice gives permission to
+
330  // minter.
+
331  env(token::setMinter(alice, minter));
+
332  env.close();
+
333  BEAST_EXPECT(
+
334  env.le(alice)->getAccountID(sfNFTokenMinter) == minter.id());
+
335 
+
336  // A lambda that checks minter's and alice's ownerCount,
+
337  // mintedCount, and burnedCount all in one fell swoop.
+
338  auto checkMintersOwnerMintedBurned = [&env, this, &alice, &minter](
+
339  std::uint32_t aliceOwners,
+
340  std::uint32_t aliceMinted,
+
341  std::uint32_t aliceBurned,
+
342  std::uint32_t minterOwners,
+
343  std::uint32_t minterMinted,
+
344  std::uint32_t minterBurned,
+
345  int line) {
+
346  auto oneCheck = [this](
+
347  char const* type,
+
348  std::uint32_t found,
+
349  std::uint32_t exp,
+
350  int line) {
+
351  if (found == exp)
+
352  pass();
+
353  else
+
354  {
+
355  std::stringstream ss;
+
356  ss << "Wrong " << type << " count. Found: " << found
+
357  << "; Expected: " << exp;
+
358  fail(ss.str(), __FILE__, line);
+
359  }
+
360  };
+
361  oneCheck("alice owner", ownerCount(env, alice), aliceOwners, line);
+
362  oneCheck(
+
363  "alice minted", mintedCount(env, alice), aliceMinted, line);
+
364  oneCheck(
+
365  "alice burned", burnedCount(env, alice), aliceBurned, line);
+
366  oneCheck(
+
367  "minter owner", ownerCount(env, minter), minterOwners, line);
+
368  oneCheck(
+
369  "minter minted", mintedCount(env, minter), minterMinted, line);
+
370  oneCheck(
+
371  "minter burned", burnedCount(env, minter), minterBurned, line);
+
372  };
+
373 
+
374  std::uint32_t nftSeq = 33;
375 
-
376  // minter still does not have enough XRP for the reserve of an NFT page.
-
377  // Just for grins (and code coverage), minter mints NFTs that include
-
378  // a URI.
-
379  env(token::mint(minter),
-
380  token::issuer(alice),
-
381  token::uri("uri"),
-
382  ter(tecINSUFFICIENT_RESERVE));
-
383  env.close();
-
384  checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
-
385 
-
386  // Pay minter enough to make the reserve for an NFT page.
-
387  env(pay(env.master, minter, drops(11)));
+
376  // Pay minter almost enough to make the reserve for an NFT page.
+
377  env(pay(env.master, minter, XRP(50) - drops(1)));
+
378  env.close();
+
379  checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
+
380 
+
381  // minter still does not have enough XRP for the reserve of an NFT
+
382  // page. Just for grins (and code coverage), minter mints NFTs that
+
383  // include a URI.
+
384  env(token::mint(minter),
+
385  token::issuer(alice),
+
386  token::uri("uri"),
+
387  ter(tecINSUFFICIENT_RESERVE));
388  env.close();
-
389 
-
390  // Now minter can mint an NFT for alice.
-
391  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
-
392  env.close();
-
393  checkMintersOwnerMintedBurned(0, 34, nftSeq, 1, 0, 0, __LINE__);
+
389  checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
+
390 
+
391  // Pay minter enough to make the reserve for an NFT page.
+
392  env(pay(env.master, minter, drops(11)));
+
393  env.close();
394 
-
395  // Minter should be able to mint an additional 31 NFTs for alice
-
396  // without any additional reserve requirements.
-
397  for (int i = 1; i < 32; ++i)
-
398  {
-
399  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
-
400  checkMintersOwnerMintedBurned(0, i + 34, nftSeq, 1, 0, 0, __LINE__);
-
401  }
-
402 
-
403  // Pay minter almost enough for the reserve of an additional NFT page.
-
404  env(pay(env.master, minter, XRP(50) + drops(319)));
-
405  env.close();
-
406 
-
407  // That NFT page is full. Creating an additional NFT page requires
-
408  // additional reserve.
-
409  env(token::mint(minter),
-
410  token::issuer(alice),
-
411  token::uri("uri"),
-
412  ter(tecINSUFFICIENT_RESERVE));
-
413  env.close();
-
414  checkMintersOwnerMintedBurned(0, 65, nftSeq, 1, 0, 0, __LINE__);
-
415 
-
416  // Pay minter enough for the reserve of an additional NFT page.
-
417  env(pay(env.master, minter, drops(11)));
-
418  env.close();
-
419 
-
420  // Now minter can mint an NFT.
-
421  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
-
422  env.close();
-
423  checkMintersOwnerMintedBurned(0, 66, nftSeq, 2, 0, 0, __LINE__);
-
424 
-
425  // minter burns the NFTs she created.
-
426  while (nftSeq < 65)
-
427  {
-
428  env(token::burn(minter, token::getID(alice, 0, nftSeq++)));
-
429  env.close();
-
430  checkMintersOwnerMintedBurned(
-
431  0, 66, nftSeq, (65 - seq) ? 1 : 0, 0, 0, __LINE__);
-
432  }
-
433 
-
434  // minter has one more NFT to burn. Should take her owner count to 0.
-
435  env(token::burn(minter, token::getID(alice, 0, nftSeq++)));
-
436  env.close();
-
437  checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
-
438 
-
439  // minter burns a non-existent NFT.
-
440  env(token::burn(minter, token::getID(alice, 2009, 3)),
-
441  ter(tecNO_ENTRY));
-
442  env.close();
-
443  checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
-
444  }
+
395  // Now minter can mint an NFT for alice.
+
396  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
+
397  env.close();
+
398  checkMintersOwnerMintedBurned(0, 34, nftSeq, 1, 0, 0, __LINE__);
+
399 
+
400  // Minter should be able to mint an additional 31 NFTs for alice
+
401  // without any additional reserve requirements.
+
402  for (int i = 1; i < 32; ++i)
+
403  {
+
404  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
+
405  checkMintersOwnerMintedBurned(0, i + 34, nftSeq, 1, 0, 0, __LINE__);
+
406  }
+
407 
+
408  // Pay minter almost enough for the reserve of an additional NFT
+
409  // page.
+
410  env(pay(env.master, minter, XRP(50) + drops(319)));
+
411  env.close();
+
412 
+
413  // That NFT page is full. Creating an additional NFT page requires
+
414  // additional reserve.
+
415  env(token::mint(minter),
+
416  token::issuer(alice),
+
417  token::uri("uri"),
+
418  ter(tecINSUFFICIENT_RESERVE));
+
419  env.close();
+
420  checkMintersOwnerMintedBurned(0, 65, nftSeq, 1, 0, 0, __LINE__);
+
421 
+
422  // Pay minter enough for the reserve of an additional NFT page.
+
423  env(pay(env.master, minter, drops(11)));
+
424  env.close();
+
425 
+
426  // Now minter can mint an NFT.
+
427  env(token::mint(minter), token::issuer(alice), token::uri("uri"));
+
428  env.close();
+
429  checkMintersOwnerMintedBurned(0, 66, nftSeq, 2, 0, 0, __LINE__);
+
430 
+
431  // minter burns the NFTs she created.
+
432  while (nftSeq < 65)
+
433  {
+
434  env(token::burn(minter, token::getID(env, alice, 0, nftSeq++)));
+
435  env.close();
+
436  checkMintersOwnerMintedBurned(
+
437  0, 66, nftSeq, (65 - seq) ? 1 : 0, 0, 0, __LINE__);
+
438  }
+
439 
+
440  // minter has one more NFT to burn. Should take her owner count to
+
441  // 0.
+
442  env(token::burn(minter, token::getID(env, alice, 0, nftSeq++)));
+
443  env.close();
+
444  checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
445 
-
446  void
-
447  testMintMaxTokens(FeatureBitset features)
-
448  {
-
449  // Make sure that an account cannot cause the sfMintedNFTokens
-
450  // field to wrap by minting more than 0xFFFF'FFFF tokens.
-
451  testcase("Mint max tokens");
+
446  // minter burns a non-existent NFT.
+
447  env(token::burn(minter, token::getID(env, alice, 2009, 3)),
+
448  ter(tecNO_ENTRY));
+
449  env.close();
+
450  checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
+
451  }
452 
-
453  using namespace test::jtx;
-
454 
-
455  Account const alice{"alice"};
-
456  Env env{*this, features};
-
457  env.fund(XRP(1000), alice);
-
458  env.close();
+
453  void
+
454  testMintMaxTokens(FeatureBitset features)
+
455  {
+
456  // Make sure that an account cannot cause the sfMintedNFTokens
+
457  // field to wrap by minting more than 0xFFFF'FFFF tokens.
+
458  testcase("Mint max tokens");
459 
-
460  // We're going to hack the ledger in order to avoid generating
-
461  // 4 billion or so NFTs. Because we're hacking the ledger we
-
462  // need alice's account to have non-zero sfMintedNFTokens and
-
463  // sfBurnedNFTokens fields. This prevents an exception when the
-
464  // AccountRoot template is applied.
-
465  {
-
466  uint256 const nftId0{token::getNextID(env, alice, 0u)};
-
467  env(token::mint(alice, 0u));
-
468  env.close();
-
469 
-
470  env(token::burn(alice, nftId0));
-
471  env.close();
-
472  }
-
473 
-
474  // Note that we're bypassing almost all of the ledger's safety
-
475  // checks with this modify() call. If you call close() between
-
476  // here and the end of the test all the effort will be lost.
-
477  env.app().openLedger().modify(
-
478  [&alice](OpenView& view, beast::Journal j) {
-
479  // Get the account root we want to hijack.
-
480  auto const sle = view.read(keylet::account(alice.id()));
-
481  if (!sle)
-
482  return false; // This would be really surprising!
-
483 
-
484  // Just for sanity's sake we'll check that the current value
-
485  // of sfMintedNFTokens matches what we expect.
-
486  auto replacement = std::make_shared<SLE>(*sle, sle->key());
-
487  if (replacement->getFieldU32(sfMintedNFTokens) != 1)
-
488  return false; // Unexpected test conditions.
-
489 
-
490  // Now replace sfMintedNFTokens with the largest valid value.
-
491  (*replacement)[sfMintedNFTokens] = 0xFFFF'FFFE;
-
492  view.rawReplace(replacement);
-
493  return true;
-
494  });
-
495 
-
496  // See whether alice is at the boundary that causes an error.
-
497  env(token::mint(alice, 0u), ter(tesSUCCESS));
-
498  env(token::mint(alice, 0u), ter(tecMAX_SEQUENCE_REACHED));
-
499  }
-
500 
-
501  void
-
502  testMintInvalid(FeatureBitset features)
-
503  {
-
504  // Explore many of the invalid ways to mint an NFT.
-
505  testcase("Mint invalid");
-
506 
-
507  using namespace test::jtx;
-
508 
-
509  Env env{*this, features};
-
510  Account const alice{"alice"};
-
511  Account const minter{"minter"};
-
512 
-
513  // Fund alice and minter enough to exist, but not enough to meet
-
514  // the reserve for creating their first NFT. Account reserve for unit
-
515  // tests is 200 XRP, not 20.
-
516  env.fund(XRP(200), alice, minter);
-
517  env.close();
-
518 
-
519  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
-
520  env.close();
-
521 
-
522  // Fund alice enough to start minting NFTs.
-
523  env(pay(env.master, alice, XRP(1000)));
-
524  env.close();
-
525 
-
526  //----------------------------------------------------------------------
-
527  // preflight
+
460  using namespace test::jtx;
+
461 
+
462  Account const alice{"alice"};
+
463  Env env{*this, features};
+
464  env.fund(XRP(1000), alice);
+
465  env.close();
+
466 
+
467  // We're going to hack the ledger in order to avoid generating
+
468  // 4 billion or so NFTs. Because we're hacking the ledger we
+
469  // need alice's account to have non-zero sfMintedNFTokens and
+
470  // sfBurnedNFTokens fields. This prevents an exception when the
+
471  // AccountRoot template is applied.
+
472  {
+
473  uint256 const nftId0{token::getNextID(env, alice, 0u)};
+
474  env(token::mint(alice, 0u));
+
475  env.close();
+
476 
+
477  env(token::burn(alice, nftId0));
+
478  env.close();
+
479  }
+
480 
+
481  // Note that we're bypassing almost all of the ledger's safety
+
482  // checks with this modify() call. If you call close() between
+
483  // here and the end of the test all the effort will be lost.
+
484  env.app().openLedger().modify(
+
485  [&alice, &env](OpenView& view, beast::Journal j) {
+
486  // Get the account root we want to hijack.
+
487  auto const sle = view.read(keylet::account(alice.id()));
+
488  if (!sle)
+
489  return false; // This would be really surprising!
+
490 
+
491  // Just for sanity's sake we'll check that the current value
+
492  // of sfMintedNFTokens matches what we expect.
+
493  auto replacement = std::make_shared<SLE>(*sle, sle->key());
+
494  if (replacement->getFieldU32(sfMintedNFTokens) != 1)
+
495  return false; // Unexpected test conditions.
+
496 
+
497  if (env.current()->rules().enabled(fixNFTokenRemint))
+
498  {
+
499  // If fixNFTokenRemint is enabled, sequence number is
+
500  // generated by sfFirstNFTokenSequence + sfMintedNFTokens.
+
501  // We can replace the two fields with any numbers as long as
+
502  // they add up to the largest valid number. In our case,
+
503  // sfFirstNFTokenSequence is set to the largest valid
+
504  // number, and sfMintedNFTokens is set to zero.
+
505  (*replacement)[sfFirstNFTokenSequence] = 0xFFFF'FFFE;
+
506  (*replacement)[sfMintedNFTokens] = 0x0000'0000;
+
507  }
+
508  else
+
509  {
+
510  // Now replace sfMintedNFTokens with the largest valid
+
511  // value.
+
512  (*replacement)[sfMintedNFTokens] = 0xFFFF'FFFE;
+
513  }
+
514  view.rawReplace(replacement);
+
515  return true;
+
516  });
+
517 
+
518  // See whether alice is at the boundary that causes an error.
+
519  env(token::mint(alice, 0u), ter(tesSUCCESS));
+
520  env(token::mint(alice, 0u), ter(tecMAX_SEQUENCE_REACHED));
+
521  }
+
522 
+
523  void
+
524  testMintInvalid(FeatureBitset features)
+
525  {
+
526  // Explore many of the invalid ways to mint an NFT.
+
527  testcase("Mint invalid");
528 
-
529  // Set a negative fee.
-
530  env(token::mint(alice, 0u),
-
531  fee(STAmount(10ull, true)),
-
532  ter(temBAD_FEE));
-
533 
-
534  // Set an invalid flag.
-
535  env(token::mint(alice, 0u), txflags(0x00008000), ter(temINVALID_FLAG));
-
536 
-
537  // Can't set a transfer fee if the NFT does not have the tfTRANSFERABLE
-
538  // flag set.
-
539  env(token::mint(alice, 0u),
-
540  token::xferFee(maxTransferFee),
-
541  ter(temMALFORMED));
-
542 
-
543  // Set a bad transfer fee.
-
544  env(token::mint(alice, 0u),
-
545  token::xferFee(maxTransferFee + 1),
-
546  txflags(tfTransferable),
-
547  ter(temBAD_NFTOKEN_TRANSFER_FEE));
-
548 
-
549  // Account can't also be issuer.
-
550  env(token::mint(alice, 0u), token::issuer(alice), ter(temMALFORMED));
-
551 
-
552  // Invalid URI: zero length.
-
553  env(token::mint(alice, 0u), token::uri(""), ter(temMALFORMED));
-
554 
-
555  // Invalid URI: too long.
-
556  env(token::mint(alice, 0u),
-
557  token::uri(std::string(maxTokenURILength + 1, 'q')),
-
558  ter(temMALFORMED));
-
559 
-
560  //----------------------------------------------------------------------
-
561  // preflight
-
562 
-
563  // Non-existent issuer.
-
564  env(token::mint(alice, 0u),
-
565  token::issuer(Account("demon")),
-
566  ter(tecNO_ISSUER));
-
567 
-
568  //----------------------------------------------------------------------
-
569  // doApply
+
529  using namespace test::jtx;
+
530 
+
531  Env env{*this, features};
+
532  Account const alice{"alice"};
+
533  Account const minter{"minter"};
+
534 
+
535  // Fund alice and minter enough to exist, but not enough to meet
+
536  // the reserve for creating their first NFT. Account reserve for unit
+
537  // tests is 200 XRP, not 20.
+
538  env.fund(XRP(200), alice, minter);
+
539  env.close();
+
540 
+
541  env(token::mint(alice, 0u), ter(tecINSUFFICIENT_RESERVE));
+
542  env.close();
+
543 
+
544  // Fund alice enough to start minting NFTs.
+
545  env(pay(env.master, alice, XRP(1000)));
+
546  env.close();
+
547 
+
548  //----------------------------------------------------------------------
+
549  // preflight
+
550 
+
551  // Set a negative fee.
+
552  env(token::mint(alice, 0u),
+
553  fee(STAmount(10ull, true)),
+
554  ter(temBAD_FEE));
+
555 
+
556  // Set an invalid flag.
+
557  env(token::mint(alice, 0u), txflags(0x00008000), ter(temINVALID_FLAG));
+
558 
+
559  // Can't set a transfer fee if the NFT does not have the tfTRANSFERABLE
+
560  // flag set.
+
561  env(token::mint(alice, 0u),
+
562  token::xferFee(maxTransferFee),
+
563  ter(temMALFORMED));
+
564 
+
565  // Set a bad transfer fee.
+
566  env(token::mint(alice, 0u),
+
567  token::xferFee(maxTransferFee + 1),
+
568  txflags(tfTransferable),
+
569  ter(temBAD_NFTOKEN_TRANSFER_FEE));
570 
-
571  // Existent issuer, but not given minting permission
-
572  env(token::mint(minter, 0u),
-
573  token::issuer(alice),
-
574  ter(tecNO_PERMISSION));
-
575  }
+
571  // Account can't also be issuer.
+
572  env(token::mint(alice, 0u), token::issuer(alice), ter(temMALFORMED));
+
573 
+
574  // Invalid URI: zero length.
+
575  env(token::mint(alice, 0u), token::uri(""), ter(temMALFORMED));
576 
-
577  void
-
578  testBurnInvalid(FeatureBitset features)
-
579  {
-
580  // Explore many of the invalid ways to burn an NFT.
-
581  testcase("Burn invalid");
-
582 
-
583  using namespace test::jtx;
+
577  // Invalid URI: too long.
+
578  env(token::mint(alice, 0u),
+
579  token::uri(std::string(maxTokenURILength + 1, 'q')),
+
580  ter(temMALFORMED));
+
581 
+
582  //----------------------------------------------------------------------
+
583  // preflight
584 
-
585  Env env{*this, features};
-
586  Account const alice{"alice"};
-
587  Account const buyer{"buyer"};
-
588  Account const minter{"minter"};
-
589  Account const gw("gw");
-
590  IOU const gwAUD(gw["AUD"]);
-
591 
-
592  // Fund alice and minter enough to exist and create an NFT, but not
-
593  // enough to meet the reserve for creating their first NFTOffer.
-
594  // Account reserve for unit tests is 200 XRP, not 20.
-
595  env.fund(XRP(250), alice, buyer, minter, gw);
-
596  env.close();
-
597  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
585  // Non-existent issuer.
+
586  env(token::mint(alice, 0u),
+
587  token::issuer(Account("demon")),
+
588  ter(tecNO_ISSUER));
+
589 
+
590  //----------------------------------------------------------------------
+
591  // doApply
+
592 
+
593  // Existent issuer, but not given minting permission
+
594  env(token::mint(minter, 0u),
+
595  token::issuer(alice),
+
596  ter(tecNO_PERMISSION));
+
597  }
598 
-
599  uint256 const nftAlice0ID =
-
600  token::getNextID(env, alice, 0, tfTransferable);
-
601  env(token::mint(alice, 0u), txflags(tfTransferable));
-
602  env.close();
-
603  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
599  void
+
600  testBurnInvalid(FeatureBitset features)
+
601  {
+
602  // Explore many of the invalid ways to burn an NFT.
+
603  testcase("Burn invalid");
604 
-
605  //----------------------------------------------------------------------
-
606  // preflight
-
607 
-
608  // Set a negative fee.
-
609  env(token::burn(alice, nftAlice0ID),
-
610  fee(STAmount(10ull, true)),
-
611  ter(temBAD_FEE));
-
612  env.close();
-
613  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
614 
-
615  // Set an invalid flag.
-
616  env(token::burn(alice, nftAlice0ID),
-
617  txflags(0x00008000),
-
618  ter(temINVALID_FLAG));
-
619  env.close();
-
620  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
621 
-
622  //----------------------------------------------------------------------
-
623  // preclaim
-
624 
-
625  // Try to burn a token that doesn't exist.
-
626  env(token::burn(alice, token::getID(alice, 0, 1)), ter(tecNO_ENTRY));
-
627  env.close();
-
628  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
605  using namespace test::jtx;
+
606 
+
607  Env env{*this, features};
+
608  Account const alice{"alice"};
+
609  Account const buyer{"buyer"};
+
610  Account const minter{"minter"};
+
611  Account const gw("gw");
+
612  IOU const gwAUD(gw["AUD"]);
+
613 
+
614  // Fund alice and minter enough to exist and create an NFT, but not
+
615  // enough to meet the reserve for creating their first NFTOffer.
+
616  // Account reserve for unit tests is 200 XRP, not 20.
+
617  env.fund(XRP(250), alice, buyer, minter, gw);
+
618  env.close();
+
619  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
620 
+
621  uint256 const nftAlice0ID =
+
622  token::getNextID(env, alice, 0, tfTransferable);
+
623  env(token::mint(alice, 0u), txflags(tfTransferable));
+
624  env.close();
+
625  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
626 
+
627  //----------------------------------------------------------------------
+
628  // preflight
629 
-
630  // Can't burn a token with many buy or sell offers. But that is
-
631  // verified in testManyNftOffers().
-
632 
-
633  //----------------------------------------------------------------------
-
634  // doApply
-
635  }
+
630  // Set a negative fee.
+
631  env(token::burn(alice, nftAlice0ID),
+
632  fee(STAmount(10ull, true)),
+
633  ter(temBAD_FEE));
+
634  env.close();
+
635  BEAST_EXPECT(ownerCount(env, alice) == 1);
636 
-
637  void
-
638  testCreateOfferInvalid(FeatureBitset features)
-
639  {
-
640  testcase("Invalid NFT offer create");
-
641 
-
642  using namespace test::jtx;
+
637  // Set an invalid flag.
+
638  env(token::burn(alice, nftAlice0ID),
+
639  txflags(0x00008000),
+
640  ter(temINVALID_FLAG));
+
641  env.close();
+
642  BEAST_EXPECT(ownerCount(env, buyer) == 0);
643 
-
644  Env env{*this, features};
-
645  Account const alice{"alice"};
-
646  Account const buyer{"buyer"};
-
647  Account const gw("gw");
-
648  IOU const gwAUD(gw["AUD"]);
-
649 
-
650  // Fund alice enough to exist and create an NFT, but not
-
651  // enough to meet the reserve for creating their first NFTOffer.
-
652  // Account reserve for unit tests is 200 XRP, not 20.
-
653  env.fund(XRP(250), alice, buyer, gw);
-
654  env.close();
-
655  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
656 
-
657  uint256 const nftAlice0ID =
-
658  token::getNextID(env, alice, 0, tfTransferable, 10);
-
659  env(token::mint(alice, 0u),
-
660  txflags(tfTransferable),
-
661  token::xferFee(10));
-
662  env.close();
-
663  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
644  //----------------------------------------------------------------------
+
645  // preclaim
+
646 
+
647  // Try to burn a token that doesn't exist.
+
648  env(token::burn(alice, token::getID(env, alice, 0, 1)),
+
649  ter(tecNO_ENTRY));
+
650  env.close();
+
651  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
652 
+
653  // Can't burn a token with many buy or sell offers. But that is
+
654  // verified in testManyNftOffers().
+
655 
+
656  //----------------------------------------------------------------------
+
657  // doApply
+
658  }
+
659 
+
660  void
+
661  testCreateOfferInvalid(FeatureBitset features)
+
662  {
+
663  testcase("Invalid NFT offer create");
664 
-
665  uint256 const nftXrpOnlyID =
-
666  token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
-
667  env(token::mint(alice, 0), txflags(tfOnlyXRP | tfTransferable));
-
668  env.close();
-
669  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
670 
-
671  uint256 nftNoXferID = token::getNextID(env, alice, 0);
-
672  env(token::mint(alice, 0));
-
673  env.close();
-
674  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
675 
-
676  //----------------------------------------------------------------------
-
677  // preflight
-
678 
-
679  // buyer burns a fee, so they no longer have enough XRP to cover the
-
680  // reserve for a token offer.
-
681  env(noop(buyer));
-
682  env.close();
-
683 
-
684  // buyer tries to create an NFTokenOffer, but doesn't have the reserve.
-
685  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
-
686  token::owner(alice),
-
687  ter(tecINSUFFICIENT_RESERVE));
-
688  env.close();
-
689  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
690 
-
691  // Set a negative fee.
-
692  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
-
693  fee(STAmount(10ull, true)),
-
694  ter(temBAD_FEE));
-
695  env.close();
-
696  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
697 
-
698  // Set an invalid flag.
-
699  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
-
700  txflags(0x00008000),
-
701  ter(temINVALID_FLAG));
-
702  env.close();
-
703  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
704 
-
705  // Set an invalid amount.
-
706  env(token::createOffer(buyer, nftXrpOnlyID, buyer["USD"](1)),
-
707  ter(temBAD_AMOUNT));
-
708  env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](0)),
-
709  ter(temBAD_AMOUNT));
-
710  env(token::createOffer(buyer, nftXrpOnlyID, drops(0)),
-
711  ter(temBAD_AMOUNT));
-
712  env.close();
-
713  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
714 
-
715  // Set a bad expiration.
-
716  env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](1)),
-
717  token::expiration(0),
-
718  ter(temBAD_EXPIRATION));
-
719  env.close();
-
720  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
721 
-
722  // Invalid Owner field and tfSellToken flag relationships.
-
723  // A buy offer must specify the owner.
-
724  env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)),
-
725  ter(temMALFORMED));
-
726  env.close();
-
727  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
728 
-
729  // A sell offer must not specify the owner; the owner is implicit.
-
730  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
-
731  token::owner(alice),
-
732  txflags(tfSellNFToken),
-
733  ter(temMALFORMED));
-
734  env.close();
-
735  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
736 
-
737  // An owner may not offer to buy their own token.
-
738  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
-
739  token::owner(alice),
-
740  ter(temMALFORMED));
-
741  env.close();
-
742  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
743 
-
744  // The destination may not be the account submitting the transaction.
-
745  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
-
746  token::destination(alice),
-
747  txflags(tfSellNFToken),
+
665  using namespace test::jtx;
+
666 
+
667  Env env{*this, features};
+
668  Account const alice{"alice"};
+
669  Account const buyer{"buyer"};
+
670  Account const gw("gw");
+
671  IOU const gwAUD(gw["AUD"]);
+
672 
+
673  // Fund alice enough to exist and create an NFT, but not
+
674  // enough to meet the reserve for creating their first NFTOffer.
+
675  // Account reserve for unit tests is 200 XRP, not 20.
+
676  env.fund(XRP(250), alice, buyer, gw);
+
677  env.close();
+
678  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
679 
+
680  uint256 const nftAlice0ID =
+
681  token::getNextID(env, alice, 0, tfTransferable, 10);
+
682  env(token::mint(alice, 0u),
+
683  txflags(tfTransferable),
+
684  token::xferFee(10));
+
685  env.close();
+
686  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
687 
+
688  uint256 const nftXrpOnlyID =
+
689  token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
+
690  env(token::mint(alice, 0), txflags(tfOnlyXRP | tfTransferable));
+
691  env.close();
+
692  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
693 
+
694  uint256 nftNoXferID = token::getNextID(env, alice, 0);
+
695  env(token::mint(alice, 0));
+
696  env.close();
+
697  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
698 
+
699  //----------------------------------------------------------------------
+
700  // preflight
+
701 
+
702  // buyer burns a fee, so they no longer have enough XRP to cover the
+
703  // reserve for a token offer.
+
704  env(noop(buyer));
+
705  env.close();
+
706 
+
707  // buyer tries to create an NFTokenOffer, but doesn't have the reserve.
+
708  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
+
709  token::owner(alice),
+
710  ter(tecINSUFFICIENT_RESERVE));
+
711  env.close();
+
712  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
713 
+
714  // Set a negative fee.
+
715  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
+
716  fee(STAmount(10ull, true)),
+
717  ter(temBAD_FEE));
+
718  env.close();
+
719  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
720 
+
721  // Set an invalid flag.
+
722  env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
+
723  txflags(0x00008000),
+
724  ter(temINVALID_FLAG));
+
725  env.close();
+
726  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
727 
+
728  // Set an invalid amount.
+
729  env(token::createOffer(buyer, nftXrpOnlyID, buyer["USD"](1)),
+
730  ter(temBAD_AMOUNT));
+
731  env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](0)),
+
732  ter(temBAD_AMOUNT));
+
733  env(token::createOffer(buyer, nftXrpOnlyID, drops(0)),
+
734  ter(temBAD_AMOUNT));
+
735  env.close();
+
736  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
737 
+
738  // Set a bad expiration.
+
739  env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](1)),
+
740  token::expiration(0),
+
741  ter(temBAD_EXPIRATION));
+
742  env.close();
+
743  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
744 
+
745  // Invalid Owner field and tfSellToken flag relationships.
+
746  // A buy offer must specify the owner.
+
747  env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)),
748  ter(temMALFORMED));
749  env.close();
-
750  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
750  BEAST_EXPECT(ownerCount(env, buyer) == 0);
751 
-
752  // The destination must be an account already established in the ledger.
+
752  // A sell offer must not specify the owner; the owner is implicit.
753  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
-
754  token::destination(Account("demon")),
+
754  token::owner(alice),
755  txflags(tfSellNFToken),
-
756  ter(tecNO_DST));
+
756  ter(temMALFORMED));
757  env.close();
758  BEAST_EXPECT(ownerCount(env, alice) == 1);
759 
-
760  //----------------------------------------------------------------------
-
761  // preclaim
-
762 
-
763  // The new NFTokenOffer may not have passed its expiration time.
-
764  env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)),
-
765  token::owner(alice),
-
766  token::expiration(lastClose(env)),
-
767  ter(tecEXPIRED));
-
768  env.close();
-
769  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
770 
-
771  // The nftID must be present in the ledger.
-
772  env(token::createOffer(buyer, token::getID(alice, 0, 1), XRP(1000)),
-
773  token::owner(alice),
-
774  ter(tecNO_ENTRY));
-
775  env.close();
-
776  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
777 
-
778  // The nftID must be present in the ledger of a sell offer too.
-
779  env(token::createOffer(alice, token::getID(alice, 0, 1), XRP(1000)),
-
780  txflags(tfSellNFToken),
-
781  ter(tecNO_ENTRY));
-
782  env.close();
-
783  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
784 
-
785  // buyer must have the funds to pay for their offer.
-
786  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
787  token::owner(alice),
-
788  ter(tecNO_LINE));
-
789  env.close();
-
790  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
791 
-
792  env(trust(buyer, gwAUD(1000)));
-
793  env.close();
-
794  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
795  env.close();
-
796 
-
797  // Issuer (alice) must have a trust line for the offered funds.
-
798  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
799  token::owner(alice),
-
800  ter(tecNO_LINE));
-
801  env.close();
-
802  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
803 
-
804  // Give alice the needed trust line, but freeze it.
-
805  env(trust(gw, alice["AUD"](999), tfSetFreeze));
-
806  env.close();
-
807 
-
808  // Issuer (alice) must have a trust line for the offered funds and
-
809  // the trust line may not be frozen.
-
810  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
811  token::owner(alice),
-
812  ter(tecFROZEN));
-
813  env.close();
-
814  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
815 
-
816  // Unfreeze alice's trustline.
-
817  env(trust(gw, alice["AUD"](999), tfClearFreeze));
+
760  // An owner may not offer to buy their own token.
+
761  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
+
762  token::owner(alice),
+
763  ter(temMALFORMED));
+
764  env.close();
+
765  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
766 
+
767  // The destination may not be the account submitting the transaction.
+
768  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
+
769  token::destination(alice),
+
770  txflags(tfSellNFToken),
+
771  ter(temMALFORMED));
+
772  env.close();
+
773  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
774 
+
775  // The destination must be an account already established in the ledger.
+
776  env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
+
777  token::destination(Account("demon")),
+
778  txflags(tfSellNFToken),
+
779  ter(tecNO_DST));
+
780  env.close();
+
781  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
782 
+
783  //----------------------------------------------------------------------
+
784  // preclaim
+
785 
+
786  // The new NFTokenOffer may not have passed its expiration time.
+
787  env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)),
+
788  token::owner(alice),
+
789  token::expiration(lastClose(env)),
+
790  ter(tecEXPIRED));
+
791  env.close();
+
792  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
793 
+
794  // The nftID must be present in the ledger.
+
795  env(token::createOffer(
+
796  buyer, token::getID(env, alice, 0, 1), XRP(1000)),
+
797  token::owner(alice),
+
798  ter(tecNO_ENTRY));
+
799  env.close();
+
800  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
801 
+
802  // The nftID must be present in the ledger of a sell offer too.
+
803  env(token::createOffer(
+
804  alice, token::getID(env, alice, 0, 1), XRP(1000)),
+
805  txflags(tfSellNFToken),
+
806  ter(tecNO_ENTRY));
+
807  env.close();
+
808  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
809 
+
810  // buyer must have the funds to pay for their offer.
+
811  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
812  token::owner(alice),
+
813  ter(tecNO_LINE));
+
814  env.close();
+
815  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
816 
+
817  env(trust(buyer, gwAUD(1000)));
818  env.close();
-
819 
-
820  // Can't transfer the NFT if the transferable flag is not set.
-
821  env(token::createOffer(buyer, nftNoXferID, gwAUD(1000)),
-
822  token::owner(alice),
-
823  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
824  env.close();
-
825  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
826 
-
827  // Give buyer the needed trust line, but freeze it.
-
828  env(trust(gw, buyer["AUD"](999), tfSetFreeze));
-
829  env.close();
-
830 
-
831  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
832  token::owner(alice),
-
833  ter(tecFROZEN));
-
834  env.close();
-
835  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
836 
-
837  // Unfreeze buyer's trust line, but buyer has no actual gwAUD.
-
838  // to cover the offer.
-
839  env(trust(gw, buyer["AUD"](999), tfClearFreeze));
-
840  env(trust(buyer, gwAUD(1000)));
-
841  env.close();
-
842 
-
843  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
844  token::owner(alice),
-
845  ter(tecUNFUNDED_OFFER));
-
846  env.close();
-
847  BEAST_EXPECT(ownerCount(env, buyer) == 1); // the trust line.
-
848 
-
849  //----------------------------------------------------------------------
-
850  // doApply
+
819  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
820  env.close();
+
821 
+
822  // Issuer (alice) must have a trust line for the offered funds.
+
823  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
824  token::owner(alice),
+
825  ter(tecNO_LINE));
+
826  env.close();
+
827  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
828 
+
829  // Give alice the needed trust line, but freeze it.
+
830  env(trust(gw, alice["AUD"](999), tfSetFreeze));
+
831  env.close();
+
832 
+
833  // Issuer (alice) must have a trust line for the offered funds and
+
834  // the trust line may not be frozen.
+
835  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
836  token::owner(alice),
+
837  ter(tecFROZEN));
+
838  env.close();
+
839  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
840 
+
841  // Unfreeze alice's trustline.
+
842  env(trust(gw, alice["AUD"](999), tfClearFreeze));
+
843  env.close();
+
844 
+
845  // Can't transfer the NFT if the transferable flag is not set.
+
846  env(token::createOffer(buyer, nftNoXferID, gwAUD(1000)),
+
847  token::owner(alice),
+
848  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
849  env.close();
+
850  BEAST_EXPECT(ownerCount(env, buyer) == 1);
851 
-
852  // Give buyer almost enough AUD to cover the offer...
-
853  env(pay(gw, buyer, gwAUD(999)));
+
852  // Give buyer the needed trust line, but freeze it.
+
853  env(trust(gw, buyer["AUD"](999), tfSetFreeze));
854  env.close();
855 
-
856  // However buyer doesn't have enough XRP to cover the reserve for
-
857  // an NFT offer.
-
858  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
859  token::owner(alice),
-
860  ter(tecINSUFFICIENT_RESERVE));
-
861  env.close();
-
862  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
863 
-
864  // Give buyer almost enough XRP to cover the reserve.
-
865  env(pay(env.master, buyer, XRP(50) + drops(119)));
+
856  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
857  token::owner(alice),
+
858  ter(tecFROZEN));
+
859  env.close();
+
860  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
861 
+
862  // Unfreeze buyer's trust line, but buyer has no actual gwAUD.
+
863  // to cover the offer.
+
864  env(trust(gw, buyer["AUD"](999), tfClearFreeze));
+
865  env(trust(buyer, gwAUD(1000)));
866  env.close();
867 
868  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
869  token::owner(alice),
-
870  ter(tecINSUFFICIENT_RESERVE));
+
870  ter(tecUNFUNDED_OFFER));
871  env.close();
-
872  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
872  BEAST_EXPECT(ownerCount(env, buyer) == 1); // the trust line.
873 
-
874  // Give buyer just enough XRP to cover the reserve for the offer.
-
875  env(pay(env.master, buyer, drops(11)));
-
876  env.close();
-
877 
-
878  // We don't care whether the offer is fully funded until the offer is
-
879  // accepted. Success at last!
-
880  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
-
881  token::owner(alice),
-
882  ter(tesSUCCESS));
-
883  env.close();
-
884  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
885  }
-
886 
-
887  void
-
888  testCancelOfferInvalid(FeatureBitset features)
-
889  {
-
890  testcase("Invalid NFT offer cancel");
-
891 
-
892  using namespace test::jtx;
-
893 
-
894  Env env{*this, features};
-
895  Account const alice{"alice"};
-
896  Account const buyer{"buyer"};
-
897  Account const gw("gw");
-
898  IOU const gwAUD(gw["AUD"]);
-
899 
-
900  env.fund(XRP(1000), alice, buyer, gw);
+
874  //----------------------------------------------------------------------
+
875  // doApply
+
876 
+
877  // Give buyer almost enough AUD to cover the offer...
+
878  env(pay(gw, buyer, gwAUD(999)));
+
879  env.close();
+
880 
+
881  // However buyer doesn't have enough XRP to cover the reserve for
+
882  // an NFT offer.
+
883  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
884  token::owner(alice),
+
885  ter(tecINSUFFICIENT_RESERVE));
+
886  env.close();
+
887  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
888 
+
889  // Give buyer almost enough XRP to cover the reserve.
+
890  env(pay(env.master, buyer, XRP(50) + drops(119)));
+
891  env.close();
+
892 
+
893  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
894  token::owner(alice),
+
895  ter(tecINSUFFICIENT_RESERVE));
+
896  env.close();
+
897  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
898 
+
899  // Give buyer just enough XRP to cover the reserve for the offer.
+
900  env(pay(env.master, buyer, drops(11)));
901  env.close();
-
902  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
903 
-
904  uint256 const nftAlice0ID =
-
905  token::getNextID(env, alice, 0, tfTransferable);
-
906  env(token::mint(alice, 0u), txflags(tfTransferable));
-
907  env.close();
-
908  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
909 
-
910  // This is the offer we'll try to cancel.
-
911  uint256 const buyerOfferIndex =
-
912  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
913  env(token::createOffer(buyer, nftAlice0ID, XRP(1)),
-
914  token::owner(alice),
-
915  ter(tesSUCCESS));
-
916  env.close();
-
917  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
902 
+
903  // We don't care whether the offer is fully funded until the offer is
+
904  // accepted. Success at last!
+
905  env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
+
906  token::owner(alice),
+
907  ter(tesSUCCESS));
+
908  env.close();
+
909  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
910  }
+
911 
+
912  void
+
913  testCancelOfferInvalid(FeatureBitset features)
+
914  {
+
915  testcase("Invalid NFT offer cancel");
+
916 
+
917  using namespace test::jtx;
918 
-
919  //----------------------------------------------------------------------
-
920  // preflight
-
921 
-
922  // Set a negative fee.
-
923  env(token::cancelOffer(buyer, {buyerOfferIndex}),
-
924  fee(STAmount(10ull, true)),
-
925  ter(temBAD_FEE));
+
919  Env env{*this, features};
+
920  Account const alice{"alice"};
+
921  Account const buyer{"buyer"};
+
922  Account const gw("gw");
+
923  IOU const gwAUD(gw["AUD"]);
+
924 
+
925  env.fund(XRP(1000), alice, buyer, gw);
926  env.close();
-
927  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
927  BEAST_EXPECT(ownerCount(env, alice) == 0);
928 
-
929  // Set an invalid flag.
-
930  env(token::cancelOffer(buyer, {buyerOfferIndex}),
-
931  txflags(0x00008000),
-
932  ter(temINVALID_FLAG));
-
933  env.close();
-
934  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
935 
-
936  // Empty list of tokens to delete.
-
937  {
-
938  Json::Value jv = token::cancelOffer(buyer);
-
939  jv[sfNFTokenOffers.jsonName] = Json::arrayValue;
-
940  env(jv, ter(temMALFORMED));
-
941  env.close();
-
942  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
943  }
-
944 
-
945  // List of tokens to delete is too long.
-
946  {
-
947  std::vector<uint256> offers(
-
948  maxTokenOfferCancelCount + 1, buyerOfferIndex);
-
949 
-
950  env(token::cancelOffer(buyer, offers), ter(temMALFORMED));
-
951  env.close();
-
952  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
953  }
-
954 
-
955  // Duplicate entries are not allowed in the list of offers to cancel.
-
956  env(token::cancelOffer(buyer, {buyerOfferIndex, buyerOfferIndex}),
-
957  ter(temMALFORMED));
+
929  uint256 const nftAlice0ID =
+
930  token::getNextID(env, alice, 0, tfTransferable);
+
931  env(token::mint(alice, 0u), txflags(tfTransferable));
+
932  env.close();
+
933  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
934 
+
935  // This is the offer we'll try to cancel.
+
936  uint256 const buyerOfferIndex =
+
937  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
938  env(token::createOffer(buyer, nftAlice0ID, XRP(1)),
+
939  token::owner(alice),
+
940  ter(tesSUCCESS));
+
941  env.close();
+
942  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
943 
+
944  //----------------------------------------------------------------------
+
945  // preflight
+
946 
+
947  // Set a negative fee.
+
948  env(token::cancelOffer(buyer, {buyerOfferIndex}),
+
949  fee(STAmount(10ull, true)),
+
950  ter(temBAD_FEE));
+
951  env.close();
+
952  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
953 
+
954  // Set an invalid flag.
+
955  env(token::cancelOffer(buyer, {buyerOfferIndex}),
+
956  txflags(0x00008000),
+
957  ter(temINVALID_FLAG));
958  env.close();
959  BEAST_EXPECT(ownerCount(env, buyer) == 1);
960 
-
961  // Provide neither offers to cancel nor a root index.
-
962  env(token::cancelOffer(buyer), ter(temMALFORMED));
-
963  env.close();
-
964  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
965 
-
966  //----------------------------------------------------------------------
-
967  // preclaim
-
968 
-
969  // Make a non-root directory that we can pass as a root index.
-
970  env(pay(env.master, gw, XRP(5000)));
-
971  env.close();
-
972  for (std::uint32_t i = 1; i < 34; ++i)
-
973  {
-
974  env(offer(gw, XRP(i), gwAUD(1)));
-
975  env.close();
-
976  }
-
977 
-
978  {
-
979  // gw attempts to cancel a Check as through it is an NFTokenOffer.
-
980  auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
-
981  env(check::create(gw, env.master, XRP(300)));
-
982  env.close();
-
983 
-
984  env(token::cancelOffer(gw, {gwCheckId}), ter(tecNO_PERMISSION));
-
985  env.close();
-
986 
-
987  // Cancel the check so it doesn't mess up later tests.
-
988  env(check::cancel(gw, gwCheckId));
-
989  env.close();
-
990  }
-
991 
-
992  // gw attempts to cancel an offer they don't have permission to cancel.
-
993  env(token::cancelOffer(gw, {buyerOfferIndex}), ter(tecNO_PERMISSION));
-
994  env.close();
-
995  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
996 
-
997  //----------------------------------------------------------------------
-
998  // doApply
-
999  //
-
1000  // The tefBAD_LEDGER conditions are too hard to test.
-
1001  // But let's see a successful offer cancel.
-
1002  env(token::cancelOffer(buyer, {buyerOfferIndex}));
-
1003  env.close();
-
1004  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1005  }
-
1006 
-
1007  void
-
1008  testAcceptOfferInvalid(FeatureBitset features)
-
1009  {
-
1010  testcase("Invalid NFT offer accept");
+
961  // Empty list of tokens to delete.
+
962  {
+
963  Json::Value jv = token::cancelOffer(buyer);
+
964  jv[sfNFTokenOffers.jsonName] = Json::arrayValue;
+
965  env(jv, ter(temMALFORMED));
+
966  env.close();
+
967  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
968  }
+
969 
+
970  // List of tokens to delete is too long.
+
971  {
+
972  std::vector<uint256> offers(
+
973  maxTokenOfferCancelCount + 1, buyerOfferIndex);
+
974 
+
975  env(token::cancelOffer(buyer, offers), ter(temMALFORMED));
+
976  env.close();
+
977  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
978  }
+
979 
+
980  // Duplicate entries are not allowed in the list of offers to cancel.
+
981  env(token::cancelOffer(buyer, {buyerOfferIndex, buyerOfferIndex}),
+
982  ter(temMALFORMED));
+
983  env.close();
+
984  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
985 
+
986  // Provide neither offers to cancel nor a root index.
+
987  env(token::cancelOffer(buyer), ter(temMALFORMED));
+
988  env.close();
+
989  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
990 
+
991  //----------------------------------------------------------------------
+
992  // preclaim
+
993 
+
994  // Make a non-root directory that we can pass as a root index.
+
995  env(pay(env.master, gw, XRP(5000)));
+
996  env.close();
+
997  for (std::uint32_t i = 1; i < 34; ++i)
+
998  {
+
999  env(offer(gw, XRP(i), gwAUD(1)));
+
1000  env.close();
+
1001  }
+
1002 
+
1003  {
+
1004  // gw attempts to cancel a Check as through it is an NFTokenOffer.
+
1005  auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
+
1006  env(check::create(gw, env.master, XRP(300)));
+
1007  env.close();
+
1008 
+
1009  env(token::cancelOffer(gw, {gwCheckId}), ter(tecNO_PERMISSION));
+
1010  env.close();
1011 
-
1012  using namespace test::jtx;
-
1013 
-
1014  Env env{*this, features};
-
1015  Account const alice{"alice"};
-
1016  Account const buyer{"buyer"};
-
1017  Account const gw("gw");
-
1018  IOU const gwAUD(gw["AUD"]);
-
1019 
-
1020  env.fund(XRP(1000), alice, buyer, gw);
-
1021  env.close();
-
1022  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1023 
-
1024  uint256 const nftAlice0ID =
-
1025  token::getNextID(env, alice, 0, tfTransferable);
-
1026  env(token::mint(alice, 0u), txflags(tfTransferable));
-
1027  env.close();
-
1028  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1029 
-
1030  uint256 const nftXrpOnlyID =
-
1031  token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
-
1032  env(token::mint(alice, 0), txflags(tfOnlyXRP | tfTransferable));
-
1033  env.close();
-
1034  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1035 
-
1036  uint256 nftNoXferID = token::getNextID(env, alice, 0);
-
1037  env(token::mint(alice, 0));
-
1038  env.close();
-
1039  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1040 
-
1041  // alice creates sell offers for her nfts.
-
1042  uint256 const plainOfferIndex =
-
1043  keylet::nftoffer(alice, env.seq(alice)).key;
-
1044  env(token::createOffer(alice, nftAlice0ID, XRP(10)),
-
1045  txflags(tfSellNFToken));
+
1012  // Cancel the check so it doesn't mess up later tests.
+
1013  env(check::cancel(gw, gwCheckId));
+
1014  env.close();
+
1015  }
+
1016 
+
1017  // gw attempts to cancel an offer they don't have permission to cancel.
+
1018  env(token::cancelOffer(gw, {buyerOfferIndex}), ter(tecNO_PERMISSION));
+
1019  env.close();
+
1020  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1021 
+
1022  //----------------------------------------------------------------------
+
1023  // doApply
+
1024  //
+
1025  // The tefBAD_LEDGER conditions are too hard to test.
+
1026  // But let's see a successful offer cancel.
+
1027  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1028  env.close();
+
1029  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1030  }
+
1031 
+
1032  void
+
1033  testAcceptOfferInvalid(FeatureBitset features)
+
1034  {
+
1035  testcase("Invalid NFT offer accept");
+
1036 
+
1037  using namespace test::jtx;
+
1038 
+
1039  Env env{*this, features};
+
1040  Account const alice{"alice"};
+
1041  Account const buyer{"buyer"};
+
1042  Account const gw("gw");
+
1043  IOU const gwAUD(gw["AUD"]);
+
1044 
+
1045  env.fund(XRP(1000), alice, buyer, gw);
1046  env.close();
-
1047  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1047  BEAST_EXPECT(ownerCount(env, alice) == 0);
1048 
-
1049  uint256 const audOfferIndex =
-
1050  keylet::nftoffer(alice, env.seq(alice)).key;
-
1051  env(token::createOffer(alice, nftAlice0ID, gwAUD(30)),
-
1052  txflags(tfSellNFToken));
-
1053  env.close();
-
1054  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
1055 
-
1056  uint256 const xrpOnlyOfferIndex =
-
1057  keylet::nftoffer(alice, env.seq(alice)).key;
-
1058  env(token::createOffer(alice, nftXrpOnlyID, XRP(20)),
-
1059  txflags(tfSellNFToken));
-
1060  env.close();
-
1061  BEAST_EXPECT(ownerCount(env, alice) == 4);
-
1062 
-
1063  uint256 const noXferOfferIndex =
-
1064  keylet::nftoffer(alice, env.seq(alice)).key;
-
1065  env(token::createOffer(alice, nftNoXferID, XRP(30)),
-
1066  txflags(tfSellNFToken));
-
1067  env.close();
-
1068  BEAST_EXPECT(ownerCount(env, alice) == 5);
-
1069 
-
1070  // alice creates a sell offer that will expire soon.
-
1071  uint256 const aliceExpOfferIndex =
-
1072  keylet::nftoffer(alice, env.seq(alice)).key;
-
1073  env(token::createOffer(alice, nftNoXferID, XRP(40)),
-
1074  txflags(tfSellNFToken),
-
1075  token::expiration(lastClose(env) + 5));
-
1076  env.close();
-
1077  BEAST_EXPECT(ownerCount(env, alice) == 6);
-
1078 
-
1079  //----------------------------------------------------------------------
-
1080  // preflight
-
1081 
-
1082  // Set a negative fee.
-
1083  env(token::acceptSellOffer(buyer, noXferOfferIndex),
-
1084  fee(STAmount(10ull, true)),
-
1085  ter(temBAD_FEE));
-
1086  env.close();
-
1087  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1088 
-
1089  // Set an invalid flag.
-
1090  env(token::acceptSellOffer(buyer, noXferOfferIndex),
-
1091  txflags(0x00008000),
-
1092  ter(temINVALID_FLAG));
-
1093  env.close();
-
1094  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1095 
-
1096  // Supply nether an sfNFTokenBuyOffer nor an sfNFTokenSellOffer field.
-
1097  {
-
1098  Json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
-
1099  jv.removeMember(sfNFTokenSellOffer.jsonName);
-
1100  env(jv, ter(temMALFORMED));
-
1101  env.close();
-
1102  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1103  }
-
1104 
-
1105  // A buy offer may not contain a sfNFTokenBrokerFee field.
-
1106  {
-
1107  Json::Value jv = token::acceptBuyOffer(buyer, noXferOfferIndex);
-
1108  jv[sfNFTokenBrokerFee.jsonName] =
-
1109  STAmount(500000).getJson(JsonOptions::none);
-
1110  env(jv, ter(temMALFORMED));
-
1111  env.close();
-
1112  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1113  }
-
1114 
-
1115  // A sell offer may not contain a sfNFTokenBrokerFee field.
-
1116  {
-
1117  Json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
-
1118  jv[sfNFTokenBrokerFee.jsonName] =
-
1119  STAmount(500000).getJson(JsonOptions::none);
-
1120  env(jv, ter(temMALFORMED));
-
1121  env.close();
-
1122  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1123  }
-
1124 
-
1125  // A brokered offer may not contain a negative or zero brokerFee.
-
1126  env(token::brokerOffers(buyer, noXferOfferIndex, xrpOnlyOfferIndex),
-
1127  token::brokerFee(gwAUD(0)),
-
1128  ter(temMALFORMED));
-
1129  env.close();
-
1130  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1131 
-
1132  //----------------------------------------------------------------------
-
1133  // preclaim
-
1134 
-
1135  // The buy offer must be non-zero.
-
1136  env(token::acceptBuyOffer(buyer, beast::zero),
-
1137  ter(tecOBJECT_NOT_FOUND));
-
1138  env.close();
-
1139  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1140 
-
1141  // The buy offer must be present in the ledger.
-
1142  uint256 const missingOfferIndex = keylet::nftoffer(alice, 1).key;
-
1143  env(token::acceptBuyOffer(buyer, missingOfferIndex),
-
1144  ter(tecOBJECT_NOT_FOUND));
-
1145  env.close();
-
1146  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1147 
-
1148  // The buy offer must not have expired.
-
1149  env(token::acceptBuyOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
-
1150  env.close();
-
1151  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1152 
-
1153  // The sell offer must be non-zero.
-
1154  env(token::acceptSellOffer(buyer, beast::zero),
-
1155  ter(tecOBJECT_NOT_FOUND));
-
1156  env.close();
-
1157  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1158 
-
1159  // The sell offer must be present in the ledger.
-
1160  env(token::acceptSellOffer(buyer, missingOfferIndex),
-
1161  ter(tecOBJECT_NOT_FOUND));
-
1162  env.close();
-
1163  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1164 
-
1165  // The sell offer must not have expired.
-
1166  env(token::acceptSellOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
-
1167  env.close();
-
1168  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1169 
-
1170  //----------------------------------------------------------------------
-
1171  // preclaim brokered
+
1049  uint256 const nftAlice0ID =
+
1050  token::getNextID(env, alice, 0, tfTransferable);
+
1051  env(token::mint(alice, 0u), txflags(tfTransferable));
+
1052  env.close();
+
1053  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1054 
+
1055  uint256 const nftXrpOnlyID =
+
1056  token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
+
1057  env(token::mint(alice, 0), txflags(tfOnlyXRP | tfTransferable));
+
1058  env.close();
+
1059  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1060 
+
1061  uint256 nftNoXferID = token::getNextID(env, alice, 0);
+
1062  env(token::mint(alice, 0));
+
1063  env.close();
+
1064  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1065 
+
1066  // alice creates sell offers for her nfts.
+
1067  uint256 const plainOfferIndex =
+
1068  keylet::nftoffer(alice, env.seq(alice)).key;
+
1069  env(token::createOffer(alice, nftAlice0ID, XRP(10)),
+
1070  txflags(tfSellNFToken));
+
1071  env.close();
+
1072  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1073 
+
1074  uint256 const audOfferIndex =
+
1075  keylet::nftoffer(alice, env.seq(alice)).key;
+
1076  env(token::createOffer(alice, nftAlice0ID, gwAUD(30)),
+
1077  txflags(tfSellNFToken));
+
1078  env.close();
+
1079  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
1080 
+
1081  uint256 const xrpOnlyOfferIndex =
+
1082  keylet::nftoffer(alice, env.seq(alice)).key;
+
1083  env(token::createOffer(alice, nftXrpOnlyID, XRP(20)),
+
1084  txflags(tfSellNFToken));
+
1085  env.close();
+
1086  BEAST_EXPECT(ownerCount(env, alice) == 4);
+
1087 
+
1088  uint256 const noXferOfferIndex =
+
1089  keylet::nftoffer(alice, env.seq(alice)).key;
+
1090  env(token::createOffer(alice, nftNoXferID, XRP(30)),
+
1091  txflags(tfSellNFToken));
+
1092  env.close();
+
1093  BEAST_EXPECT(ownerCount(env, alice) == 5);
+
1094 
+
1095  // alice creates a sell offer that will expire soon.
+
1096  uint256 const aliceExpOfferIndex =
+
1097  keylet::nftoffer(alice, env.seq(alice)).key;
+
1098  env(token::createOffer(alice, nftNoXferID, XRP(40)),
+
1099  txflags(tfSellNFToken),
+
1100  token::expiration(lastClose(env) + 5));
+
1101  env.close();
+
1102  BEAST_EXPECT(ownerCount(env, alice) == 6);
+
1103 
+
1104  //----------------------------------------------------------------------
+
1105  // preflight
+
1106 
+
1107  // Set a negative fee.
+
1108  env(token::acceptSellOffer(buyer, noXferOfferIndex),
+
1109  fee(STAmount(10ull, true)),
+
1110  ter(temBAD_FEE));
+
1111  env.close();
+
1112  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1113 
+
1114  // Set an invalid flag.
+
1115  env(token::acceptSellOffer(buyer, noXferOfferIndex),
+
1116  txflags(0x00008000),
+
1117  ter(temINVALID_FLAG));
+
1118  env.close();
+
1119  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1120 
+
1121  // Supply nether an sfNFTokenBuyOffer nor an sfNFTokenSellOffer field.
+
1122  {
+
1123  Json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
+
1124  jv.removeMember(sfNFTokenSellOffer.jsonName);
+
1125  env(jv, ter(temMALFORMED));
+
1126  env.close();
+
1127  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1128  }
+
1129 
+
1130  // A buy offer may not contain a sfNFTokenBrokerFee field.
+
1131  {
+
1132  Json::Value jv = token::acceptBuyOffer(buyer, noXferOfferIndex);
+
1133  jv[sfNFTokenBrokerFee.jsonName] =
+
1134  STAmount(500000).getJson(JsonOptions::none);
+
1135  env(jv, ter(temMALFORMED));
+
1136  env.close();
+
1137  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1138  }
+
1139 
+
1140  // A sell offer may not contain a sfNFTokenBrokerFee field.
+
1141  {
+
1142  Json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
+
1143  jv[sfNFTokenBrokerFee.jsonName] =
+
1144  STAmount(500000).getJson(JsonOptions::none);
+
1145  env(jv, ter(temMALFORMED));
+
1146  env.close();
+
1147  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1148  }
+
1149 
+
1150  // A brokered offer may not contain a negative or zero brokerFee.
+
1151  env(token::brokerOffers(buyer, noXferOfferIndex, xrpOnlyOfferIndex),
+
1152  token::brokerFee(gwAUD(0)),
+
1153  ter(temMALFORMED));
+
1154  env.close();
+
1155  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1156 
+
1157  //----------------------------------------------------------------------
+
1158  // preclaim
+
1159 
+
1160  // The buy offer must be non-zero.
+
1161  env(token::acceptBuyOffer(buyer, beast::zero),
+
1162  ter(tecOBJECT_NOT_FOUND));
+
1163  env.close();
+
1164  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1165 
+
1166  // The buy offer must be present in the ledger.
+
1167  uint256 const missingOfferIndex = keylet::nftoffer(alice, 1).key;
+
1168  env(token::acceptBuyOffer(buyer, missingOfferIndex),
+
1169  ter(tecOBJECT_NOT_FOUND));
+
1170  env.close();
+
1171  BEAST_EXPECT(ownerCount(env, buyer) == 0);
1172 
-
1173  // alice and buyer need trustlines before buyer can to create an
-
1174  // offer for gwAUD.
-
1175  env(trust(alice, gwAUD(1000)));
-
1176  env(trust(buyer, gwAUD(1000)));
-
1177  env.close();
-
1178  env(pay(gw, buyer, gwAUD(30)));
-
1179  env.close();
-
1180  BEAST_EXPECT(ownerCount(env, alice) == 7);
-
1181  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1182 
-
1183  // We're about to exercise offer brokering, so we need
-
1184  // corresponding buy and sell offers.
-
1185  {
-
1186  // buyer creates a buy offer for one of alice's nfts.
-
1187  uint256 const buyerOfferIndex =
-
1188  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
1189  env(token::createOffer(buyer, nftAlice0ID, gwAUD(29)),
-
1190  token::owner(alice));
-
1191  env.close();
-
1192  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1193 
-
1194  // gw attempts to broker offers that are not for the same token.
-
1195  env(token::brokerOffers(gw, buyerOfferIndex, xrpOnlyOfferIndex),
-
1196  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
-
1197  env.close();
-
1198  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1199 
-
1200  // gw attempts to broker offers that are not for the same currency.
-
1201  env(token::brokerOffers(gw, buyerOfferIndex, plainOfferIndex),
-
1202  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
-
1203  env.close();
-
1204  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1205 
-
1206  // In a brokered offer, the buyer must offer greater than or
-
1207  // equal to the selling price.
-
1208  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
-
1209  ter(tecINSUFFICIENT_PAYMENT));
-
1210  env.close();
-
1211  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1212 
-
1213  // Remove buyer's offer.
-
1214  env(token::cancelOffer(buyer, {buyerOfferIndex}));
-
1215  env.close();
-
1216  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1217  }
-
1218  {
-
1219  // buyer creates a buy offer for one of alice's nfts.
-
1220  uint256 const buyerOfferIndex =
-
1221  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
1222  env(token::createOffer(buyer, nftAlice0ID, gwAUD(31)),
-
1223  token::owner(alice));
-
1224  env.close();
-
1225  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1226 
-
1227  // Broker sets their fee in a denomination other than the one
-
1228  // used by the offers
-
1229  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
-
1230  token::brokerFee(XRP(40)),
-
1231  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
-
1232  env.close();
-
1233  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1234 
-
1235  // Broker fee way too big.
-
1236  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
-
1237  token::brokerFee(gwAUD(31)),
-
1238  ter(tecINSUFFICIENT_PAYMENT));
-
1239  env.close();
-
1240  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1241 
-
1242  // Broker fee is smaller, but still too big once the offer
-
1243  // seller's minimum is taken into account.
-
1244  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
-
1245  token::brokerFee(gwAUD(1.5)),
-
1246  ter(tecINSUFFICIENT_PAYMENT));
-
1247  env.close();
-
1248  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1249 
-
1250  // Remove buyer's offer.
-
1251  env(token::cancelOffer(buyer, {buyerOfferIndex}));
-
1252  env.close();
-
1253  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1254  }
-
1255  //----------------------------------------------------------------------
-
1256  // preclaim buy
-
1257  {
-
1258  // buyer creates a buy offer for one of alice's nfts.
-
1259  uint256 const buyerOfferIndex =
-
1260  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
1261  env(token::createOffer(buyer, nftAlice0ID, gwAUD(30)),
-
1262  token::owner(alice));
-
1263  env.close();
-
1264  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1265 
-
1266  // Don't accept a buy offer if the sell flag is set.
-
1267  env(token::acceptBuyOffer(buyer, plainOfferIndex),
-
1268  ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
-
1269  env.close();
-
1270  BEAST_EXPECT(ownerCount(env, alice) == 7);
-
1271 
-
1272  // An account can't accept its own offer.
-
1273  env(token::acceptBuyOffer(buyer, buyerOfferIndex),
-
1274  ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
-
1275  env.close();
-
1276  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1277 
-
1278  // An offer acceptor must have enough funds to pay for the offer.
-
1279  env(pay(buyer, gw, gwAUD(30)));
-
1280  env.close();
-
1281  BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
-
1282  env(token::acceptBuyOffer(alice, buyerOfferIndex),
-
1283  ter(tecINSUFFICIENT_FUNDS));
-
1284  env.close();
-
1285  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1286 
-
1287  // alice gives her NFT to gw, so alice no longer owns nftAlice0.
-
1288  {
-
1289  uint256 const offerIndex =
-
1290  keylet::nftoffer(alice, env.seq(alice)).key;
-
1291  env(token::createOffer(alice, nftAlice0ID, XRP(0)),
-
1292  txflags(tfSellNFToken));
-
1293  env.close();
-
1294  env(token::acceptSellOffer(gw, offerIndex));
-
1295  env.close();
-
1296  BEAST_EXPECT(ownerCount(env, alice) == 7);
-
1297  }
-
1298  env(pay(gw, buyer, gwAUD(30)));
-
1299  env.close();
-
1300 
-
1301  // alice can't accept a buy offer for an NFT she no longer owns.
-
1302  env(token::acceptBuyOffer(alice, buyerOfferIndex),
-
1303  ter(tecNO_PERMISSION));
-
1304  env.close();
-
1305  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1306 
-
1307  // Remove buyer's offer.
-
1308  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1173  // The buy offer must not have expired.
+
1174  env(token::acceptBuyOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
+
1175  env.close();
+
1176  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1177 
+
1178  // The sell offer must be non-zero.
+
1179  env(token::acceptSellOffer(buyer, beast::zero),
+
1180  ter(tecOBJECT_NOT_FOUND));
+
1181  env.close();
+
1182  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1183 
+
1184  // The sell offer must be present in the ledger.
+
1185  env(token::acceptSellOffer(buyer, missingOfferIndex),
+
1186  ter(tecOBJECT_NOT_FOUND));
+
1187  env.close();
+
1188  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1189 
+
1190  // The sell offer must not have expired.
+
1191  env(token::acceptSellOffer(buyer, aliceExpOfferIndex), ter(tecEXPIRED));
+
1192  env.close();
+
1193  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1194 
+
1195  //----------------------------------------------------------------------
+
1196  // preclaim brokered
+
1197 
+
1198  // alice and buyer need trustlines before buyer can to create an
+
1199  // offer for gwAUD.
+
1200  env(trust(alice, gwAUD(1000)));
+
1201  env(trust(buyer, gwAUD(1000)));
+
1202  env.close();
+
1203  env(pay(gw, buyer, gwAUD(30)));
+
1204  env.close();
+
1205  BEAST_EXPECT(ownerCount(env, alice) == 7);
+
1206  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1207 
+
1208  // We're about to exercise offer brokering, so we need
+
1209  // corresponding buy and sell offers.
+
1210  {
+
1211  // buyer creates a buy offer for one of alice's nfts.
+
1212  uint256 const buyerOfferIndex =
+
1213  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
1214  env(token::createOffer(buyer, nftAlice0ID, gwAUD(29)),
+
1215  token::owner(alice));
+
1216  env.close();
+
1217  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1218 
+
1219  // gw attempts to broker offers that are not for the same token.
+
1220  env(token::brokerOffers(gw, buyerOfferIndex, xrpOnlyOfferIndex),
+
1221  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
+
1222  env.close();
+
1223  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1224 
+
1225  // gw attempts to broker offers that are not for the same currency.
+
1226  env(token::brokerOffers(gw, buyerOfferIndex, plainOfferIndex),
+
1227  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
+
1228  env.close();
+
1229  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1230 
+
1231  // In a brokered offer, the buyer must offer greater than or
+
1232  // equal to the selling price.
+
1233  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
+
1234  ter(tecINSUFFICIENT_PAYMENT));
+
1235  env.close();
+
1236  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1237 
+
1238  // Remove buyer's offer.
+
1239  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1240  env.close();
+
1241  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1242  }
+
1243  {
+
1244  // buyer creates a buy offer for one of alice's nfts.
+
1245  uint256 const buyerOfferIndex =
+
1246  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
1247  env(token::createOffer(buyer, nftAlice0ID, gwAUD(31)),
+
1248  token::owner(alice));
+
1249  env.close();
+
1250  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1251 
+
1252  // Broker sets their fee in a denomination other than the one
+
1253  // used by the offers
+
1254  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
+
1255  token::brokerFee(XRP(40)),
+
1256  ter(tecNFTOKEN_BUY_SELL_MISMATCH));
+
1257  env.close();
+
1258  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1259 
+
1260  // Broker fee way too big.
+
1261  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
+
1262  token::brokerFee(gwAUD(31)),
+
1263  ter(tecINSUFFICIENT_PAYMENT));
+
1264  env.close();
+
1265  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1266 
+
1267  // Broker fee is smaller, but still too big once the offer
+
1268  // seller's minimum is taken into account.
+
1269  env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
+
1270  token::brokerFee(gwAUD(1.5)),
+
1271  ter(tecINSUFFICIENT_PAYMENT));
+
1272  env.close();
+
1273  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1274 
+
1275  // Remove buyer's offer.
+
1276  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1277  env.close();
+
1278  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1279  }
+
1280  //----------------------------------------------------------------------
+
1281  // preclaim buy
+
1282  {
+
1283  // buyer creates a buy offer for one of alice's nfts.
+
1284  uint256 const buyerOfferIndex =
+
1285  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
1286  env(token::createOffer(buyer, nftAlice0ID, gwAUD(30)),
+
1287  token::owner(alice));
+
1288  env.close();
+
1289  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1290 
+
1291  // Don't accept a buy offer if the sell flag is set.
+
1292  env(token::acceptBuyOffer(buyer, plainOfferIndex),
+
1293  ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
+
1294  env.close();
+
1295  BEAST_EXPECT(ownerCount(env, alice) == 7);
+
1296 
+
1297  // An account can't accept its own offer.
+
1298  env(token::acceptBuyOffer(buyer, buyerOfferIndex),
+
1299  ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
+
1300  env.close();
+
1301  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1302 
+
1303  // An offer acceptor must have enough funds to pay for the offer.
+
1304  env(pay(buyer, gw, gwAUD(30)));
+
1305  env.close();
+
1306  BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
+
1307  env(token::acceptBuyOffer(alice, buyerOfferIndex),
+
1308  ter(tecINSUFFICIENT_FUNDS));
1309  env.close();
-
1310  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1311  }
-
1312  //----------------------------------------------------------------------
-
1313  // preclaim sell
-
1314  {
-
1315  // buyer creates a buy offer for one of alice's nfts.
-
1316  uint256 const buyerOfferIndex =
-
1317  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
1318  env(token::createOffer(buyer, nftXrpOnlyID, XRP(30)),
-
1319  token::owner(alice));
-
1320  env.close();
-
1321  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1322 
-
1323  // Don't accept a sell offer without the sell flag set.
-
1324  env(token::acceptSellOffer(alice, buyerOfferIndex),
-
1325  ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
-
1326  env.close();
-
1327  BEAST_EXPECT(ownerCount(env, alice) == 7);
-
1328 
-
1329  // An account can't accept its own offer.
-
1330  env(token::acceptSellOffer(alice, plainOfferIndex),
-
1331  ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
-
1332  env.close();
-
1333  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1334 
-
1335  // The seller must currently be in possession of the token they
-
1336  // are selling. alice gave nftAlice0ID to gw.
-
1337  env(token::acceptSellOffer(buyer, plainOfferIndex),
-
1338  ter(tecNO_PERMISSION));
-
1339  env.close();
-
1340  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1341 
-
1342  // gw gives nftAlice0ID back to alice. That allows us to check
-
1343  // buyer attempting to accept one of alice's offers with
-
1344  // insufficient funds.
-
1345  {
-
1346  uint256 const offerIndex =
-
1347  keylet::nftoffer(gw, env.seq(gw)).key;
-
1348  env(token::createOffer(gw, nftAlice0ID, XRP(0)),
-
1349  txflags(tfSellNFToken));
-
1350  env.close();
-
1351  env(token::acceptSellOffer(alice, offerIndex));
-
1352  env.close();
-
1353  BEAST_EXPECT(ownerCount(env, alice) == 7);
-
1354  }
-
1355  env(pay(buyer, gw, gwAUD(30)));
-
1356  env.close();
-
1357  BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
-
1358  env(token::acceptSellOffer(buyer, audOfferIndex),
-
1359  ter(tecINSUFFICIENT_FUNDS));
-
1360  env.close();
-
1361  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1362  }
-
1363 
-
1364  //----------------------------------------------------------------------
-
1365  // doApply
-
1366  //
-
1367  // As far as I can see none of the failure modes are accessible as
-
1368  // long as the preflight and preclaim conditions are met.
-
1369  }
-
1370 
-
1371  void
-
1372  testMintFlagBurnable(FeatureBitset features)
-
1373  {
-
1374  // Exercise NFTs with flagBurnable set and not set.
-
1375  testcase("Mint flagBurnable");
-
1376 
-
1377  using namespace test::jtx;
-
1378 
-
1379  Env env{*this, features};
-
1380  Account const alice{"alice"};
-
1381  Account const buyer{"buyer"};
-
1382  Account const minter1{"minter1"};
-
1383  Account const minter2{"minter2"};
-
1384 
-
1385  env.fund(XRP(1000), alice, buyer, minter1, minter2);
-
1386  env.close();
-
1387  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1310  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1311 
+
1312  // alice gives her NFT to gw, so alice no longer owns nftAlice0.
+
1313  {
+
1314  uint256 const offerIndex =
+
1315  keylet::nftoffer(alice, env.seq(alice)).key;
+
1316  env(token::createOffer(alice, nftAlice0ID, XRP(0)),
+
1317  txflags(tfSellNFToken));
+
1318  env.close();
+
1319  env(token::acceptSellOffer(gw, offerIndex));
+
1320  env.close();
+
1321  BEAST_EXPECT(ownerCount(env, alice) == 7);
+
1322  }
+
1323  env(pay(gw, buyer, gwAUD(30)));
+
1324  env.close();
+
1325 
+
1326  // alice can't accept a buy offer for an NFT she no longer owns.
+
1327  env(token::acceptBuyOffer(alice, buyerOfferIndex),
+
1328  ter(tecNO_PERMISSION));
+
1329  env.close();
+
1330  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1331 
+
1332  // Remove buyer's offer.
+
1333  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1334  env.close();
+
1335  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1336  }
+
1337  //----------------------------------------------------------------------
+
1338  // preclaim sell
+
1339  {
+
1340  // buyer creates a buy offer for one of alice's nfts.
+
1341  uint256 const buyerOfferIndex =
+
1342  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
1343  env(token::createOffer(buyer, nftXrpOnlyID, XRP(30)),
+
1344  token::owner(alice));
+
1345  env.close();
+
1346  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1347 
+
1348  // Don't accept a sell offer without the sell flag set.
+
1349  env(token::acceptSellOffer(alice, buyerOfferIndex),
+
1350  ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
+
1351  env.close();
+
1352  BEAST_EXPECT(ownerCount(env, alice) == 7);
+
1353 
+
1354  // An account can't accept its own offer.
+
1355  env(token::acceptSellOffer(alice, plainOfferIndex),
+
1356  ter(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER));
+
1357  env.close();
+
1358  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1359 
+
1360  // The seller must currently be in possession of the token they
+
1361  // are selling. alice gave nftAlice0ID to gw.
+
1362  env(token::acceptSellOffer(buyer, plainOfferIndex),
+
1363  ter(tecNO_PERMISSION));
+
1364  env.close();
+
1365  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1366 
+
1367  // gw gives nftAlice0ID back to alice. That allows us to check
+
1368  // buyer attempting to accept one of alice's offers with
+
1369  // insufficient funds.
+
1370  {
+
1371  uint256 const offerIndex =
+
1372  keylet::nftoffer(gw, env.seq(gw)).key;
+
1373  env(token::createOffer(gw, nftAlice0ID, XRP(0)),
+
1374  txflags(tfSellNFToken));
+
1375  env.close();
+
1376  env(token::acceptSellOffer(alice, offerIndex));
+
1377  env.close();
+
1378  BEAST_EXPECT(ownerCount(env, alice) == 7);
+
1379  }
+
1380  env(pay(buyer, gw, gwAUD(30)));
+
1381  env.close();
+
1382  BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
+
1383  env(token::acceptSellOffer(buyer, audOfferIndex),
+
1384  ter(tecINSUFFICIENT_FUNDS));
+
1385  env.close();
+
1386  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1387  }
1388 
-
1389  // alice selects minter as her minter.
-
1390  env(token::setMinter(alice, minter1));
-
1391  env.close();
-
1392 
-
1393  // A lambda that...
-
1394  // 1. creates an alice nft
-
1395  // 2. minted by minter and
-
1396  // 3. transfers that nft to buyer.
-
1397  auto nftToBuyer = [&env, &alice, &minter1, &buyer](
-
1398  std::uint32_t flags) {
-
1399  uint256 const nftID{token::getNextID(env, alice, 0u, flags)};
-
1400  env(token::mint(minter1, 0u), token::issuer(alice), txflags(flags));
-
1401  env.close();
-
1402 
-
1403  uint256 const offerIndex =
-
1404  keylet::nftoffer(minter1, env.seq(minter1)).key;
-
1405  env(token::createOffer(minter1, nftID, XRP(0)),
-
1406  txflags(tfSellNFToken));
-
1407  env.close();
-
1408 
-
1409  env(token::acceptSellOffer(buyer, offerIndex));
-
1410  env.close();
-
1411 
-
1412  return nftID;
-
1413  };
-
1414 
-
1415  // An NFT without flagBurnable can only be burned by its owner.
-
1416  {
-
1417  uint256 const noBurnID = nftToBuyer(0);
-
1418  env(token::burn(alice, noBurnID),
-
1419  token::owner(buyer),
-
1420  ter(tecNO_PERMISSION));
-
1421  env.close();
-
1422  env(token::burn(minter1, noBurnID),
-
1423  token::owner(buyer),
-
1424  ter(tecNO_PERMISSION));
-
1425  env.close();
-
1426  env(token::burn(minter2, noBurnID),
-
1427  token::owner(buyer),
-
1428  ter(tecNO_PERMISSION));
-
1429  env.close();
-
1430 
-
1431  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1432  env(token::burn(buyer, noBurnID), token::owner(buyer));
-
1433  env.close();
-
1434  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1435  }
-
1436  // An NFT with flagBurnable can be burned by the issuer.
-
1437  {
-
1438  uint256 const burnableID = nftToBuyer(tfBurnable);
-
1439  env(token::burn(minter2, burnableID),
-
1440  token::owner(buyer),
-
1441  ter(tecNO_PERMISSION));
-
1442  env.close();
-
1443 
-
1444  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1445  env(token::burn(alice, burnableID), token::owner(buyer));
+
1389  //----------------------------------------------------------------------
+
1390  // doApply
+
1391  //
+
1392  // As far as I can see none of the failure modes are accessible as
+
1393  // long as the preflight and preclaim conditions are met.
+
1394  }
+
1395 
+
1396  void
+
1397  testMintFlagBurnable(FeatureBitset features)
+
1398  {
+
1399  // Exercise NFTs with flagBurnable set and not set.
+
1400  testcase("Mint flagBurnable");
+
1401 
+
1402  using namespace test::jtx;
+
1403 
+
1404  Env env{*this, features};
+
1405  Account const alice{"alice"};
+
1406  Account const buyer{"buyer"};
+
1407  Account const minter1{"minter1"};
+
1408  Account const minter2{"minter2"};
+
1409 
+
1410  env.fund(XRP(1000), alice, buyer, minter1, minter2);
+
1411  env.close();
+
1412  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1413 
+
1414  // alice selects minter as her minter.
+
1415  env(token::setMinter(alice, minter1));
+
1416  env.close();
+
1417 
+
1418  // A lambda that...
+
1419  // 1. creates an alice nft
+
1420  // 2. minted by minter and
+
1421  // 3. transfers that nft to buyer.
+
1422  auto nftToBuyer = [&env, &alice, &minter1, &buyer](
+
1423  std::uint32_t flags) {
+
1424  uint256 const nftID{token::getNextID(env, alice, 0u, flags)};
+
1425  env(token::mint(minter1, 0u), token::issuer(alice), txflags(flags));
+
1426  env.close();
+
1427 
+
1428  uint256 const offerIndex =
+
1429  keylet::nftoffer(minter1, env.seq(minter1)).key;
+
1430  env(token::createOffer(minter1, nftID, XRP(0)),
+
1431  txflags(tfSellNFToken));
+
1432  env.close();
+
1433 
+
1434  env(token::acceptSellOffer(buyer, offerIndex));
+
1435  env.close();
+
1436 
+
1437  return nftID;
+
1438  };
+
1439 
+
1440  // An NFT without flagBurnable can only be burned by its owner.
+
1441  {
+
1442  uint256 const noBurnID = nftToBuyer(0);
+
1443  env(token::burn(alice, noBurnID),
+
1444  token::owner(buyer),
+
1445  ter(tecNO_PERMISSION));
1446  env.close();
-
1447  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1448  }
-
1449  // An NFT with flagBurnable can be burned by the owner.
-
1450  {
-
1451  uint256 const burnableID = nftToBuyer(tfBurnable);
-
1452  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1453  env(token::burn(buyer, burnableID));
+
1447  env(token::burn(minter1, noBurnID),
+
1448  token::owner(buyer),
+
1449  ter(tecNO_PERMISSION));
+
1450  env.close();
+
1451  env(token::burn(minter2, noBurnID),
+
1452  token::owner(buyer),
+
1453  ter(tecNO_PERMISSION));
1454  env.close();
-
1455  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1456  }
-
1457  // An NFT with flagBurnable can be burned by the minter.
-
1458  {
-
1459  uint256 const burnableID = nftToBuyer(tfBurnable);
-
1460  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1461  env(token::burn(buyer, burnableID), token::owner(buyer));
-
1462  env.close();
-
1463  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1464  }
-
1465  // An nft with flagBurnable may be burned by the issuers' minter,
-
1466  // who may not be the original minter.
-
1467  {
-
1468  uint256 const burnableID = nftToBuyer(tfBurnable);
+
1455 
+
1456  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1457  env(token::burn(buyer, noBurnID), token::owner(buyer));
+
1458  env.close();
+
1459  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1460  }
+
1461  // An NFT with flagBurnable can be burned by the issuer.
+
1462  {
+
1463  uint256 const burnableID = nftToBuyer(tfBurnable);
+
1464  env(token::burn(minter2, burnableID),
+
1465  token::owner(buyer),
+
1466  ter(tecNO_PERMISSION));
+
1467  env.close();
+
1468 
1469  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1470 
-
1471  env(token::setMinter(alice, minter2));
-
1472  env.close();
-
1473 
-
1474  // minter1 is no longer alice's minter, so no longer has
-
1475  // permisson to burn alice's nfts.
-
1476  env(token::burn(minter1, burnableID),
-
1477  token::owner(buyer),
-
1478  ter(tecNO_PERMISSION));
+
1470  env(token::burn(alice, burnableID), token::owner(buyer));
+
1471  env.close();
+
1472  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1473  }
+
1474  // An NFT with flagBurnable can be burned by the owner.
+
1475  {
+
1476  uint256 const burnableID = nftToBuyer(tfBurnable);
+
1477  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1478  env(token::burn(buyer, burnableID));
1479  env.close();
-
1480  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1481 
-
1482  // minter2, however, can burn alice's nfts.
-
1483  env(token::burn(minter2, burnableID), token::owner(buyer));
-
1484  env.close();
-
1485  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
1486  }
-
1487  }
-
1488 
-
1489  void
-
1490  testMintFlagOnlyXRP(FeatureBitset features)
-
1491  {
-
1492  // Exercise NFTs with flagOnlyXRP set and not set.
-
1493  testcase("Mint flagOnlyXRP");
-
1494 
-
1495  using namespace test::jtx;
-
1496 
-
1497  Env env{*this, features};
-
1498  Account const alice{"alice"};
-
1499  Account const buyer{"buyer"};
-
1500  Account const gw("gw");
-
1501  IOU const gwAUD(gw["AUD"]);
-
1502 
-
1503  // Set trust lines so alice and buyer can use gwAUD.
-
1504  env.fund(XRP(1000), alice, buyer, gw);
-
1505  env.close();
-
1506  env(trust(alice, gwAUD(1000)));
-
1507  env(trust(buyer, gwAUD(1000)));
-
1508  env.close();
-
1509  env(pay(gw, buyer, gwAUD(100)));
-
1510 
-
1511  // Don't set flagOnlyXRP and offers can be made with IOUs.
-
1512  {
-
1513  uint256 const nftIOUsOkayID{
-
1514  token::getNextID(env, alice, 0u, tfTransferable)};
-
1515  env(token::mint(alice, 0u), txflags(tfTransferable));
-
1516  env.close();
-
1517 
-
1518  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1519  uint256 const aliceOfferIndex =
-
1520  keylet::nftoffer(alice, env.seq(alice)).key;
-
1521  env(token::createOffer(alice, nftIOUsOkayID, gwAUD(50)),
-
1522  txflags(tfSellNFToken));
-
1523  env.close();
-
1524  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
1525 
-
1526  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1527  uint256 const buyerOfferIndex =
-
1528  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
1529  env(token::createOffer(buyer, nftIOUsOkayID, gwAUD(50)),
-
1530  token::owner(alice));
-
1531  env.close();
-
1532  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1533 
-
1534  // Cancel the two offers just to be tidy.
-
1535  env(token::cancelOffer(alice, {aliceOfferIndex}));
-
1536  env(token::cancelOffer(buyer, {buyerOfferIndex}));
-
1537  env.close();
-
1538  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1539  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1540 
-
1541  // Also burn alice's nft.
-
1542  env(token::burn(alice, nftIOUsOkayID));
-
1543  env.close();
-
1544  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1545  }
-
1546 
-
1547  // Set flagOnlyXRP and offers using IOUs are rejected.
-
1548  {
-
1549  uint256 const nftOnlyXRPID{
-
1550  token::getNextID(env, alice, 0u, tfOnlyXRP | tfTransferable)};
-
1551  env(token::mint(alice, 0u), txflags(tfOnlyXRP | tfTransferable));
-
1552  env.close();
-
1553 
-
1554  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1555  env(token::createOffer(alice, nftOnlyXRPID, gwAUD(50)),
-
1556  txflags(tfSellNFToken),
-
1557  ter(temBAD_AMOUNT));
-
1558  env.close();
-
1559  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1560 
-
1561  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1562  env(token::createOffer(buyer, nftOnlyXRPID, gwAUD(50)),
-
1563  token::owner(alice),
-
1564  ter(temBAD_AMOUNT));
-
1565  env.close();
-
1566  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1567 
-
1568  // However offers for XRP are okay.
-
1569  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1570  env(token::createOffer(alice, nftOnlyXRPID, XRP(60)),
-
1571  txflags(tfSellNFToken));
-
1572  env.close();
-
1573  BEAST_EXPECT(ownerCount(env, alice) == 3);
-
1574 
-
1575  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
1576  env(token::createOffer(buyer, nftOnlyXRPID, XRP(60)),
-
1577  token::owner(alice));
-
1578  env.close();
-
1579  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
1580  }
-
1581  }
-
1582 
-
1583  void
-
1584  testMintFlagCreateTrustLine(FeatureBitset features)
-
1585  {
-
1586  // Exercise NFTs with flagCreateTrustLines set and not set.
-
1587  testcase("Mint flagCreateTrustLines");
-
1588 
-
1589  using namespace test::jtx;
-
1590 
-
1591  Account const alice{"alice"};
-
1592  Account const becky{"becky"};
-
1593  Account const cheri{"cheri"};
-
1594  Account const gw("gw");
-
1595  IOU const gwAUD(gw["AUD"]);
-
1596  IOU const gwCAD(gw["CAD"]);
-
1597  IOU const gwEUR(gw["EUR"]);
-
1598 
-
1599  // The behavior of this test changes dramatically based on the
-
1600  // presence (or absence) of the fixRemoveNFTokenAutoTrustLine
-
1601  // amendment. So we test both cases here.
-
1602  for (auto const& tweakedFeatures :
-
1603  {features - fixRemoveNFTokenAutoTrustLine,
-
1604  features | fixRemoveNFTokenAutoTrustLine})
-
1605  {
-
1606  Env env{*this, tweakedFeatures};
-
1607  env.fund(XRP(1000), alice, becky, cheri, gw);
-
1608  env.close();
-
1609 
-
1610  // Set trust lines so becky and cheri can use gw's currency.
-
1611  env(trust(becky, gwAUD(1000)));
-
1612  env(trust(cheri, gwAUD(1000)));
-
1613  env(trust(becky, gwCAD(1000)));
-
1614  env(trust(cheri, gwCAD(1000)));
-
1615  env(trust(becky, gwEUR(1000)));
-
1616  env(trust(cheri, gwEUR(1000)));
-
1617  env.close();
-
1618  env(pay(gw, becky, gwAUD(500)));
-
1619  env(pay(gw, becky, gwCAD(500)));
-
1620  env(pay(gw, becky, gwEUR(500)));
-
1621  env(pay(gw, cheri, gwAUD(500)));
-
1622  env(pay(gw, cheri, gwCAD(500)));
-
1623  env.close();
-
1624 
-
1625  // An nft without flagCreateTrustLines but with a non-zero transfer
-
1626  // fee will not allow creating offers that use IOUs for payment.
-
1627  for (std::uint32_t xferFee : {0, 1})
-
1628  {
-
1629  uint256 const nftNoAutoTrustID{
-
1630  token::getNextID(env, alice, 0u, tfTransferable, xferFee)};
-
1631  env(token::mint(alice, 0u),
-
1632  token::xferFee(xferFee),
-
1633  txflags(tfTransferable));
-
1634  env.close();
-
1635 
-
1636  // becky buys the nft for 1 drop.
-
1637  uint256 const beckyBuyOfferIndex =
-
1638  keylet::nftoffer(becky, env.seq(becky)).key;
-
1639  env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
-
1640  token::owner(alice));
-
1641  env.close();
-
1642  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
-
1643  env.close();
-
1644 
-
1645  // becky attempts to sell the nft for AUD.
-
1646  TER const createOfferTER =
-
1647  xferFee ? TER(tecNO_LINE) : TER(tesSUCCESS);
-
1648  uint256 const beckyOfferIndex =
-
1649  keylet::nftoffer(becky, env.seq(becky)).key;
-
1650  env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
-
1651  txflags(tfSellNFToken),
-
1652  ter(createOfferTER));
-
1653  env.close();
-
1654 
-
1655  // cheri offers to buy the nft for CAD.
-
1656  uint256 const cheriOfferIndex =
-
1657  keylet::nftoffer(cheri, env.seq(cheri)).key;
-
1658  env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
-
1659  token::owner(becky),
-
1660  ter(createOfferTER));
-
1661  env.close();
-
1662 
-
1663  // To keep things tidy, cancel the offers.
-
1664  env(token::cancelOffer(becky, {beckyOfferIndex}));
-
1665  env(token::cancelOffer(cheri, {cheriOfferIndex}));
+
1480  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1481  }
+
1482  // An NFT with flagBurnable can be burned by the minter.
+
1483  {
+
1484  uint256 const burnableID = nftToBuyer(tfBurnable);
+
1485  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1486  env(token::burn(buyer, burnableID), token::owner(buyer));
+
1487  env.close();
+
1488  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1489  }
+
1490  // An nft with flagBurnable may be burned by the issuers' minter,
+
1491  // who may not be the original minter.
+
1492  {
+
1493  uint256 const burnableID = nftToBuyer(tfBurnable);
+
1494  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1495 
+
1496  env(token::setMinter(alice, minter2));
+
1497  env.close();
+
1498 
+
1499  // minter1 is no longer alice's minter, so no longer has
+
1500  // permisson to burn alice's nfts.
+
1501  env(token::burn(minter1, burnableID),
+
1502  token::owner(buyer),
+
1503  ter(tecNO_PERMISSION));
+
1504  env.close();
+
1505  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1506 
+
1507  // minter2, however, can burn alice's nfts.
+
1508  env(token::burn(minter2, burnableID), token::owner(buyer));
+
1509  env.close();
+
1510  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
1511  }
+
1512  }
+
1513 
+
1514  void
+
1515  testMintFlagOnlyXRP(FeatureBitset features)
+
1516  {
+
1517  // Exercise NFTs with flagOnlyXRP set and not set.
+
1518  testcase("Mint flagOnlyXRP");
+
1519 
+
1520  using namespace test::jtx;
+
1521 
+
1522  Env env{*this, features};
+
1523  Account const alice{"alice"};
+
1524  Account const buyer{"buyer"};
+
1525  Account const gw("gw");
+
1526  IOU const gwAUD(gw["AUD"]);
+
1527 
+
1528  // Set trust lines so alice and buyer can use gwAUD.
+
1529  env.fund(XRP(1000), alice, buyer, gw);
+
1530  env.close();
+
1531  env(trust(alice, gwAUD(1000)));
+
1532  env(trust(buyer, gwAUD(1000)));
+
1533  env.close();
+
1534  env(pay(gw, buyer, gwAUD(100)));
+
1535 
+
1536  // Don't set flagOnlyXRP and offers can be made with IOUs.
+
1537  {
+
1538  uint256 const nftIOUsOkayID{
+
1539  token::getNextID(env, alice, 0u, tfTransferable)};
+
1540  env(token::mint(alice, 0u), txflags(tfTransferable));
+
1541  env.close();
+
1542 
+
1543  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1544  uint256 const aliceOfferIndex =
+
1545  keylet::nftoffer(alice, env.seq(alice)).key;
+
1546  env(token::createOffer(alice, nftIOUsOkayID, gwAUD(50)),
+
1547  txflags(tfSellNFToken));
+
1548  env.close();
+
1549  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
1550 
+
1551  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1552  uint256 const buyerOfferIndex =
+
1553  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
1554  env(token::createOffer(buyer, nftIOUsOkayID, gwAUD(50)),
+
1555  token::owner(alice));
+
1556  env.close();
+
1557  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1558 
+
1559  // Cancel the two offers just to be tidy.
+
1560  env(token::cancelOffer(alice, {aliceOfferIndex}));
+
1561  env(token::cancelOffer(buyer, {buyerOfferIndex}));
+
1562  env.close();
+
1563  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1564  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1565 
+
1566  // Also burn alice's nft.
+
1567  env(token::burn(alice, nftIOUsOkayID));
+
1568  env.close();
+
1569  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1570  }
+
1571 
+
1572  // Set flagOnlyXRP and offers using IOUs are rejected.
+
1573  {
+
1574  uint256 const nftOnlyXRPID{
+
1575  token::getNextID(env, alice, 0u, tfOnlyXRP | tfTransferable)};
+
1576  env(token::mint(alice, 0u), txflags(tfOnlyXRP | tfTransferable));
+
1577  env.close();
+
1578 
+
1579  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1580  env(token::createOffer(alice, nftOnlyXRPID, gwAUD(50)),
+
1581  txflags(tfSellNFToken),
+
1582  ter(temBAD_AMOUNT));
+
1583  env.close();
+
1584  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1585 
+
1586  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1587  env(token::createOffer(buyer, nftOnlyXRPID, gwAUD(50)),
+
1588  token::owner(alice),
+
1589  ter(temBAD_AMOUNT));
+
1590  env.close();
+
1591  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1592 
+
1593  // However offers for XRP are okay.
+
1594  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
1595  env(token::createOffer(alice, nftOnlyXRPID, XRP(60)),
+
1596  txflags(tfSellNFToken));
+
1597  env.close();
+
1598  BEAST_EXPECT(ownerCount(env, alice) == 3);
+
1599 
+
1600  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
1601  env(token::createOffer(buyer, nftOnlyXRPID, XRP(60)),
+
1602  token::owner(alice));
+
1603  env.close();
+
1604  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
1605  }
+
1606  }
+
1607 
+
1608  void
+
1609  testMintFlagCreateTrustLine(FeatureBitset features)
+
1610  {
+
1611  // Exercise NFTs with flagCreateTrustLines set and not set.
+
1612  testcase("Mint flagCreateTrustLines");
+
1613 
+
1614  using namespace test::jtx;
+
1615 
+
1616  Account const alice{"alice"};
+
1617  Account const becky{"becky"};
+
1618  Account const cheri{"cheri"};
+
1619  Account const gw("gw");
+
1620  IOU const gwAUD(gw["AUD"]);
+
1621  IOU const gwCAD(gw["CAD"]);
+
1622  IOU const gwEUR(gw["EUR"]);
+
1623 
+
1624  // The behavior of this test changes dramatically based on the
+
1625  // presence (or absence) of the fixRemoveNFTokenAutoTrustLine
+
1626  // amendment. So we test both cases here.
+
1627  for (auto const& tweakedFeatures :
+
1628  {features - fixRemoveNFTokenAutoTrustLine,
+
1629  features | fixRemoveNFTokenAutoTrustLine})
+
1630  {
+
1631  Env env{*this, tweakedFeatures};
+
1632  env.fund(XRP(1000), alice, becky, cheri, gw);
+
1633  env.close();
+
1634 
+
1635  // Set trust lines so becky and cheri can use gw's currency.
+
1636  env(trust(becky, gwAUD(1000)));
+
1637  env(trust(cheri, gwAUD(1000)));
+
1638  env(trust(becky, gwCAD(1000)));
+
1639  env(trust(cheri, gwCAD(1000)));
+
1640  env(trust(becky, gwEUR(1000)));
+
1641  env(trust(cheri, gwEUR(1000)));
+
1642  env.close();
+
1643  env(pay(gw, becky, gwAUD(500)));
+
1644  env(pay(gw, becky, gwCAD(500)));
+
1645  env(pay(gw, becky, gwEUR(500)));
+
1646  env(pay(gw, cheri, gwAUD(500)));
+
1647  env(pay(gw, cheri, gwCAD(500)));
+
1648  env.close();
+
1649 
+
1650  // An nft without flagCreateTrustLines but with a non-zero transfer
+
1651  // fee will not allow creating offers that use IOUs for payment.
+
1652  for (std::uint32_t xferFee : {0, 1})
+
1653  {
+
1654  uint256 const nftNoAutoTrustID{
+
1655  token::getNextID(env, alice, 0u, tfTransferable, xferFee)};
+
1656  env(token::mint(alice, 0u),
+
1657  token::xferFee(xferFee),
+
1658  txflags(tfTransferable));
+
1659  env.close();
+
1660 
+
1661  // becky buys the nft for 1 drop.
+
1662  uint256 const beckyBuyOfferIndex =
+
1663  keylet::nftoffer(becky, env.seq(becky)).key;
+
1664  env(token::createOffer(becky, nftNoAutoTrustID, drops(1)),
+
1665  token::owner(alice));
1666  env.close();
-
1667  }
-
1668  // An nft with flagCreateTrustLines but with a non-zero transfer
-
1669  // fee allows transfers using IOUs for payment.
-
1670  {
-
1671  std::uint16_t transferFee = 10000; // 10%
-
1672 
-
1673  uint256 const nftAutoTrustID{token::getNextID(
-
1674  env, alice, 0u, tfTransferable | tfTrustLine, transferFee)};
-
1675 
-
1676  // If the fixRemoveNFTokenAutoTrustLine amendment is active
-
1677  // then this transaction fails.
-
1678  {
-
1679  TER const mintTER =
-
1680  tweakedFeatures[fixRemoveNFTokenAutoTrustLine]
-
1681  ? static_cast<TER>(temINVALID_FLAG)
-
1682  : static_cast<TER>(tesSUCCESS);
-
1683 
-
1684  env(token::mint(alice, 0u),
-
1685  token::xferFee(transferFee),
-
1686  txflags(tfTransferable | tfTrustLine),
-
1687  ter(mintTER));
-
1688  env.close();
-
1689 
-
1690  // If fixRemoveNFTokenAutoTrustLine is active the rest
-
1691  // of this test falls on its face.
-
1692  if (tweakedFeatures[fixRemoveNFTokenAutoTrustLine])
-
1693  break;
-
1694  }
-
1695  // becky buys the nft for 1 drop.
-
1696  uint256 const beckyBuyOfferIndex =
-
1697  keylet::nftoffer(becky, env.seq(becky)).key;
-
1698  env(token::createOffer(becky, nftAutoTrustID, drops(1)),
-
1699  token::owner(alice));
-
1700  env.close();
-
1701  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
-
1702  env.close();
-
1703 
-
1704  // becky sells the nft for AUD.
-
1705  uint256 const beckySellOfferIndex =
-
1706  keylet::nftoffer(becky, env.seq(becky)).key;
-
1707  env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
-
1708  txflags(tfSellNFToken));
-
1709  env.close();
-
1710  env(token::acceptSellOffer(cheri, beckySellOfferIndex));
-
1711  env.close();
-
1712 
-
1713  // alice should now have a trust line for gwAUD.
-
1714  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
-
1715 
-
1716  // becky buys the nft back for CAD.
-
1717  uint256 const beckyBuyBackOfferIndex =
-
1718  keylet::nftoffer(becky, env.seq(becky)).key;
-
1719  env(token::createOffer(becky, nftAutoTrustID, gwCAD(50)),
-
1720  token::owner(cheri));
-
1721  env.close();
-
1722  env(token::acceptBuyOffer(cheri, beckyBuyBackOfferIndex));
-
1723  env.close();
-
1724 
-
1725  // alice should now have a trust line for gwAUD and gwCAD.
-
1726  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
-
1727  BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(5));
-
1728  }
-
1729  // Now that alice has trust lines preestablished, an nft without
-
1730  // flagCreateTrustLines will work for preestablished trust lines.
-
1731  {
-
1732  std::uint16_t transferFee = 5000; // 5%
-
1733  uint256 const nftNoAutoTrustID{token::getNextID(
-
1734  env, alice, 0u, tfTransferable, transferFee)};
-
1735  env(token::mint(alice, 0u),
-
1736  token::xferFee(transferFee),
-
1737  txflags(tfTransferable));
-
1738  env.close();
-
1739 
-
1740  // alice sells the nft using AUD.
-
1741  uint256 const aliceSellOfferIndex =
-
1742  keylet::nftoffer(alice, env.seq(alice)).key;
-
1743  env(token::createOffer(alice, nftNoAutoTrustID, gwAUD(200)),
-
1744  txflags(tfSellNFToken));
-
1745  env.close();
-
1746  env(token::acceptSellOffer(cheri, aliceSellOfferIndex));
-
1747  env.close();
-
1748 
-
1749  // alice should now have AUD(210):
-
1750  // o 200 for this sale and
-
1751  // o 10 for the previous sale's fee.
-
1752  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(210));
-
1753 
-
1754  // cheri can't sell the NFT for EUR, but can for CAD.
-
1755  env(token::createOffer(cheri, nftNoAutoTrustID, gwEUR(50)),
-
1756  txflags(tfSellNFToken),
-
1757  ter(tecNO_LINE));
-
1758  env.close();
-
1759  uint256 const cheriSellOfferIndex =
-
1760  keylet::nftoffer(cheri, env.seq(cheri)).key;
-
1761  env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
-
1762  txflags(tfSellNFToken));
+
1667  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
1668  env.close();
+
1669 
+
1670  // becky attempts to sell the nft for AUD.
+
1671  TER const createOfferTER =
+
1672  xferFee ? TER(tecNO_LINE) : TER(tesSUCCESS);
+
1673  uint256 const beckyOfferIndex =
+
1674  keylet::nftoffer(becky, env.seq(becky)).key;
+
1675  env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
+
1676  txflags(tfSellNFToken),
+
1677  ter(createOfferTER));
+
1678  env.close();
+
1679 
+
1680  // cheri offers to buy the nft for CAD.
+
1681  uint256 const cheriOfferIndex =
+
1682  keylet::nftoffer(cheri, env.seq(cheri)).key;
+
1683  env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
+
1684  token::owner(becky),
+
1685  ter(createOfferTER));
+
1686  env.close();
+
1687 
+
1688  // To keep things tidy, cancel the offers.
+
1689  env(token::cancelOffer(becky, {beckyOfferIndex}));
+
1690  env(token::cancelOffer(cheri, {cheriOfferIndex}));
+
1691  env.close();
+
1692  }
+
1693  // An nft with flagCreateTrustLines but with a non-zero transfer
+
1694  // fee allows transfers using IOUs for payment.
+
1695  {
+
1696  std::uint16_t transferFee = 10000; // 10%
+
1697 
+
1698  uint256 const nftAutoTrustID{token::getNextID(
+
1699  env, alice, 0u, tfTransferable | tfTrustLine, transferFee)};
+
1700 
+
1701  // If the fixRemoveNFTokenAutoTrustLine amendment is active
+
1702  // then this transaction fails.
+
1703  {
+
1704  TER const mintTER =
+
1705  tweakedFeatures[fixRemoveNFTokenAutoTrustLine]
+
1706  ? static_cast<TER>(temINVALID_FLAG)
+
1707  : static_cast<TER>(tesSUCCESS);
+
1708 
+
1709  env(token::mint(alice, 0u),
+
1710  token::xferFee(transferFee),
+
1711  txflags(tfTransferable | tfTrustLine),
+
1712  ter(mintTER));
+
1713  env.close();
+
1714 
+
1715  // If fixRemoveNFTokenAutoTrustLine is active the rest
+
1716  // of this test falls on its face.
+
1717  if (tweakedFeatures[fixRemoveNFTokenAutoTrustLine])
+
1718  break;
+
1719  }
+
1720  // becky buys the nft for 1 drop.
+
1721  uint256 const beckyBuyOfferIndex =
+
1722  keylet::nftoffer(becky, env.seq(becky)).key;
+
1723  env(token::createOffer(becky, nftAutoTrustID, drops(1)),
+
1724  token::owner(alice));
+
1725  env.close();
+
1726  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
1727  env.close();
+
1728 
+
1729  // becky sells the nft for AUD.
+
1730  uint256 const beckySellOfferIndex =
+
1731  keylet::nftoffer(becky, env.seq(becky)).key;
+
1732  env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)),
+
1733  txflags(tfSellNFToken));
+
1734  env.close();
+
1735  env(token::acceptSellOffer(cheri, beckySellOfferIndex));
+
1736  env.close();
+
1737 
+
1738  // alice should now have a trust line for gwAUD.
+
1739  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
+
1740 
+
1741  // becky buys the nft back for CAD.
+
1742  uint256 const beckyBuyBackOfferIndex =
+
1743  keylet::nftoffer(becky, env.seq(becky)).key;
+
1744  env(token::createOffer(becky, nftAutoTrustID, gwCAD(50)),
+
1745  token::owner(cheri));
+
1746  env.close();
+
1747  env(token::acceptBuyOffer(cheri, beckyBuyBackOfferIndex));
+
1748  env.close();
+
1749 
+
1750  // alice should now have a trust line for gwAUD and gwCAD.
+
1751  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
+
1752  BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(5));
+
1753  }
+
1754  // Now that alice has trust lines preestablished, an nft without
+
1755  // flagCreateTrustLines will work for preestablished trust lines.
+
1756  {
+
1757  std::uint16_t transferFee = 5000; // 5%
+
1758  uint256 const nftNoAutoTrustID{token::getNextID(
+
1759  env, alice, 0u, tfTransferable, transferFee)};
+
1760  env(token::mint(alice, 0u),
+
1761  token::xferFee(transferFee),
+
1762  txflags(tfTransferable));
1763  env.close();
-
1764  env(token::acceptSellOffer(becky, cheriSellOfferIndex));
-
1765  env.close();
-
1766 
-
1767  // alice should now have CAD(10):
-
1768  // o 5 from this sale's fee and
-
1769  // o 5 for the previous sale's fee.
-
1770  BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(10));
-
1771  }
-
1772  }
-
1773  }
-
1774 
-
1775  void
-
1776  testMintFlagTransferable(FeatureBitset features)
-
1777  {
-
1778  // Exercise NFTs with flagTransferable set and not set.
-
1779  testcase("Mint flagTransferable");
-
1780 
-
1781  using namespace test::jtx;
-
1782 
-
1783  Env env{*this, features};
-
1784 
-
1785  Account const alice{"alice"};
-
1786  Account const becky{"becky"};
-
1787  Account const minter{"minter"};
-
1788 
-
1789  env.fund(XRP(1000), alice, becky, minter);
-
1790  env.close();
+
1764 
+
1765  // alice sells the nft using AUD.
+
1766  uint256 const aliceSellOfferIndex =
+
1767  keylet::nftoffer(alice, env.seq(alice)).key;
+
1768  env(token::createOffer(alice, nftNoAutoTrustID, gwAUD(200)),
+
1769  txflags(tfSellNFToken));
+
1770  env.close();
+
1771  env(token::acceptSellOffer(cheri, aliceSellOfferIndex));
+
1772  env.close();
+
1773 
+
1774  // alice should now have AUD(210):
+
1775  // o 200 for this sale and
+
1776  // o 10 for the previous sale's fee.
+
1777  BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(210));
+
1778 
+
1779  // cheri can't sell the NFT for EUR, but can for CAD.
+
1780  env(token::createOffer(cheri, nftNoAutoTrustID, gwEUR(50)),
+
1781  txflags(tfSellNFToken),
+
1782  ter(tecNO_LINE));
+
1783  env.close();
+
1784  uint256 const cheriSellOfferIndex =
+
1785  keylet::nftoffer(cheri, env.seq(cheri)).key;
+
1786  env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
+
1787  txflags(tfSellNFToken));
+
1788  env.close();
+
1789  env(token::acceptSellOffer(becky, cheriSellOfferIndex));
+
1790  env.close();
1791 
-
1792  // First try an nft made by alice without flagTransferable set.
-
1793  {
-
1794  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1795  uint256 const nftAliceNoTransferID{
-
1796  token::getNextID(env, alice, 0u)};
-
1797  env(token::mint(alice, 0u), token::xferFee(0));
-
1798  env.close();
-
1799  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1800 
-
1801  // becky tries to offer to buy alice's nft.
-
1802  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1803  env(token::createOffer(becky, nftAliceNoTransferID, XRP(20)),
-
1804  token::owner(alice),
-
1805  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1806 
-
1807  // alice offers to sell the nft and becky accepts the offer.
-
1808  uint256 const aliceSellOfferIndex =
-
1809  keylet::nftoffer(alice, env.seq(alice)).key;
-
1810  env(token::createOffer(alice, nftAliceNoTransferID, XRP(20)),
-
1811  txflags(tfSellNFToken));
-
1812  env.close();
-
1813  env(token::acceptSellOffer(becky, aliceSellOfferIndex));
-
1814  env.close();
-
1815  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1816  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
1817 
-
1818  // becky tries to offer the nft for sale.
-
1819  env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
-
1820  txflags(tfSellNFToken),
-
1821  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1822  env.close();
-
1823  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1824  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1792  // alice should now have CAD(10):
+
1793  // o 5 from this sale's fee and
+
1794  // o 5 for the previous sale's fee.
+
1795  BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(10));
+
1796  }
+
1797  }
+
1798  }
+
1799 
+
1800  void
+
1801  testMintFlagTransferable(FeatureBitset features)
+
1802  {
+
1803  // Exercise NFTs with flagTransferable set and not set.
+
1804  testcase("Mint flagTransferable");
+
1805 
+
1806  using namespace test::jtx;
+
1807 
+
1808  Env env{*this, features};
+
1809 
+
1810  Account const alice{"alice"};
+
1811  Account const becky{"becky"};
+
1812  Account const minter{"minter"};
+
1813 
+
1814  env.fund(XRP(1000), alice, becky, minter);
+
1815  env.close();
+
1816 
+
1817  // First try an nft made by alice without flagTransferable set.
+
1818  {
+
1819  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1820  uint256 const nftAliceNoTransferID{
+
1821  token::getNextID(env, alice, 0u)};
+
1822  env(token::mint(alice, 0u), token::xferFee(0));
+
1823  env.close();
+
1824  BEAST_EXPECT(ownerCount(env, alice) == 1);
1825 
-
1826  // becky tries to offer the nft for sale with alice as the
-
1827  // destination. That also doesn't work.
-
1828  env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
-
1829  txflags(tfSellNFToken),
-
1830  token::destination(alice),
-
1831  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1832  env.close();
-
1833  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1834  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
1835 
-
1836  // alice offers to buy the nft back from becky. becky accepts
-
1837  // the offer.
-
1838  uint256 const aliceBuyOfferIndex =
-
1839  keylet::nftoffer(alice, env.seq(alice)).key;
-
1840  env(token::createOffer(alice, nftAliceNoTransferID, XRP(22)),
-
1841  token::owner(becky));
-
1842  env.close();
-
1843  env(token::acceptBuyOffer(becky, aliceBuyOfferIndex));
-
1844  env.close();
-
1845  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1846  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1847 
-
1848  // alice burns her nft so accounting is simpler below.
-
1849  env(token::burn(alice, nftAliceNoTransferID));
-
1850  env.close();
-
1851  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1852  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1853  }
-
1854  // Try an nft minted by minter for alice without flagTransferable set.
-
1855  {
-
1856  env(token::setMinter(alice, minter));
+
1826  // becky tries to offer to buy alice's nft.
+
1827  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1828  env(token::createOffer(becky, nftAliceNoTransferID, XRP(20)),
+
1829  token::owner(alice),
+
1830  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1831 
+
1832  // alice offers to sell the nft and becky accepts the offer.
+
1833  uint256 const aliceSellOfferIndex =
+
1834  keylet::nftoffer(alice, env.seq(alice)).key;
+
1835  env(token::createOffer(alice, nftAliceNoTransferID, XRP(20)),
+
1836  txflags(tfSellNFToken));
+
1837  env.close();
+
1838  env(token::acceptSellOffer(becky, aliceSellOfferIndex));
+
1839  env.close();
+
1840  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1841  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1842 
+
1843  // becky tries to offer the nft for sale.
+
1844  env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
+
1845  txflags(tfSellNFToken),
+
1846  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1847  env.close();
+
1848  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1849  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1850 
+
1851  // becky tries to offer the nft for sale with alice as the
+
1852  // destination. That also doesn't work.
+
1853  env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
+
1854  txflags(tfSellNFToken),
+
1855  token::destination(alice),
+
1856  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
1857  env.close();
-
1858 
-
1859  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1860  uint256 const nftMinterNoTransferID{
-
1861  token::getNextID(env, alice, 0u)};
-
1862  env(token::mint(minter), token::issuer(alice));
-
1863  env.close();
-
1864  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1865 
-
1866  // becky tries to offer to buy minter's nft.
-
1867  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1868  env(token::createOffer(becky, nftMinterNoTransferID, XRP(20)),
-
1869  token::owner(minter),
-
1870  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1871  env.close();
-
1872  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1873 
-
1874  // alice removes authorization of minter.
-
1875  env(token::clearMinter(alice));
-
1876  env.close();
-
1877 
-
1878  // minter tries to offer their nft for sale.
-
1879  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1880  env(token::createOffer(minter, nftMinterNoTransferID, XRP(21)),
-
1881  txflags(tfSellNFToken),
-
1882  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1883  env.close();
-
1884  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1885 
-
1886  // Let enough ledgers pass that old transactions are no longer
-
1887  // retried, then alice gives authorization back to minter.
-
1888  for (int i = 0; i < 10; ++i)
-
1889  env.close();
+
1858  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1859  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1860 
+
1861  // alice offers to buy the nft back from becky. becky accepts
+
1862  // the offer.
+
1863  uint256 const aliceBuyOfferIndex =
+
1864  keylet::nftoffer(alice, env.seq(alice)).key;
+
1865  env(token::createOffer(alice, nftAliceNoTransferID, XRP(22)),
+
1866  token::owner(becky));
+
1867  env.close();
+
1868  env(token::acceptBuyOffer(becky, aliceBuyOfferIndex));
+
1869  env.close();
+
1870  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1871  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1872 
+
1873  // alice burns her nft so accounting is simpler below.
+
1874  env(token::burn(alice, nftAliceNoTransferID));
+
1875  env.close();
+
1876  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1877  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1878  }
+
1879  // Try an nft minted by minter for alice without flagTransferable set.
+
1880  {
+
1881  env(token::setMinter(alice, minter));
+
1882  env.close();
+
1883 
+
1884  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
1885  uint256 const nftMinterNoTransferID{
+
1886  token::getNextID(env, alice, 0u)};
+
1887  env(token::mint(minter), token::issuer(alice));
+
1888  env.close();
+
1889  BEAST_EXPECT(ownerCount(env, minter) == 1);
1890 
-
1891  env(token::setMinter(alice, minter));
-
1892  env.close();
-
1893  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1894 
-
1895  // minter successfully offers their nft for sale.
-
1896  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1897  uint256 const minterSellOfferIndex =
-
1898  keylet::nftoffer(minter, env.seq(minter)).key;
-
1899  env(token::createOffer(minter, nftMinterNoTransferID, XRP(22)),
-
1900  txflags(tfSellNFToken));
+
1891  // becky tries to offer to buy minter's nft.
+
1892  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1893  env(token::createOffer(becky, nftMinterNoTransferID, XRP(20)),
+
1894  token::owner(minter),
+
1895  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1896  env.close();
+
1897  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1898 
+
1899  // alice removes authorization of minter.
+
1900  env(token::clearMinter(alice));
1901  env.close();
-
1902  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
1903 
-
1904  // alice removes authorization of minter so we can see whether
-
1905  // minter's pre-existing offer still works.
-
1906  env(token::clearMinter(alice));
-
1907  env.close();
-
1908 
-
1909  // becky buys minter's nft even though minter is no longer alice's
-
1910  // official minter.
-
1911  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1912  env(token::acceptSellOffer(becky, minterSellOfferIndex));
-
1913  env.close();
-
1914  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
1915  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1916 
-
1917  // becky attempts to sell the nft.
-
1918  env(token::createOffer(becky, nftMinterNoTransferID, XRP(23)),
-
1919  txflags(tfSellNFToken),
-
1920  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1921  env.close();
-
1922 
-
1923  // Since minter is not, at the moment, alice's official minter
-
1924  // they cannot create an offer to buy the nft they minted.
-
1925  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1926  env(token::createOffer(minter, nftMinterNoTransferID, XRP(24)),
-
1927  token::owner(becky),
-
1928  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
-
1929  env.close();
-
1930  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1931 
-
1932  // alice can create an offer to buy the nft.
-
1933  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1934  uint256 const aliceBuyOfferIndex =
-
1935  keylet::nftoffer(alice, env.seq(alice)).key;
-
1936  env(token::createOffer(alice, nftMinterNoTransferID, XRP(25)),
-
1937  token::owner(becky));
+
1902 
+
1903  // minter tries to offer their nft for sale.
+
1904  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1905  env(token::createOffer(minter, nftMinterNoTransferID, XRP(21)),
+
1906  txflags(tfSellNFToken),
+
1907  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1908  env.close();
+
1909  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1910 
+
1911  // Let enough ledgers pass that old transactions are no longer
+
1912  // retried, then alice gives authorization back to minter.
+
1913  for (int i = 0; i < 10; ++i)
+
1914  env.close();
+
1915 
+
1916  env(token::setMinter(alice, minter));
+
1917  env.close();
+
1918  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1919 
+
1920  // minter successfully offers their nft for sale.
+
1921  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1922  uint256 const minterSellOfferIndex =
+
1923  keylet::nftoffer(minter, env.seq(minter)).key;
+
1924  env(token::createOffer(minter, nftMinterNoTransferID, XRP(22)),
+
1925  txflags(tfSellNFToken));
+
1926  env.close();
+
1927  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
1928 
+
1929  // alice removes authorization of minter so we can see whether
+
1930  // minter's pre-existing offer still works.
+
1931  env(token::clearMinter(alice));
+
1932  env.close();
+
1933 
+
1934  // becky buys minter's nft even though minter is no longer alice's
+
1935  // official minter.
+
1936  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1937  env(token::acceptSellOffer(becky, minterSellOfferIndex));
1938  env.close();
-
1939  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1940 
-
1941  // Let enough ledgers pass that old transactions are no longer
-
1942  // retried, then alice gives authorization back to minter.
-
1943  for (int i = 0; i < 10; ++i)
-
1944  env.close();
-
1945 
-
1946  env(token::setMinter(alice, minter));
-
1947  env.close();
-
1948 
-
1949  // Now minter can create an offer to buy the nft.
+
1939  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1940  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
1941 
+
1942  // becky attempts to sell the nft.
+
1943  env(token::createOffer(becky, nftMinterNoTransferID, XRP(23)),
+
1944  txflags(tfSellNFToken),
+
1945  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1946  env.close();
+
1947 
+
1948  // Since minter is not, at the moment, alice's official minter
+
1949  // they cannot create an offer to buy the nft they minted.
1950  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1951  uint256 const minterBuyOfferIndex =
-
1952  keylet::nftoffer(minter, env.seq(minter)).key;
-
1953  env(token::createOffer(minter, nftMinterNoTransferID, XRP(26)),
-
1954  token::owner(becky));
-
1955  env.close();
-
1956  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1957 
-
1958  // alice removes authorization of minter so we can see whether
-
1959  // minter's pre-existing buy offer still works.
-
1960  env(token::clearMinter(alice));
-
1961  env.close();
-
1962 
-
1963  // becky accepts minter's sell offer.
-
1964  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1965  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
1966  env(token::acceptBuyOffer(becky, minterBuyOfferIndex));
-
1967  env.close();
-
1968  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
1969  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1970  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1971 
-
1972  // minter burns their nft and alice cancels her offer so the
-
1973  // next tests can start with a clean slate.
-
1974  env(token::burn(minter, nftMinterNoTransferID), ter(tesSUCCESS));
-
1975  env.close();
-
1976  env(token::cancelOffer(alice, {aliceBuyOfferIndex}));
-
1977  env.close();
-
1978  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1979  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
1980  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
1981  }
-
1982  // nfts with flagTransferable set should be buyable and salable
-
1983  // by anybody.
-
1984  {
-
1985  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
1986  uint256 const nftAliceID{
-
1987  token::getNextID(env, alice, 0u, tfTransferable)};
-
1988  env(token::mint(alice, 0u), txflags(tfTransferable));
-
1989  env.close();
-
1990  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
1991 
-
1992  // Both alice and becky can make offers for alice's nft.
-
1993  uint256 const aliceSellOfferIndex =
-
1994  keylet::nftoffer(alice, env.seq(alice)).key;
-
1995  env(token::createOffer(alice, nftAliceID, XRP(20)),
-
1996  txflags(tfSellNFToken));
-
1997  env.close();
-
1998  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
1999 
-
2000  uint256 const beckyBuyOfferIndex =
-
2001  keylet::nftoffer(becky, env.seq(becky)).key;
-
2002  env(token::createOffer(becky, nftAliceID, XRP(21)),
-
2003  token::owner(alice));
-
2004  env.close();
-
2005  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
2006 
-
2007  // becky accepts alice's sell offer.
-
2008  env(token::acceptSellOffer(becky, aliceSellOfferIndex));
-
2009  env.close();
+
1951  env(token::createOffer(minter, nftMinterNoTransferID, XRP(24)),
+
1952  token::owner(becky),
+
1953  ter(tefNFTOKEN_IS_NOT_TRANSFERABLE));
+
1954  env.close();
+
1955  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
1956 
+
1957  // alice can create an offer to buy the nft.
+
1958  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
1959  uint256 const aliceBuyOfferIndex =
+
1960  keylet::nftoffer(alice, env.seq(alice)).key;
+
1961  env(token::createOffer(alice, nftMinterNoTransferID, XRP(25)),
+
1962  token::owner(becky));
+
1963  env.close();
+
1964  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1965 
+
1966  // Let enough ledgers pass that old transactions are no longer
+
1967  // retried, then alice gives authorization back to minter.
+
1968  for (int i = 0; i < 10; ++i)
+
1969  env.close();
+
1970 
+
1971  env(token::setMinter(alice, minter));
+
1972  env.close();
+
1973 
+
1974  // Now minter can create an offer to buy the nft.
+
1975  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
1976  uint256 const minterBuyOfferIndex =
+
1977  keylet::nftoffer(minter, env.seq(minter)).key;
+
1978  env(token::createOffer(minter, nftMinterNoTransferID, XRP(26)),
+
1979  token::owner(becky));
+
1980  env.close();
+
1981  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1982 
+
1983  // alice removes authorization of minter so we can see whether
+
1984  // minter's pre-existing buy offer still works.
+
1985  env(token::clearMinter(alice));
+
1986  env.close();
+
1987 
+
1988  // becky accepts minter's sell offer.
+
1989  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1990  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
1991  env(token::acceptBuyOffer(becky, minterBuyOfferIndex));
+
1992  env.close();
+
1993  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
1994  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
1995  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
1996 
+
1997  // minter burns their nft and alice cancels her offer so the
+
1998  // next tests can start with a clean slate.
+
1999  env(token::burn(minter, nftMinterNoTransferID), ter(tesSUCCESS));
+
2000  env.close();
+
2001  env(token::cancelOffer(alice, {aliceBuyOfferIndex}));
+
2002  env.close();
+
2003  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2004  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
2005  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2006  }
+
2007  // nfts with flagTransferable set should be buyable and salable
+
2008  // by anybody.
+
2009  {
2010  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
2011  BEAST_EXPECT(ownerCount(env, becky) == 2);
-
2012 
-
2013  // becky offers to sell the nft.
-
2014  uint256 const beckySellOfferIndex =
-
2015  keylet::nftoffer(becky, env.seq(becky)).key;
-
2016  env(token::createOffer(becky, nftAliceID, XRP(22)),
-
2017  txflags(tfSellNFToken));
-
2018  env.close();
-
2019  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
2020  BEAST_EXPECT(ownerCount(env, becky) == 3);
-
2021 
-
2022  // minter buys the nft (even though minter is not currently
-
2023  // alice's minter).
-
2024  env(token::acceptSellOffer(minter, beckySellOfferIndex));
-
2025  env.close();
-
2026  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
2027  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2028  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2029 
-
2030  // minter offers to sell the nft.
-
2031  uint256 const minterSellOfferIndex =
-
2032  keylet::nftoffer(minter, env.seq(minter)).key;
-
2033  env(token::createOffer(minter, nftAliceID, XRP(23)),
-
2034  txflags(tfSellNFToken));
-
2035  env.close();
-
2036  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
2037  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2038  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
2039 
-
2040  // alice buys back the nft.
-
2041  env(token::acceptSellOffer(alice, minterSellOfferIndex));
-
2042  env.close();
-
2043  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
2044  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2045  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2011  uint256 const nftAliceID{
+
2012  token::getNextID(env, alice, 0u, tfTransferable)};
+
2013  env(token::mint(alice, 0u), txflags(tfTransferable));
+
2014  env.close();
+
2015  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2016 
+
2017  // Both alice and becky can make offers for alice's nft.
+
2018  uint256 const aliceSellOfferIndex =
+
2019  keylet::nftoffer(alice, env.seq(alice)).key;
+
2020  env(token::createOffer(alice, nftAliceID, XRP(20)),
+
2021  txflags(tfSellNFToken));
+
2022  env.close();
+
2023  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
2024 
+
2025  uint256 const beckyBuyOfferIndex =
+
2026  keylet::nftoffer(becky, env.seq(becky)).key;
+
2027  env(token::createOffer(becky, nftAliceID, XRP(21)),
+
2028  token::owner(alice));
+
2029  env.close();
+
2030  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
2031 
+
2032  // becky accepts alice's sell offer.
+
2033  env(token::acceptSellOffer(becky, aliceSellOfferIndex));
+
2034  env.close();
+
2035  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2036  BEAST_EXPECT(ownerCount(env, becky) == 2);
+
2037 
+
2038  // becky offers to sell the nft.
+
2039  uint256 const beckySellOfferIndex =
+
2040  keylet::nftoffer(becky, env.seq(becky)).key;
+
2041  env(token::createOffer(becky, nftAliceID, XRP(22)),
+
2042  txflags(tfSellNFToken));
+
2043  env.close();
+
2044  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2045  BEAST_EXPECT(ownerCount(env, becky) == 3);
2046 
-
2047  // Remember the buy offer that becky made for alice's token way
-
2048  // back when? It's still in the ledger, and alice accepts it.
-
2049  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2047  // minter buys the nft (even though minter is not currently
+
2048  // alice's minter).
+
2049  env(token::acceptSellOffer(minter, beckySellOfferIndex));
2050  env.close();
2051  BEAST_EXPECT(ownerCount(env, alice) == 0);
2052  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2053  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2053  BEAST_EXPECT(ownerCount(env, minter) == 1);
2054 
-
2055  // Just for tidyness, becky burns the token before shutting
-
2056  // things down.
-
2057  env(token::burn(becky, nftAliceID));
-
2058  env.close();
-
2059  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
2060  BEAST_EXPECT(ownerCount(env, becky) == 0);
-
2061  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
2062  }
-
2063  }
+
2055  // minter offers to sell the nft.
+
2056  uint256 const minterSellOfferIndex =
+
2057  keylet::nftoffer(minter, env.seq(minter)).key;
+
2058  env(token::createOffer(minter, nftAliceID, XRP(23)),
+
2059  txflags(tfSellNFToken));
+
2060  env.close();
+
2061  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2062  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2063  BEAST_EXPECT(ownerCount(env, minter) == 2);
2064 
-
2065  void
-
2066  testMintTransferFee(FeatureBitset features)
-
2067  {
-
2068  // Exercise NFTs with and without a transferFee.
-
2069  testcase("Mint transferFee");
-
2070 
-
2071  using namespace test::jtx;
-
2072 
-
2073  Env env{*this, features};
-
2074 
-
2075  Account const alice{"alice"};
-
2076  Account const becky{"becky"};
-
2077  Account const carol{"carol"};
-
2078  Account const minter{"minter"};
-
2079  Account const gw{"gw"};
-
2080  IOU const gwXAU(gw["XAU"]);
-
2081 
-
2082  env.fund(XRP(1000), alice, becky, carol, minter, gw);
-
2083  env.close();
-
2084 
-
2085  env(trust(alice, gwXAU(2000)));
-
2086  env(trust(becky, gwXAU(2000)));
-
2087  env(trust(carol, gwXAU(2000)));
-
2088  env(trust(minter, gwXAU(2000)));
-
2089  env.close();
-
2090  env(pay(gw, alice, gwXAU(1000)));
-
2091  env(pay(gw, becky, gwXAU(1000)));
-
2092  env(pay(gw, carol, gwXAU(1000)));
-
2093  env(pay(gw, minter, gwXAU(1000)));
-
2094  env.close();
+
2065  // alice buys back the nft.
+
2066  env(token::acceptSellOffer(alice, minterSellOfferIndex));
+
2067  env.close();
+
2068  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2069  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2070  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2071 
+
2072  // Remember the buy offer that becky made for alice's token way
+
2073  // back when? It's still in the ledger, and alice accepts it.
+
2074  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2075  env.close();
+
2076  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2077  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2078  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2079 
+
2080  // Just for tidyness, becky burns the token before shutting
+
2081  // things down.
+
2082  env(token::burn(becky, nftAliceID));
+
2083  env.close();
+
2084  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
2085  BEAST_EXPECT(ownerCount(env, becky) == 0);
+
2086  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2087  }
+
2088  }
+
2089 
+
2090  void
+
2091  testMintTransferFee(FeatureBitset features)
+
2092  {
+
2093  // Exercise NFTs with and without a transferFee.
+
2094  testcase("Mint transferFee");
2095 
-
2096  // Giving alice a minter helps us see if transfer rates are affected
-
2097  // by that.
-
2098  env(token::setMinter(alice, minter));
-
2099  env.close();
-
2100 
-
2101  // If there is no transferFee, then alice gets nothing for the
-
2102  // transfer.
-
2103  {
-
2104  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
2105  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2106  BEAST_EXPECT(ownerCount(env, carol) == 1);
-
2107  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2108 
-
2109  uint256 const nftID =
-
2110  token::getNextID(env, alice, 0u, tfTransferable);
-
2111  env(token::mint(alice), txflags(tfTransferable));
-
2112  env.close();
-
2113 
-
2114  // Becky buys the nft for XAU(10). Check balances.
-
2115  uint256 const beckyBuyOfferIndex =
-
2116  keylet::nftoffer(becky, env.seq(becky)).key;
-
2117  env(token::createOffer(becky, nftID, gwXAU(10)),
-
2118  token::owner(alice));
-
2119  env.close();
-
2120  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2121  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2122 
-
2123  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
-
2124  env.close();
-
2125  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
-
2126  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
-
2127 
-
2128  // becky sells nft to carol. alice's balance should not change.
-
2129  uint256 const beckySellOfferIndex =
-
2130  keylet::nftoffer(becky, env.seq(becky)).key;
-
2131  env(token::createOffer(becky, nftID, gwXAU(10)),
-
2132  txflags(tfSellNFToken));
-
2133  env.close();
-
2134  env(token::acceptSellOffer(carol, beckySellOfferIndex));
-
2135  env.close();
-
2136  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
-
2137  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2138  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
-
2139 
-
2140  // minter buys nft from carol. alice's balance should not change.
-
2141  uint256 const minterBuyOfferIndex =
-
2142  keylet::nftoffer(minter, env.seq(minter)).key;
-
2143  env(token::createOffer(minter, nftID, gwXAU(10)),
-
2144  token::owner(carol));
-
2145  env.close();
-
2146  env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
-
2147  env.close();
-
2148  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
-
2149  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2150  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
-
2151  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
+
2096  using namespace test::jtx;
+
2097 
+
2098  Env env{*this, features};
+
2099 
+
2100  Account const alice{"alice"};
+
2101  Account const becky{"becky"};
+
2102  Account const carol{"carol"};
+
2103  Account const minter{"minter"};
+
2104  Account const gw{"gw"};
+
2105  IOU const gwXAU(gw["XAU"]);
+
2106 
+
2107  env.fund(XRP(1000), alice, becky, carol, minter, gw);
+
2108  env.close();
+
2109 
+
2110  env(trust(alice, gwXAU(2000)));
+
2111  env(trust(becky, gwXAU(2000)));
+
2112  env(trust(carol, gwXAU(2000)));
+
2113  env(trust(minter, gwXAU(2000)));
+
2114  env.close();
+
2115  env(pay(gw, alice, gwXAU(1000)));
+
2116  env(pay(gw, becky, gwXAU(1000)));
+
2117  env(pay(gw, carol, gwXAU(1000)));
+
2118  env(pay(gw, minter, gwXAU(1000)));
+
2119  env.close();
+
2120 
+
2121  // Giving alice a minter helps us see if transfer rates are affected
+
2122  // by that.
+
2123  env(token::setMinter(alice, minter));
+
2124  env.close();
+
2125 
+
2126  // If there is no transferFee, then alice gets nothing for the
+
2127  // transfer.
+
2128  {
+
2129  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2130  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2131  BEAST_EXPECT(ownerCount(env, carol) == 1);
+
2132  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2133 
+
2134  uint256 const nftID =
+
2135  token::getNextID(env, alice, 0u, tfTransferable);
+
2136  env(token::mint(alice), txflags(tfTransferable));
+
2137  env.close();
+
2138 
+
2139  // Becky buys the nft for XAU(10). Check balances.
+
2140  uint256 const beckyBuyOfferIndex =
+
2141  keylet::nftoffer(becky, env.seq(becky)).key;
+
2142  env(token::createOffer(becky, nftID, gwXAU(10)),
+
2143  token::owner(alice));
+
2144  env.close();
+
2145  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2146  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2147 
+
2148  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2149  env.close();
+
2150  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
+
2151  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
2152 
-
2153  // minter sells the nft to alice. gwXAU balances should finish
-
2154  // where they started.
-
2155  uint256 const minterSellOfferIndex =
-
2156  keylet::nftoffer(minter, env.seq(minter)).key;
-
2157  env(token::createOffer(minter, nftID, gwXAU(10)),
-
2158  txflags(tfSellNFToken));
-
2159  env.close();
-
2160  env(token::acceptSellOffer(alice, minterSellOfferIndex));
-
2161  env.close();
-
2162  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2163  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2164  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
-
2165  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
2166 
-
2167  // alice burns the nft to make later tests easier to think about.
-
2168  env(token::burn(alice, nftID));
-
2169  env.close();
-
2170  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
2171  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2172  BEAST_EXPECT(ownerCount(env, carol) == 1);
-
2173  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2174  }
-
2175 
-
2176  // Set the smallest possible transfer fee.
-
2177  {
-
2178  // An nft with a transfer fee of 1 basis point.
-
2179  uint256 const nftID =
-
2180  token::getNextID(env, alice, 0u, tfTransferable, 1);
-
2181  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
-
2182  env.close();
-
2183 
-
2184  // Becky buys the nft for XAU(10). Check balances.
-
2185  uint256 const beckyBuyOfferIndex =
-
2186  keylet::nftoffer(becky, env.seq(becky)).key;
-
2187  env(token::createOffer(becky, nftID, gwXAU(10)),
-
2188  token::owner(alice));
-
2189  env.close();
-
2190  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2191  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2192 
-
2193  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2153  // becky sells nft to carol. alice's balance should not change.
+
2154  uint256 const beckySellOfferIndex =
+
2155  keylet::nftoffer(becky, env.seq(becky)).key;
+
2156  env(token::createOffer(becky, nftID, gwXAU(10)),
+
2157  txflags(tfSellNFToken));
+
2158  env.close();
+
2159  env(token::acceptSellOffer(carol, beckySellOfferIndex));
+
2160  env.close();
+
2161  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
+
2162  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2163  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
+
2164 
+
2165  // minter buys nft from carol. alice's balance should not change.
+
2166  uint256 const minterBuyOfferIndex =
+
2167  keylet::nftoffer(minter, env.seq(minter)).key;
+
2168  env(token::createOffer(minter, nftID, gwXAU(10)),
+
2169  token::owner(carol));
+
2170  env.close();
+
2171  env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
+
2172  env.close();
+
2173  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
+
2174  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2175  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
+
2176  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
+
2177 
+
2178  // minter sells the nft to alice. gwXAU balances should finish
+
2179  // where they started.
+
2180  uint256 const minterSellOfferIndex =
+
2181  keylet::nftoffer(minter, env.seq(minter)).key;
+
2182  env(token::createOffer(minter, nftID, gwXAU(10)),
+
2183  txflags(tfSellNFToken));
+
2184  env.close();
+
2185  env(token::acceptSellOffer(alice, minterSellOfferIndex));
+
2186  env.close();
+
2187  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2188  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2189  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
+
2190  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
2191 
+
2192  // alice burns the nft to make later tests easier to think about.
+
2193  env(token::burn(alice, nftID));
2194  env.close();
-
2195  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
-
2196  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
-
2197 
-
2198  // becky sells nft to carol. alice's balance goes up.
-
2199  uint256 const beckySellOfferIndex =
-
2200  keylet::nftoffer(becky, env.seq(becky)).key;
-
2201  env(token::createOffer(becky, nftID, gwXAU(10)),
-
2202  txflags(tfSellNFToken));
-
2203  env.close();
-
2204  env(token::acceptSellOffer(carol, beckySellOfferIndex));
-
2205  env.close();
-
2206 
-
2207  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0001));
-
2208  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
-
2209  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
-
2210 
-
2211  // minter buys nft from carol. alice's balance goes up.
-
2212  uint256 const minterBuyOfferIndex =
-
2213  keylet::nftoffer(minter, env.seq(minter)).key;
-
2214  env(token::createOffer(minter, nftID, gwXAU(10)),
-
2215  token::owner(carol));
-
2216  env.close();
-
2217  env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
-
2218  env.close();
-
2219 
-
2220  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0002));
-
2221  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
-
2222  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
-
2223  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
-
2224 
-
2225  // minter sells the nft to alice. Because alice is part of the
-
2226  // transaction no tranfer fee is removed.
-
2227  uint256 const minterSellOfferIndex =
-
2228  keylet::nftoffer(minter, env.seq(minter)).key;
-
2229  env(token::createOffer(minter, nftID, gwXAU(10)),
-
2230  txflags(tfSellNFToken));
-
2231  env.close();
-
2232  env(token::acceptSellOffer(alice, minterSellOfferIndex));
-
2233  env.close();
-
2234  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000.0002));
-
2235  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
-
2236  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
-
2237  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
2238 
-
2239  // alice pays to becky and carol so subsequent tests are easier
-
2240  // to think about.
-
2241  env(pay(alice, becky, gwXAU(0.0001)));
-
2242  env(pay(alice, carol, gwXAU(0.0001)));
+
2195  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2196  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2197  BEAST_EXPECT(ownerCount(env, carol) == 1);
+
2198  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2199  }
+
2200 
+
2201  // Set the smallest possible transfer fee.
+
2202  {
+
2203  // An nft with a transfer fee of 1 basis point.
+
2204  uint256 const nftID =
+
2205  token::getNextID(env, alice, 0u, tfTransferable, 1);
+
2206  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
+
2207  env.close();
+
2208 
+
2209  // Becky buys the nft for XAU(10). Check balances.
+
2210  uint256 const beckyBuyOfferIndex =
+
2211  keylet::nftoffer(becky, env.seq(becky)).key;
+
2212  env(token::createOffer(becky, nftID, gwXAU(10)),
+
2213  token::owner(alice));
+
2214  env.close();
+
2215  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2216  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2217 
+
2218  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2219  env.close();
+
2220  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
+
2221  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
+
2222 
+
2223  // becky sells nft to carol. alice's balance goes up.
+
2224  uint256 const beckySellOfferIndex =
+
2225  keylet::nftoffer(becky, env.seq(becky)).key;
+
2226  env(token::createOffer(becky, nftID, gwXAU(10)),
+
2227  txflags(tfSellNFToken));
+
2228  env.close();
+
2229  env(token::acceptSellOffer(carol, beckySellOfferIndex));
+
2230  env.close();
+
2231 
+
2232  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0001));
+
2233  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
+
2234  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
+
2235 
+
2236  // minter buys nft from carol. alice's balance goes up.
+
2237  uint256 const minterBuyOfferIndex =
+
2238  keylet::nftoffer(minter, env.seq(minter)).key;
+
2239  env(token::createOffer(minter, nftID, gwXAU(10)),
+
2240  token::owner(carol));
+
2241  env.close();
+
2242  env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
2243  env.close();
2244 
-
2245  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2246  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2247  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
-
2248  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
2245  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0002));
+
2246  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
+
2247  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
+
2248  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
2249 
-
2250  // alice burns the nft to make later tests easier to think about.
-
2251  env(token::burn(alice, nftID));
-
2252  env.close();
-
2253  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
2254  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2255  BEAST_EXPECT(ownerCount(env, carol) == 1);
-
2256  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2257  }
-
2258 
-
2259  // Set the largest allowed transfer fee.
-
2260  {
-
2261  // A transfer fee greater than 50% is not allowed.
-
2262  env(token::mint(alice),
-
2263  txflags(tfTransferable),
-
2264  token::xferFee(maxTransferFee + 1),
-
2265  ter(temBAD_NFTOKEN_TRANSFER_FEE));
-
2266  env.close();
-
2267 
-
2268  // Make an nft with a transfer fee of 50%.
-
2269  uint256 const nftID = token::getNextID(
-
2270  env, alice, 0u, tfTransferable, maxTransferFee);
-
2271  env(token::mint(alice),
-
2272  txflags(tfTransferable),
-
2273  token::xferFee(maxTransferFee));
-
2274  env.close();
-
2275 
-
2276  // Becky buys the nft for XAU(10). Check balances.
-
2277  uint256 const beckyBuyOfferIndex =
-
2278  keylet::nftoffer(becky, env.seq(becky)).key;
-
2279  env(token::createOffer(becky, nftID, gwXAU(10)),
-
2280  token::owner(alice));
-
2281  env.close();
-
2282  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2283  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2284 
-
2285  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
-
2286  env.close();
-
2287  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
-
2288  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
-
2289 
-
2290  // becky sells nft to minter. alice's balance goes up.
-
2291  uint256 const beckySellOfferIndex =
-
2292  keylet::nftoffer(becky, env.seq(becky)).key;
-
2293  env(token::createOffer(becky, nftID, gwXAU(100)),
-
2294  txflags(tfSellNFToken));
-
2295  env.close();
-
2296  env(token::acceptSellOffer(minter, beckySellOfferIndex));
-
2297  env.close();
-
2298 
-
2299  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1060));
-
2300  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
-
2301  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
-
2302 
-
2303  // carol buys nft from minter. alice's balance goes up.
-
2304  uint256 const carolBuyOfferIndex =
-
2305  keylet::nftoffer(carol, env.seq(carol)).key;
-
2306  env(token::createOffer(carol, nftID, gwXAU(10)),
-
2307  token::owner(minter));
-
2308  env.close();
-
2309  env(token::acceptBuyOffer(minter, carolBuyOfferIndex));
-
2310  env.close();
-
2311 
-
2312  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1065));
-
2313  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
-
2314  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
-
2315  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
-
2316 
-
2317  // carol sells the nft to alice. Because alice is part of the
-
2318  // transaction no tranfer fee is removed.
-
2319  uint256 const carolSellOfferIndex =
-
2320  keylet::nftoffer(carol, env.seq(carol)).key;
-
2321  env(token::createOffer(carol, nftID, gwXAU(10)),
-
2322  txflags(tfSellNFToken));
-
2323  env.close();
-
2324  env(token::acceptSellOffer(alice, carolSellOfferIndex));
-
2325  env.close();
-
2326 
-
2327  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1055));
-
2328  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
-
2329  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
-
2330  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
-
2331 
-
2332  // rebalance so subsequent tests are easier to think about.
-
2333  env(pay(alice, minter, gwXAU(55)));
-
2334  env(pay(becky, minter, gwXAU(40)));
+
2250  // minter sells the nft to alice. Because alice is part of the
+
2251  // transaction no tranfer fee is removed.
+
2252  uint256 const minterSellOfferIndex =
+
2253  keylet::nftoffer(minter, env.seq(minter)).key;
+
2254  env(token::createOffer(minter, nftID, gwXAU(10)),
+
2255  txflags(tfSellNFToken));
+
2256  env.close();
+
2257  env(token::acceptSellOffer(alice, minterSellOfferIndex));
+
2258  env.close();
+
2259  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000.0002));
+
2260  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
+
2261  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
+
2262  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
2263 
+
2264  // alice pays to becky and carol so subsequent tests are easier
+
2265  // to think about.
+
2266  env(pay(alice, becky, gwXAU(0.0001)));
+
2267  env(pay(alice, carol, gwXAU(0.0001)));
+
2268  env.close();
+
2269 
+
2270  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2271  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2272  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
+
2273  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
2274 
+
2275  // alice burns the nft to make later tests easier to think about.
+
2276  env(token::burn(alice, nftID));
+
2277  env.close();
+
2278  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2279  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2280  BEAST_EXPECT(ownerCount(env, carol) == 1);
+
2281  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2282  }
+
2283 
+
2284  // Set the largest allowed transfer fee.
+
2285  {
+
2286  // A transfer fee greater than 50% is not allowed.
+
2287  env(token::mint(alice),
+
2288  txflags(tfTransferable),
+
2289  token::xferFee(maxTransferFee + 1),
+
2290  ter(temBAD_NFTOKEN_TRANSFER_FEE));
+
2291  env.close();
+
2292 
+
2293  // Make an nft with a transfer fee of 50%.
+
2294  uint256 const nftID = token::getNextID(
+
2295  env, alice, 0u, tfTransferable, maxTransferFee);
+
2296  env(token::mint(alice),
+
2297  txflags(tfTransferable),
+
2298  token::xferFee(maxTransferFee));
+
2299  env.close();
+
2300 
+
2301  // Becky buys the nft for XAU(10). Check balances.
+
2302  uint256 const beckyBuyOfferIndex =
+
2303  keylet::nftoffer(becky, env.seq(becky)).key;
+
2304  env(token::createOffer(becky, nftID, gwXAU(10)),
+
2305  token::owner(alice));
+
2306  env.close();
+
2307  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2308  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2309 
+
2310  env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
+
2311  env.close();
+
2312  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
+
2313  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
+
2314 
+
2315  // becky sells nft to minter. alice's balance goes up.
+
2316  uint256 const beckySellOfferIndex =
+
2317  keylet::nftoffer(becky, env.seq(becky)).key;
+
2318  env(token::createOffer(becky, nftID, gwXAU(100)),
+
2319  txflags(tfSellNFToken));
+
2320  env.close();
+
2321  env(token::acceptSellOffer(minter, beckySellOfferIndex));
+
2322  env.close();
+
2323 
+
2324  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1060));
+
2325  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
+
2326  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
+
2327 
+
2328  // carol buys nft from minter. alice's balance goes up.
+
2329  uint256 const carolBuyOfferIndex =
+
2330  keylet::nftoffer(carol, env.seq(carol)).key;
+
2331  env(token::createOffer(carol, nftID, gwXAU(10)),
+
2332  token::owner(minter));
+
2333  env.close();
+
2334  env(token::acceptBuyOffer(minter, carolBuyOfferIndex));
2335  env.close();
-
2336  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
-
2337  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
-
2338  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
-
2339  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
2340 
-
2341  // alice burns the nft to make later tests easier to think about.
-
2342  env(token::burn(alice, nftID));
-
2343  env.close();
-
2344  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
2345  BEAST_EXPECT(ownerCount(env, becky) == 1);
-
2346  BEAST_EXPECT(ownerCount(env, carol) == 1);
-
2347  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2348  }
-
2349 
-
2350  // See the impact of rounding when the nft is sold for small amounts
-
2351  // of drops.
-
2352  for (auto NumberSwitchOver : {true})
-
2353  {
-
2354  if (NumberSwitchOver)
-
2355  env.enableFeature(fixUniversalNumber);
-
2356  else
-
2357  env.disableFeature(fixUniversalNumber);
-
2358 
-
2359  // An nft with a transfer fee of 1 basis point.
-
2360  uint256 const nftID =
-
2361  token::getNextID(env, alice, 0u, tfTransferable, 1);
-
2362  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
-
2363  env.close();
-
2364 
-
2365  // minter buys the nft for XRP(1). Since the transfer involves
-
2366  // alice there should be no transfer fee.
-
2367  STAmount fee = drops(10);
-
2368  STAmount aliceBalance = env.balance(alice);
-
2369  STAmount minterBalance = env.balance(minter);
-
2370  uint256 const minterBuyOfferIndex =
-
2371  keylet::nftoffer(minter, env.seq(minter)).key;
-
2372  env(token::createOffer(minter, nftID, XRP(1)), token::owner(alice));
-
2373  env.close();
-
2374  env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
-
2375  env.close();
-
2376  aliceBalance += XRP(1) - fee;
-
2377  minterBalance -= XRP(1) + fee;
-
2378  BEAST_EXPECT(env.balance(alice) == aliceBalance);
-
2379  BEAST_EXPECT(env.balance(minter) == minterBalance);
-
2380 
-
2381  // minter sells to carol. The payment is just small enough that
-
2382  // alice does not get any transfer fee.
-
2383  auto pmt = NumberSwitchOver ? drops(50000) : drops(99999);
-
2384  STAmount carolBalance = env.balance(carol);
-
2385  uint256 const minterSellOfferIndex =
-
2386  keylet::nftoffer(minter, env.seq(minter)).key;
-
2387  env(token::createOffer(minter, nftID, pmt), txflags(tfSellNFToken));
+
2336 
+
2337  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1065));
+
2338  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
+
2339  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
+
2340  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
+
2341 
+
2342  // carol sells the nft to alice. Because alice is part of the
+
2343  // transaction no tranfer fee is removed.
+
2344  uint256 const carolSellOfferIndex =
+
2345  keylet::nftoffer(carol, env.seq(carol)).key;
+
2346  env(token::createOffer(carol, nftID, gwXAU(10)),
+
2347  txflags(tfSellNFToken));
+
2348  env.close();
+
2349  env(token::acceptSellOffer(alice, carolSellOfferIndex));
+
2350  env.close();
+
2351 
+
2352  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1055));
+
2353  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
+
2354  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
+
2355  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
+
2356 
+
2357  // rebalance so subsequent tests are easier to think about.
+
2358  env(pay(alice, minter, gwXAU(55)));
+
2359  env(pay(becky, minter, gwXAU(40)));
+
2360  env.close();
+
2361  BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
+
2362  BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
+
2363  BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
+
2364  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
2365 
+
2366  // alice burns the nft to make later tests easier to think about.
+
2367  env(token::burn(alice, nftID));
+
2368  env.close();
+
2369  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
2370  BEAST_EXPECT(ownerCount(env, becky) == 1);
+
2371  BEAST_EXPECT(ownerCount(env, carol) == 1);
+
2372  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2373  }
+
2374 
+
2375  // See the impact of rounding when the nft is sold for small amounts
+
2376  // of drops.
+
2377  for (auto NumberSwitchOver : {true})
+
2378  {
+
2379  if (NumberSwitchOver)
+
2380  env.enableFeature(fixUniversalNumber);
+
2381  else
+
2382  env.disableFeature(fixUniversalNumber);
+
2383 
+
2384  // An nft with a transfer fee of 1 basis point.
+
2385  uint256 const nftID =
+
2386  token::getNextID(env, alice, 0u, tfTransferable, 1);
+
2387  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
2388  env.close();
-
2389  env(token::acceptSellOffer(carol, minterSellOfferIndex));
-
2390  env.close();
-
2391  minterBalance += pmt - fee;
-
2392  carolBalance -= pmt + fee;
-
2393  BEAST_EXPECT(env.balance(alice) == aliceBalance);
-
2394  BEAST_EXPECT(env.balance(minter) == minterBalance);
-
2395  BEAST_EXPECT(env.balance(carol) == carolBalance);
-
2396 
-
2397  // carol sells to becky. This is the smallest amount to pay for a
-
2398  // transfer that enables a transfer fee of 1 basis point.
-
2399  STAmount beckyBalance = env.balance(becky);
-
2400  uint256 const beckyBuyOfferIndex =
-
2401  keylet::nftoffer(becky, env.seq(becky)).key;
-
2402  pmt = NumberSwitchOver ? drops(50001) : drops(100000);
-
2403  env(token::createOffer(becky, nftID, pmt), token::owner(carol));
-
2404  env.close();
-
2405  env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
-
2406  env.close();
-
2407  carolBalance += pmt - drops(1) - fee;
-
2408  beckyBalance -= pmt + fee;
-
2409  aliceBalance += drops(1);
-
2410 
-
2411  BEAST_EXPECT(env.balance(alice) == aliceBalance);
-
2412  BEAST_EXPECT(env.balance(minter) == minterBalance);
-
2413  BEAST_EXPECT(env.balance(carol) == carolBalance);
-
2414  BEAST_EXPECT(env.balance(becky) == beckyBalance);
-
2415  }
-
2416 
-
2417  // See the impact of rounding when the nft is sold for small amounts
-
2418  // of an IOU.
-
2419  {
-
2420  // An nft with a transfer fee of 1 basis point.
-
2421  uint256 const nftID =
-
2422  token::getNextID(env, alice, 0u, tfTransferable, 1);
-
2423  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
-
2424  env.close();
-
2425 
-
2426  // Due to the floating point nature of IOUs we need to
-
2427  // significantly reduce the gwXAU balances of our accounts prior
-
2428  // to the iou transfer. Otherwise no transfers will happen.
-
2429  env(pay(alice, gw, env.balance(alice, gwXAU)));
-
2430  env(pay(minter, gw, env.balance(minter, gwXAU)));
-
2431  env(pay(becky, gw, env.balance(becky, gwXAU)));
-
2432  env.close();
-
2433 
-
2434  STAmount const startXAUBalance(
-
2435  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5);
-
2436  env(pay(gw, alice, startXAUBalance));
-
2437  env(pay(gw, minter, startXAUBalance));
-
2438  env(pay(gw, becky, startXAUBalance));
-
2439  env.close();
-
2440 
-
2441  // Here is the smallest expressible gwXAU amount.
-
2442  STAmount tinyXAU(
-
2443  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset);
-
2444 
-
2445  // minter buys the nft for tinyXAU. Since the transfer involves
-
2446  // alice there should be no transfer fee.
-
2447  STAmount aliceBalance = env.balance(alice, gwXAU);
-
2448  STAmount minterBalance = env.balance(minter, gwXAU);
-
2449  uint256 const minterBuyOfferIndex =
-
2450  keylet::nftoffer(minter, env.seq(minter)).key;
-
2451  env(token::createOffer(minter, nftID, tinyXAU),
-
2452  token::owner(alice));
-
2453  env.close();
-
2454  env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
-
2455  env.close();
-
2456  aliceBalance += tinyXAU;
-
2457  minterBalance -= tinyXAU;
-
2458  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
-
2459  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
-
2460 
-
2461  // minter sells to carol.
-
2462  STAmount carolBalance = env.balance(carol, gwXAU);
-
2463  uint256 const minterSellOfferIndex =
-
2464  keylet::nftoffer(minter, env.seq(minter)).key;
-
2465  env(token::createOffer(minter, nftID, tinyXAU),
-
2466  txflags(tfSellNFToken));
-
2467  env.close();
-
2468  env(token::acceptSellOffer(carol, minterSellOfferIndex));
-
2469  env.close();
-
2470 
-
2471  minterBalance += tinyXAU;
-
2472  carolBalance -= tinyXAU;
-
2473  // tiny XAU is so small that alice does not get a transfer fee.
-
2474  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
-
2475  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
-
2476  BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
-
2477 
-
2478  // carol sells to becky. This is the smallest gwXAU amount
-
2479  // to pay for a transfer that enables a transfer fee of 1.
-
2480  STAmount const cheapNFT(
-
2481  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5);
-
2482 
-
2483  STAmount beckyBalance = env.balance(becky, gwXAU);
-
2484  uint256 const beckyBuyOfferIndex =
-
2485  keylet::nftoffer(becky, env.seq(becky)).key;
-
2486  env(token::createOffer(becky, nftID, cheapNFT),
-
2487  token::owner(carol));
-
2488  env.close();
-
2489  env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
-
2490  env.close();
-
2491 
-
2492  aliceBalance += tinyXAU;
-
2493  beckyBalance -= cheapNFT;
-
2494  carolBalance += cheapNFT - tinyXAU;
-
2495  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
-
2496  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
-
2497  BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
-
2498  BEAST_EXPECT(env.balance(becky, gwXAU) == beckyBalance);
-
2499  }
-
2500  }
-
2501 
-
2502  void
-
2503  testMintTaxon(FeatureBitset features)
-
2504  {
-
2505  // Exercise the NFT taxon field.
-
2506  testcase("Mint taxon");
+
2389 
+
2390  // minter buys the nft for XRP(1). Since the transfer involves
+
2391  // alice there should be no transfer fee.
+
2392  STAmount fee = drops(10);
+
2393  STAmount aliceBalance = env.balance(alice);
+
2394  STAmount minterBalance = env.balance(minter);
+
2395  uint256 const minterBuyOfferIndex =
+
2396  keylet::nftoffer(minter, env.seq(minter)).key;
+
2397  env(token::createOffer(minter, nftID, XRP(1)), token::owner(alice));
+
2398  env.close();
+
2399  env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
+
2400  env.close();
+
2401  aliceBalance += XRP(1) - fee;
+
2402  minterBalance -= XRP(1) + fee;
+
2403  BEAST_EXPECT(env.balance(alice) == aliceBalance);
+
2404  BEAST_EXPECT(env.balance(minter) == minterBalance);
+
2405 
+
2406  // minter sells to carol. The payment is just small enough that
+
2407  // alice does not get any transfer fee.
+
2408  auto pmt = NumberSwitchOver ? drops(50000) : drops(99999);
+
2409  STAmount carolBalance = env.balance(carol);
+
2410  uint256 const minterSellOfferIndex =
+
2411  keylet::nftoffer(minter, env.seq(minter)).key;
+
2412  env(token::createOffer(minter, nftID, pmt), txflags(tfSellNFToken));
+
2413  env.close();
+
2414  env(token::acceptSellOffer(carol, minterSellOfferIndex));
+
2415  env.close();
+
2416  minterBalance += pmt - fee;
+
2417  carolBalance -= pmt + fee;
+
2418  BEAST_EXPECT(env.balance(alice) == aliceBalance);
+
2419  BEAST_EXPECT(env.balance(minter) == minterBalance);
+
2420  BEAST_EXPECT(env.balance(carol) == carolBalance);
+
2421 
+
2422  // carol sells to becky. This is the smallest amount to pay for a
+
2423  // transfer that enables a transfer fee of 1 basis point.
+
2424  STAmount beckyBalance = env.balance(becky);
+
2425  uint256 const beckyBuyOfferIndex =
+
2426  keylet::nftoffer(becky, env.seq(becky)).key;
+
2427  pmt = NumberSwitchOver ? drops(50001) : drops(100000);
+
2428  env(token::createOffer(becky, nftID, pmt), token::owner(carol));
+
2429  env.close();
+
2430  env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
+
2431  env.close();
+
2432  carolBalance += pmt - drops(1) - fee;
+
2433  beckyBalance -= pmt + fee;
+
2434  aliceBalance += drops(1);
+
2435 
+
2436  BEAST_EXPECT(env.balance(alice) == aliceBalance);
+
2437  BEAST_EXPECT(env.balance(minter) == minterBalance);
+
2438  BEAST_EXPECT(env.balance(carol) == carolBalance);
+
2439  BEAST_EXPECT(env.balance(becky) == beckyBalance);
+
2440  }
+
2441 
+
2442  // See the impact of rounding when the nft is sold for small amounts
+
2443  // of an IOU.
+
2444  {
+
2445  // An nft with a transfer fee of 1 basis point.
+
2446  uint256 const nftID =
+
2447  token::getNextID(env, alice, 0u, tfTransferable, 1);
+
2448  env(token::mint(alice), txflags(tfTransferable), token::xferFee(1));
+
2449  env.close();
+
2450 
+
2451  // Due to the floating point nature of IOUs we need to
+
2452  // significantly reduce the gwXAU balances of our accounts prior
+
2453  // to the iou transfer. Otherwise no transfers will happen.
+
2454  env(pay(alice, gw, env.balance(alice, gwXAU)));
+
2455  env(pay(minter, gw, env.balance(minter, gwXAU)));
+
2456  env(pay(becky, gw, env.balance(becky, gwXAU)));
+
2457  env.close();
+
2458 
+
2459  STAmount const startXAUBalance(
+
2460  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5);
+
2461  env(pay(gw, alice, startXAUBalance));
+
2462  env(pay(gw, minter, startXAUBalance));
+
2463  env(pay(gw, becky, startXAUBalance));
+
2464  env.close();
+
2465 
+
2466  // Here is the smallest expressible gwXAU amount.
+
2467  STAmount tinyXAU(
+
2468  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset);
+
2469 
+
2470  // minter buys the nft for tinyXAU. Since the transfer involves
+
2471  // alice there should be no transfer fee.
+
2472  STAmount aliceBalance = env.balance(alice, gwXAU);
+
2473  STAmount minterBalance = env.balance(minter, gwXAU);
+
2474  uint256 const minterBuyOfferIndex =
+
2475  keylet::nftoffer(minter, env.seq(minter)).key;
+
2476  env(token::createOffer(minter, nftID, tinyXAU),
+
2477  token::owner(alice));
+
2478  env.close();
+
2479  env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
+
2480  env.close();
+
2481  aliceBalance += tinyXAU;
+
2482  minterBalance -= tinyXAU;
+
2483  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
+
2484  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
+
2485 
+
2486  // minter sells to carol.
+
2487  STAmount carolBalance = env.balance(carol, gwXAU);
+
2488  uint256 const minterSellOfferIndex =
+
2489  keylet::nftoffer(minter, env.seq(minter)).key;
+
2490  env(token::createOffer(minter, nftID, tinyXAU),
+
2491  txflags(tfSellNFToken));
+
2492  env.close();
+
2493  env(token::acceptSellOffer(carol, minterSellOfferIndex));
+
2494  env.close();
+
2495 
+
2496  minterBalance += tinyXAU;
+
2497  carolBalance -= tinyXAU;
+
2498  // tiny XAU is so small that alice does not get a transfer fee.
+
2499  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
+
2500  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
+
2501  BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
+
2502 
+
2503  // carol sells to becky. This is the smallest gwXAU amount
+
2504  // to pay for a transfer that enables a transfer fee of 1.
+
2505  STAmount const cheapNFT(
+
2506  gwXAU.issue(), STAmount::cMinValue, STAmount::cMinOffset + 5);
2507 
-
2508  using namespace test::jtx;
-
2509 
-
2510  Env env{*this, features};
-
2511 
-
2512  Account const alice{"alice"};
-
2513  Account const becky{"becky"};
-
2514 
-
2515  env.fund(XRP(1000), alice, becky);
-
2516  env.close();
-
2517 
-
2518  // The taxon field is incorporated straight into the NFT ID. So
-
2519  // tests only need to operate on NFT IDs; we don't need to generate
-
2520  // any transactions.
-
2521 
-
2522  // The taxon value should be recoverable from the NFT ID.
-
2523  {
-
2524  uint256 const nftID = token::getNextID(env, alice, 0u);
-
2525  BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon(0));
-
2526  }
-
2527 
-
2528  // Make sure the full range of taxon values work. We just tried
-
2529  // the minimum. Now try the largest.
-
2530  {
-
2531  uint256 const nftID = token::getNextID(env, alice, 0xFFFFFFFFu);
-
2532  BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon((0xFFFFFFFF)));
-
2533  }
+
2508  STAmount beckyBalance = env.balance(becky, gwXAU);
+
2509  uint256 const beckyBuyOfferIndex =
+
2510  keylet::nftoffer(becky, env.seq(becky)).key;
+
2511  env(token::createOffer(becky, nftID, cheapNFT),
+
2512  token::owner(carol));
+
2513  env.close();
+
2514  env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
+
2515  env.close();
+
2516 
+
2517  aliceBalance += tinyXAU;
+
2518  beckyBalance -= cheapNFT;
+
2519  carolBalance += cheapNFT - tinyXAU;
+
2520  BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
+
2521  BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
+
2522  BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
+
2523  BEAST_EXPECT(env.balance(becky, gwXAU) == beckyBalance);
+
2524  }
+
2525  }
+
2526 
+
2527  void
+
2528  testMintTaxon(FeatureBitset features)
+
2529  {
+
2530  // Exercise the NFT taxon field.
+
2531  testcase("Mint taxon");
+
2532 
+
2533  using namespace test::jtx;
2534 
-
2535  // Do some touch testing to show that the taxon is recoverable no
-
2536  // matter what else changes around it in the nft ID.
-
2537  {
-
2538  std::uint32_t const taxon = rand_int<std::uint32_t>();
-
2539  for (int i = 0; i < 10; ++i)
-
2540  {
-
2541  // lambda to produce a useful message on error.
-
2542  auto check = [this](std::uint32_t taxon, uint256 const& nftID) {
-
2543  nft::Taxon const gotTaxon = nft::getTaxon(nftID);
-
2544  if (nft::toTaxon(taxon) == gotTaxon)
-
2545  pass();
-
2546  else
-
2547  {
-
2548  std::stringstream ss;
-
2549  ss << "Taxon recovery failed from nftID "
-
2550  << to_string(nftID) << ". Expected: " << taxon
-
2551  << "; got: " << gotTaxon;
-
2552  fail(ss.str());
-
2553  }
-
2554  };
-
2555 
-
2556  uint256 const nftAliceID = token::getID(
-
2557  alice,
-
2558  taxon,
-
2559  rand_int<std::uint32_t>(),
-
2560  rand_int<std::uint16_t>(),
-
2561  rand_int<std::uint16_t>());
-
2562  check(taxon, nftAliceID);
-
2563 
-
2564  uint256 const nftBeckyID = token::getID(
-
2565  becky,
-
2566  taxon,
-
2567  rand_int<std::uint32_t>(),
-
2568  rand_int<std::uint16_t>(),
-
2569  rand_int<std::uint16_t>());
-
2570  check(taxon, nftBeckyID);
-
2571  }
-
2572  }
-
2573  }
-
2574 
-
2575  void
-
2576  testMintURI(FeatureBitset features)
-
2577  {
-
2578  // Exercise the NFT URI field.
-
2579  // 1. Create a number of NFTs with and without URIs.
-
2580  // 2. Retrieve the NFTs from the server.
-
2581  // 3. Make sure the right URI is attached to each NFT.
-
2582  testcase("Mint URI");
-
2583 
-
2584  using namespace test::jtx;
-
2585 
-
2586  Env env{*this, features};
-
2587 
-
2588  Account const alice{"alice"};
-
2589  Account const becky{"becky"};
-
2590 
-
2591  env.fund(XRP(10000), alice, becky);
-
2592  env.close();
-
2593 
-
2594  // lambda that returns a randomly generated string which fits
-
2595  // the constraints of a URI. Empty strings may be returned.
-
2596  // In the empty string case do not add the URI to the nft.
-
2597  auto randURI = []() {
-
2598  std::string ret;
-
2599 
-
2600  // About 20% of the returned strings should be empty
-
2601  if (rand_int(4) == 0)
-
2602  return ret;
-
2603 
-
2604  std::size_t const strLen = rand_int(256);
-
2605  ret.reserve(strLen);
-
2606  for (std::size_t i = 0; i < strLen; ++i)
-
2607  ret.push_back(rand_byte());
-
2608 
-
2609  return ret;
-
2610  };
-
2611 
-
2612  // Make a list of URIs that we'll put in nfts.
-
2613  struct Entry
-
2614  {
-
2615  std::string uri;
-
2616  std::uint32_t taxon;
+
2535  Env env{*this, features};
+
2536 
+
2537  Account const alice{"alice"};
+
2538  Account const becky{"becky"};
+
2539 
+
2540  env.fund(XRP(1000), alice, becky);
+
2541  env.close();
+
2542 
+
2543  // The taxon field is incorporated straight into the NFT ID. So
+
2544  // tests only need to operate on NFT IDs; we don't need to generate
+
2545  // any transactions.
+
2546 
+
2547  // The taxon value should be recoverable from the NFT ID.
+
2548  {
+
2549  uint256 const nftID = token::getNextID(env, alice, 0u);
+
2550  BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon(0));
+
2551  }
+
2552 
+
2553  // Make sure the full range of taxon values work. We just tried
+
2554  // the minimum. Now try the largest.
+
2555  {
+
2556  uint256 const nftID = token::getNextID(env, alice, 0xFFFFFFFFu);
+
2557  BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon((0xFFFFFFFF)));
+
2558  }
+
2559 
+
2560  // Do some touch testing to show that the taxon is recoverable no
+
2561  // matter what else changes around it in the nft ID.
+
2562  {
+
2563  std::uint32_t const taxon = rand_int<std::uint32_t>();
+
2564  for (int i = 0; i < 10; ++i)
+
2565  {
+
2566  // lambda to produce a useful message on error.
+
2567  auto check = [this](std::uint32_t taxon, uint256 const& nftID) {
+
2568  nft::Taxon const gotTaxon = nft::getTaxon(nftID);
+
2569  if (nft::toTaxon(taxon) == gotTaxon)
+
2570  pass();
+
2571  else
+
2572  {
+
2573  std::stringstream ss;
+
2574  ss << "Taxon recovery failed from nftID "
+
2575  << to_string(nftID) << ". Expected: " << taxon
+
2576  << "; got: " << gotTaxon;
+
2577  fail(ss.str());
+
2578  }
+
2579  };
+
2580 
+
2581  uint256 const nftAliceID = token::getID(
+
2582  env,
+
2583  alice,
+
2584  taxon,
+
2585  rand_int<std::uint32_t>(),
+
2586  rand_int<std::uint16_t>(),
+
2587  rand_int<std::uint16_t>());
+
2588  check(taxon, nftAliceID);
+
2589 
+
2590  uint256 const nftBeckyID = token::getID(
+
2591  env,
+
2592  becky,
+
2593  taxon,
+
2594  rand_int<std::uint32_t>(),
+
2595  rand_int<std::uint16_t>(),
+
2596  rand_int<std::uint16_t>());
+
2597  check(taxon, nftBeckyID);
+
2598  }
+
2599  }
+
2600  }
+
2601 
+
2602  void
+
2603  testMintURI(FeatureBitset features)
+
2604  {
+
2605  // Exercise the NFT URI field.
+
2606  // 1. Create a number of NFTs with and without URIs.
+
2607  // 2. Retrieve the NFTs from the server.
+
2608  // 3. Make sure the right URI is attached to each NFT.
+
2609  testcase("Mint URI");
+
2610 
+
2611  using namespace test::jtx;
+
2612 
+
2613  Env env{*this, features};
+
2614 
+
2615  Account const alice{"alice"};
+
2616  Account const becky{"becky"};
2617 
-
2618  Entry(std::string uri_, std::uint32_t taxon_)
-
2619  : uri(std::move(uri_)), taxon(taxon_)
-
2620  {
-
2621  }
-
2622  };
-
2623 
-
2624  std::vector<Entry> entries;
-
2625  entries.reserve(100);
-
2626  for (std::size_t i = 0; i < 100; ++i)
-
2627  entries.emplace_back(randURI(), rand_int<std::uint32_t>());
-
2628 
-
2629  // alice creates nfts using entries.
-
2630  for (Entry const& entry : entries)
-
2631  {
-
2632  if (entry.uri.empty())
-
2633  {
-
2634  env(token::mint(alice, entry.taxon));
-
2635  }
-
2636  else
-
2637  {
-
2638  env(token::mint(alice, entry.taxon), token::uri(entry.uri));
-
2639  }
-
2640  env.close();
-
2641  }
-
2642 
-
2643  // Recover alice's nfts from the ledger.
-
2644  Json::Value aliceNFTs = [&env, &alice]() {
-
2645  Json::Value params;
-
2646  params[jss::account] = alice.human();
-
2647  params[jss::type] = "state";
-
2648  return env.rpc("json", "account_nfts", to_string(params));
-
2649  }();
+
2618  env.fund(XRP(10000), alice, becky);
+
2619  env.close();
+
2620 
+
2621  // lambda that returns a randomly generated string which fits
+
2622  // the constraints of a URI. Empty strings may be returned.
+
2623  // In the empty string case do not add the URI to the nft.
+
2624  auto randURI = []() {
+
2625  std::string ret;
+
2626 
+
2627  // About 20% of the returned strings should be empty
+
2628  if (rand_int(4) == 0)
+
2629  return ret;
+
2630 
+
2631  std::size_t const strLen = rand_int(256);
+
2632  ret.reserve(strLen);
+
2633  for (std::size_t i = 0; i < strLen; ++i)
+
2634  ret.push_back(rand_byte());
+
2635 
+
2636  return ret;
+
2637  };
+
2638 
+
2639  // Make a list of URIs that we'll put in nfts.
+
2640  struct Entry
+
2641  {
+
2642  std::string uri;
+
2643  std::uint32_t taxon;
+
2644 
+
2645  Entry(std::string uri_, std::uint32_t taxon_)
+
2646  : uri(std::move(uri_)), taxon(taxon_)
+
2647  {
+
2648  }
+
2649  };
2650 
-
2651  // Verify that the returned NFTs match what we sent.
-
2652  Json::Value& nfts = aliceNFTs[jss::result][jss::account_nfts];
-
2653  if (!BEAST_EXPECT(nfts.size() == entries.size()))
-
2654  return;
+
2651  std::vector<Entry> entries;
+
2652  entries.reserve(100);
+
2653  for (std::size_t i = 0; i < 100; ++i)
+
2654  entries.emplace_back(randURI(), rand_int<std::uint32_t>());
2655 
-
2656  // Sort the returned NFTs by nft_serial so the are in the same order
-
2657  // as entries.
-
2658  std::vector<Json::Value> sortedNFTs;
-
2659  sortedNFTs.reserve(nfts.size());
-
2660  for (std::size_t i = 0; i < nfts.size(); ++i)
-
2661  sortedNFTs.push_back(nfts[i]);
-
2662  std::sort(
-
2663  sortedNFTs.begin(),
-
2664  sortedNFTs.end(),
-
2665  [](Json::Value const& lhs, Json::Value const& rhs) {
-
2666  return lhs[jss::nft_serial] < rhs[jss::nft_serial];
-
2667  });
-
2668 
-
2669  for (std::size_t i = 0; i < entries.size(); ++i)
-
2670  {
-
2671  Entry const& entry = entries[i];
-
2672  Json::Value const& ret = sortedNFTs[i];
-
2673  BEAST_EXPECT(entry.taxon == ret[sfNFTokenTaxon.jsonName]);
-
2674  if (entry.uri.empty())
-
2675  {
-
2676  BEAST_EXPECT(!ret.isMember(sfURI.jsonName));
-
2677  }
-
2678  else
-
2679  {
-
2680  BEAST_EXPECT(strHex(entry.uri) == ret[sfURI.jsonName]);
-
2681  }
-
2682  }
-
2683  }
-
2684 
-
2685  void
-
2686  testCreateOfferDestination(FeatureBitset features)
-
2687  {
-
2688  // Explore the CreateOffer Destination field.
-
2689  testcase("Create offer destination");
-
2690 
-
2691  using namespace test::jtx;
-
2692 
-
2693  Env env{*this, features};
-
2694 
-
2695  Account const issuer{"issuer"};
-
2696  Account const minter{"minter"};
-
2697  Account const buyer{"buyer"};
-
2698  Account const broker{"broker"};
-
2699 
-
2700  env.fund(XRP(1000), issuer, minter, buyer, broker);
-
2701 
-
2702  // We want to explore how issuers vs minters fits into the permission
-
2703  // scheme. So issuer issues and minter mints.
-
2704  env(token::setMinter(issuer, minter));
-
2705  env.close();
-
2706 
-
2707  uint256 const nftokenID =
-
2708  token::getNextID(env, issuer, 0, tfTransferable);
-
2709  env(token::mint(minter, 0),
-
2710  token::issuer(issuer),
-
2711  txflags(tfTransferable));
-
2712  env.close();
-
2713 
-
2714  // Test how adding a Destination field to an offer affects permissions
-
2715  // for canceling offers.
-
2716  {
-
2717  uint256 const offerMinterToIssuer =
-
2718  keylet::nftoffer(minter, env.seq(minter)).key;
-
2719  env(token::createOffer(minter, nftokenID, drops(1)),
-
2720  token::destination(issuer),
-
2721  txflags(tfSellNFToken));
-
2722 
-
2723  uint256 const offerMinterToBuyer =
-
2724  keylet::nftoffer(minter, env.seq(minter)).key;
-
2725  env(token::createOffer(minter, nftokenID, drops(1)),
-
2726  token::destination(buyer),
-
2727  txflags(tfSellNFToken));
+
2656  // alice creates nfts using entries.
+
2657  for (Entry const& entry : entries)
+
2658  {
+
2659  if (entry.uri.empty())
+
2660  {
+
2661  env(token::mint(alice, entry.taxon));
+
2662  }
+
2663  else
+
2664  {
+
2665  env(token::mint(alice, entry.taxon), token::uri(entry.uri));
+
2666  }
+
2667  env.close();
+
2668  }
+
2669 
+
2670  // Recover alice's nfts from the ledger.
+
2671  Json::Value aliceNFTs = [&env, &alice]() {
+
2672  Json::Value params;
+
2673  params[jss::account] = alice.human();
+
2674  params[jss::type] = "state";
+
2675  return env.rpc("json", "account_nfts", to_string(params));
+
2676  }();
+
2677 
+
2678  // Verify that the returned NFTs match what we sent.
+
2679  Json::Value& nfts = aliceNFTs[jss::result][jss::account_nfts];
+
2680  if (!BEAST_EXPECT(nfts.size() == entries.size()))
+
2681  return;
+
2682 
+
2683  // Sort the returned NFTs by nft_serial so the are in the same order
+
2684  // as entries.
+
2685  std::vector<Json::Value> sortedNFTs;
+
2686  sortedNFTs.reserve(nfts.size());
+
2687  for (std::size_t i = 0; i < nfts.size(); ++i)
+
2688  sortedNFTs.push_back(nfts[i]);
+
2689  std::sort(
+
2690  sortedNFTs.begin(),
+
2691  sortedNFTs.end(),
+
2692  [](Json::Value const& lhs, Json::Value const& rhs) {
+
2693  return lhs[jss::nft_serial] < rhs[jss::nft_serial];
+
2694  });
+
2695 
+
2696  for (std::size_t i = 0; i < entries.size(); ++i)
+
2697  {
+
2698  Entry const& entry = entries[i];
+
2699  Json::Value const& ret = sortedNFTs[i];
+
2700  BEAST_EXPECT(entry.taxon == ret[sfNFTokenTaxon.jsonName]);
+
2701  if (entry.uri.empty())
+
2702  {
+
2703  BEAST_EXPECT(!ret.isMember(sfURI.jsonName));
+
2704  }
+
2705  else
+
2706  {
+
2707  BEAST_EXPECT(strHex(entry.uri) == ret[sfURI.jsonName]);
+
2708  }
+
2709  }
+
2710  }
+
2711 
+
2712  void
+
2713  testCreateOfferDestination(FeatureBitset features)
+
2714  {
+
2715  // Explore the CreateOffer Destination field.
+
2716  testcase("Create offer destination");
+
2717 
+
2718  using namespace test::jtx;
+
2719 
+
2720  Env env{*this, features};
+
2721 
+
2722  Account const issuer{"issuer"};
+
2723  Account const minter{"minter"};
+
2724  Account const buyer{"buyer"};
+
2725  Account const broker{"broker"};
+
2726 
+
2727  env.fund(XRP(1000), issuer, minter, buyer, broker);
2728 
-
2729  uint256 const offerIssuerToMinter =
-
2730  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
2731  env(token::createOffer(issuer, nftokenID, drops(1)),
-
2732  token::owner(minter),
-
2733  token::destination(minter));
-
2734 
-
2735  uint256 const offerIssuerToBuyer =
-
2736  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
2737  env(token::createOffer(issuer, nftokenID, drops(1)),
-
2738  token::owner(minter),
-
2739  token::destination(buyer));
+
2729  // We want to explore how issuers vs minters fits into the permission
+
2730  // scheme. So issuer issues and minter mints.
+
2731  env(token::setMinter(issuer, minter));
+
2732  env.close();
+
2733 
+
2734  uint256 const nftokenID =
+
2735  token::getNextID(env, issuer, 0, tfTransferable);
+
2736  env(token::mint(minter, 0),
+
2737  token::issuer(issuer),
+
2738  txflags(tfTransferable));
+
2739  env.close();
2740 
-
2741  env.close();
-
2742  BEAST_EXPECT(ownerCount(env, issuer) == 2);
-
2743  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
2744  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2745 
-
2746  // Test who gets to cancel the offers. Anyone outside of the
-
2747  // offer-owner/destination pair should not be able to cancel the
-
2748  // offers.
-
2749  //
-
2750  // Note that issuer does not have any special permissions regarding
-
2751  // offer cancellation. issuer cannot cancel an offer for an
-
2752  // NFToken they issued.
-
2753  env(token::cancelOffer(issuer, {offerMinterToBuyer}),
-
2754  ter(tecNO_PERMISSION));
-
2755  env(token::cancelOffer(buyer, {offerMinterToIssuer}),
-
2756  ter(tecNO_PERMISSION));
-
2757  env(token::cancelOffer(buyer, {offerIssuerToMinter}),
-
2758  ter(tecNO_PERMISSION));
-
2759  env(token::cancelOffer(minter, {offerIssuerToBuyer}),
-
2760  ter(tecNO_PERMISSION));
-
2761  env.close();
-
2762  BEAST_EXPECT(ownerCount(env, issuer) == 2);
-
2763  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
2764  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2765 
-
2766  // Both the offer creator and and destination should be able to
-
2767  // cancel the offers.
-
2768  env(token::cancelOffer(buyer, {offerMinterToBuyer}));
-
2769  env(token::cancelOffer(minter, {offerMinterToIssuer}));
-
2770  env(token::cancelOffer(buyer, {offerIssuerToBuyer}));
-
2771  env(token::cancelOffer(issuer, {offerIssuerToMinter}));
-
2772  env.close();
-
2773  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2774  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2775  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2776  }
-
2777 
-
2778  // Test how adding a Destination field to a sell offer affects
-
2779  // accepting that offer.
-
2780  {
-
2781  uint256 const offerMinterSellsToBuyer =
-
2782  keylet::nftoffer(minter, env.seq(minter)).key;
-
2783  env(token::createOffer(minter, nftokenID, drops(1)),
-
2784  token::destination(buyer),
-
2785  txflags(tfSellNFToken));
-
2786  env.close();
-
2787  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2788  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
2789  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2790 
-
2791  // issuer cannot accept a sell offer where they are not the
-
2792  // destination.
-
2793  env(token::acceptSellOffer(issuer, offerMinterSellsToBuyer),
-
2794  ter(tecNO_PERMISSION));
-
2795  env.close();
-
2796  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2797  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
2798  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2799 
-
2800  // However buyer can accept the sell offer.
-
2801  env(token::acceptSellOffer(buyer, offerMinterSellsToBuyer));
-
2802  env.close();
-
2803  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2804  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
2805  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2806  }
-
2807 
-
2808  // Test how adding a Destination field to a buy offer affects
-
2809  // accepting that offer.
-
2810  {
-
2811  uint256 const offerMinterBuysFromBuyer =
-
2812  keylet::nftoffer(minter, env.seq(minter)).key;
-
2813  env(token::createOffer(minter, nftokenID, drops(1)),
-
2814  token::owner(buyer),
-
2815  token::destination(buyer));
-
2816  env.close();
-
2817  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2818  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2819  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2820 
-
2821  // issuer cannot accept a buy offer where they are the
-
2822  // destination.
-
2823  env(token::acceptBuyOffer(issuer, offerMinterBuysFromBuyer),
-
2824  ter(tecNO_PERMISSION));
-
2825  env.close();
-
2826  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2827  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2828  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2829 
-
2830  // Buyer accepts minter's offer.
-
2831  env(token::acceptBuyOffer(buyer, offerMinterBuysFromBuyer));
-
2832  env.close();
-
2833  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2834  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2835  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2836 
-
2837  // If a destination other than the NFToken owner is set, that
-
2838  // destination must act as a broker. The NFToken owner may not
-
2839  // simply accept the offer.
-
2840  uint256 const offerBuyerBuysFromMinter =
-
2841  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
2842  env(token::createOffer(buyer, nftokenID, drops(1)),
-
2843  token::owner(minter),
-
2844  token::destination(broker));
-
2845  env.close();
-
2846  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2847  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2848  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2849 
-
2850  env(token::acceptBuyOffer(minter, offerBuyerBuysFromMinter),
+
2741  // Test how adding a Destination field to an offer affects permissions
+
2742  // for canceling offers.
+
2743  {
+
2744  uint256 const offerMinterToIssuer =
+
2745  keylet::nftoffer(minter, env.seq(minter)).key;
+
2746  env(token::createOffer(minter, nftokenID, drops(1)),
+
2747  token::destination(issuer),
+
2748  txflags(tfSellNFToken));
+
2749 
+
2750  uint256 const offerMinterToBuyer =
+
2751  keylet::nftoffer(minter, env.seq(minter)).key;
+
2752  env(token::createOffer(minter, nftokenID, drops(1)),
+
2753  token::destination(buyer),
+
2754  txflags(tfSellNFToken));
+
2755 
+
2756  uint256 const offerIssuerToMinter =
+
2757  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
2758  env(token::createOffer(issuer, nftokenID, drops(1)),
+
2759  token::owner(minter),
+
2760  token::destination(minter));
+
2761 
+
2762  uint256 const offerIssuerToBuyer =
+
2763  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
2764  env(token::createOffer(issuer, nftokenID, drops(1)),
+
2765  token::owner(minter),
+
2766  token::destination(buyer));
+
2767 
+
2768  env.close();
+
2769  BEAST_EXPECT(ownerCount(env, issuer) == 2);
+
2770  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
2771  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2772 
+
2773  // Test who gets to cancel the offers. Anyone outside of the
+
2774  // offer-owner/destination pair should not be able to cancel the
+
2775  // offers.
+
2776  //
+
2777  // Note that issuer does not have any special permissions regarding
+
2778  // offer cancellation. issuer cannot cancel an offer for an
+
2779  // NFToken they issued.
+
2780  env(token::cancelOffer(issuer, {offerMinterToBuyer}),
+
2781  ter(tecNO_PERMISSION));
+
2782  env(token::cancelOffer(buyer, {offerMinterToIssuer}),
+
2783  ter(tecNO_PERMISSION));
+
2784  env(token::cancelOffer(buyer, {offerIssuerToMinter}),
+
2785  ter(tecNO_PERMISSION));
+
2786  env(token::cancelOffer(minter, {offerIssuerToBuyer}),
+
2787  ter(tecNO_PERMISSION));
+
2788  env.close();
+
2789  BEAST_EXPECT(ownerCount(env, issuer) == 2);
+
2790  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
2791  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2792 
+
2793  // Both the offer creator and and destination should be able to
+
2794  // cancel the offers.
+
2795  env(token::cancelOffer(buyer, {offerMinterToBuyer}));
+
2796  env(token::cancelOffer(minter, {offerMinterToIssuer}));
+
2797  env(token::cancelOffer(buyer, {offerIssuerToBuyer}));
+
2798  env(token::cancelOffer(issuer, {offerIssuerToMinter}));
+
2799  env.close();
+
2800  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2801  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2802  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2803  }
+
2804 
+
2805  // Test how adding a Destination field to a sell offer affects
+
2806  // accepting that offer.
+
2807  {
+
2808  uint256 const offerMinterSellsToBuyer =
+
2809  keylet::nftoffer(minter, env.seq(minter)).key;
+
2810  env(token::createOffer(minter, nftokenID, drops(1)),
+
2811  token::destination(buyer),
+
2812  txflags(tfSellNFToken));
+
2813  env.close();
+
2814  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2815  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
2816  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2817 
+
2818  // issuer cannot accept a sell offer where they are not the
+
2819  // destination.
+
2820  env(token::acceptSellOffer(issuer, offerMinterSellsToBuyer),
+
2821  ter(tecNO_PERMISSION));
+
2822  env.close();
+
2823  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2824  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
2825  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2826 
+
2827  // However buyer can accept the sell offer.
+
2828  env(token::acceptSellOffer(buyer, offerMinterSellsToBuyer));
+
2829  env.close();
+
2830  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2831  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2832  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2833  }
+
2834 
+
2835  // Test how adding a Destination field to a buy offer affects
+
2836  // accepting that offer.
+
2837  {
+
2838  uint256 const offerMinterBuysFromBuyer =
+
2839  keylet::nftoffer(minter, env.seq(minter)).key;
+
2840  env(token::createOffer(minter, nftokenID, drops(1)),
+
2841  token::owner(buyer),
+
2842  token::destination(buyer));
+
2843  env.close();
+
2844  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2845  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2846  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2847 
+
2848  // issuer cannot accept a buy offer where they are the
+
2849  // destination.
+
2850  env(token::acceptBuyOffer(issuer, offerMinterBuysFromBuyer),
2851  ter(tecNO_PERMISSION));
2852  env.close();
-
2853 
-
2854  // Clean up the unused offer.
-
2855  env(token::cancelOffer(buyer, {offerBuyerBuysFromMinter}));
-
2856  env.close();
-
2857  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2858  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2859  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2860  }
-
2861 
-
2862  // Show that a sell offer's Destination can broker that sell offer
-
2863  // to another account.
-
2864  {
-
2865  uint256 const offerMinterToBroker =
-
2866  keylet::nftoffer(minter, env.seq(minter)).key;
-
2867  env(token::createOffer(minter, nftokenID, drops(1)),
-
2868  token::destination(broker),
-
2869  txflags(tfSellNFToken));
-
2870 
-
2871  uint256 const offerBuyerToMinter =
-
2872  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
2873  env(token::createOffer(buyer, nftokenID, drops(1)),
-
2874  token::owner(minter));
-
2875 
-
2876  env.close();
-
2877  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2878  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
2879  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2853  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2854  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2855  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2856 
+
2857  // Buyer accepts minter's offer.
+
2858  env(token::acceptBuyOffer(buyer, offerMinterBuysFromBuyer));
+
2859  env.close();
+
2860  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2861  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2862  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2863 
+
2864  // If a destination other than the NFToken owner is set, that
+
2865  // destination must act as a broker. The NFToken owner may not
+
2866  // simply accept the offer.
+
2867  uint256 const offerBuyerBuysFromMinter =
+
2868  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
2869  env(token::createOffer(buyer, nftokenID, drops(1)),
+
2870  token::owner(minter),
+
2871  token::destination(broker));
+
2872  env.close();
+
2873  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2874  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2875  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2876 
+
2877  env(token::acceptBuyOffer(minter, offerBuyerBuysFromMinter),
+
2878  ter(tecNO_PERMISSION));
+
2879  env.close();
2880 
-
2881  {
-
2882  // issuer cannot broker the offers, because they are not the
-
2883  // Destination.
-
2884  TER const expectTer = features[fixNonFungibleTokensV1_2]
-
2885  ? tecNO_PERMISSION
-
2886  : tecNFTOKEN_BUY_SELL_MISMATCH;
-
2887  env(token::brokerOffers(
-
2888  issuer, offerBuyerToMinter, offerMinterToBroker),
-
2889  ter(expectTer));
-
2890  env.close();
-
2891  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2892  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
2893  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2894  }
-
2895 
-
2896  // Since broker is the sell offer's destination, they can broker
-
2897  // the two offers.
-
2898  env(token::brokerOffers(
-
2899  broker, offerBuyerToMinter, offerMinterToBroker));
-
2900  env.close();
-
2901  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2902  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
2903  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
2904  }
-
2905 
-
2906  // Show that brokered mode cannot complete a transfer where the
-
2907  // Destination doesn't match, but can complete if the Destination
-
2908  // does match.
-
2909  {
-
2910  uint256 const offerBuyerToMinter =
-
2911  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
2912  env(token::createOffer(buyer, nftokenID, drops(1)),
-
2913  token::destination(minter),
-
2914  txflags(tfSellNFToken));
-
2915 
-
2916  uint256 const offerMinterToBuyer =
-
2917  keylet::nftoffer(minter, env.seq(minter)).key;
-
2918  env(token::createOffer(minter, nftokenID, drops(1)),
-
2919  token::owner(buyer));
-
2920 
-
2921  uint256 const offerIssuerToBuyer =
-
2922  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
2923  env(token::createOffer(issuer, nftokenID, drops(1)),
-
2924  token::owner(buyer));
-
2925 
-
2926  env.close();
-
2927  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
2928  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2929  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
2930 
-
2931  {
-
2932  // Cannot broker offers when the sell destination is not the
-
2933  // buyer.
-
2934  TER const expectTer = features[fixNonFungibleTokensV1_2]
-
2935  ? tecNO_PERMISSION
-
2936  : tecNFTOKEN_BUY_SELL_MISMATCH;
-
2937  env(token::brokerOffers(
-
2938  broker, offerIssuerToBuyer, offerBuyerToMinter),
-
2939  ter(expectTer));
-
2940  env.close();
-
2941 
-
2942  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
2943  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2944  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
2945 
-
2946  // amendment switch: When enabled the broker fails, when
-
2947  // disabled the broker succeeds if the destination is the buyer.
-
2948  TER const eexpectTer = features[fixNonFungibleTokensV1_2]
-
2949  ? tecNO_PERMISSION
-
2950  : TER(tesSUCCESS);
-
2951  env(token::brokerOffers(
-
2952  broker, offerMinterToBuyer, offerBuyerToMinter),
-
2953  ter(eexpectTer));
-
2954  env.close();
-
2955 
-
2956  if (features[fixNonFungibleTokensV1_2])
-
2957  // Buyer is successful with acceptOffer.
-
2958  env(token::acceptBuyOffer(buyer, offerMinterToBuyer));
-
2959  env.close();
-
2960 
-
2961  // Clean out the unconsumed offer.
-
2962  env(token::cancelOffer(buyer, {offerBuyerToMinter}));
-
2963  env.close();
-
2964 
-
2965  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
2966  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2967  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2881  // Clean up the unused offer.
+
2882  env(token::cancelOffer(buyer, {offerBuyerBuysFromMinter}));
+
2883  env.close();
+
2884  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2885  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2886  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2887  }
+
2888 
+
2889  // Show that a sell offer's Destination can broker that sell offer
+
2890  // to another account.
+
2891  {
+
2892  uint256 const offerMinterToBroker =
+
2893  keylet::nftoffer(minter, env.seq(minter)).key;
+
2894  env(token::createOffer(minter, nftokenID, drops(1)),
+
2895  token::destination(broker),
+
2896  txflags(tfSellNFToken));
+
2897 
+
2898  uint256 const offerBuyerToMinter =
+
2899  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
2900  env(token::createOffer(buyer, nftokenID, drops(1)),
+
2901  token::owner(minter));
+
2902 
+
2903  env.close();
+
2904  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2905  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
2906  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2907 
+
2908  {
+
2909  // issuer cannot broker the offers, because they are not the
+
2910  // Destination.
+
2911  TER const expectTer = features[fixNonFungibleTokensV1_2]
+
2912  ? tecNO_PERMISSION
+
2913  : tecNFTOKEN_BUY_SELL_MISMATCH;
+
2914  env(token::brokerOffers(
+
2915  issuer, offerBuyerToMinter, offerMinterToBroker),
+
2916  ter(expectTer));
+
2917  env.close();
+
2918  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2919  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
2920  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2921  }
+
2922 
+
2923  // Since broker is the sell offer's destination, they can broker
+
2924  // the two offers.
+
2925  env(token::brokerOffers(
+
2926  broker, offerBuyerToMinter, offerMinterToBroker));
+
2927  env.close();
+
2928  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
2929  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
2930  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
2931  }
+
2932 
+
2933  // Show that brokered mode cannot complete a transfer where the
+
2934  // Destination doesn't match, but can complete if the Destination
+
2935  // does match.
+
2936  {
+
2937  uint256 const offerBuyerToMinter =
+
2938  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
2939  env(token::createOffer(buyer, nftokenID, drops(1)),
+
2940  token::destination(minter),
+
2941  txflags(tfSellNFToken));
+
2942 
+
2943  uint256 const offerMinterToBuyer =
+
2944  keylet::nftoffer(minter, env.seq(minter)).key;
+
2945  env(token::createOffer(minter, nftokenID, drops(1)),
+
2946  token::owner(buyer));
+
2947 
+
2948  uint256 const offerIssuerToBuyer =
+
2949  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
2950  env(token::createOffer(issuer, nftokenID, drops(1)),
+
2951  token::owner(buyer));
+
2952 
+
2953  env.close();
+
2954  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
2955  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2956  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
2957 
+
2958  {
+
2959  // Cannot broker offers when the sell destination is not the
+
2960  // buyer.
+
2961  TER const expectTer = features[fixNonFungibleTokensV1_2]
+
2962  ? tecNO_PERMISSION
+
2963  : tecNFTOKEN_BUY_SELL_MISMATCH;
+
2964  env(token::brokerOffers(
+
2965  broker, offerIssuerToBuyer, offerBuyerToMinter),
+
2966  ter(expectTer));
+
2967  env.close();
2968 
-
2969  // Clean out the unconsumed offer.
-
2970  env(token::cancelOffer(issuer, {offerIssuerToBuyer}));
-
2971  env.close();
-
2972  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
2973  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
2974  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
2975  return;
-
2976  }
-
2977  }
-
2978 
-
2979  // Show that if a buy and a sell offer both have the same destination,
-
2980  // then that destination can broker the offers.
-
2981  {
-
2982  uint256 const offerMinterToBroker =
-
2983  keylet::nftoffer(minter, env.seq(minter)).key;
-
2984  env(token::createOffer(minter, nftokenID, drops(1)),
-
2985  token::destination(broker),
-
2986  txflags(tfSellNFToken));
+
2969  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
2970  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2971  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
2972 
+
2973  // amendment switch: When enabled the broker fails, when
+
2974  // disabled the broker succeeds if the destination is the buyer.
+
2975  TER const eexpectTer = features[fixNonFungibleTokensV1_2]
+
2976  ? tecNO_PERMISSION
+
2977  : TER(tesSUCCESS);
+
2978  env(token::brokerOffers(
+
2979  broker, offerMinterToBuyer, offerBuyerToMinter),
+
2980  ter(eexpectTer));
+
2981  env.close();
+
2982 
+
2983  if (features[fixNonFungibleTokensV1_2])
+
2984  // Buyer is successful with acceptOffer.
+
2985  env(token::acceptBuyOffer(buyer, offerMinterToBuyer));
+
2986  env.close();
2987 
-
2988  uint256 const offerBuyerToBroker =
-
2989  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
2990  env(token::createOffer(buyer, nftokenID, drops(1)),
-
2991  token::owner(minter),
-
2992  token::destination(broker));
-
2993 
-
2994  {
-
2995  // Cannot broker offers when the sell destination is not the
-
2996  // buyer or the broker.
-
2997  TER const expectTer = features[fixNonFungibleTokensV1_2]
-
2998  ? tecNO_PERMISSION
-
2999  : tecNFTOKEN_BUY_SELL_MISMATCH;
-
3000  env(token::brokerOffers(
-
3001  issuer, offerBuyerToBroker, offerMinterToBroker),
-
3002  ter(expectTer));
-
3003  env.close();
-
3004  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3005  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3006  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3007  }
-
3008 
-
3009  // Broker is successful if they are the destination of both offers.
-
3010  env(token::brokerOffers(
-
3011  broker, offerBuyerToBroker, offerMinterToBroker));
-
3012  env.close();
-
3013  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3014  BEAST_EXPECT(ownerCount(env, minter) == 0);
-
3015  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3016  }
-
3017  }
-
3018 
-
3019  void
-
3020  testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
-
3021  {
-
3022  testcase("Create offer destination disallow incoming");
-
3023 
-
3024  using namespace test::jtx;
-
3025 
-
3026  // test flag doesn't set unless amendment enabled
-
3027  {
-
3028  Env env{*this, features - disallowIncoming};
-
3029  Account const alice{"alice"};
-
3030  env.fund(XRP(10000), alice);
-
3031  env(fset(alice, asfDisallowIncomingNFTokenOffer));
-
3032  env.close();
-
3033  auto const sle = env.le(alice);
-
3034  uint32_t flags = sle->getFlags();
-
3035  BEAST_EXPECT(!(flags & lsfDisallowIncomingNFTokenOffer));
-
3036  }
-
3037 
-
3038  Env env{*this, features | disallowIncoming};
-
3039 
-
3040  Account const issuer{"issuer"};
-
3041  Account const minter{"minter"};
-
3042  Account const buyer{"buyer"};
-
3043  Account const alice{"alice"};
-
3044 
-
3045  env.fund(XRP(1000), issuer, minter, buyer, alice);
-
3046 
-
3047  env(token::setMinter(issuer, minter));
-
3048  env.close();
-
3049 
-
3050  uint256 const nftokenID =
-
3051  token::getNextID(env, issuer, 0, tfTransferable);
-
3052  env(token::mint(minter, 0),
-
3053  token::issuer(issuer),
-
3054  txflags(tfTransferable));
-
3055  env.close();
-
3056 
-
3057  // enable flag
-
3058  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
-
3059  env.close();
-
3060 
-
3061  // a sell offer from the minter to the buyer should be rejected
-
3062  {
-
3063  env(token::createOffer(minter, nftokenID, drops(1)),
-
3064  token::destination(buyer),
-
3065  txflags(tfSellNFToken),
-
3066  ter(tecNO_PERMISSION));
-
3067  env.close();
-
3068  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3069  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3070  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3071  }
-
3072 
-
3073  // disable the flag
-
3074  env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
+
2988  // Clean out the unconsumed offer.
+
2989  env(token::cancelOffer(buyer, {offerBuyerToMinter}));
+
2990  env.close();
+
2991 
+
2992  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
2993  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
2994  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
2995 
+
2996  // Clean out the unconsumed offer.
+
2997  env(token::cancelOffer(issuer, {offerIssuerToBuyer}));
+
2998  env.close();
+
2999  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3000  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3001  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3002  return;
+
3003  }
+
3004  }
+
3005 
+
3006  // Show that if a buy and a sell offer both have the same destination,
+
3007  // then that destination can broker the offers.
+
3008  {
+
3009  uint256 const offerMinterToBroker =
+
3010  keylet::nftoffer(minter, env.seq(minter)).key;
+
3011  env(token::createOffer(minter, nftokenID, drops(1)),
+
3012  token::destination(broker),
+
3013  txflags(tfSellNFToken));
+
3014 
+
3015  uint256 const offerBuyerToBroker =
+
3016  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3017  env(token::createOffer(buyer, nftokenID, drops(1)),
+
3018  token::owner(minter),
+
3019  token::destination(broker));
+
3020 
+
3021  {
+
3022  // Cannot broker offers when the sell destination is not the
+
3023  // buyer or the broker.
+
3024  TER const expectTer = features[fixNonFungibleTokensV1_2]
+
3025  ? tecNO_PERMISSION
+
3026  : tecNFTOKEN_BUY_SELL_MISMATCH;
+
3027  env(token::brokerOffers(
+
3028  issuer, offerBuyerToBroker, offerMinterToBroker),
+
3029  ter(expectTer));
+
3030  env.close();
+
3031  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3032  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3033  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3034  }
+
3035 
+
3036  // Broker is successful if they are the destination of both offers.
+
3037  env(token::brokerOffers(
+
3038  broker, offerBuyerToBroker, offerMinterToBroker));
+
3039  env.close();
+
3040  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3041  BEAST_EXPECT(ownerCount(env, minter) == 0);
+
3042  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3043  }
+
3044  }
+
3045 
+
3046  void
+
3047  testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
+
3048  {
+
3049  testcase("Create offer destination disallow incoming");
+
3050 
+
3051  using namespace test::jtx;
+
3052 
+
3053  // test flag doesn't set unless amendment enabled
+
3054  {
+
3055  Env env{*this, features - disallowIncoming};
+
3056  Account const alice{"alice"};
+
3057  env.fund(XRP(10000), alice);
+
3058  env(fset(alice, asfDisallowIncomingNFTokenOffer));
+
3059  env.close();
+
3060  auto const sle = env.le(alice);
+
3061  uint32_t flags = sle->getFlags();
+
3062  BEAST_EXPECT(!(flags & lsfDisallowIncomingNFTokenOffer));
+
3063  }
+
3064 
+
3065  Env env{*this, features | disallowIncoming};
+
3066 
+
3067  Account const issuer{"issuer"};
+
3068  Account const minter{"minter"};
+
3069  Account const buyer{"buyer"};
+
3070  Account const alice{"alice"};
+
3071 
+
3072  env.fund(XRP(1000), issuer, minter, buyer, alice);
+
3073 
+
3074  env(token::setMinter(issuer, minter));
3075  env.close();
3076 
-
3077  // create offer (allowed now) then cancel
-
3078  {
-
3079  uint256 const offerIndex =
-
3080  keylet::nftoffer(minter, env.seq(minter)).key;
-
3081 
-
3082  env(token::createOffer(minter, nftokenID, drops(1)),
-
3083  token::destination(buyer),
-
3084  txflags(tfSellNFToken));
-
3085  env.close();
-
3086 
-
3087  env(token::cancelOffer(minter, {offerIndex}));
-
3088  env.close();
-
3089  }
-
3090 
-
3091  // create offer, enable flag, then cancel
-
3092  {
-
3093  uint256 const offerIndex =
-
3094  keylet::nftoffer(minter, env.seq(minter)).key;
-
3095 
-
3096  env(token::createOffer(minter, nftokenID, drops(1)),
-
3097  token::destination(buyer),
-
3098  txflags(tfSellNFToken));
-
3099  env.close();
-
3100 
-
3101  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
-
3102  env.close();
+
3077  uint256 const nftokenID =
+
3078  token::getNextID(env, issuer, 0, tfTransferable);
+
3079  env(token::mint(minter, 0),
+
3080  token::issuer(issuer),
+
3081  txflags(tfTransferable));
+
3082  env.close();
+
3083 
+
3084  // enable flag
+
3085  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
+
3086  env.close();
+
3087 
+
3088  // a sell offer from the minter to the buyer should be rejected
+
3089  {
+
3090  env(token::createOffer(minter, nftokenID, drops(1)),
+
3091  token::destination(buyer),
+
3092  txflags(tfSellNFToken),
+
3093  ter(tecNO_PERMISSION));
+
3094  env.close();
+
3095  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3096  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3097  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3098  }
+
3099 
+
3100  // disable the flag
+
3101  env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
+
3102  env.close();
3103 
-
3104  env(token::cancelOffer(minter, {offerIndex}));
-
3105  env.close();
-
3106 
-
3107  env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
-
3108  env.close();
-
3109  }
-
3110 
-
3111  // create offer then transfer
-
3112  {
-
3113  uint256 const offerIndex =
-
3114  keylet::nftoffer(minter, env.seq(minter)).key;
-
3115 
-
3116  env(token::createOffer(minter, nftokenID, drops(1)),
-
3117  token::destination(buyer),
-
3118  txflags(tfSellNFToken));
-
3119  env.close();
-
3120 
-
3121  env(token::acceptSellOffer(buyer, offerIndex));
-
3122  env.close();
-
3123  }
-
3124 
-
3125  // buyer now owns the token
-
3126 
-
3127  // enable flag again
-
3128  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
-
3129  env.close();
+
3104  // create offer (allowed now) then cancel
+
3105  {
+
3106  uint256 const offerIndex =
+
3107  keylet::nftoffer(minter, env.seq(minter)).key;
+
3108 
+
3109  env(token::createOffer(minter, nftokenID, drops(1)),
+
3110  token::destination(buyer),
+
3111  txflags(tfSellNFToken));
+
3112  env.close();
+
3113 
+
3114  env(token::cancelOffer(minter, {offerIndex}));
+
3115  env.close();
+
3116  }
+
3117 
+
3118  // create offer, enable flag, then cancel
+
3119  {
+
3120  uint256 const offerIndex =
+
3121  keylet::nftoffer(minter, env.seq(minter)).key;
+
3122 
+
3123  env(token::createOffer(minter, nftokenID, drops(1)),
+
3124  token::destination(buyer),
+
3125  txflags(tfSellNFToken));
+
3126  env.close();
+
3127 
+
3128  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
+
3129  env.close();
3130 
-
3131  // a random offer to buy the token
-
3132  {
-
3133  env(token::createOffer(alice, nftokenID, drops(1)),
-
3134  token::owner(buyer),
-
3135  ter(tecNO_PERMISSION));
-
3136  env.close();
-
3137  }
-
3138 
-
3139  // minter offer to buy the token
-
3140  {
-
3141  env(token::createOffer(minter, nftokenID, drops(1)),
-
3142  token::owner(buyer),
-
3143  ter(tecNO_PERMISSION));
-
3144  env.close();
-
3145  }
-
3146  }
+
3131  env(token::cancelOffer(minter, {offerIndex}));
+
3132  env.close();
+
3133 
+
3134  env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
+
3135  env.close();
+
3136  }
+
3137 
+
3138  // create offer then transfer
+
3139  {
+
3140  uint256 const offerIndex =
+
3141  keylet::nftoffer(minter, env.seq(minter)).key;
+
3142 
+
3143  env(token::createOffer(minter, nftokenID, drops(1)),
+
3144  token::destination(buyer),
+
3145  txflags(tfSellNFToken));
+
3146  env.close();
3147 
-
3148  void
-
3149  testCreateOfferExpiration(FeatureBitset features)
-
3150  {
-
3151  // Explore the CreateOffer Expiration field.
-
3152  testcase("Create offer expiration");
+
3148  env(token::acceptSellOffer(buyer, offerIndex));
+
3149  env.close();
+
3150  }
+
3151 
+
3152  // buyer now owns the token
3153 
-
3154  using namespace test::jtx;
-
3155 
-
3156  Env env{*this, features};
+
3154  // enable flag again
+
3155  env(fset(buyer, asfDisallowIncomingNFTokenOffer));
+
3156  env.close();
3157 
-
3158  Account const issuer{"issuer"};
-
3159  Account const minter{"minter"};
-
3160  Account const buyer{"buyer"};
-
3161 
-
3162  env.fund(XRP(1000), issuer, minter, buyer);
-
3163 
-
3164  // We want to explore how issuers vs minters fits into the permission
-
3165  // scheme. So issuer issues and minter mints.
-
3166  env(token::setMinter(issuer, minter));
-
3167  env.close();
-
3168 
-
3169  uint256 const nftokenID0 =
-
3170  token::getNextID(env, issuer, 0, tfTransferable);
-
3171  env(token::mint(minter, 0),
-
3172  token::issuer(issuer),
-
3173  txflags(tfTransferable));
-
3174  env.close();
-
3175 
-
3176  uint256 const nftokenID1 =
-
3177  token::getNextID(env, issuer, 0, tfTransferable);
-
3178  env(token::mint(minter, 0),
-
3179  token::issuer(issuer),
-
3180  txflags(tfTransferable));
-
3181  env.close();
+
3158  // a random offer to buy the token
+
3159  {
+
3160  env(token::createOffer(alice, nftokenID, drops(1)),
+
3161  token::owner(buyer),
+
3162  ter(tecNO_PERMISSION));
+
3163  env.close();
+
3164  }
+
3165 
+
3166  // minter offer to buy the token
+
3167  {
+
3168  env(token::createOffer(minter, nftokenID, drops(1)),
+
3169  token::owner(buyer),
+
3170  ter(tecNO_PERMISSION));
+
3171  env.close();
+
3172  }
+
3173  }
+
3174 
+
3175  void
+
3176  testCreateOfferExpiration(FeatureBitset features)
+
3177  {
+
3178  // Explore the CreateOffer Expiration field.
+
3179  testcase("Create offer expiration");
+
3180 
+
3181  using namespace test::jtx;
3182 
-
3183  // Test how adding an Expiration field to an offer affects permissions
-
3184  // for cancelling offers.
-
3185  {
-
3186  std::uint32_t const expiration = lastClose(env) + 25;
-
3187 
-
3188  uint256 const offerMinterToIssuer =
-
3189  keylet::nftoffer(minter, env.seq(minter)).key;
-
3190  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3191  token::destination(issuer),
-
3192  token::expiration(expiration),
-
3193  txflags(tfSellNFToken));
-
3194 
-
3195  uint256 const offerMinterToAnyone =
-
3196  keylet::nftoffer(minter, env.seq(minter)).key;
-
3197  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3198  token::expiration(expiration),
-
3199  txflags(tfSellNFToken));
-
3200 
-
3201  uint256 const offerIssuerToMinter =
-
3202  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
3203  env(token::createOffer(issuer, nftokenID0, drops(1)),
-
3204  token::owner(minter),
-
3205  token::expiration(expiration));
-
3206 
-
3207  uint256 const offerBuyerToMinter =
-
3208  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3209  env(token::createOffer(buyer, nftokenID0, drops(1)),
-
3210  token::owner(minter),
-
3211  token::expiration(expiration));
-
3212  env.close();
-
3213  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
3214  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3215  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3216 
-
3217  // Test who gets to cancel the offers. Anyone outside of the
-
3218  // offer-owner/destination pair should not be able to cancel
-
3219  // unexpired offers.
-
3220  //
-
3221  // Note that these are tec responses, so these transactions will
-
3222  // not be retried by the ledger.
-
3223  env(token::cancelOffer(issuer, {offerMinterToAnyone}),
-
3224  ter(tecNO_PERMISSION));
-
3225  env(token::cancelOffer(buyer, {offerIssuerToMinter}),
-
3226  ter(tecNO_PERMISSION));
-
3227  env.close();
-
3228  BEAST_EXPECT(lastClose(env) < expiration);
-
3229  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
3230  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3231  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3232 
-
3233  // The offer creator can cancel their own unexpired offer.
-
3234  env(token::cancelOffer(minter, {offerMinterToAnyone}));
-
3235 
-
3236  // The destination of a sell offer can cancel the NFT owner's
-
3237  // unexpired offer.
-
3238  env(token::cancelOffer(issuer, {offerMinterToIssuer}));
-
3239 
-
3240  // Close enough ledgers to get past the expiration.
-
3241  while (lastClose(env) < expiration)
-
3242  env.close();
+
3183  Env env{*this, features};
+
3184 
+
3185  Account const issuer{"issuer"};
+
3186  Account const minter{"minter"};
+
3187  Account const buyer{"buyer"};
+
3188 
+
3189  env.fund(XRP(1000), issuer, minter, buyer);
+
3190 
+
3191  // We want to explore how issuers vs minters fits into the permission
+
3192  // scheme. So issuer issues and minter mints.
+
3193  env(token::setMinter(issuer, minter));
+
3194  env.close();
+
3195 
+
3196  uint256 const nftokenID0 =
+
3197  token::getNextID(env, issuer, 0, tfTransferable);
+
3198  env(token::mint(minter, 0),
+
3199  token::issuer(issuer),
+
3200  txflags(tfTransferable));
+
3201  env.close();
+
3202 
+
3203  uint256 const nftokenID1 =
+
3204  token::getNextID(env, issuer, 0, tfTransferable);
+
3205  env(token::mint(minter, 0),
+
3206  token::issuer(issuer),
+
3207  txflags(tfTransferable));
+
3208  env.close();
+
3209 
+
3210  // Test how adding an Expiration field to an offer affects permissions
+
3211  // for cancelling offers.
+
3212  {
+
3213  std::uint32_t const expiration = lastClose(env) + 25;
+
3214 
+
3215  uint256 const offerMinterToIssuer =
+
3216  keylet::nftoffer(minter, env.seq(minter)).key;
+
3217  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3218  token::destination(issuer),
+
3219  token::expiration(expiration),
+
3220  txflags(tfSellNFToken));
+
3221 
+
3222  uint256 const offerMinterToAnyone =
+
3223  keylet::nftoffer(minter, env.seq(minter)).key;
+
3224  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3225  token::expiration(expiration),
+
3226  txflags(tfSellNFToken));
+
3227 
+
3228  uint256 const offerIssuerToMinter =
+
3229  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
3230  env(token::createOffer(issuer, nftokenID0, drops(1)),
+
3231  token::owner(minter),
+
3232  token::expiration(expiration));
+
3233 
+
3234  uint256 const offerBuyerToMinter =
+
3235  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3236  env(token::createOffer(buyer, nftokenID0, drops(1)),
+
3237  token::owner(minter),
+
3238  token::expiration(expiration));
+
3239  env.close();
+
3240  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
3241  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3242  BEAST_EXPECT(ownerCount(env, buyer) == 1);
3243 
-
3244  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
3245  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3246  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3247 
-
3248  // Anyone can cancel expired offers.
-
3249  env(token::cancelOffer(issuer, {offerBuyerToMinter}));
-
3250  env(token::cancelOffer(buyer, {offerIssuerToMinter}));
-
3251  env.close();
-
3252  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3253  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3254  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3255  }
-
3256  // Show that:
-
3257  // 1. An unexpired sell offer with an expiration can be accepted.
-
3258  // 2. An expired sell offer cannot be accepted and remains
-
3259  // in ledger after the accept fails.
-
3260  {
-
3261  std::uint32_t const expiration = lastClose(env) + 25;
+
3244  // Test who gets to cancel the offers. Anyone outside of the
+
3245  // offer-owner/destination pair should not be able to cancel
+
3246  // unexpired offers.
+
3247  //
+
3248  // Note that these are tec responses, so these transactions will
+
3249  // not be retried by the ledger.
+
3250  env(token::cancelOffer(issuer, {offerMinterToAnyone}),
+
3251  ter(tecNO_PERMISSION));
+
3252  env(token::cancelOffer(buyer, {offerIssuerToMinter}),
+
3253  ter(tecNO_PERMISSION));
+
3254  env.close();
+
3255  BEAST_EXPECT(lastClose(env) < expiration);
+
3256  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
3257  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3258  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3259 
+
3260  // The offer creator can cancel their own unexpired offer.
+
3261  env(token::cancelOffer(minter, {offerMinterToAnyone}));
3262 
-
3263  uint256 const offer0 =
-
3264  keylet::nftoffer(minter, env.seq(minter)).key;
-
3265  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3266  token::expiration(expiration),
-
3267  txflags(tfSellNFToken));
-
3268 
-
3269  uint256 const offer1 =
-
3270  keylet::nftoffer(minter, env.seq(minter)).key;
-
3271  env(token::createOffer(minter, nftokenID1, drops(1)),
-
3272  token::expiration(expiration),
-
3273  txflags(tfSellNFToken));
-
3274  env.close();
-
3275  BEAST_EXPECT(lastClose(env) < expiration);
-
3276  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3277  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3278  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3279 
-
3280  // Anyone can accept an unexpired sell offer.
-
3281  env(token::acceptSellOffer(buyer, offer0));
-
3282 
-
3283  // Close enough ledgers to get past the expiration.
-
3284  while (lastClose(env) < expiration)
-
3285  env.close();
-
3286 
-
3287  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3288  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3289  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3290 
-
3291  // No one can accept an expired sell offer.
-
3292  env(token::acceptSellOffer(buyer, offer1), ter(tecEXPIRED));
-
3293  env(token::acceptSellOffer(issuer, offer1), ter(tecEXPIRED));
-
3294  env.close();
+
3263  // The destination of a sell offer can cancel the NFT owner's
+
3264  // unexpired offer.
+
3265  env(token::cancelOffer(issuer, {offerMinterToIssuer}));
+
3266 
+
3267  // Close enough ledgers to get past the expiration.
+
3268  while (lastClose(env) < expiration)
+
3269  env.close();
+
3270 
+
3271  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
3272  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3273  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3274 
+
3275  // Anyone can cancel expired offers.
+
3276  env(token::cancelOffer(issuer, {offerBuyerToMinter}));
+
3277  env(token::cancelOffer(buyer, {offerIssuerToMinter}));
+
3278  env.close();
+
3279  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3280  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3281  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3282  }
+
3283  // Show that:
+
3284  // 1. An unexpired sell offer with an expiration can be accepted.
+
3285  // 2. An expired sell offer cannot be accepted and remains
+
3286  // in ledger after the accept fails.
+
3287  {
+
3288  std::uint32_t const expiration = lastClose(env) + 25;
+
3289 
+
3290  uint256 const offer0 =
+
3291  keylet::nftoffer(minter, env.seq(minter)).key;
+
3292  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3293  token::expiration(expiration),
+
3294  txflags(tfSellNFToken));
3295 
-
3296  // The expired sell offer is still in the ledger.
-
3297  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3298  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3299  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3300 
-
3301  // Anyone can cancel the expired sell offer.
-
3302  env(token::cancelOffer(issuer, {offer1}));
-
3303  env.close();
-
3304  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3305  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3306  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3307 
-
3308  // Transfer nftokenID0 back to minter so we start the next test in
-
3309  // a simple place.
-
3310  uint256 const offerSellBack =
-
3311  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3312  env(token::createOffer(buyer, nftokenID0, XRP(0)),
-
3313  txflags(tfSellNFToken),
-
3314  token::destination(minter));
-
3315  env.close();
-
3316  env(token::acceptSellOffer(minter, offerSellBack));
-
3317  env.close();
-
3318  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3319  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3320  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3321  }
-
3322  // Show that:
-
3323  // 1. An unexpired buy offer with an expiration can be accepted.
-
3324  // 2. An expired buy offer cannot be accepted and remains
-
3325  // in ledger after the accept fails.
-
3326  {
-
3327  std::uint32_t const expiration = lastClose(env) + 25;
-
3328 
-
3329  uint256 const offer0 = keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3330  env(token::createOffer(buyer, nftokenID0, drops(1)),
-
3331  token::owner(minter),
-
3332  token::expiration(expiration));
-
3333 
-
3334  uint256 const offer1 = keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3335  env(token::createOffer(buyer, nftokenID1, drops(1)),
-
3336  token::owner(minter),
-
3337  token::expiration(expiration));
-
3338  env.close();
-
3339  BEAST_EXPECT(lastClose(env) < expiration);
-
3340  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3341  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3342  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3343 
-
3344  // An unexpired buy offer can be accepted.
-
3345  env(token::acceptBuyOffer(minter, offer0));
-
3346 
-
3347  // Close enough ledgers to get past the expiration.
-
3348  while (lastClose(env) < expiration)
-
3349  env.close();
-
3350 
-
3351  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3352  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3353  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3354 
-
3355  // An expired buy offer cannot be accepted.
-
3356  env(token::acceptBuyOffer(minter, offer1), ter(tecEXPIRED));
-
3357  env(token::acceptBuyOffer(issuer, offer1), ter(tecEXPIRED));
-
3358  env.close();
-
3359 
-
3360  // The expired buy offer is still in the ledger.
-
3361  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3362  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3363  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3364 
-
3365  // Anyone can cancel the expired buy offer.
-
3366  env(token::cancelOffer(issuer, {offer1}));
-
3367  env.close();
-
3368  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3369  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3370  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3371 
-
3372  // Transfer nftokenID0 back to minter so we start the next test in
-
3373  // a simple place.
-
3374  uint256 const offerSellBack =
-
3375  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3376  env(token::createOffer(buyer, nftokenID0, XRP(0)),
-
3377  txflags(tfSellNFToken),
-
3378  token::destination(minter));
-
3379  env.close();
-
3380  env(token::acceptSellOffer(minter, offerSellBack));
-
3381  env.close();
-
3382  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3383  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3384  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3385  }
-
3386  // Show that in brokered mode:
-
3387  // 1. An unexpired sell offer with an expiration can be accepted.
-
3388  // 2. An expired sell offer cannot be accepted and remains
-
3389  // in ledger after the accept fails.
-
3390  {
-
3391  std::uint32_t const expiration = lastClose(env) + 25;
-
3392 
-
3393  uint256 const sellOffer0 =
-
3394  keylet::nftoffer(minter, env.seq(minter)).key;
-
3395  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3396  token::expiration(expiration),
-
3397  txflags(tfSellNFToken));
+
3296  uint256 const offer1 =
+
3297  keylet::nftoffer(minter, env.seq(minter)).key;
+
3298  env(token::createOffer(minter, nftokenID1, drops(1)),
+
3299  token::expiration(expiration),
+
3300  txflags(tfSellNFToken));
+
3301  env.close();
+
3302  BEAST_EXPECT(lastClose(env) < expiration);
+
3303  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3304  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3305  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3306 
+
3307  // Anyone can accept an unexpired sell offer.
+
3308  env(token::acceptSellOffer(buyer, offer0));
+
3309 
+
3310  // Close enough ledgers to get past the expiration.
+
3311  while (lastClose(env) < expiration)
+
3312  env.close();
+
3313 
+
3314  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3315  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3316  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3317 
+
3318  // No one can accept an expired sell offer.
+
3319  env(token::acceptSellOffer(buyer, offer1), ter(tecEXPIRED));
+
3320  env(token::acceptSellOffer(issuer, offer1), ter(tecEXPIRED));
+
3321  env.close();
+
3322 
+
3323  // The expired sell offer is still in the ledger.
+
3324  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3325  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3326  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3327 
+
3328  // Anyone can cancel the expired sell offer.
+
3329  env(token::cancelOffer(issuer, {offer1}));
+
3330  env.close();
+
3331  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3332  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3333  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3334 
+
3335  // Transfer nftokenID0 back to minter so we start the next test in
+
3336  // a simple place.
+
3337  uint256 const offerSellBack =
+
3338  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3339  env(token::createOffer(buyer, nftokenID0, XRP(0)),
+
3340  txflags(tfSellNFToken),
+
3341  token::destination(minter));
+
3342  env.close();
+
3343  env(token::acceptSellOffer(minter, offerSellBack));
+
3344  env.close();
+
3345  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3346  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3347  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3348  }
+
3349  // Show that:
+
3350  // 1. An unexpired buy offer with an expiration can be accepted.
+
3351  // 2. An expired buy offer cannot be accepted and remains
+
3352  // in ledger after the accept fails.
+
3353  {
+
3354  std::uint32_t const expiration = lastClose(env) + 25;
+
3355 
+
3356  uint256 const offer0 = keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3357  env(token::createOffer(buyer, nftokenID0, drops(1)),
+
3358  token::owner(minter),
+
3359  token::expiration(expiration));
+
3360 
+
3361  uint256 const offer1 = keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3362  env(token::createOffer(buyer, nftokenID1, drops(1)),
+
3363  token::owner(minter),
+
3364  token::expiration(expiration));
+
3365  env.close();
+
3366  BEAST_EXPECT(lastClose(env) < expiration);
+
3367  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3368  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3369  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3370 
+
3371  // An unexpired buy offer can be accepted.
+
3372  env(token::acceptBuyOffer(minter, offer0));
+
3373 
+
3374  // Close enough ledgers to get past the expiration.
+
3375  while (lastClose(env) < expiration)
+
3376  env.close();
+
3377 
+
3378  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3379  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3380  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3381 
+
3382  // An expired buy offer cannot be accepted.
+
3383  env(token::acceptBuyOffer(minter, offer1), ter(tecEXPIRED));
+
3384  env(token::acceptBuyOffer(issuer, offer1), ter(tecEXPIRED));
+
3385  env.close();
+
3386 
+
3387  // The expired buy offer is still in the ledger.
+
3388  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3389  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3390  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3391 
+
3392  // Anyone can cancel the expired buy offer.
+
3393  env(token::cancelOffer(issuer, {offer1}));
+
3394  env.close();
+
3395  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3396  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3397  BEAST_EXPECT(ownerCount(env, buyer) == 1);
3398 
-
3399  uint256 const sellOffer1 =
-
3400  keylet::nftoffer(minter, env.seq(minter)).key;
-
3401  env(token::createOffer(minter, nftokenID1, drops(1)),
-
3402  token::expiration(expiration),
-
3403  txflags(tfSellNFToken));
-
3404 
-
3405  uint256 const buyOffer0 =
-
3406  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3407  env(token::createOffer(buyer, nftokenID0, drops(1)),
-
3408  token::owner(minter));
-
3409 
-
3410  uint256 const buyOffer1 =
-
3411  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3412  env(token::createOffer(buyer, nftokenID1, drops(1)),
-
3413  token::owner(minter));
-
3414 
-
3415  env.close();
-
3416  BEAST_EXPECT(lastClose(env) < expiration);
-
3417  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3418  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3419  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3420 
-
3421  // An unexpired offer can be brokered.
-
3422  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
-
3423 
-
3424  // Close enough ledgers to get past the expiration.
-
3425  while (lastClose(env) < expiration)
-
3426  env.close();
-
3427 
-
3428  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3429  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3430  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3399  // Transfer nftokenID0 back to minter so we start the next test in
+
3400  // a simple place.
+
3401  uint256 const offerSellBack =
+
3402  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3403  env(token::createOffer(buyer, nftokenID0, XRP(0)),
+
3404  txflags(tfSellNFToken),
+
3405  token::destination(minter));
+
3406  env.close();
+
3407  env(token::acceptSellOffer(minter, offerSellBack));
+
3408  env.close();
+
3409  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3410  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3411  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3412  }
+
3413  // Show that in brokered mode:
+
3414  // 1. An unexpired sell offer with an expiration can be accepted.
+
3415  // 2. An expired sell offer cannot be accepted and remains
+
3416  // in ledger after the accept fails.
+
3417  {
+
3418  std::uint32_t const expiration = lastClose(env) + 25;
+
3419 
+
3420  uint256 const sellOffer0 =
+
3421  keylet::nftoffer(minter, env.seq(minter)).key;
+
3422  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3423  token::expiration(expiration),
+
3424  txflags(tfSellNFToken));
+
3425 
+
3426  uint256 const sellOffer1 =
+
3427  keylet::nftoffer(minter, env.seq(minter)).key;
+
3428  env(token::createOffer(minter, nftokenID1, drops(1)),
+
3429  token::expiration(expiration),
+
3430  txflags(tfSellNFToken));
3431 
-
3432  // If the sell offer is expired it cannot be brokered.
-
3433  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
-
3434  ter(tecEXPIRED));
-
3435  env.close();
+
3432  uint256 const buyOffer0 =
+
3433  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3434  env(token::createOffer(buyer, nftokenID0, drops(1)),
+
3435  token::owner(minter));
3436 
-
3437  // The expired sell offer is still in the ledger.
-
3438  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3439  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3440  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3437  uint256 const buyOffer1 =
+
3438  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3439  env(token::createOffer(buyer, nftokenID1, drops(1)),
+
3440  token::owner(minter));
3441 
-
3442  // Anyone can cancel the expired sell offer.
-
3443  env(token::cancelOffer(buyer, {buyOffer1, sellOffer1}));
-
3444  env.close();
-
3445  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3446  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3447  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3448 
-
3449  // Transfer nftokenID0 back to minter so we start the next test in
-
3450  // a simple place.
-
3451  uint256 const offerSellBack =
-
3452  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3453  env(token::createOffer(buyer, nftokenID0, XRP(0)),
-
3454  txflags(tfSellNFToken),
-
3455  token::destination(minter));
-
3456  env.close();
-
3457  env(token::acceptSellOffer(minter, offerSellBack));
-
3458  env.close();
-
3459  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3460  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3461  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3462  }
-
3463  // Show that in brokered mode:
-
3464  // 1. An unexpired buy offer with an expiration can be accepted.
-
3465  // 2. An expired buy offer cannot be accepted and remains
-
3466  // in ledger after the accept fails.
-
3467  {
-
3468  std::uint32_t const expiration = lastClose(env) + 25;
-
3469 
-
3470  uint256 const sellOffer0 =
-
3471  keylet::nftoffer(minter, env.seq(minter)).key;
-
3472  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3473  txflags(tfSellNFToken));
-
3474 
-
3475  uint256 const sellOffer1 =
-
3476  keylet::nftoffer(minter, env.seq(minter)).key;
-
3477  env(token::createOffer(minter, nftokenID1, drops(1)),
-
3478  txflags(tfSellNFToken));
-
3479 
-
3480  uint256 const buyOffer0 =
-
3481  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3482  env(token::createOffer(buyer, nftokenID0, drops(1)),
-
3483  token::expiration(expiration),
-
3484  token::owner(minter));
-
3485 
-
3486  uint256 const buyOffer1 =
-
3487  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3488  env(token::createOffer(buyer, nftokenID1, drops(1)),
-
3489  token::expiration(expiration),
-
3490  token::owner(minter));
-
3491 
-
3492  env.close();
-
3493  BEAST_EXPECT(lastClose(env) < expiration);
-
3494  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3495  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3496  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3497 
-
3498  // An unexpired offer can be brokered.
-
3499  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
-
3500 
-
3501  // Close enough ledgers to get past the expiration.
-
3502  while (lastClose(env) < expiration)
-
3503  env.close();
-
3504 
-
3505  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3506  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3507  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3508 
-
3509  // If the buy offer is expired it cannot be brokered.
-
3510  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
-
3511  ter(tecEXPIRED));
-
3512  env.close();
-
3513 
-
3514  // The expired buy offer is still in the ledger.
-
3515  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3516  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3517  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3442  env.close();
+
3443  BEAST_EXPECT(lastClose(env) < expiration);
+
3444  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3445  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3446  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3447 
+
3448  // An unexpired offer can be brokered.
+
3449  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
+
3450 
+
3451  // Close enough ledgers to get past the expiration.
+
3452  while (lastClose(env) < expiration)
+
3453  env.close();
+
3454 
+
3455  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3456  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3457  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3458 
+
3459  // If the sell offer is expired it cannot be brokered.
+
3460  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
+
3461  ter(tecEXPIRED));
+
3462  env.close();
+
3463 
+
3464  // The expired sell offer is still in the ledger.
+
3465  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3466  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3467  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3468 
+
3469  // Anyone can cancel the expired sell offer.
+
3470  env(token::cancelOffer(buyer, {buyOffer1, sellOffer1}));
+
3471  env.close();
+
3472  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3473  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3474  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3475 
+
3476  // Transfer nftokenID0 back to minter so we start the next test in
+
3477  // a simple place.
+
3478  uint256 const offerSellBack =
+
3479  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3480  env(token::createOffer(buyer, nftokenID0, XRP(0)),
+
3481  txflags(tfSellNFToken),
+
3482  token::destination(minter));
+
3483  env.close();
+
3484  env(token::acceptSellOffer(minter, offerSellBack));
+
3485  env.close();
+
3486  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3487  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3488  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3489  }
+
3490  // Show that in brokered mode:
+
3491  // 1. An unexpired buy offer with an expiration can be accepted.
+
3492  // 2. An expired buy offer cannot be accepted and remains
+
3493  // in ledger after the accept fails.
+
3494  {
+
3495  std::uint32_t const expiration = lastClose(env) + 25;
+
3496 
+
3497  uint256 const sellOffer0 =
+
3498  keylet::nftoffer(minter, env.seq(minter)).key;
+
3499  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3500  txflags(tfSellNFToken));
+
3501 
+
3502  uint256 const sellOffer1 =
+
3503  keylet::nftoffer(minter, env.seq(minter)).key;
+
3504  env(token::createOffer(minter, nftokenID1, drops(1)),
+
3505  txflags(tfSellNFToken));
+
3506 
+
3507  uint256 const buyOffer0 =
+
3508  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3509  env(token::createOffer(buyer, nftokenID0, drops(1)),
+
3510  token::expiration(expiration),
+
3511  token::owner(minter));
+
3512 
+
3513  uint256 const buyOffer1 =
+
3514  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3515  env(token::createOffer(buyer, nftokenID1, drops(1)),
+
3516  token::expiration(expiration),
+
3517  token::owner(minter));
3518 
-
3519  // Anyone can cancel the expired buy offer.
-
3520  env(token::cancelOffer(minter, {buyOffer1, sellOffer1}));
-
3521  env.close();
-
3522  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3523  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3524  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3525 
-
3526  // Transfer nftokenID0 back to minter so we start the next test in
-
3527  // a simple place.
-
3528  uint256 const offerSellBack =
-
3529  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3530  env(token::createOffer(buyer, nftokenID0, XRP(0)),
-
3531  txflags(tfSellNFToken),
-
3532  token::destination(minter));
-
3533  env.close();
-
3534  env(token::acceptSellOffer(minter, offerSellBack));
-
3535  env.close();
-
3536  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3537  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3538  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3539  }
-
3540  // Show that in brokered mode:
-
3541  // 1. An unexpired buy/sell offer pair with an expiration can be
-
3542  // accepted.
-
3543  // 2. An expired buy/sell offer pair cannot be accepted and they
-
3544  // remain in ledger after the accept fails.
-
3545  {
-
3546  std::uint32_t const expiration = lastClose(env) + 25;
-
3547 
-
3548  uint256 const sellOffer0 =
-
3549  keylet::nftoffer(minter, env.seq(minter)).key;
-
3550  env(token::createOffer(minter, nftokenID0, drops(1)),
-
3551  token::expiration(expiration),
-
3552  txflags(tfSellNFToken));
-
3553 
-
3554  uint256 const sellOffer1 =
-
3555  keylet::nftoffer(minter, env.seq(minter)).key;
-
3556  env(token::createOffer(minter, nftokenID1, drops(1)),
-
3557  token::expiration(expiration),
-
3558  txflags(tfSellNFToken));
-
3559 
-
3560  uint256 const buyOffer0 =
-
3561  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3562  env(token::createOffer(buyer, nftokenID0, drops(1)),
-
3563  token::expiration(expiration),
-
3564  token::owner(minter));
-
3565 
-
3566  uint256 const buyOffer1 =
-
3567  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3568  env(token::createOffer(buyer, nftokenID1, drops(1)),
-
3569  token::expiration(expiration),
-
3570  token::owner(minter));
-
3571 
-
3572  env.close();
-
3573  BEAST_EXPECT(lastClose(env) < expiration);
-
3574  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3575  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
3576  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3577 
-
3578  // Unexpired offers can be brokered.
-
3579  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
+
3519  env.close();
+
3520  BEAST_EXPECT(lastClose(env) < expiration);
+
3521  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3522  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3523  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3524 
+
3525  // An unexpired offer can be brokered.
+
3526  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
+
3527 
+
3528  // Close enough ledgers to get past the expiration.
+
3529  while (lastClose(env) < expiration)
+
3530  env.close();
+
3531 
+
3532  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3533  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3534  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3535 
+
3536  // If the buy offer is expired it cannot be brokered.
+
3537  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
+
3538  ter(tecEXPIRED));
+
3539  env.close();
+
3540 
+
3541  // The expired buy offer is still in the ledger.
+
3542  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3543  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3544  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3545 
+
3546  // Anyone can cancel the expired buy offer.
+
3547  env(token::cancelOffer(minter, {buyOffer1, sellOffer1}));
+
3548  env.close();
+
3549  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3550  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3551  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3552 
+
3553  // Transfer nftokenID0 back to minter so we start the next test in
+
3554  // a simple place.
+
3555  uint256 const offerSellBack =
+
3556  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3557  env(token::createOffer(buyer, nftokenID0, XRP(0)),
+
3558  txflags(tfSellNFToken),
+
3559  token::destination(minter));
+
3560  env.close();
+
3561  env(token::acceptSellOffer(minter, offerSellBack));
+
3562  env.close();
+
3563  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3564  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3565  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3566  }
+
3567  // Show that in brokered mode:
+
3568  // 1. An unexpired buy/sell offer pair with an expiration can be
+
3569  // accepted.
+
3570  // 2. An expired buy/sell offer pair cannot be accepted and they
+
3571  // remain in ledger after the accept fails.
+
3572  {
+
3573  std::uint32_t const expiration = lastClose(env) + 25;
+
3574 
+
3575  uint256 const sellOffer0 =
+
3576  keylet::nftoffer(minter, env.seq(minter)).key;
+
3577  env(token::createOffer(minter, nftokenID0, drops(1)),
+
3578  token::expiration(expiration),
+
3579  txflags(tfSellNFToken));
3580 
-
3581  // Close enough ledgers to get past the expiration.
-
3582  while (lastClose(env) < expiration)
-
3583  env.close();
-
3584 
-
3585  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3586  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3587  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
3588 
-
3589  // If the offers are expired they cannot be brokered.
-
3590  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
-
3591  ter(tecEXPIRED));
-
3592  env.close();
-
3593 
-
3594  // The expired offers are still in the ledger.
-
3595  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3596  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3597  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3581  uint256 const sellOffer1 =
+
3582  keylet::nftoffer(minter, env.seq(minter)).key;
+
3583  env(token::createOffer(minter, nftokenID1, drops(1)),
+
3584  token::expiration(expiration),
+
3585  txflags(tfSellNFToken));
+
3586 
+
3587  uint256 const buyOffer0 =
+
3588  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3589  env(token::createOffer(buyer, nftokenID0, drops(1)),
+
3590  token::expiration(expiration),
+
3591  token::owner(minter));
+
3592 
+
3593  uint256 const buyOffer1 =
+
3594  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3595  env(token::createOffer(buyer, nftokenID1, drops(1)),
+
3596  token::expiration(expiration),
+
3597  token::owner(minter));
3598 
-
3599  // Anyone can cancel the expired offers.
-
3600  env(token::cancelOffer(issuer, {buyOffer1, sellOffer1}));
-
3601  env.close();
-
3602  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3603  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3604  BEAST_EXPECT(ownerCount(env, buyer) == 1);
-
3605 
-
3606  // Transfer nftokenID0 back to minter so we start the next test in
-
3607  // a simple place.
-
3608  uint256 const offerSellBack =
-
3609  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3610  env(token::createOffer(buyer, nftokenID0, XRP(0)),
-
3611  txflags(tfSellNFToken),
-
3612  token::destination(minter));
-
3613  env.close();
-
3614  env(token::acceptSellOffer(minter, offerSellBack));
-
3615  env.close();
-
3616  BEAST_EXPECT(ownerCount(env, issuer) == 0);
-
3617  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3618  BEAST_EXPECT(ownerCount(env, buyer) == 0);
-
3619  }
-
3620  }
-
3621 
-
3622  void
-
3623  testCancelOffers(FeatureBitset features)
-
3624  {
-
3625  // Look at offer canceling.
-
3626  testcase("Cancel offers");
-
3627 
-
3628  using namespace test::jtx;
-
3629 
-
3630  Env env{*this, features};
-
3631 
-
3632  Account const alice("alice");
-
3633  Account const becky("becky");
-
3634  Account const minter("minter");
-
3635  env.fund(XRP(50000), alice, becky, minter);
-
3636  env.close();
-
3637 
-
3638  // alice has a minter to see if minters have offer canceling permission.
-
3639  env(token::setMinter(alice, minter));
-
3640  env.close();
-
3641 
-
3642  uint256 const nftokenID =
-
3643  token::getNextID(env, alice, 0, tfTransferable);
-
3644  env(token::mint(alice, 0), txflags(tfTransferable));
-
3645  env.close();
-
3646 
-
3647  // Anyone can cancel an expired offer.
-
3648  uint256 const expiredOfferIndex =
-
3649  keylet::nftoffer(alice, env.seq(alice)).key;
-
3650 
-
3651  env(token::createOffer(alice, nftokenID, XRP(1000)),
-
3652  txflags(tfSellNFToken),
-
3653  token::expiration(lastClose(env) + 13));
-
3654  env.close();
-
3655 
-
3656  // The offer has not expired yet, so becky can't cancel it now.
-
3657  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
3658  env(token::cancelOffer(becky, {expiredOfferIndex}),
-
3659  ter(tecNO_PERMISSION));
-
3660  env.close();
-
3661 
-
3662  // Close a couple of ledgers and advance the time. Then becky
-
3663  // should be able to cancel the (now) expired offer.
-
3664  env.close();
-
3665  env.close();
-
3666  env(token::cancelOffer(becky, {expiredOfferIndex}));
+
3599  env.close();
+
3600  BEAST_EXPECT(lastClose(env) < expiration);
+
3601  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3602  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
3603  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3604 
+
3605  // Unexpired offers can be brokered.
+
3606  env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
+
3607 
+
3608  // Close enough ledgers to get past the expiration.
+
3609  while (lastClose(env) < expiration)
+
3610  env.close();
+
3611 
+
3612  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3613  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3614  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3615 
+
3616  // If the offers are expired they cannot be brokered.
+
3617  env(token::brokerOffers(issuer, buyOffer1, sellOffer1),
+
3618  ter(tecEXPIRED));
+
3619  env.close();
+
3620 
+
3621  // The expired offers are still in the ledger.
+
3622  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3623  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3624  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
3625 
+
3626  // Anyone can cancel the expired offers.
+
3627  env(token::cancelOffer(issuer, {buyOffer1, sellOffer1}));
+
3628  env.close();
+
3629  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3630  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3631  BEAST_EXPECT(ownerCount(env, buyer) == 1);
+
3632 
+
3633  // Transfer nftokenID0 back to minter so we start the next test in
+
3634  // a simple place.
+
3635  uint256 const offerSellBack =
+
3636  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3637  env(token::createOffer(buyer, nftokenID0, XRP(0)),
+
3638  txflags(tfSellNFToken),
+
3639  token::destination(minter));
+
3640  env.close();
+
3641  env(token::acceptSellOffer(minter, offerSellBack));
+
3642  env.close();
+
3643  BEAST_EXPECT(ownerCount(env, issuer) == 0);
+
3644  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3645  BEAST_EXPECT(ownerCount(env, buyer) == 0);
+
3646  }
+
3647  }
+
3648 
+
3649  void
+
3650  testCancelOffers(FeatureBitset features)
+
3651  {
+
3652  // Look at offer canceling.
+
3653  testcase("Cancel offers");
+
3654 
+
3655  using namespace test::jtx;
+
3656 
+
3657  Env env{*this, features};
+
3658 
+
3659  Account const alice("alice");
+
3660  Account const becky("becky");
+
3661  Account const minter("minter");
+
3662  env.fund(XRP(50000), alice, becky, minter);
+
3663  env.close();
+
3664 
+
3665  // alice has a minter to see if minters have offer canceling permission.
+
3666  env(token::setMinter(alice, minter));
3667  env.close();
-
3668  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
3669 
-
3670  // Create a couple of offers with a destination. Those offers
-
3671  // should be cancellable by the creator and the destination.
-
3672  uint256 const dest1OfferIndex =
-
3673  keylet::nftoffer(alice, env.seq(alice)).key;
-
3674 
-
3675  env(token::createOffer(alice, nftokenID, XRP(1000)),
-
3676  token::destination(becky),
-
3677  txflags(tfSellNFToken));
-
3678  env.close();
-
3679  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
3680 
-
3681  // Minter can't cancel that offer, but becky (the destination) can.
-
3682  env(token::cancelOffer(minter, {dest1OfferIndex}),
-
3683  ter(tecNO_PERMISSION));
-
3684  env.close();
-
3685  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
3686 
-
3687  env(token::cancelOffer(becky, {dest1OfferIndex}));
-
3688  env.close();
-
3689  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
3690 
-
3691  // alice can cancel her own offer, even if becky is the destination.
-
3692  uint256 const dest2OfferIndex =
-
3693  keylet::nftoffer(alice, env.seq(alice)).key;
-
3694 
-
3695  env(token::createOffer(alice, nftokenID, XRP(1000)),
-
3696  token::destination(becky),
-
3697  txflags(tfSellNFToken));
-
3698  env.close();
-
3699  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
3700 
-
3701  env(token::cancelOffer(alice, {dest2OfferIndex}));
-
3702  env.close();
-
3703  BEAST_EXPECT(ownerCount(env, alice) == 1);
-
3704 
-
3705  // The issuer has no special permissions regarding offer cancellation.
-
3706  // Minter creates a token with alice as issuer. alice cannot cancel
-
3707  // minter's offer.
-
3708  uint256 const mintersNFTokenID =
-
3709  token::getNextID(env, alice, 0, tfTransferable);
-
3710  env(token::mint(minter, 0),
-
3711  token::issuer(alice),
-
3712  txflags(tfTransferable));
-
3713  env.close();
-
3714 
-
3715  uint256 const minterOfferIndex =
-
3716  keylet::nftoffer(minter, env.seq(minter)).key;
+
3668 
+
3669  uint256 const nftokenID =
+
3670  token::getNextID(env, alice, 0, tfTransferable);
+
3671  env(token::mint(alice, 0), txflags(tfTransferable));
+
3672  env.close();
+
3673 
+
3674  // Anyone can cancel an expired offer.
+
3675  uint256 const expiredOfferIndex =
+
3676  keylet::nftoffer(alice, env.seq(alice)).key;
+
3677 
+
3678  env(token::createOffer(alice, nftokenID, XRP(1000)),
+
3679  txflags(tfSellNFToken),
+
3680  token::expiration(lastClose(env) + 13));
+
3681  env.close();
+
3682 
+
3683  // The offer has not expired yet, so becky can't cancel it now.
+
3684  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
3685  env(token::cancelOffer(becky, {expiredOfferIndex}),
+
3686  ter(tecNO_PERMISSION));
+
3687  env.close();
+
3688 
+
3689  // Close a couple of ledgers and advance the time. Then becky
+
3690  // should be able to cancel the (now) expired offer.
+
3691  env.close();
+
3692  env.close();
+
3693  env(token::cancelOffer(becky, {expiredOfferIndex}));
+
3694  env.close();
+
3695  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
3696 
+
3697  // Create a couple of offers with a destination. Those offers
+
3698  // should be cancellable by the creator and the destination.
+
3699  uint256 const dest1OfferIndex =
+
3700  keylet::nftoffer(alice, env.seq(alice)).key;
+
3701 
+
3702  env(token::createOffer(alice, nftokenID, XRP(1000)),
+
3703  token::destination(becky),
+
3704  txflags(tfSellNFToken));
+
3705  env.close();
+
3706  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
3707 
+
3708  // Minter can't cancel that offer, but becky (the destination) can.
+
3709  env(token::cancelOffer(minter, {dest1OfferIndex}),
+
3710  ter(tecNO_PERMISSION));
+
3711  env.close();
+
3712  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
3713 
+
3714  env(token::cancelOffer(becky, {dest1OfferIndex}));
+
3715  env.close();
+
3716  BEAST_EXPECT(ownerCount(env, alice) == 1);
3717 
-
3718  env(token::createOffer(minter, mintersNFTokenID, XRP(1000)),
-
3719  txflags(tfSellNFToken));
-
3720  env.close();
-
3721  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3722 
-
3723  // Nobody other than minter should be able to cancel minter's offer.
-
3724  env(token::cancelOffer(alice, {minterOfferIndex}),
-
3725  ter(tecNO_PERMISSION));
-
3726  env(token::cancelOffer(becky, {minterOfferIndex}),
-
3727  ter(tecNO_PERMISSION));
-
3728  env.close();
-
3729  BEAST_EXPECT(ownerCount(env, minter) == 2);
-
3730 
-
3731  env(token::cancelOffer(minter, {minterOfferIndex}));
-
3732  env.close();
-
3733  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
3734  }
-
3735 
-
3736  void
-
3737  testCancelTooManyOffers(FeatureBitset features)
-
3738  {
-
3739  // Look at the case where too many offers are passed in a cancel.
-
3740  testcase("Cancel too many offers");
+
3718  // alice can cancel her own offer, even if becky is the destination.
+
3719  uint256 const dest2OfferIndex =
+
3720  keylet::nftoffer(alice, env.seq(alice)).key;
+
3721 
+
3722  env(token::createOffer(alice, nftokenID, XRP(1000)),
+
3723  token::destination(becky),
+
3724  txflags(tfSellNFToken));
+
3725  env.close();
+
3726  BEAST_EXPECT(ownerCount(env, alice) == 2);
+
3727 
+
3728  env(token::cancelOffer(alice, {dest2OfferIndex}));
+
3729  env.close();
+
3730  BEAST_EXPECT(ownerCount(env, alice) == 1);
+
3731 
+
3732  // The issuer has no special permissions regarding offer cancellation.
+
3733  // Minter creates a token with alice as issuer. alice cannot cancel
+
3734  // minter's offer.
+
3735  uint256 const mintersNFTokenID =
+
3736  token::getNextID(env, alice, 0, tfTransferable);
+
3737  env(token::mint(minter, 0),
+
3738  token::issuer(alice),
+
3739  txflags(tfTransferable));
+
3740  env.close();
3741 
-
3742  using namespace test::jtx;
-
3743 
-
3744  Env env{*this, features};
-
3745 
-
3746  // We want to maximize the metadata from a cancel offer transaction to
-
3747  // make sure we don't hit metadata limits. The way we'll do that is:
-
3748  //
-
3749  // 1. Generate twice as many separate funded accounts as we have
-
3750  // offers.
-
3751  // 2.
-
3752  // a. One of these accounts mints an NFT with a full URL.
-
3753  // b. The other account makes an offer that will expire soon.
-
3754  // 3. After all of these offers have expired, cancel all of the
-
3755  // expired offers in a single transaction.
-
3756  //
-
3757  // I can't think of any way to increase the metadata beyond this,
-
3758  // but I'm open to ideas.
-
3759  Account const alice("alice");
-
3760  env.fund(XRP(1000), alice);
-
3761  env.close();
+
3742  uint256 const minterOfferIndex =
+
3743  keylet::nftoffer(minter, env.seq(minter)).key;
+
3744 
+
3745  env(token::createOffer(minter, mintersNFTokenID, XRP(1000)),
+
3746  txflags(tfSellNFToken));
+
3747  env.close();
+
3748  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3749 
+
3750  // Nobody other than minter should be able to cancel minter's offer.
+
3751  env(token::cancelOffer(alice, {minterOfferIndex}),
+
3752  ter(tecNO_PERMISSION));
+
3753  env(token::cancelOffer(becky, {minterOfferIndex}),
+
3754  ter(tecNO_PERMISSION));
+
3755  env.close();
+
3756  BEAST_EXPECT(ownerCount(env, minter) == 2);
+
3757 
+
3758  env(token::cancelOffer(minter, {minterOfferIndex}));
+
3759  env.close();
+
3760  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
3761  }
3762 
-
3763  std::string const uri(maxTokenURILength, '?');
-
3764  std::vector<uint256> offerIndexes;
-
3765  offerIndexes.reserve(maxTokenOfferCancelCount + 1);
-
3766  for (uint32_t i = 0; i < maxTokenOfferCancelCount + 1; ++i)
-
3767  {
-
3768  Account const nftAcct(std::string("nftAcct") + std::to_string(i));
-
3769  Account const offerAcct(
-
3770  std::string("offerAcct") + std::to_string(i));
-
3771  env.fund(XRP(1000), nftAcct, offerAcct);
-
3772  env.close();
-
3773 
-
3774  uint256 const nftokenID =
-
3775  token::getNextID(env, nftAcct, 0, tfTransferable);
-
3776  env(token::mint(nftAcct, 0),
-
3777  token::uri(uri),
-
3778  txflags(tfTransferable));
-
3779  env.close();
-
3780 
-
3781  offerIndexes.push_back(
-
3782  keylet::nftoffer(offerAcct, env.seq(offerAcct)).key);
-
3783  env(token::createOffer(offerAcct, nftokenID, drops(1)),
-
3784  token::owner(nftAcct),
-
3785  token::expiration(lastClose(env) + 5));
-
3786  env.close();
-
3787  }
-
3788 
-
3789  // Close the ledger so the last of the offers expire.
-
3790  env.close();
-
3791 
-
3792  // All offers should be in the ledger.
-
3793  for (uint256 const& offerIndex : offerIndexes)
+
3763  void
+
3764  testCancelTooManyOffers(FeatureBitset features)
+
3765  {
+
3766  // Look at the case where too many offers are passed in a cancel.
+
3767  testcase("Cancel too many offers");
+
3768 
+
3769  using namespace test::jtx;
+
3770 
+
3771  Env env{*this, features};
+
3772 
+
3773  // We want to maximize the metadata from a cancel offer transaction to
+
3774  // make sure we don't hit metadata limits. The way we'll do that is:
+
3775  //
+
3776  // 1. Generate twice as many separate funded accounts as we have
+
3777  // offers.
+
3778  // 2.
+
3779  // a. One of these accounts mints an NFT with a full URL.
+
3780  // b. The other account makes an offer that will expire soon.
+
3781  // 3. After all of these offers have expired, cancel all of the
+
3782  // expired offers in a single transaction.
+
3783  //
+
3784  // I can't think of any way to increase the metadata beyond this,
+
3785  // but I'm open to ideas.
+
3786  Account const alice("alice");
+
3787  env.fund(XRP(1000), alice);
+
3788  env.close();
+
3789 
+
3790  std::string const uri(maxTokenURILength, '?');
+
3791  std::vector<uint256> offerIndexes;
+
3792  offerIndexes.reserve(maxTokenOfferCancelCount + 1);
+
3793  for (uint32_t i = 0; i < maxTokenOfferCancelCount + 1; ++i)
3794  {
-
3795  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
-
3796  }
-
3797 
-
3798  // alice attempts to cancel all of the expired offers. There is one
-
3799  // too many so the request fails.
-
3800  env(token::cancelOffer(alice, offerIndexes), ter(temMALFORMED));
-
3801  env.close();
-
3802 
-
3803  // However alice can cancel just one of the offers.
-
3804  env(token::cancelOffer(alice, {offerIndexes.back()}));
-
3805  env.close();
-
3806 
-
3807  // Verify that offer is gone from the ledger.
-
3808  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndexes.back())));
-
3809  offerIndexes.pop_back();
-
3810 
-
3811  // But alice adds a sell offer to the list...
-
3812  {
-
3813  uint256 const nftokenID =
-
3814  token::getNextID(env, alice, 0, tfTransferable);
-
3815  env(token::mint(alice, 0),
-
3816  token::uri(uri),
-
3817  txflags(tfTransferable));
-
3818  env.close();
-
3819 
-
3820  offerIndexes.push_back(keylet::nftoffer(alice, env.seq(alice)).key);
-
3821  env(token::createOffer(alice, nftokenID, drops(1)),
-
3822  txflags(tfSellNFToken));
-
3823  env.close();
+
3795  Account const nftAcct(std::string("nftAcct") + std::to_string(i));
+
3796  Account const offerAcct(
+
3797  std::string("offerAcct") + std::to_string(i));
+
3798  env.fund(XRP(1000), nftAcct, offerAcct);
+
3799  env.close();
+
3800 
+
3801  uint256 const nftokenID =
+
3802  token::getNextID(env, nftAcct, 0, tfTransferable);
+
3803  env(token::mint(nftAcct, 0),
+
3804  token::uri(uri),
+
3805  txflags(tfTransferable));
+
3806  env.close();
+
3807 
+
3808  offerIndexes.push_back(
+
3809  keylet::nftoffer(offerAcct, env.seq(offerAcct)).key);
+
3810  env(token::createOffer(offerAcct, nftokenID, drops(1)),
+
3811  token::owner(nftAcct),
+
3812  token::expiration(lastClose(env) + 5));
+
3813  env.close();
+
3814  }
+
3815 
+
3816  // Close the ledger so the last of the offers expire.
+
3817  env.close();
+
3818 
+
3819  // All offers should be in the ledger.
+
3820  for (uint256 const& offerIndex : offerIndexes)
+
3821  {
+
3822  BEAST_EXPECT(env.le(keylet::nftoffer(offerIndex)));
+
3823  }
3824 
-
3825  // alice's owner count should now to 2 for the nft and the offer.
-
3826  BEAST_EXPECT(ownerCount(env, alice) == 2);
-
3827 
-
3828  // Because alice added the sell offer there are still too many
-
3829  // offers in the list to cancel.
-
3830  env(token::cancelOffer(alice, offerIndexes), ter(temMALFORMED));
-
3831  env.close();
-
3832 
-
3833  // alice burns her nft which removes the nft and the offer.
-
3834  env(token::burn(alice, nftokenID));
-
3835  env.close();
-
3836 
-
3837  // If alice's owner count is zero we can see that the offer
-
3838  // and nft are both gone.
-
3839  BEAST_EXPECT(ownerCount(env, alice) == 0);
-
3840  offerIndexes.pop_back();
-
3841  }
-
3842 
-
3843  // Now there are few enough offers in the list that they can all
-
3844  // be cancelled in a single transaction.
-
3845  env(token::cancelOffer(alice, offerIndexes));
-
3846  env.close();
-
3847 
-
3848  // Verify that remaining offers are gone from the ledger.
-
3849  for (uint256 const& offerIndex : offerIndexes)
-
3850  {
-
3851  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
-
3852  }
-
3853  }
+
3825  // alice attempts to cancel all of the expired offers. There is one
+
3826  // too many so the request fails.
+
3827  env(token::cancelOffer(alice, offerIndexes), ter(temMALFORMED));
+
3828  env.close();
+
3829 
+
3830  // However alice can cancel just one of the offers.
+
3831  env(token::cancelOffer(alice, {offerIndexes.back()}));
+
3832  env.close();
+
3833 
+
3834  // Verify that offer is gone from the ledger.
+
3835  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndexes.back())));
+
3836  offerIndexes.pop_back();
+
3837 
+
3838  // But alice adds a sell offer to the list...
+
3839  {
+
3840  uint256 const nftokenID =
+
3841  token::getNextID(env, alice, 0, tfTransferable);
+
3842  env(token::mint(alice, 0),
+
3843  token::uri(uri),
+
3844  txflags(tfTransferable));
+
3845  env.close();
+
3846 
+
3847  offerIndexes.push_back(keylet::nftoffer(alice, env.seq(alice)).key);
+
3848  env(token::createOffer(alice, nftokenID, drops(1)),
+
3849  txflags(tfSellNFToken));
+
3850  env.close();
+
3851 
+
3852  // alice's owner count should now to 2 for the nft and the offer.
+
3853  BEAST_EXPECT(ownerCount(env, alice) == 2);
3854 
-
3855  void
-
3856  testBrokeredAccept(FeatureBitset features)
-
3857  {
-
3858  // Look at the case where too many offers are passed in a cancel.
-
3859  testcase("Brokered NFT offer accept");
-
3860 
-
3861  using namespace test::jtx;
-
3862 
-
3863  for (auto const& tweakedFeatures :
-
3864  {features - fixNonFungibleTokensV1_2,
-
3865  features | fixNonFungibleTokensV1_2})
-
3866  {
-
3867  Env env{*this, tweakedFeatures};
-
3868 
-
3869  // The most important thing to explore here is the way funds are
-
3870  // assigned from the buyer to...
-
3871  // o the Seller,
-
3872  // o the Broker, and
-
3873  // o the Issuer (in the case of a transfer fee).
+
3855  // Because alice added the sell offer there are still too many
+
3856  // offers in the list to cancel.
+
3857  env(token::cancelOffer(alice, offerIndexes), ter(temMALFORMED));
+
3858  env.close();
+
3859 
+
3860  // alice burns her nft which removes the nft and the offer.
+
3861  env(token::burn(alice, nftokenID));
+
3862  env.close();
+
3863 
+
3864  // If alice's owner count is zero we can see that the offer
+
3865  // and nft are both gone.
+
3866  BEAST_EXPECT(ownerCount(env, alice) == 0);
+
3867  offerIndexes.pop_back();
+
3868  }
+
3869 
+
3870  // Now there are few enough offers in the list that they can all
+
3871  // be cancelled in a single transaction.
+
3872  env(token::cancelOffer(alice, offerIndexes));
+
3873  env.close();
3874 
-
3875  Account const issuer{"issuer"};
-
3876  Account const minter{"minter"};
-
3877  Account const buyer{"buyer"};
-
3878  Account const broker{"broker"};
-
3879  Account const gw{"gw"};
-
3880  IOU const gwXAU(gw["XAU"]);
+
3875  // Verify that remaining offers are gone from the ledger.
+
3876  for (uint256 const& offerIndex : offerIndexes)
+
3877  {
+
3878  BEAST_EXPECT(!env.le(keylet::nftoffer(offerIndex)));
+
3879  }
+
3880  }
3881 
-
3882  env.fund(XRP(1000), issuer, minter, buyer, broker, gw);
-
3883  env.close();
-
3884 
-
3885  env(trust(issuer, gwXAU(2000)));
-
3886  env(trust(minter, gwXAU(2000)));
-
3887  env(trust(buyer, gwXAU(2000)));
-
3888  env(trust(broker, gwXAU(2000)));
-
3889  env.close();
-
3890 
-
3891  env(token::setMinter(issuer, minter));
-
3892  env.close();
-
3893 
-
3894  // Lambda to check owner count of all accounts is one.
-
3895  auto checkOwnerCountIsOne =
-
3896  [this, &env](
-
3897  std::initializer_list<std::reference_wrapper<Account const>>
-
3898  accounts,
-
3899  int line) {
-
3900  for (Account const& acct : accounts)
-
3901  {
-
3902  if (std::uint32_t ownerCount =
-
3903  this->ownerCount(env, acct);
-
3904  ownerCount != 1)
-
3905  {
-
3906  std::stringstream ss;
-
3907  ss << "Account " << acct.human()
-
3908  << " expected ownerCount == 1. Got "
-
3909  << ownerCount;
-
3910  fail(ss.str(), __FILE__, line);
-
3911  }
-
3912  }
-
3913  };
-
3914 
-
3915  // Lambda that mints an NFT and returns the nftID.
-
3916  auto mintNFT = [&env, &issuer, &minter](std::uint16_t xferFee = 0) {
-
3917  uint256 const nftID =
-
3918  token::getNextID(env, issuer, 0, tfTransferable, xferFee);
-
3919  env(token::mint(minter, 0),
-
3920  token::issuer(issuer),
-
3921  token::xferFee(xferFee),
-
3922  txflags(tfTransferable));
-
3923  env.close();
-
3924  return nftID;
-
3925  };
-
3926 
-
3927  // o Seller is selling for zero XRP.
-
3928  // o Broker charges no fee.
-
3929  // o No transfer fee.
-
3930  //
-
3931  // Since minter is selling for zero the currency must be XRP.
-
3932  {
-
3933  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
3934 
-
3935  uint256 const nftID = mintNFT();
-
3936 
-
3937  // minter creates their offer.
-
3938  uint256 const minterOfferIndex =
-
3939  keylet::nftoffer(minter, env.seq(minter)).key;
-
3940  env(token::createOffer(minter, nftID, XRP(0)),
-
3941  txflags(tfSellNFToken));
-
3942  env.close();
-
3943 
-
3944  // buyer creates their offer. Note: a buy offer can never
-
3945  // offer zero.
-
3946  uint256 const buyOfferIndex =
-
3947  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3948  env(token::createOffer(buyer, nftID, XRP(1)),
-
3949  token::owner(minter));
+
3882  void
+
3883  testBrokeredAccept(FeatureBitset features)
+
3884  {
+
3885  // Look at the case where too many offers are passed in a cancel.
+
3886  testcase("Brokered NFT offer accept");
+
3887 
+
3888  using namespace test::jtx;
+
3889 
+
3890  for (auto const& tweakedFeatures :
+
3891  {features - fixNonFungibleTokensV1_2,
+
3892  features | fixNonFungibleTokensV1_2})
+
3893  {
+
3894  Env env{*this, tweakedFeatures};
+
3895 
+
3896  // The most important thing to explore here is the way funds are
+
3897  // assigned from the buyer to...
+
3898  // o the Seller,
+
3899  // o the Broker, and
+
3900  // o the Issuer (in the case of a transfer fee).
+
3901 
+
3902  Account const issuer{"issuer"};
+
3903  Account const minter{"minter"};
+
3904  Account const buyer{"buyer"};
+
3905  Account const broker{"broker"};
+
3906  Account const gw{"gw"};
+
3907  IOU const gwXAU(gw["XAU"]);
+
3908 
+
3909  env.fund(XRP(1000), issuer, minter, buyer, broker, gw);
+
3910  env.close();
+
3911 
+
3912  env(trust(issuer, gwXAU(2000)));
+
3913  env(trust(minter, gwXAU(2000)));
+
3914  env(trust(buyer, gwXAU(2000)));
+
3915  env(trust(broker, gwXAU(2000)));
+
3916  env.close();
+
3917 
+
3918  env(token::setMinter(issuer, minter));
+
3919  env.close();
+
3920 
+
3921  // Lambda to check owner count of all accounts is one.
+
3922  auto checkOwnerCountIsOne =
+
3923  [this, &env](
+
3924  std::initializer_list<std::reference_wrapper<Account const>>
+
3925  accounts,
+
3926  int line) {
+
3927  for (Account const& acct : accounts)
+
3928  {
+
3929  if (std::uint32_t ownerCount =
+
3930  this->ownerCount(env, acct);
+
3931  ownerCount != 1)
+
3932  {
+
3933  std::stringstream ss;
+
3934  ss << "Account " << acct.human()
+
3935  << " expected ownerCount == 1. Got "
+
3936  << ownerCount;
+
3937  fail(ss.str(), __FILE__, line);
+
3938  }
+
3939  }
+
3940  };
+
3941 
+
3942  // Lambda that mints an NFT and returns the nftID.
+
3943  auto mintNFT = [&env, &issuer, &minter](std::uint16_t xferFee = 0) {
+
3944  uint256 const nftID =
+
3945  token::getNextID(env, issuer, 0, tfTransferable, xferFee);
+
3946  env(token::mint(minter, 0),
+
3947  token::issuer(issuer),
+
3948  token::xferFee(xferFee),
+
3949  txflags(tfTransferable));
3950  env.close();
-
3951 
-
3952  auto const minterBalance = env.balance(minter);
-
3953  auto const buyerBalance = env.balance(buyer);
-
3954  auto const brokerBalance = env.balance(broker);
-
3955  auto const issuerBalance = env.balance(issuer);
-
3956 
-
3957  // Broker charges no brokerFee.
-
3958  env(token::brokerOffers(
-
3959  broker, buyOfferIndex, minterOfferIndex));
-
3960  env.close();
+
3951  return nftID;
+
3952  };
+
3953 
+
3954  // o Seller is selling for zero XRP.
+
3955  // o Broker charges no fee.
+
3956  // o No transfer fee.
+
3957  //
+
3958  // Since minter is selling for zero the currency must be XRP.
+
3959  {
+
3960  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3961 
-
3962  // Note that minter's XRP balance goes up even though they
-
3963  // requested XRP(0).
-
3964  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(1));
-
3965  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
-
3966  BEAST_EXPECT(env.balance(broker) == brokerBalance - drops(10));
-
3967  BEAST_EXPECT(env.balance(issuer) == issuerBalance);
-
3968 
-
3969  // Burn the NFT so the next test starts with a clean state.
-
3970  env(token::burn(buyer, nftID));
-
3971  env.close();
-
3972  }
-
3973 
-
3974  // o Seller is selling for zero XRP.
-
3975  // o Broker charges a fee.
-
3976  // o No transfer fee.
-
3977  //
-
3978  // Since minter is selling for zero the currency must be XRP.
-
3979  {
-
3980  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
3981 
-
3982  uint256 const nftID = mintNFT();
+
3962  uint256 const nftID = mintNFT();
+
3963 
+
3964  // minter creates their offer.
+
3965  uint256 const minterOfferIndex =
+
3966  keylet::nftoffer(minter, env.seq(minter)).key;
+
3967  env(token::createOffer(minter, nftID, XRP(0)),
+
3968  txflags(tfSellNFToken));
+
3969  env.close();
+
3970 
+
3971  // buyer creates their offer. Note: a buy offer can never
+
3972  // offer zero.
+
3973  uint256 const buyOfferIndex =
+
3974  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
3975  env(token::createOffer(buyer, nftID, XRP(1)),
+
3976  token::owner(minter));
+
3977  env.close();
+
3978 
+
3979  auto const minterBalance = env.balance(minter);
+
3980  auto const buyerBalance = env.balance(buyer);
+
3981  auto const brokerBalance = env.balance(broker);
+
3982  auto const issuerBalance = env.balance(issuer);
3983 
-
3984  // minter creates their offer.
-
3985  uint256 const minterOfferIndex =
-
3986  keylet::nftoffer(minter, env.seq(minter)).key;
-
3987  env(token::createOffer(minter, nftID, XRP(0)),
-
3988  txflags(tfSellNFToken));
-
3989  env.close();
-
3990 
-
3991  // buyer creates their offer. Note: a buy offer can never
-
3992  // offer zero.
-
3993  uint256 const buyOfferIndex =
-
3994  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
3995  env(token::createOffer(buyer, nftID, XRP(1)),
-
3996  token::owner(minter));
-
3997  env.close();
-
3998 
-
3999  // Broker attempts to charge a 1.1 XRP brokerFee and fails.
-
4000  env(token::brokerOffers(
-
4001  broker, buyOfferIndex, minterOfferIndex),
-
4002  token::brokerFee(XRP(1.1)),
-
4003  ter(tecINSUFFICIENT_PAYMENT));
-
4004  env.close();
-
4005 
-
4006  auto const minterBalance = env.balance(minter);
-
4007  auto const buyerBalance = env.balance(buyer);
-
4008  auto const brokerBalance = env.balance(broker);
-
4009  auto const issuerBalance = env.balance(issuer);
+
3984  // Broker charges no brokerFee.
+
3985  env(token::brokerOffers(
+
3986  broker, buyOfferIndex, minterOfferIndex));
+
3987  env.close();
+
3988 
+
3989  // Note that minter's XRP balance goes up even though they
+
3990  // requested XRP(0).
+
3991  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(1));
+
3992  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
+
3993  BEAST_EXPECT(env.balance(broker) == brokerBalance - drops(10));
+
3994  BEAST_EXPECT(env.balance(issuer) == issuerBalance);
+
3995 
+
3996  // Burn the NFT so the next test starts with a clean state.
+
3997  env(token::burn(buyer, nftID));
+
3998  env.close();
+
3999  }
+
4000 
+
4001  // o Seller is selling for zero XRP.
+
4002  // o Broker charges a fee.
+
4003  // o No transfer fee.
+
4004  //
+
4005  // Since minter is selling for zero the currency must be XRP.
+
4006  {
+
4007  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4008 
+
4009  uint256 const nftID = mintNFT();
4010 
-
4011  // Broker charges a 0.5 XRP brokerFee.
-
4012  env(token::brokerOffers(
-
4013  broker, buyOfferIndex, minterOfferIndex),
-
4014  token::brokerFee(XRP(0.5)));
-
4015  env.close();
-
4016 
-
4017  // Note that minter's XRP balance goes up even though they
-
4018  // requested XRP(0).
-
4019  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
-
4020  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
-
4021  BEAST_EXPECT(
-
4022  env.balance(broker) ==
-
4023  brokerBalance + XRP(0.5) - drops(10));
-
4024  BEAST_EXPECT(env.balance(issuer) == issuerBalance);
+
4011  // minter creates their offer.
+
4012  uint256 const minterOfferIndex =
+
4013  keylet::nftoffer(minter, env.seq(minter)).key;
+
4014  env(token::createOffer(minter, nftID, XRP(0)),
+
4015  txflags(tfSellNFToken));
+
4016  env.close();
+
4017 
+
4018  // buyer creates their offer. Note: a buy offer can never
+
4019  // offer zero.
+
4020  uint256 const buyOfferIndex =
+
4021  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4022  env(token::createOffer(buyer, nftID, XRP(1)),
+
4023  token::owner(minter));
+
4024  env.close();
4025 
-
4026  // Burn the NFT so the next test starts with a clean state.
-
4027  env(token::burn(buyer, nftID));
-
4028  env.close();
-
4029  }
-
4030 
-
4031  // o Seller is selling for zero XRP.
-
4032  // o Broker charges no fee.
-
4033  // o 50% transfer fee.
-
4034  //
-
4035  // Since minter is selling for zero the currency must be XRP.
-
4036  {
-
4037  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4038 
-
4039  uint256 const nftID = mintNFT(maxTransferFee);
-
4040 
-
4041  // minter creates their offer.
-
4042  uint256 const minterOfferIndex =
-
4043  keylet::nftoffer(minter, env.seq(minter)).key;
-
4044  env(token::createOffer(minter, nftID, XRP(0)),
-
4045  txflags(tfSellNFToken));
-
4046  env.close();
-
4047 
-
4048  // buyer creates their offer. Note: a buy offer can never
-
4049  // offer zero.
-
4050  uint256 const buyOfferIndex =
-
4051  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4052  env(token::createOffer(buyer, nftID, XRP(1)),
-
4053  token::owner(minter));
-
4054  env.close();
-
4055 
-
4056  auto const minterBalance = env.balance(minter);
-
4057  auto const buyerBalance = env.balance(buyer);
-
4058  auto const brokerBalance = env.balance(broker);
-
4059  auto const issuerBalance = env.balance(issuer);
-
4060 
-
4061  // Broker charges no brokerFee.
-
4062  env(token::brokerOffers(
-
4063  broker, buyOfferIndex, minterOfferIndex));
-
4064  env.close();
+
4026  // Broker attempts to charge a 1.1 XRP brokerFee and fails.
+
4027  env(token::brokerOffers(
+
4028  broker, buyOfferIndex, minterOfferIndex),
+
4029  token::brokerFee(XRP(1.1)),
+
4030  ter(tecINSUFFICIENT_PAYMENT));
+
4031  env.close();
+
4032 
+
4033  auto const minterBalance = env.balance(minter);
+
4034  auto const buyerBalance = env.balance(buyer);
+
4035  auto const brokerBalance = env.balance(broker);
+
4036  auto const issuerBalance = env.balance(issuer);
+
4037 
+
4038  // Broker charges a 0.5 XRP brokerFee.
+
4039  env(token::brokerOffers(
+
4040  broker, buyOfferIndex, minterOfferIndex),
+
4041  token::brokerFee(XRP(0.5)));
+
4042  env.close();
+
4043 
+
4044  // Note that minter's XRP balance goes up even though they
+
4045  // requested XRP(0).
+
4046  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
+
4047  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
+
4048  BEAST_EXPECT(
+
4049  env.balance(broker) ==
+
4050  brokerBalance + XRP(0.5) - drops(10));
+
4051  BEAST_EXPECT(env.balance(issuer) == issuerBalance);
+
4052 
+
4053  // Burn the NFT so the next test starts with a clean state.
+
4054  env(token::burn(buyer, nftID));
+
4055  env.close();
+
4056  }
+
4057 
+
4058  // o Seller is selling for zero XRP.
+
4059  // o Broker charges no fee.
+
4060  // o 50% transfer fee.
+
4061  //
+
4062  // Since minter is selling for zero the currency must be XRP.
+
4063  {
+
4064  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
4065 
-
4066  // Note that minter's XRP balance goes up even though they
-
4067  // requested XRP(0).
-
4068  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
-
4069  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
-
4070  BEAST_EXPECT(env.balance(broker) == brokerBalance - drops(10));
-
4071  BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.5));
-
4072 
-
4073  // Burn the NFT so the next test starts with a clean state.
-
4074  env(token::burn(buyer, nftID));
-
4075  env.close();
-
4076  }
-
4077 
-
4078  // o Seller is selling for zero XRP.
-
4079  // o Broker charges 0.5 XRP.
-
4080  // o 50% transfer fee.
-
4081  //
-
4082  // Since minter is selling for zero the currency must be XRP.
-
4083  {
-
4084  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4085 
-
4086  uint256 const nftID = mintNFT(maxTransferFee);
+
4066  uint256 const nftID = mintNFT(maxTransferFee);
+
4067 
+
4068  // minter creates their offer.
+
4069  uint256 const minterOfferIndex =
+
4070  keylet::nftoffer(minter, env.seq(minter)).key;
+
4071  env(token::createOffer(minter, nftID, XRP(0)),
+
4072  txflags(tfSellNFToken));
+
4073  env.close();
+
4074 
+
4075  // buyer creates their offer. Note: a buy offer can never
+
4076  // offer zero.
+
4077  uint256 const buyOfferIndex =
+
4078  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4079  env(token::createOffer(buyer, nftID, XRP(1)),
+
4080  token::owner(minter));
+
4081  env.close();
+
4082 
+
4083  auto const minterBalance = env.balance(minter);
+
4084  auto const buyerBalance = env.balance(buyer);
+
4085  auto const brokerBalance = env.balance(broker);
+
4086  auto const issuerBalance = env.balance(issuer);
4087 
-
4088  // minter creates their offer.
-
4089  uint256 const minterOfferIndex =
-
4090  keylet::nftoffer(minter, env.seq(minter)).key;
-
4091  env(token::createOffer(minter, nftID, XRP(0)),
-
4092  txflags(tfSellNFToken));
-
4093  env.close();
-
4094 
-
4095  // buyer creates their offer. Note: a buy offer can never
-
4096  // offer zero.
-
4097  uint256 const buyOfferIndex =
-
4098  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4099  env(token::createOffer(buyer, nftID, XRP(1)),
-
4100  token::owner(minter));
-
4101  env.close();
-
4102 
-
4103  auto const minterBalance = env.balance(minter);
-
4104  auto const buyerBalance = env.balance(buyer);
-
4105  auto const brokerBalance = env.balance(broker);
-
4106  auto const issuerBalance = env.balance(issuer);
-
4107 
-
4108  // Broker charges a 0.75 XRP brokerFee.
-
4109  env(token::brokerOffers(
-
4110  broker, buyOfferIndex, minterOfferIndex),
-
4111  token::brokerFee(XRP(0.75)));
-
4112  env.close();
-
4113 
-
4114  // Note that, with a 50% transfer fee, issuer gets 1/2 of what's
-
4115  // left _after_ broker takes their fee. minter gets the
-
4116  // remainder after both broker and minter take their cuts
-
4117  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.125));
-
4118  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
-
4119  BEAST_EXPECT(
-
4120  env.balance(broker) ==
-
4121  brokerBalance + XRP(0.75) - drops(10));
-
4122  BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.125));
-
4123 
-
4124  // Burn the NFT so the next test starts with a clean state.
-
4125  env(token::burn(buyer, nftID));
-
4126  env.close();
-
4127  }
-
4128 
-
4129  // Lambda to set the balance of all passed in accounts to
-
4130  // gwXAU(amount).
-
4131  auto setXAUBalance =
-
4132  [this, &gw, &gwXAU, &env](
-
4133  std::initializer_list<std::reference_wrapper<Account const>>
-
4134  accounts,
-
4135  int amount,
-
4136  int line) {
-
4137  for (Account const& acct : accounts)
-
4138  {
-
4139  auto const xauAmt = gwXAU(amount);
-
4140  auto const balance = env.balance(acct, gwXAU);
-
4141  if (balance < xauAmt)
-
4142  {
-
4143  env(pay(gw, acct, xauAmt - balance));
-
4144  env.close();
-
4145  }
-
4146  else if (balance > xauAmt)
-
4147  {
-
4148  env(pay(acct, gw, balance - xauAmt));
-
4149  env.close();
-
4150  }
-
4151  if (env.balance(acct, gwXAU) != xauAmt)
-
4152  {
-
4153  std::stringstream ss;
-
4154  ss << "Unable to set " << acct.human()
-
4155  << " account balance to gwXAU(" << amount << ")";
-
4156  this->fail(ss.str(), __FILE__, line);
-
4157  }
-
4158  }
-
4159  };
-
4160 
-
4161  // The buyer and seller have identical amounts and there is no
-
4162  // transfer fee.
-
4163  {
-
4164  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4165  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
-
4166 
-
4167  uint256 const nftID = mintNFT();
-
4168 
-
4169  // minter creates their offer.
-
4170  uint256 const minterOfferIndex =
-
4171  keylet::nftoffer(minter, env.seq(minter)).key;
-
4172  env(token::createOffer(minter, nftID, gwXAU(1000)),
-
4173  txflags(tfSellNFToken));
-
4174  env.close();
-
4175 
-
4176  {
-
4177  // buyer creates an offer for more XAU than they currently
-
4178  // own.
-
4179  uint256 const buyOfferIndex =
-
4180  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4181  env(token::createOffer(buyer, nftID, gwXAU(1001)),
-
4182  token::owner(minter));
-
4183  env.close();
-
4184 
-
4185  // broker attempts to broker the offers but cannot.
-
4186  env(token::brokerOffers(
-
4187  broker, buyOfferIndex, minterOfferIndex),
-
4188  ter(tecINSUFFICIENT_FUNDS));
-
4189  env.close();
-
4190 
-
4191  // Cancel buyer's bad offer so the next test starts in a
-
4192  // clean state.
-
4193  env(token::cancelOffer(buyer, {buyOfferIndex}));
-
4194  env.close();
-
4195  }
-
4196  {
-
4197  // buyer creates an offer for less that what minter is
-
4198  // asking.
-
4199  uint256 const buyOfferIndex =
-
4200  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4201  env(token::createOffer(buyer, nftID, gwXAU(999)),
-
4202  token::owner(minter));
-
4203  env.close();
-
4204 
-
4205  // broker attempts to broker the offers but cannot.
-
4206  env(token::brokerOffers(
-
4207  broker, buyOfferIndex, minterOfferIndex),
-
4208  ter(tecINSUFFICIENT_PAYMENT));
-
4209  env.close();
-
4210 
-
4211  // Cancel buyer's bad offer so the next test starts in a
-
4212  // clean state.
-
4213  env(token::cancelOffer(buyer, {buyOfferIndex}));
-
4214  env.close();
-
4215  }
-
4216 
-
4217  // buyer creates a large enough offer.
-
4218  uint256 const buyOfferIndex =
-
4219  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4220  env(token::createOffer(buyer, nftID, gwXAU(1000)),
-
4221  token::owner(minter));
-
4222  env.close();
-
4223 
-
4224  // Broker attempts to charge a brokerFee but cannot.
-
4225  env(token::brokerOffers(
-
4226  broker, buyOfferIndex, minterOfferIndex),
-
4227  token::brokerFee(gwXAU(0.1)),
-
4228  ter(tecINSUFFICIENT_PAYMENT));
-
4229  env.close();
-
4230 
-
4231  // broker charges no brokerFee and succeeds.
-
4232  env(token::brokerOffers(
-
4233  broker, buyOfferIndex, minterOfferIndex));
-
4234  env.close();
-
4235 
-
4236  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
4237  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
4238  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
4239  BEAST_EXPECT(ownerCount(env, broker) == 1);
-
4240  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1000));
-
4241  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(2000));
-
4242  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
4243  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1000));
-
4244 
-
4245  // Burn the NFT so the next test starts with a clean state.
-
4246  env(token::burn(buyer, nftID));
-
4247  env.close();
-
4248  }
-
4249 
-
4250  // seller offers more than buyer is asking.
-
4251  // There are both transfer and broker fees.
-
4252  {
-
4253  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4254  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
-
4255 
-
4256  uint256 const nftID = mintNFT(maxTransferFee);
+
4088  // Broker charges no brokerFee.
+
4089  env(token::brokerOffers(
+
4090  broker, buyOfferIndex, minterOfferIndex));
+
4091  env.close();
+
4092 
+
4093  // Note that minter's XRP balance goes up even though they
+
4094  // requested XRP(0).
+
4095  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
+
4096  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
+
4097  BEAST_EXPECT(env.balance(broker) == brokerBalance - drops(10));
+
4098  BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.5));
+
4099 
+
4100  // Burn the NFT so the next test starts with a clean state.
+
4101  env(token::burn(buyer, nftID));
+
4102  env.close();
+
4103  }
+
4104 
+
4105  // o Seller is selling for zero XRP.
+
4106  // o Broker charges 0.5 XRP.
+
4107  // o 50% transfer fee.
+
4108  //
+
4109  // Since minter is selling for zero the currency must be XRP.
+
4110  {
+
4111  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4112 
+
4113  uint256 const nftID = mintNFT(maxTransferFee);
+
4114 
+
4115  // minter creates their offer.
+
4116  uint256 const minterOfferIndex =
+
4117  keylet::nftoffer(minter, env.seq(minter)).key;
+
4118  env(token::createOffer(minter, nftID, XRP(0)),
+
4119  txflags(tfSellNFToken));
+
4120  env.close();
+
4121 
+
4122  // buyer creates their offer. Note: a buy offer can never
+
4123  // offer zero.
+
4124  uint256 const buyOfferIndex =
+
4125  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4126  env(token::createOffer(buyer, nftID, XRP(1)),
+
4127  token::owner(minter));
+
4128  env.close();
+
4129 
+
4130  auto const minterBalance = env.balance(minter);
+
4131  auto const buyerBalance = env.balance(buyer);
+
4132  auto const brokerBalance = env.balance(broker);
+
4133  auto const issuerBalance = env.balance(issuer);
+
4134 
+
4135  // Broker charges a 0.75 XRP brokerFee.
+
4136  env(token::brokerOffers(
+
4137  broker, buyOfferIndex, minterOfferIndex),
+
4138  token::brokerFee(XRP(0.75)));
+
4139  env.close();
+
4140 
+
4141  // Note that, with a 50% transfer fee, issuer gets 1/2 of what's
+
4142  // left _after_ broker takes their fee. minter gets the
+
4143  // remainder after both broker and minter take their cuts
+
4144  BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.125));
+
4145  BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
+
4146  BEAST_EXPECT(
+
4147  env.balance(broker) ==
+
4148  brokerBalance + XRP(0.75) - drops(10));
+
4149  BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.125));
+
4150 
+
4151  // Burn the NFT so the next test starts with a clean state.
+
4152  env(token::burn(buyer, nftID));
+
4153  env.close();
+
4154  }
+
4155 
+
4156  // Lambda to set the balance of all passed in accounts to
+
4157  // gwXAU(amount).
+
4158  auto setXAUBalance =
+
4159  [this, &gw, &gwXAU, &env](
+
4160  std::initializer_list<std::reference_wrapper<Account const>>
+
4161  accounts,
+
4162  int amount,
+
4163  int line) {
+
4164  for (Account const& acct : accounts)
+
4165  {
+
4166  auto const xauAmt = gwXAU(amount);
+
4167  auto const balance = env.balance(acct, gwXAU);
+
4168  if (balance < xauAmt)
+
4169  {
+
4170  env(pay(gw, acct, xauAmt - balance));
+
4171  env.close();
+
4172  }
+
4173  else if (balance > xauAmt)
+
4174  {
+
4175  env(pay(acct, gw, balance - xauAmt));
+
4176  env.close();
+
4177  }
+
4178  if (env.balance(acct, gwXAU) != xauAmt)
+
4179  {
+
4180  std::stringstream ss;
+
4181  ss << "Unable to set " << acct.human()
+
4182  << " account balance to gwXAU(" << amount << ")";
+
4183  this->fail(ss.str(), __FILE__, line);
+
4184  }
+
4185  }
+
4186  };
+
4187 
+
4188  // The buyer and seller have identical amounts and there is no
+
4189  // transfer fee.
+
4190  {
+
4191  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4192  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
+
4193 
+
4194  uint256 const nftID = mintNFT();
+
4195 
+
4196  // minter creates their offer.
+
4197  uint256 const minterOfferIndex =
+
4198  keylet::nftoffer(minter, env.seq(minter)).key;
+
4199  env(token::createOffer(minter, nftID, gwXAU(1000)),
+
4200  txflags(tfSellNFToken));
+
4201  env.close();
+
4202 
+
4203  {
+
4204  // buyer creates an offer for more XAU than they currently
+
4205  // own.
+
4206  uint256 const buyOfferIndex =
+
4207  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4208  env(token::createOffer(buyer, nftID, gwXAU(1001)),
+
4209  token::owner(minter));
+
4210  env.close();
+
4211 
+
4212  // broker attempts to broker the offers but cannot.
+
4213  env(token::brokerOffers(
+
4214  broker, buyOfferIndex, minterOfferIndex),
+
4215  ter(tecINSUFFICIENT_FUNDS));
+
4216  env.close();
+
4217 
+
4218  // Cancel buyer's bad offer so the next test starts in a
+
4219  // clean state.
+
4220  env(token::cancelOffer(buyer, {buyOfferIndex}));
+
4221  env.close();
+
4222  }
+
4223  {
+
4224  // buyer creates an offer for less that what minter is
+
4225  // asking.
+
4226  uint256 const buyOfferIndex =
+
4227  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4228  env(token::createOffer(buyer, nftID, gwXAU(999)),
+
4229  token::owner(minter));
+
4230  env.close();
+
4231 
+
4232  // broker attempts to broker the offers but cannot.
+
4233  env(token::brokerOffers(
+
4234  broker, buyOfferIndex, minterOfferIndex),
+
4235  ter(tecINSUFFICIENT_PAYMENT));
+
4236  env.close();
+
4237 
+
4238  // Cancel buyer's bad offer so the next test starts in a
+
4239  // clean state.
+
4240  env(token::cancelOffer(buyer, {buyOfferIndex}));
+
4241  env.close();
+
4242  }
+
4243 
+
4244  // buyer creates a large enough offer.
+
4245  uint256 const buyOfferIndex =
+
4246  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4247  env(token::createOffer(buyer, nftID, gwXAU(1000)),
+
4248  token::owner(minter));
+
4249  env.close();
+
4250 
+
4251  // Broker attempts to charge a brokerFee but cannot.
+
4252  env(token::brokerOffers(
+
4253  broker, buyOfferIndex, minterOfferIndex),
+
4254  token::brokerFee(gwXAU(0.1)),
+
4255  ter(tecINSUFFICIENT_PAYMENT));
+
4256  env.close();
4257 
-
4258  // minter creates their offer.
-
4259  uint256 const minterOfferIndex =
-
4260  keylet::nftoffer(minter, env.seq(minter)).key;
-
4261  env(token::createOffer(minter, nftID, gwXAU(900)),
-
4262  txflags(tfSellNFToken));
-
4263  env.close();
-
4264  {
-
4265  // buyer creates an offer for more XAU than they currently
-
4266  // own.
-
4267  uint256 const buyOfferIndex =
-
4268  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4269  env(token::createOffer(buyer, nftID, gwXAU(1001)),
-
4270  token::owner(minter));
-
4271  env.close();
-
4272 
-
4273  // broker attempts to broker the offers but cannot.
-
4274  env(token::brokerOffers(
-
4275  broker, buyOfferIndex, minterOfferIndex),
-
4276  ter(tecINSUFFICIENT_FUNDS));
-
4277  env.close();
-
4278 
-
4279  // Cancel buyer's bad offer so the next test starts in a
-
4280  // clean state.
-
4281  env(token::cancelOffer(buyer, {buyOfferIndex}));
-
4282  env.close();
-
4283  }
-
4284  {
-
4285  // buyer creates an offer for less that what minter is
-
4286  // asking.
-
4287  uint256 const buyOfferIndex =
-
4288  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4289  env(token::createOffer(buyer, nftID, gwXAU(899)),
-
4290  token::owner(minter));
-
4291  env.close();
-
4292 
-
4293  // broker attempts to broker the offers but cannot.
-
4294  env(token::brokerOffers(
-
4295  broker, buyOfferIndex, minterOfferIndex),
-
4296  ter(tecINSUFFICIENT_PAYMENT));
-
4297  env.close();
-
4298 
-
4299  // Cancel buyer's bad offer so the next test starts in a
-
4300  // clean state.
-
4301  env(token::cancelOffer(buyer, {buyOfferIndex}));
-
4302  env.close();
-
4303  }
-
4304  // buyer creates a large enough offer.
-
4305  uint256 const buyOfferIndex =
-
4306  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4307  env(token::createOffer(buyer, nftID, gwXAU(1000)),
-
4308  token::owner(minter));
-
4309  env.close();
-
4310 
-
4311  // Broker attempts to charge a brokerFee larger than the
-
4312  // difference between the two offers but cannot.
-
4313  env(token::brokerOffers(
-
4314  broker, buyOfferIndex, minterOfferIndex),
-
4315  token::brokerFee(gwXAU(101)),
-
4316  ter(tecINSUFFICIENT_PAYMENT));
-
4317  env.close();
-
4318 
-
4319  // broker charges the full difference between the two offers and
-
4320  // succeeds.
-
4321  env(token::brokerOffers(
-
4322  broker, buyOfferIndex, minterOfferIndex),
-
4323  token::brokerFee(gwXAU(100)));
-
4324  env.close();
+
4258  // broker charges no brokerFee and succeeds.
+
4259  env(token::brokerOffers(
+
4260  broker, buyOfferIndex, minterOfferIndex));
+
4261  env.close();
+
4262 
+
4263  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
4264  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
4265  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
4266  BEAST_EXPECT(ownerCount(env, broker) == 1);
+
4267  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1000));
+
4268  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(2000));
+
4269  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
4270  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1000));
+
4271 
+
4272  // Burn the NFT so the next test starts with a clean state.
+
4273  env(token::burn(buyer, nftID));
+
4274  env.close();
+
4275  }
+
4276 
+
4277  // seller offers more than buyer is asking.
+
4278  // There are both transfer and broker fees.
+
4279  {
+
4280  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4281  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
+
4282 
+
4283  uint256 const nftID = mintNFT(maxTransferFee);
+
4284 
+
4285  // minter creates their offer.
+
4286  uint256 const minterOfferIndex =
+
4287  keylet::nftoffer(minter, env.seq(minter)).key;
+
4288  env(token::createOffer(minter, nftID, gwXAU(900)),
+
4289  txflags(tfSellNFToken));
+
4290  env.close();
+
4291  {
+
4292  // buyer creates an offer for more XAU than they currently
+
4293  // own.
+
4294  uint256 const buyOfferIndex =
+
4295  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4296  env(token::createOffer(buyer, nftID, gwXAU(1001)),
+
4297  token::owner(minter));
+
4298  env.close();
+
4299 
+
4300  // broker attempts to broker the offers but cannot.
+
4301  env(token::brokerOffers(
+
4302  broker, buyOfferIndex, minterOfferIndex),
+
4303  ter(tecINSUFFICIENT_FUNDS));
+
4304  env.close();
+
4305 
+
4306  // Cancel buyer's bad offer so the next test starts in a
+
4307  // clean state.
+
4308  env(token::cancelOffer(buyer, {buyOfferIndex}));
+
4309  env.close();
+
4310  }
+
4311  {
+
4312  // buyer creates an offer for less that what minter is
+
4313  // asking.
+
4314  uint256 const buyOfferIndex =
+
4315  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4316  env(token::createOffer(buyer, nftID, gwXAU(899)),
+
4317  token::owner(minter));
+
4318  env.close();
+
4319 
+
4320  // broker attempts to broker the offers but cannot.
+
4321  env(token::brokerOffers(
+
4322  broker, buyOfferIndex, minterOfferIndex),
+
4323  ter(tecINSUFFICIENT_PAYMENT));
+
4324  env.close();
4325 
-
4326  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
4327  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
4328  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
4329  BEAST_EXPECT(ownerCount(env, broker) == 1);
-
4330  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1450));
-
4331  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1450));
-
4332  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
4333  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1100));
-
4334 
-
4335  // Burn the NFT so the next test starts with a clean state.
-
4336  env(token::burn(buyer, nftID));
-
4337  env.close();
-
4338  }
-
4339  // seller offers more than buyer is asking.
-
4340  // There are both transfer and broker fees, but broker takes less
-
4341  // than the maximum.
-
4342  {
-
4343  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4344  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
+
4326  // Cancel buyer's bad offer so the next test starts in a
+
4327  // clean state.
+
4328  env(token::cancelOffer(buyer, {buyOfferIndex}));
+
4329  env.close();
+
4330  }
+
4331  // buyer creates a large enough offer.
+
4332  uint256 const buyOfferIndex =
+
4333  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4334  env(token::createOffer(buyer, nftID, gwXAU(1000)),
+
4335  token::owner(minter));
+
4336  env.close();
+
4337 
+
4338  // Broker attempts to charge a brokerFee larger than the
+
4339  // difference between the two offers but cannot.
+
4340  env(token::brokerOffers(
+
4341  broker, buyOfferIndex, minterOfferIndex),
+
4342  token::brokerFee(gwXAU(101)),
+
4343  ter(tecINSUFFICIENT_PAYMENT));
+
4344  env.close();
4345 
-
4346  uint256 const nftID = mintNFT(maxTransferFee / 2); // 25%
-
4347 
-
4348  // minter creates their offer.
-
4349  uint256 const minterOfferIndex =
-
4350  keylet::nftoffer(minter, env.seq(minter)).key;
-
4351  env(token::createOffer(minter, nftID, gwXAU(900)),
-
4352  txflags(tfSellNFToken));
-
4353  env.close();
-
4354 
-
4355  // buyer creates a large enough offer.
-
4356  uint256 const buyOfferIndex =
-
4357  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4358  env(token::createOffer(buyer, nftID, gwXAU(1000)),
-
4359  token::owner(minter));
-
4360  env.close();
+
4346  // broker charges the full difference between the two offers and
+
4347  // succeeds.
+
4348  env(token::brokerOffers(
+
4349  broker, buyOfferIndex, minterOfferIndex),
+
4350  token::brokerFee(gwXAU(100)));
+
4351  env.close();
+
4352 
+
4353  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
4354  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
4355  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
4356  BEAST_EXPECT(ownerCount(env, broker) == 1);
+
4357  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1450));
+
4358  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1450));
+
4359  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
4360  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1100));
4361 
-
4362  // broker charges half difference between the two offers and
-
4363  // succeeds. 25% of the remaining difference goes to issuer.
-
4364  // The rest goes to minter.
-
4365  env(token::brokerOffers(
-
4366  broker, buyOfferIndex, minterOfferIndex),
-
4367  token::brokerFee(gwXAU(50)));
-
4368  env.close();
-
4369 
-
4370  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
4371  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
4372  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
4373  BEAST_EXPECT(ownerCount(env, broker) == 1);
-
4374  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
-
4375  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
-
4376  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
4377  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1050));
-
4378 
-
4379  // Burn the NFT so the next test starts with a clean state.
-
4380  env(token::burn(buyer, nftID));
-
4381  env.close();
-
4382  }
-
4383  // Broker has a balance less than the seller offer
-
4384  {
-
4385  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
-
4386  setXAUBalance({issuer, minter, buyer}, 1000, __LINE__);
-
4387  setXAUBalance({broker}, 500, __LINE__);
-
4388  uint256 const nftID = mintNFT(maxTransferFee / 2); // 25%
-
4389 
-
4390  // minter creates their offer.
-
4391  uint256 const minterOfferIndex =
-
4392  keylet::nftoffer(minter, env.seq(minter)).key;
-
4393  env(token::createOffer(minter, nftID, gwXAU(900)),
-
4394  txflags(tfSellNFToken));
+
4362  // Burn the NFT so the next test starts with a clean state.
+
4363  env(token::burn(buyer, nftID));
+
4364  env.close();
+
4365  }
+
4366  // seller offers more than buyer is asking.
+
4367  // There are both transfer and broker fees, but broker takes less
+
4368  // than the maximum.
+
4369  {
+
4370  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4371  setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
+
4372 
+
4373  uint256 const nftID = mintNFT(maxTransferFee / 2); // 25%
+
4374 
+
4375  // minter creates their offer.
+
4376  uint256 const minterOfferIndex =
+
4377  keylet::nftoffer(minter, env.seq(minter)).key;
+
4378  env(token::createOffer(minter, nftID, gwXAU(900)),
+
4379  txflags(tfSellNFToken));
+
4380  env.close();
+
4381 
+
4382  // buyer creates a large enough offer.
+
4383  uint256 const buyOfferIndex =
+
4384  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4385  env(token::createOffer(buyer, nftID, gwXAU(1000)),
+
4386  token::owner(minter));
+
4387  env.close();
+
4388 
+
4389  // broker charges half difference between the two offers and
+
4390  // succeeds. 25% of the remaining difference goes to issuer.
+
4391  // The rest goes to minter.
+
4392  env(token::brokerOffers(
+
4393  broker, buyOfferIndex, minterOfferIndex),
+
4394  token::brokerFee(gwXAU(50)));
4395  env.close();
4396 
-
4397  // buyer creates a large enough offer.
-
4398  uint256 const buyOfferIndex =
-
4399  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
4400  env(token::createOffer(buyer, nftID, gwXAU(1000)),
-
4401  token::owner(minter));
-
4402  env.close();
-
4403 
-
4404  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
4405  {
-
4406  env(token::brokerOffers(
-
4407  broker, buyOfferIndex, minterOfferIndex),
-
4408  token::brokerFee(gwXAU(50)));
-
4409  env.close();
-
4410  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
4411  BEAST_EXPECT(ownerCount(env, minter) == 1);
-
4412  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
4413  BEAST_EXPECT(ownerCount(env, broker) == 1);
-
4414  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
-
4415  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
-
4416  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
4417  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(550));
-
4418 
-
4419  // Burn the NFT so the next test starts with a clean state.
-
4420  env(token::burn(buyer, nftID));
-
4421  env.close();
-
4422  }
-
4423  else
-
4424  {
-
4425  env(token::brokerOffers(
-
4426  broker, buyOfferIndex, minterOfferIndex),
-
4427  token::brokerFee(gwXAU(50)),
-
4428  ter(tecINSUFFICIENT_FUNDS));
-
4429  env.close();
-
4430  BEAST_EXPECT(ownerCount(env, issuer) == 1);
-
4431  BEAST_EXPECT(ownerCount(env, minter) == 3);
-
4432  BEAST_EXPECT(ownerCount(env, buyer) == 2);
-
4433  BEAST_EXPECT(ownerCount(env, broker) == 1);
-
4434  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1000));
-
4435  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
4436  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(1000));
-
4437  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(500));
-
4438 
-
4439  // Burn the NFT so the next test starts with a clean state.
-
4440  env(token::burn(minter, nftID));
-
4441  env.close();
-
4442  }
-
4443  }
-
4444  }
-
4445  }
-
4446 
-
4447  void
-
4448  testNFTokenOfferOwner(FeatureBitset features)
-
4449  {
-
4450  // Verify the Owner field of an offer behaves as expected.
-
4451  testcase("NFToken offer owner");
-
4452 
-
4453  using namespace test::jtx;
-
4454 
-
4455  Env env{*this, features};
-
4456 
-
4457  Account const issuer{"issuer"};
-
4458  Account const buyer1{"buyer1"};
-
4459  Account const buyer2{"buyer2"};
-
4460  env.fund(XRP(10000), issuer, buyer1, buyer2);
-
4461  env.close();
-
4462 
-
4463  // issuer creates an NFT.
-
4464  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
-
4465  env(token::mint(issuer, 0u), txflags(tfTransferable));
-
4466  env.close();
-
4467 
-
4468  // Prove that issuer now owns nftId.
-
4469  BEAST_EXPECT(nftCount(env, issuer) == 1);
-
4470  BEAST_EXPECT(nftCount(env, buyer1) == 0);
-
4471  BEAST_EXPECT(nftCount(env, buyer2) == 0);
-
4472 
-
4473  // Both buyer1 and buyer2 create buy offers for nftId.
-
4474  uint256 const buyer1OfferIndex =
-
4475  keylet::nftoffer(buyer1, env.seq(buyer1)).key;
-
4476  env(token::createOffer(buyer1, nftId, XRP(100)), token::owner(issuer));
-
4477  uint256 const buyer2OfferIndex =
-
4478  keylet::nftoffer(buyer2, env.seq(buyer2)).key;
-
4479  env(token::createOffer(buyer2, nftId, XRP(100)), token::owner(issuer));
-
4480  env.close();
+
4397  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
4398  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
4399  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
4400  BEAST_EXPECT(ownerCount(env, broker) == 1);
+
4401  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
+
4402  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
+
4403  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
4404  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1050));
+
4405 
+
4406  // Burn the NFT so the next test starts with a clean state.
+
4407  env(token::burn(buyer, nftID));
+
4408  env.close();
+
4409  }
+
4410  // Broker has a balance less than the seller offer
+
4411  {
+
4412  checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
+
4413  setXAUBalance({issuer, minter, buyer}, 1000, __LINE__);
+
4414  setXAUBalance({broker}, 500, __LINE__);
+
4415  uint256 const nftID = mintNFT(maxTransferFee / 2); // 25%
+
4416 
+
4417  // minter creates their offer.
+
4418  uint256 const minterOfferIndex =
+
4419  keylet::nftoffer(minter, env.seq(minter)).key;
+
4420  env(token::createOffer(minter, nftID, gwXAU(900)),
+
4421  txflags(tfSellNFToken));
+
4422  env.close();
+
4423 
+
4424  // buyer creates a large enough offer.
+
4425  uint256 const buyOfferIndex =
+
4426  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
4427  env(token::createOffer(buyer, nftID, gwXAU(1000)),
+
4428  token::owner(minter));
+
4429  env.close();
+
4430 
+
4431  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
4432  {
+
4433  env(token::brokerOffers(
+
4434  broker, buyOfferIndex, minterOfferIndex),
+
4435  token::brokerFee(gwXAU(50)));
+
4436  env.close();
+
4437  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
4438  BEAST_EXPECT(ownerCount(env, minter) == 1);
+
4439  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
4440  BEAST_EXPECT(ownerCount(env, broker) == 1);
+
4441  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
+
4442  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
+
4443  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
4444  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(550));
+
4445 
+
4446  // Burn the NFT so the next test starts with a clean state.
+
4447  env(token::burn(buyer, nftID));
+
4448  env.close();
+
4449  }
+
4450  else
+
4451  {
+
4452  env(token::brokerOffers(
+
4453  broker, buyOfferIndex, minterOfferIndex),
+
4454  token::brokerFee(gwXAU(50)),
+
4455  ter(tecINSUFFICIENT_FUNDS));
+
4456  env.close();
+
4457  BEAST_EXPECT(ownerCount(env, issuer) == 1);
+
4458  BEAST_EXPECT(ownerCount(env, minter) == 3);
+
4459  BEAST_EXPECT(ownerCount(env, buyer) == 2);
+
4460  BEAST_EXPECT(ownerCount(env, broker) == 1);
+
4461  BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1000));
+
4462  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
4463  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(1000));
+
4464  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(500));
+
4465 
+
4466  // Burn the NFT so the next test starts with a clean state.
+
4467  env(token::burn(minter, nftID));
+
4468  env.close();
+
4469  }
+
4470  }
+
4471  }
+
4472  }
+
4473 
+
4474  void
+
4475  testNFTokenOfferOwner(FeatureBitset features)
+
4476  {
+
4477  // Verify the Owner field of an offer behaves as expected.
+
4478  testcase("NFToken offer owner");
+
4479 
+
4480  using namespace test::jtx;
4481 
-
4482  // Lambda that counts the number of buy offers for a given NFT.
-
4483  auto nftBuyOfferCount = [&env](uint256 const& nftId) -> std::size_t {
-
4484  // We know that in this case not very many offers will be
-
4485  // returned, so we skip the marker stuff.
-
4486  Json::Value params;
-
4487  params[jss::nft_id] = to_string(nftId);
-
4488  Json::Value buyOffers =
-
4489  env.rpc("json", "nft_buy_offers", to_string(params));
-
4490 
-
4491  if (buyOffers.isMember(jss::result) &&
-
4492  buyOffers[jss::result].isMember(jss::offers))
-
4493  return buyOffers[jss::result][jss::offers].size();
+
4482  Env env{*this, features};
+
4483 
+
4484  Account const issuer{"issuer"};
+
4485  Account const buyer1{"buyer1"};
+
4486  Account const buyer2{"buyer2"};
+
4487  env.fund(XRP(10000), issuer, buyer1, buyer2);
+
4488  env.close();
+
4489 
+
4490  // issuer creates an NFT.
+
4491  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
+
4492  env(token::mint(issuer, 0u), txflags(tfTransferable));
+
4493  env.close();
4494 
-
4495  return 0;
-
4496  };
-
4497 
-
4498  // Show there are two buy offers for nftId.
-
4499  BEAST_EXPECT(nftBuyOfferCount(nftId) == 2);
-
4500 
-
4501  // issuer accepts buyer1's offer.
-
4502  env(token::acceptBuyOffer(issuer, buyer1OfferIndex));
-
4503  env.close();
-
4504 
-
4505  // Prove that buyer1 now owns nftId.
-
4506  BEAST_EXPECT(nftCount(env, issuer) == 0);
-
4507  BEAST_EXPECT(nftCount(env, buyer1) == 1);
-
4508  BEAST_EXPECT(nftCount(env, buyer2) == 0);
-
4509 
-
4510  // buyer1's offer was consumed, but buyer2's offer is still in the
-
4511  // ledger.
-
4512  BEAST_EXPECT(nftBuyOfferCount(nftId) == 1);
-
4513 
-
4514  // buyer1 can now accept buyer2's offer, even though buyer2's
-
4515  // NFTokenCreateOffer transaction specified the NFT Owner as issuer.
-
4516  env(token::acceptBuyOffer(buyer1, buyer2OfferIndex));
-
4517  env.close();
-
4518 
-
4519  // Prove that buyer2 now owns nftId.
-
4520  BEAST_EXPECT(nftCount(env, issuer) == 0);
-
4521  BEAST_EXPECT(nftCount(env, buyer1) == 0);
-
4522  BEAST_EXPECT(nftCount(env, buyer2) == 1);
-
4523 
-
4524  // All of the NFTokenOffers are now consumed.
-
4525  BEAST_EXPECT(nftBuyOfferCount(nftId) == 0);
-
4526  }
+
4495  // Prove that issuer now owns nftId.
+
4496  BEAST_EXPECT(nftCount(env, issuer) == 1);
+
4497  BEAST_EXPECT(nftCount(env, buyer1) == 0);
+
4498  BEAST_EXPECT(nftCount(env, buyer2) == 0);
+
4499 
+
4500  // Both buyer1 and buyer2 create buy offers for nftId.
+
4501  uint256 const buyer1OfferIndex =
+
4502  keylet::nftoffer(buyer1, env.seq(buyer1)).key;
+
4503  env(token::createOffer(buyer1, nftId, XRP(100)), token::owner(issuer));
+
4504  uint256 const buyer2OfferIndex =
+
4505  keylet::nftoffer(buyer2, env.seq(buyer2)).key;
+
4506  env(token::createOffer(buyer2, nftId, XRP(100)), token::owner(issuer));
+
4507  env.close();
+
4508 
+
4509  // Lambda that counts the number of buy offers for a given NFT.
+
4510  auto nftBuyOfferCount = [&env](uint256 const& nftId) -> std::size_t {
+
4511  // We know that in this case not very many offers will be
+
4512  // returned, so we skip the marker stuff.
+
4513  Json::Value params;
+
4514  params[jss::nft_id] = to_string(nftId);
+
4515  Json::Value buyOffers =
+
4516  env.rpc("json", "nft_buy_offers", to_string(params));
+
4517 
+
4518  if (buyOffers.isMember(jss::result) &&
+
4519  buyOffers[jss::result].isMember(jss::offers))
+
4520  return buyOffers[jss::result][jss::offers].size();
+
4521 
+
4522  return 0;
+
4523  };
+
4524 
+
4525  // Show there are two buy offers for nftId.
+
4526  BEAST_EXPECT(nftBuyOfferCount(nftId) == 2);
4527 
-
4528  void
-
4529  testNFTokenWithTickets(FeatureBitset features)
-
4530  {
-
4531  // Make sure all NFToken transactions work with tickets.
-
4532  testcase("NFToken transactions with tickets");
-
4533 
-
4534  using namespace test::jtx;
-
4535 
-
4536  Env env{*this, features};
-
4537 
-
4538  Account const issuer{"issuer"};
-
4539  Account const buyer{"buyer"};
-
4540  env.fund(XRP(10000), issuer, buyer);
-
4541  env.close();
-
4542 
-
4543  // issuer and buyer grab enough tickets for all of the following
-
4544  // transactions. Note that once the tickets are acquired issuer's
-
4545  // and buyer's account sequence numbers should not advance.
-
4546  std::uint32_t issuerTicketSeq{env.seq(issuer) + 1};
-
4547  env(ticket::create(issuer, 10));
-
4548  env.close();
-
4549  std::uint32_t const issuerSeq{env.seq(issuer)};
-
4550  BEAST_EXPECT(ticketCount(env, issuer) == 10);
-
4551 
-
4552  std::uint32_t buyerTicketSeq{env.seq(buyer) + 1};
-
4553  env(ticket::create(buyer, 10));
-
4554  env.close();
-
4555  std::uint32_t const buyerSeq{env.seq(buyer)};
-
4556  BEAST_EXPECT(ticketCount(env, buyer) == 10);
-
4557 
-
4558  // NFTokenMint
-
4559  BEAST_EXPECT(ownerCount(env, issuer) == 10);
-
4560  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
-
4561  env(token::mint(issuer, 0u),
-
4562  txflags(tfTransferable),
-
4563  ticket::use(issuerTicketSeq++));
-
4564  env.close();
-
4565  BEAST_EXPECT(ownerCount(env, issuer) == 10);
-
4566  BEAST_EXPECT(ticketCount(env, issuer) == 9);
-
4567 
-
4568  // NFTokenCreateOffer
-
4569  BEAST_EXPECT(ownerCount(env, buyer) == 10);
-
4570  uint256 const offerIndex0 = keylet::nftoffer(buyer, buyerTicketSeq).key;
-
4571  env(token::createOffer(buyer, nftId, XRP(1)),
-
4572  token::owner(issuer),
-
4573  ticket::use(buyerTicketSeq++));
-
4574  env.close();
-
4575  BEAST_EXPECT(ownerCount(env, buyer) == 10);
-
4576  BEAST_EXPECT(ticketCount(env, buyer) == 9);
-
4577 
-
4578  // NFTokenCancelOffer
-
4579  env(token::cancelOffer(buyer, {offerIndex0}),
-
4580  ticket::use(buyerTicketSeq++));
+
4528  // issuer accepts buyer1's offer.
+
4529  env(token::acceptBuyOffer(issuer, buyer1OfferIndex));
+
4530  env.close();
+
4531 
+
4532  // Prove that buyer1 now owns nftId.
+
4533  BEAST_EXPECT(nftCount(env, issuer) == 0);
+
4534  BEAST_EXPECT(nftCount(env, buyer1) == 1);
+
4535  BEAST_EXPECT(nftCount(env, buyer2) == 0);
+
4536 
+
4537  // buyer1's offer was consumed, but buyer2's offer is still in the
+
4538  // ledger.
+
4539  BEAST_EXPECT(nftBuyOfferCount(nftId) == 1);
+
4540 
+
4541  // buyer1 can now accept buyer2's offer, even though buyer2's
+
4542  // NFTokenCreateOffer transaction specified the NFT Owner as issuer.
+
4543  env(token::acceptBuyOffer(buyer1, buyer2OfferIndex));
+
4544  env.close();
+
4545 
+
4546  // Prove that buyer2 now owns nftId.
+
4547  BEAST_EXPECT(nftCount(env, issuer) == 0);
+
4548  BEAST_EXPECT(nftCount(env, buyer1) == 0);
+
4549  BEAST_EXPECT(nftCount(env, buyer2) == 1);
+
4550 
+
4551  // All of the NFTokenOffers are now consumed.
+
4552  BEAST_EXPECT(nftBuyOfferCount(nftId) == 0);
+
4553  }
+
4554 
+
4555  void
+
4556  testNFTokenWithTickets(FeatureBitset features)
+
4557  {
+
4558  // Make sure all NFToken transactions work with tickets.
+
4559  testcase("NFToken transactions with tickets");
+
4560 
+
4561  using namespace test::jtx;
+
4562 
+
4563  Env env{*this, features};
+
4564 
+
4565  Account const issuer{"issuer"};
+
4566  Account const buyer{"buyer"};
+
4567  env.fund(XRP(10000), issuer, buyer);
+
4568  env.close();
+
4569 
+
4570  // issuer and buyer grab enough tickets for all of the following
+
4571  // transactions. Note that once the tickets are acquired issuer's
+
4572  // and buyer's account sequence numbers should not advance.
+
4573  std::uint32_t issuerTicketSeq{env.seq(issuer) + 1};
+
4574  env(ticket::create(issuer, 10));
+
4575  env.close();
+
4576  std::uint32_t const issuerSeq{env.seq(issuer)};
+
4577  BEAST_EXPECT(ticketCount(env, issuer) == 10);
+
4578 
+
4579  std::uint32_t buyerTicketSeq{env.seq(buyer) + 1};
+
4580  env(ticket::create(buyer, 10));
4581  env.close();
-
4582  BEAST_EXPECT(ownerCount(env, buyer) == 8);
-
4583  BEAST_EXPECT(ticketCount(env, buyer) == 8);
+
4582  std::uint32_t const buyerSeq{env.seq(buyer)};
+
4583  BEAST_EXPECT(ticketCount(env, buyer) == 10);
4584 
-
4585  // NFTokenCreateOffer. buyer tries again.
-
4586  uint256 const offerIndex1 = keylet::nftoffer(buyer, buyerTicketSeq).key;
-
4587  env(token::createOffer(buyer, nftId, XRP(2)),
-
4588  token::owner(issuer),
-
4589  ticket::use(buyerTicketSeq++));
-
4590  env.close();
-
4591  BEAST_EXPECT(ownerCount(env, buyer) == 8);
-
4592  BEAST_EXPECT(ticketCount(env, buyer) == 7);
-
4593 
-
4594  // NFTokenAcceptOffer. issuer accepts buyer's offer.
-
4595  env(token::acceptBuyOffer(issuer, offerIndex1),
-
4596  ticket::use(issuerTicketSeq++));
-
4597  env.close();
-
4598  BEAST_EXPECT(ownerCount(env, issuer) == 8);
-
4599  BEAST_EXPECT(ownerCount(env, buyer) == 8);
-
4600  BEAST_EXPECT(ticketCount(env, issuer) == 8);
-
4601 
-
4602  // NFTokenBurn. buyer burns the token they just bought.
-
4603  env(token::burn(buyer, nftId), ticket::use(buyerTicketSeq++));
-
4604  env.close();
-
4605  BEAST_EXPECT(ownerCount(env, issuer) == 8);
-
4606  BEAST_EXPECT(ownerCount(env, buyer) == 6);
-
4607  BEAST_EXPECT(ticketCount(env, buyer) == 6);
-
4608 
-
4609  // Verify that the account sequence numbers did not advance.
-
4610  BEAST_EXPECT(env.seq(issuer) == issuerSeq);
-
4611  BEAST_EXPECT(env.seq(buyer) == buyerSeq);
-
4612  }
-
4613 
-
4614  void
-
4615  testNFTokenDeleteAccount(FeatureBitset features)
-
4616  {
-
4617  // Account deletion rules with NFTs:
-
4618  // 1. An account holding one or more NFT offers may be deleted.
-
4619  // 2. An NFT issuer with any NFTs they have issued still in the
-
4620  // ledger may not be deleted.
-
4621  // 3. An account holding one or more NFTs may not be deleted.
-
4622  testcase("NFToken delete account");
-
4623 
-
4624  using namespace test::jtx;
-
4625 
-
4626  Env env{*this, features};
-
4627 
-
4628  Account const issuer{"issuer"};
-
4629  Account const minter{"minter"};
-
4630  Account const becky{"becky"};
-
4631  Account const carla{"carla"};
-
4632  Account const daria{"daria"};
-
4633 
-
4634  env.fund(XRP(10000), issuer, minter, becky, carla, daria);
-
4635  env.close();
-
4636 
-
4637  // Allow enough ledgers to pass so any of these accounts can be deleted.
-
4638  for (int i = 0; i < 300; ++i)
-
4639  env.close();
+
4585  // NFTokenMint
+
4586  BEAST_EXPECT(ownerCount(env, issuer) == 10);
+
4587  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
+
4588  env(token::mint(issuer, 0u),
+
4589  txflags(tfTransferable),
+
4590  ticket::use(issuerTicketSeq++));
+
4591  env.close();
+
4592  BEAST_EXPECT(ownerCount(env, issuer) == 10);
+
4593  BEAST_EXPECT(ticketCount(env, issuer) == 9);
+
4594 
+
4595  // NFTokenCreateOffer
+
4596  BEAST_EXPECT(ownerCount(env, buyer) == 10);
+
4597  uint256 const offerIndex0 = keylet::nftoffer(buyer, buyerTicketSeq).key;
+
4598  env(token::createOffer(buyer, nftId, XRP(1)),
+
4599  token::owner(issuer),
+
4600  ticket::use(buyerTicketSeq++));
+
4601  env.close();
+
4602  BEAST_EXPECT(ownerCount(env, buyer) == 10);
+
4603  BEAST_EXPECT(ticketCount(env, buyer) == 9);
+
4604 
+
4605  // NFTokenCancelOffer
+
4606  env(token::cancelOffer(buyer, {offerIndex0}),
+
4607  ticket::use(buyerTicketSeq++));
+
4608  env.close();
+
4609  BEAST_EXPECT(ownerCount(env, buyer) == 8);
+
4610  BEAST_EXPECT(ticketCount(env, buyer) == 8);
+
4611 
+
4612  // NFTokenCreateOffer. buyer tries again.
+
4613  uint256 const offerIndex1 = keylet::nftoffer(buyer, buyerTicketSeq).key;
+
4614  env(token::createOffer(buyer, nftId, XRP(2)),
+
4615  token::owner(issuer),
+
4616  ticket::use(buyerTicketSeq++));
+
4617  env.close();
+
4618  BEAST_EXPECT(ownerCount(env, buyer) == 8);
+
4619  BEAST_EXPECT(ticketCount(env, buyer) == 7);
+
4620 
+
4621  // NFTokenAcceptOffer. issuer accepts buyer's offer.
+
4622  env(token::acceptBuyOffer(issuer, offerIndex1),
+
4623  ticket::use(issuerTicketSeq++));
+
4624  env.close();
+
4625  BEAST_EXPECT(ownerCount(env, issuer) == 8);
+
4626  BEAST_EXPECT(ownerCount(env, buyer) == 8);
+
4627  BEAST_EXPECT(ticketCount(env, issuer) == 8);
+
4628 
+
4629  // NFTokenBurn. buyer burns the token they just bought.
+
4630  env(token::burn(buyer, nftId), ticket::use(buyerTicketSeq++));
+
4631  env.close();
+
4632  BEAST_EXPECT(ownerCount(env, issuer) == 8);
+
4633  BEAST_EXPECT(ownerCount(env, buyer) == 6);
+
4634  BEAST_EXPECT(ticketCount(env, buyer) == 6);
+
4635 
+
4636  // Verify that the account sequence numbers did not advance.
+
4637  BEAST_EXPECT(env.seq(issuer) == issuerSeq);
+
4638  BEAST_EXPECT(env.seq(buyer) == buyerSeq);
+
4639  }
4640 
-
4641  env(token::setMinter(issuer, minter));
-
4642  env.close();
-
4643 
-
4644  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
-
4645  env(token::mint(minter, 0u),
-
4646  token::issuer(issuer),
-
4647  txflags(tfTransferable));
-
4648  env.close();
-
4649 
-
4650  // At the moment issuer and minter cannot delete themselves.
-
4651  // o issuer has an issued NFT in the ledger.
-
4652  // o minter owns an NFT.
-
4653  env(acctdelete(issuer, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
-
4654  env(acctdelete(minter, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
-
4655  env.close();
-
4656 
-
4657  // Let enough ledgers pass so the account delete transactions are
-
4658  // not retried.
-
4659  for (int i = 0; i < 15; ++i)
-
4660  env.close();
-
4661 
-
4662  // becky and carla create offers for minter's NFT.
-
4663  env(token::createOffer(becky, nftId, XRP(2)), token::owner(minter));
-
4664  env.close();
-
4665 
-
4666  uint256 const carlaOfferIndex =
-
4667  keylet::nftoffer(carla, env.seq(carla)).key;
-
4668  env(token::createOffer(carla, nftId, XRP(3)), token::owner(minter));
+
4641  void
+
4642  testNFTokenDeleteAccount(FeatureBitset features)
+
4643  {
+
4644  // Account deletion rules with NFTs:
+
4645  // 1. An account holding one or more NFT offers may be deleted.
+
4646  // 2. An NFT issuer with any NFTs they have issued still in the
+
4647  // ledger may not be deleted.
+
4648  // 3. An account holding one or more NFTs may not be deleted.
+
4649  testcase("NFToken delete account");
+
4650 
+
4651  using namespace test::jtx;
+
4652 
+
4653  Env env{*this, features};
+
4654 
+
4655  Account const issuer{"issuer"};
+
4656  Account const minter{"minter"};
+
4657  Account const becky{"becky"};
+
4658  Account const carla{"carla"};
+
4659  Account const daria{"daria"};
+
4660 
+
4661  env.fund(XRP(10000), issuer, minter, becky, carla, daria);
+
4662  env.close();
+
4663 
+
4664  // Allow enough ledgers to pass so any of these accounts can be deleted.
+
4665  for (int i = 0; i < 300; ++i)
+
4666  env.close();
+
4667 
+
4668  env(token::setMinter(issuer, minter));
4669  env.close();
4670 
-
4671  // It should be possible for becky to delete herself, even though
-
4672  // becky has an active NFT offer.
-
4673  env(acctdelete(becky, daria), fee(XRP(50)));
-
4674  env.close();
-
4675 
-
4676  // minter accepts carla's offer.
-
4677  env(token::acceptBuyOffer(minter, carlaOfferIndex));
-
4678  env.close();
-
4679 
-
4680  // Now it should be possible for minter to delete themselves since
-
4681  // they no longer own an NFT.
-
4682  env(acctdelete(minter, daria), fee(XRP(50)));
-
4683  env.close();
-
4684 
-
4685  // 1. issuer cannot delete themselves because they issued an NFT that
-
4686  // is still in the ledger.
-
4687  // 2. carla owns an NFT, so she cannot delete herself.
-
4688  env(acctdelete(issuer, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
-
4689  env(acctdelete(carla, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
-
4690  env.close();
-
4691 
-
4692  // Let enough ledgers pass so the account delete transactions are
-
4693  // not retried.
-
4694  for (int i = 0; i < 15; ++i)
-
4695  env.close();
-
4696 
-
4697  // carla burns her NFT. Since issuer's NFT is no longer in the
-
4698  // ledger, both issuer and carla can delete themselves.
-
4699  env(token::burn(carla, nftId));
-
4700  env.close();
-
4701 
-
4702  env(acctdelete(issuer, daria), fee(XRP(50)));
-
4703  env(acctdelete(carla, daria), fee(XRP(50)));
-
4704  env.close();
-
4705  }
+
4671  uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
+
4672  env(token::mint(minter, 0u),
+
4673  token::issuer(issuer),
+
4674  txflags(tfTransferable));
+
4675  env.close();
+
4676 
+
4677  // At the moment issuer and minter cannot delete themselves.
+
4678  // o issuer has an issued NFT in the ledger.
+
4679  // o minter owns an NFT.
+
4680  env(acctdelete(issuer, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
+
4681  env(acctdelete(minter, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
+
4682  env.close();
+
4683 
+
4684  // Let enough ledgers pass so the account delete transactions are
+
4685  // not retried.
+
4686  for (int i = 0; i < 15; ++i)
+
4687  env.close();
+
4688 
+
4689  // becky and carla create offers for minter's NFT.
+
4690  env(token::createOffer(becky, nftId, XRP(2)), token::owner(minter));
+
4691  env.close();
+
4692 
+
4693  uint256 const carlaOfferIndex =
+
4694  keylet::nftoffer(carla, env.seq(carla)).key;
+
4695  env(token::createOffer(carla, nftId, XRP(3)), token::owner(minter));
+
4696  env.close();
+
4697 
+
4698  // It should be possible for becky to delete herself, even though
+
4699  // becky has an active NFT offer.
+
4700  env(acctdelete(becky, daria), fee(XRP(50)));
+
4701  env.close();
+
4702 
+
4703  // minter accepts carla's offer.
+
4704  env(token::acceptBuyOffer(minter, carlaOfferIndex));
+
4705  env.close();
4706 
-
4707  void
-
4708  testNftXxxOffers(FeatureBitset features)
-
4709  {
-
4710  testcase("nft_buy_offers and nft_sell_offers");
+
4707  // Now it should be possible for minter to delete themselves since
+
4708  // they no longer own an NFT.
+
4709  env(acctdelete(minter, daria), fee(XRP(50)));
+
4710  env.close();
4711 
-
4712  // The default limit on returned NFToken offers is 250, so we need
-
4713  // to produce more than 250 offers of each kind in order to exercise
-
4714  // the marker.
-
4715 
-
4716  // Fortunately there's nothing in the rules that says an account
-
4717  // can't hold more than one offer for the same NFT. So we only
-
4718  // need two accounts to generate the necessary offers.
-
4719  using namespace test::jtx;
-
4720 
-
4721  Env env{*this, features};
-
4722 
-
4723  Account const issuer{"issuer"};
-
4724  Account const buyer{"buyer"};
-
4725 
-
4726  // A lot of offers requires a lot for reserve.
-
4727  env.fund(XRP(1000000), issuer, buyer);
-
4728  env.close();
-
4729 
-
4730  // Create an NFT that we'll make offers for.
-
4731  uint256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
-
4732  env(token::mint(issuer, 0), txflags(tfTransferable));
-
4733  env.close();
-
4734 
-
4735  // A lambda that validates nft_XXX_offers query responses.
-
4736  auto checkOffers = [this, &env, &nftID](
-
4737  char const* request,
-
4738  int expectCount,
-
4739  int expectMarkerCount,
-
4740  int line) {
-
4741  int markerCount = 0;
-
4742  Json::Value allOffers(Json::arrayValue);
-
4743  std::string marker;
-
4744 
-
4745  // The do/while collects results until no marker is returned.
-
4746  do
-
4747  {
-
4748  Json::Value nftOffers = [&env, &nftID, &request, &marker]() {
-
4749  Json::Value params;
-
4750  params[jss::nft_id] = to_string(nftID);
-
4751 
-
4752  if (!marker.empty())
-
4753  params[jss::marker] = marker;
-
4754  return env.rpc("json", request, to_string(params));
-
4755  }();
+
4712  // 1. issuer cannot delete themselves because they issued an NFT that
+
4713  // is still in the ledger.
+
4714  // 2. carla owns an NFT, so she cannot delete herself.
+
4715  env(acctdelete(issuer, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
+
4716  env(acctdelete(carla, daria), fee(XRP(50)), ter(tecHAS_OBLIGATIONS));
+
4717  env.close();
+
4718 
+
4719  // Let enough ledgers pass so the account delete transactions are
+
4720  // not retried.
+
4721  for (int i = 0; i < 15; ++i)
+
4722  env.close();
+
4723 
+
4724  // carla burns her NFT. Since issuer's NFT is no longer in the
+
4725  // ledger, both issuer and carla can delete themselves.
+
4726  env(token::burn(carla, nftId));
+
4727  env.close();
+
4728 
+
4729  env(acctdelete(issuer, daria), fee(XRP(50)));
+
4730  env(acctdelete(carla, daria), fee(XRP(50)));
+
4731  env.close();
+
4732  }
+
4733 
+
4734  void
+
4735  testNftXxxOffers(FeatureBitset features)
+
4736  {
+
4737  testcase("nft_buy_offers and nft_sell_offers");
+
4738 
+
4739  // The default limit on returned NFToken offers is 250, so we need
+
4740  // to produce more than 250 offers of each kind in order to exercise
+
4741  // the marker.
+
4742 
+
4743  // Fortunately there's nothing in the rules that says an account
+
4744  // can't hold more than one offer for the same NFT. So we only
+
4745  // need two accounts to generate the necessary offers.
+
4746  using namespace test::jtx;
+
4747 
+
4748  Env env{*this, features};
+
4749 
+
4750  Account const issuer{"issuer"};
+
4751  Account const buyer{"buyer"};
+
4752 
+
4753  // A lot of offers requires a lot for reserve.
+
4754  env.fund(XRP(1000000), issuer, buyer);
+
4755  env.close();
4756 
-
4757  // If there are no offers for the NFT we get an error
-
4758  if (expectCount == 0)
-
4759  {
-
4760  if (expect(
-
4761  nftOffers.isMember(jss::result),
-
4762  "expected \"result\"",
-
4763  __FILE__,
-
4764  line))
-
4765  {
-
4766  if (expect(
-
4767  nftOffers[jss::result].isMember(jss::error),
-
4768  "expected \"error\"",
-
4769  __FILE__,
-
4770  line))
-
4771  {
-
4772  expect(
-
4773  nftOffers[jss::result][jss::error].asString() ==
-
4774  "objectNotFound",
-
4775  "expected \"objectNotFound\"",
-
4776  __FILE__,
-
4777  line);
-
4778  }
-
4779  }
-
4780  break;
-
4781  }
-
4782 
-
4783  marker.clear();
-
4784  if (expect(
-
4785  nftOffers.isMember(jss::result),
-
4786  "expected \"result\"",
-
4787  __FILE__,
-
4788  line))
-
4789  {
-
4790  Json::Value& result = nftOffers[jss::result];
-
4791 
-
4792  if (result.isMember(jss::marker))
-
4793  {
-
4794  ++markerCount;
-
4795  marker = result[jss::marker].asString();
-
4796  }
-
4797 
-
4798  if (expect(
-
4799  result.isMember(jss::offers),
-
4800  "expected \"offers\"",
-
4801  __FILE__,
-
4802  line))
-
4803  {
-
4804  Json::Value& someOffers = result[jss::offers];
-
4805  for (std::size_t i = 0; i < someOffers.size(); ++i)
-
4806  allOffers.append(someOffers[i]);
-
4807  }
+
4757  // Create an NFT that we'll make offers for.
+
4758  uint256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
+
4759  env(token::mint(issuer, 0), txflags(tfTransferable));
+
4760  env.close();
+
4761 
+
4762  // A lambda that validates nft_XXX_offers query responses.
+
4763  auto checkOffers = [this, &env, &nftID](
+
4764  char const* request,
+
4765  int expectCount,
+
4766  int expectMarkerCount,
+
4767  int line) {
+
4768  int markerCount = 0;
+
4769  Json::Value allOffers(Json::arrayValue);
+
4770  std::string marker;
+
4771 
+
4772  // The do/while collects results until no marker is returned.
+
4773  do
+
4774  {
+
4775  Json::Value nftOffers = [&env, &nftID, &request, &marker]() {
+
4776  Json::Value params;
+
4777  params[jss::nft_id] = to_string(nftID);
+
4778 
+
4779  if (!marker.empty())
+
4780  params[jss::marker] = marker;
+
4781  return env.rpc("json", request, to_string(params));
+
4782  }();
+
4783 
+
4784  // If there are no offers for the NFT we get an error
+
4785  if (expectCount == 0)
+
4786  {
+
4787  if (expect(
+
4788  nftOffers.isMember(jss::result),
+
4789  "expected \"result\"",
+
4790  __FILE__,
+
4791  line))
+
4792  {
+
4793  if (expect(
+
4794  nftOffers[jss::result].isMember(jss::error),
+
4795  "expected \"error\"",
+
4796  __FILE__,
+
4797  line))
+
4798  {
+
4799  expect(
+
4800  nftOffers[jss::result][jss::error].asString() ==
+
4801  "objectNotFound",
+
4802  "expected \"objectNotFound\"",
+
4803  __FILE__,
+
4804  line);
+
4805  }
+
4806  }
+
4807  break;
4808  }
-
4809  } while (!marker.empty());
-
4810 
-
4811  // Verify the contents of allOffers makes sense.
-
4812  expect(
-
4813  allOffers.size() == expectCount,
-
4814  "Unexpected returned offer count",
-
4815  __FILE__,
-
4816  line);
-
4817  expect(
-
4818  markerCount == expectMarkerCount,
-
4819  "Unexpected marker count",
-
4820  __FILE__,
-
4821  line);
-
4822  std::optional<int> globalFlags;
-
4823  std::set<std::string> offerIndexes;
-
4824  std::set<std::string> amounts;
-
4825  for (Json::Value const& offer : allOffers)
-
4826  {
-
4827  // The flags on all found offers should be the same.
-
4828  if (!globalFlags)
-
4829  globalFlags = offer[jss::flags].asInt();
-
4830 
-
4831  expect(
-
4832  *globalFlags == offer[jss::flags].asInt(),
-
4833  "Inconsistent flags returned",
-
4834  __FILE__,
-
4835  line);
-
4836 
-
4837  // The test conditions should produce unique indexes and
-
4838  // amounts for all offers.
-
4839  offerIndexes.insert(offer[jss::nft_offer_index].asString());
-
4840  amounts.insert(offer[jss::amount].asString());
-
4841  }
-
4842 
-
4843  expect(
-
4844  offerIndexes.size() == expectCount,
-
4845  "Duplicate indexes returned?",
-
4846  __FILE__,
-
4847  line);
-
4848  expect(
-
4849  amounts.size() == expectCount,
-
4850  "Duplicate amounts returned?",
-
4851  __FILE__,
-
4852  line);
-
4853  };
-
4854 
-
4855  // There are no sell offers.
-
4856  checkOffers("nft_sell_offers", 0, false, __LINE__);
+
4809 
+
4810  marker.clear();
+
4811  if (expect(
+
4812  nftOffers.isMember(jss::result),
+
4813  "expected \"result\"",
+
4814  __FILE__,
+
4815  line))
+
4816  {
+
4817  Json::Value& result = nftOffers[jss::result];
+
4818 
+
4819  if (result.isMember(jss::marker))
+
4820  {
+
4821  ++markerCount;
+
4822  marker = result[jss::marker].asString();
+
4823  }
+
4824 
+
4825  if (expect(
+
4826  result.isMember(jss::offers),
+
4827  "expected \"offers\"",
+
4828  __FILE__,
+
4829  line))
+
4830  {
+
4831  Json::Value& someOffers = result[jss::offers];
+
4832  for (std::size_t i = 0; i < someOffers.size(); ++i)
+
4833  allOffers.append(someOffers[i]);
+
4834  }
+
4835  }
+
4836  } while (!marker.empty());
+
4837 
+
4838  // Verify the contents of allOffers makes sense.
+
4839  expect(
+
4840  allOffers.size() == expectCount,
+
4841  "Unexpected returned offer count",
+
4842  __FILE__,
+
4843  line);
+
4844  expect(
+
4845  markerCount == expectMarkerCount,
+
4846  "Unexpected marker count",
+
4847  __FILE__,
+
4848  line);
+
4849  std::optional<int> globalFlags;
+
4850  std::set<std::string> offerIndexes;
+
4851  std::set<std::string> amounts;
+
4852  for (Json::Value const& offer : allOffers)
+
4853  {
+
4854  // The flags on all found offers should be the same.
+
4855  if (!globalFlags)
+
4856  globalFlags = offer[jss::flags].asInt();
4857 
-
4858  // A lambda that generates sell offers.
-
4859  STAmount sellPrice = XRP(0);
-
4860  auto makeSellOffers =
-
4861  [&env, &issuer, &nftID, &sellPrice](STAmount const& limit) {
-
4862  // Save a little test time by not closing too often.
-
4863  int offerCount = 0;
-
4864  while (sellPrice < limit)
-
4865  {
-
4866  sellPrice += XRP(1);
-
4867  env(token::createOffer(issuer, nftID, sellPrice),
-
4868  txflags(tfSellNFToken));
-
4869  if (++offerCount % 10 == 0)
-
4870  env.close();
-
4871  }
-
4872  env.close();
-
4873  };
-
4874 
-
4875  // There is one sell offer.
-
4876  makeSellOffers(XRP(1));
-
4877  checkOffers("nft_sell_offers", 1, 0, __LINE__);
-
4878 
-
4879  // There are 250 sell offers.
-
4880  makeSellOffers(XRP(250));
-
4881  checkOffers("nft_sell_offers", 250, 0, __LINE__);
-
4882 
-
4883  // There are 251 sell offers.
-
4884  makeSellOffers(XRP(251));
-
4885  checkOffers("nft_sell_offers", 251, 1, __LINE__);
-
4886 
-
4887  // There are 500 sell offers.
-
4888  makeSellOffers(XRP(500));
-
4889  checkOffers("nft_sell_offers", 500, 1, __LINE__);
-
4890 
-
4891  // There are 501 sell offers.
-
4892  makeSellOffers(XRP(501));
-
4893  checkOffers("nft_sell_offers", 501, 2, __LINE__);
-
4894 
-
4895  // There are no buy offers.
-
4896  checkOffers("nft_buy_offers", 0, 0, __LINE__);
-
4897 
-
4898  // A lambda that generates buy offers.
-
4899  STAmount buyPrice = XRP(0);
-
4900  auto makeBuyOffers =
-
4901  [&env, &buyer, &issuer, &nftID, &buyPrice](STAmount const& limit) {
-
4902  // Save a little test time by not closing too often.
-
4903  int offerCount = 0;
-
4904  while (buyPrice < limit)
-
4905  {
-
4906  buyPrice += XRP(1);
-
4907  env(token::createOffer(buyer, nftID, buyPrice),
-
4908  token::owner(issuer));
-
4909  if (++offerCount % 10 == 0)
-
4910  env.close();
-
4911  }
-
4912  env.close();
-
4913  };
-
4914 
-
4915  // There is one buy offer;
-
4916  makeBuyOffers(XRP(1));
-
4917  checkOffers("nft_buy_offers", 1, 0, __LINE__);
-
4918 
-
4919  // There are 250 buy offers.
-
4920  makeBuyOffers(XRP(250));
-
4921  checkOffers("nft_buy_offers", 250, 0, __LINE__);
-
4922 
-
4923  // There are 251 buy offers.
-
4924  makeBuyOffers(XRP(251));
-
4925  checkOffers("nft_buy_offers", 251, 1, __LINE__);
-
4926 
-
4927  // There are 500 buy offers.
-
4928  makeBuyOffers(XRP(500));
-
4929  checkOffers("nft_buy_offers", 500, 1, __LINE__);
-
4930 
-
4931  // There are 501 buy offers.
-
4932  makeBuyOffers(XRP(501));
-
4933  checkOffers("nft_buy_offers", 501, 2, __LINE__);
-
4934  }
-
4935 
-
4936  void
-
4937  testFixNFTokenNegOffer(FeatureBitset features)
-
4938  {
-
4939  // Exercise changes introduced by fixNFTokenNegOffer.
-
4940  using namespace test::jtx;
+
4858  expect(
+
4859  *globalFlags == offer[jss::flags].asInt(),
+
4860  "Inconsistent flags returned",
+
4861  __FILE__,
+
4862  line);
+
4863 
+
4864  // The test conditions should produce unique indexes and
+
4865  // amounts for all offers.
+
4866  offerIndexes.insert(offer[jss::nft_offer_index].asString());
+
4867  amounts.insert(offer[jss::amount].asString());
+
4868  }
+
4869 
+
4870  expect(
+
4871  offerIndexes.size() == expectCount,
+
4872  "Duplicate indexes returned?",
+
4873  __FILE__,
+
4874  line);
+
4875  expect(
+
4876  amounts.size() == expectCount,
+
4877  "Duplicate amounts returned?",
+
4878  __FILE__,
+
4879  line);
+
4880  };
+
4881 
+
4882  // There are no sell offers.
+
4883  checkOffers("nft_sell_offers", 0, false, __LINE__);
+
4884 
+
4885  // A lambda that generates sell offers.
+
4886  STAmount sellPrice = XRP(0);
+
4887  auto makeSellOffers =
+
4888  [&env, &issuer, &nftID, &sellPrice](STAmount const& limit) {
+
4889  // Save a little test time by not closing too often.
+
4890  int offerCount = 0;
+
4891  while (sellPrice < limit)
+
4892  {
+
4893  sellPrice += XRP(1);
+
4894  env(token::createOffer(issuer, nftID, sellPrice),
+
4895  txflags(tfSellNFToken));
+
4896  if (++offerCount % 10 == 0)
+
4897  env.close();
+
4898  }
+
4899  env.close();
+
4900  };
+
4901 
+
4902  // There is one sell offer.
+
4903  makeSellOffers(XRP(1));
+
4904  checkOffers("nft_sell_offers", 1, 0, __LINE__);
+
4905 
+
4906  // There are 250 sell offers.
+
4907  makeSellOffers(XRP(250));
+
4908  checkOffers("nft_sell_offers", 250, 0, __LINE__);
+
4909 
+
4910  // There are 251 sell offers.
+
4911  makeSellOffers(XRP(251));
+
4912  checkOffers("nft_sell_offers", 251, 1, __LINE__);
+
4913 
+
4914  // There are 500 sell offers.
+
4915  makeSellOffers(XRP(500));
+
4916  checkOffers("nft_sell_offers", 500, 1, __LINE__);
+
4917 
+
4918  // There are 501 sell offers.
+
4919  makeSellOffers(XRP(501));
+
4920  checkOffers("nft_sell_offers", 501, 2, __LINE__);
+
4921 
+
4922  // There are no buy offers.
+
4923  checkOffers("nft_buy_offers", 0, 0, __LINE__);
+
4924 
+
4925  // A lambda that generates buy offers.
+
4926  STAmount buyPrice = XRP(0);
+
4927  auto makeBuyOffers =
+
4928  [&env, &buyer, &issuer, &nftID, &buyPrice](STAmount const& limit) {
+
4929  // Save a little test time by not closing too often.
+
4930  int offerCount = 0;
+
4931  while (buyPrice < limit)
+
4932  {
+
4933  buyPrice += XRP(1);
+
4934  env(token::createOffer(buyer, nftID, buyPrice),
+
4935  token::owner(issuer));
+
4936  if (++offerCount % 10 == 0)
+
4937  env.close();
+
4938  }
+
4939  env.close();
+
4940  };
4941 
-
4942  testcase("fixNFTokenNegOffer");
-
4943 
-
4944  Account const issuer{"issuer"};
-
4945  Account const buyer{"buyer"};
-
4946  Account const gw{"gw"};
-
4947  IOU const gwXAU(gw["XAU"]);
-
4948 
-
4949  // Test both with and without fixNFTokenNegOffer and
-
4950  // fixNonFungibleTokensV1_2. Need to turn off fixNonFungibleTokensV1_2
-
4951  // as well because that amendment came later and addressed the
-
4952  // acceptance side of this issue.
-
4953  for (auto const& tweakedFeatures :
-
4954  {features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1 -
-
4955  fixNonFungibleTokensV1_2,
-
4956  features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1,
-
4957  features | fixNFTokenNegOffer})
-
4958  {
-
4959  // There was a bug in the initial NFT implementation that
-
4960  // allowed offers to be placed with negative amounts. Verify
-
4961  // that fixNFTokenNegOffer addresses the problem.
-
4962  Env env{*this, tweakedFeatures};
-
4963 
-
4964  env.fund(XRP(1000000), issuer, buyer, gw);
-
4965  env.close();
-
4966 
-
4967  env(trust(issuer, gwXAU(2000)));
-
4968  env(trust(buyer, gwXAU(2000)));
-
4969  env.close();
+
4942  // There is one buy offer;
+
4943  makeBuyOffers(XRP(1));
+
4944  checkOffers("nft_buy_offers", 1, 0, __LINE__);
+
4945 
+
4946  // There are 250 buy offers.
+
4947  makeBuyOffers(XRP(250));
+
4948  checkOffers("nft_buy_offers", 250, 0, __LINE__);
+
4949 
+
4950  // There are 251 buy offers.
+
4951  makeBuyOffers(XRP(251));
+
4952  checkOffers("nft_buy_offers", 251, 1, __LINE__);
+
4953 
+
4954  // There are 500 buy offers.
+
4955  makeBuyOffers(XRP(500));
+
4956  checkOffers("nft_buy_offers", 500, 1, __LINE__);
+
4957 
+
4958  // There are 501 buy offers.
+
4959  makeBuyOffers(XRP(501));
+
4960  checkOffers("nft_buy_offers", 501, 2, __LINE__);
+
4961  }
+
4962 
+
4963  void
+
4964  testFixNFTokenNegOffer(FeatureBitset features)
+
4965  {
+
4966  // Exercise changes introduced by fixNFTokenNegOffer.
+
4967  using namespace test::jtx;
+
4968 
+
4969  testcase("fixNFTokenNegOffer");
4970 
-
4971  env(pay(gw, issuer, gwXAU(1000)));
-
4972  env(pay(gw, buyer, gwXAU(1000)));
-
4973  env.close();
-
4974 
-
4975  // Create an NFT that we'll make XRP offers for.
-
4976  uint256 const nftID0{
-
4977  token::getNextID(env, issuer, 0u, tfTransferable)};
-
4978  env(token::mint(issuer, 0), txflags(tfTransferable));
-
4979  env.close();
-
4980 
-
4981  // Create an NFT that we'll make IOU offers for.
-
4982  uint256 const nftID1{
-
4983  token::getNextID(env, issuer, 1u, tfTransferable)};
-
4984  env(token::mint(issuer, 1), txflags(tfTransferable));
-
4985  env.close();
-
4986 
-
4987  TER const offerCreateTER = tweakedFeatures[fixNFTokenNegOffer]
-
4988  ? static_cast<TER>(temBAD_AMOUNT)
-
4989  : static_cast<TER>(tesSUCCESS);
+
4971  Account const issuer{"issuer"};
+
4972  Account const buyer{"buyer"};
+
4973  Account const gw{"gw"};
+
4974  IOU const gwXAU(gw["XAU"]);
+
4975 
+
4976  // Test both with and without fixNFTokenNegOffer and
+
4977  // fixNonFungibleTokensV1_2. Need to turn off fixNonFungibleTokensV1_2
+
4978  // as well because that amendment came later and addressed the
+
4979  // acceptance side of this issue.
+
4980  for (auto const& tweakedFeatures :
+
4981  {features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1 -
+
4982  fixNonFungibleTokensV1_2,
+
4983  features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1,
+
4984  features | fixNFTokenNegOffer})
+
4985  {
+
4986  // There was a bug in the initial NFT implementation that
+
4987  // allowed offers to be placed with negative amounts. Verify
+
4988  // that fixNFTokenNegOffer addresses the problem.
+
4989  Env env{*this, tweakedFeatures};
4990 
-
4991  // Make offers with negative amounts for the NFTs
-
4992  uint256 const sellNegXrpOfferIndex =
-
4993  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
4994  env(token::createOffer(issuer, nftID0, XRP(-2)),
-
4995  txflags(tfSellNFToken),
-
4996  ter(offerCreateTER));
-
4997  env.close();
-
4998 
-
4999  uint256 const sellNegIouOfferIndex =
-
5000  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
5001  env(token::createOffer(issuer, nftID1, gwXAU(-2)),
-
5002  txflags(tfSellNFToken),
-
5003  ter(offerCreateTER));
-
5004  env.close();
-
5005 
-
5006  uint256 const buyNegXrpOfferIndex =
-
5007  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
5008  env(token::createOffer(buyer, nftID0, XRP(-1)),
-
5009  token::owner(issuer),
-
5010  ter(offerCreateTER));
-
5011  env.close();
-
5012 
-
5013  uint256 const buyNegIouOfferIndex =
-
5014  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
5015  env(token::createOffer(buyer, nftID1, gwXAU(-1)),
-
5016  token::owner(issuer),
-
5017  ter(offerCreateTER));
-
5018  env.close();
-
5019 
-
5020  {
-
5021  // Now try to accept the offers.
-
5022  // 1. If fixNFTokenNegOffer is NOT enabled get tecINTERNAL.
-
5023  // 2. If fixNFTokenNegOffer IS enabled get tecOBJECT_NOT_FOUND.
-
5024  TER const offerAcceptTER = tweakedFeatures[fixNFTokenNegOffer]
-
5025  ? static_cast<TER>(tecOBJECT_NOT_FOUND)
-
5026  : static_cast<TER>(tecINTERNAL);
-
5027 
-
5028  // Sell offers.
-
5029  env(token::acceptSellOffer(buyer, sellNegXrpOfferIndex),
-
5030  ter(offerAcceptTER));
-
5031  env.close();
-
5032  env(token::acceptSellOffer(buyer, sellNegIouOfferIndex),
-
5033  ter(offerAcceptTER));
-
5034  env.close();
-
5035 
-
5036  // Buy offers.
-
5037  env(token::acceptBuyOffer(issuer, buyNegXrpOfferIndex),
-
5038  ter(offerAcceptTER));
-
5039  env.close();
-
5040  env(token::acceptBuyOffer(issuer, buyNegIouOfferIndex),
-
5041  ter(offerAcceptTER));
-
5042  env.close();
-
5043  }
-
5044  {
-
5045  // 1. If fixNFTokenNegOffer is enabled get tecOBJECT_NOT_FOUND
-
5046  // 2. If it is not enabled, but fixNonFungibleTokensV1_2 is
-
5047  // enabled, get tecOBJECT_NOT_FOUND.
-
5048  // 3. If neither are enabled, get tesSUCCESS.
-
5049  TER const offerAcceptTER = tweakedFeatures[fixNFTokenNegOffer]
-
5050  ? static_cast<TER>(tecOBJECT_NOT_FOUND)
-
5051  : static_cast<TER>(tesSUCCESS);
-
5052 
-
5053  // Brokered offers.
-
5054  env(token::brokerOffers(
-
5055  gw, buyNegXrpOfferIndex, sellNegXrpOfferIndex),
-
5056  ter(offerAcceptTER));
-
5057  env.close();
-
5058  env(token::brokerOffers(
-
5059  gw, buyNegIouOfferIndex, sellNegIouOfferIndex),
+
4991  env.fund(XRP(1000000), issuer, buyer, gw);
+
4992  env.close();
+
4993 
+
4994  env(trust(issuer, gwXAU(2000)));
+
4995  env(trust(buyer, gwXAU(2000)));
+
4996  env.close();
+
4997 
+
4998  env(pay(gw, issuer, gwXAU(1000)));
+
4999  env(pay(gw, buyer, gwXAU(1000)));
+
5000  env.close();
+
5001 
+
5002  // Create an NFT that we'll make XRP offers for.
+
5003  uint256 const nftID0{
+
5004  token::getNextID(env, issuer, 0u, tfTransferable)};
+
5005  env(token::mint(issuer, 0), txflags(tfTransferable));
+
5006  env.close();
+
5007 
+
5008  // Create an NFT that we'll make IOU offers for.
+
5009  uint256 const nftID1{
+
5010  token::getNextID(env, issuer, 1u, tfTransferable)};
+
5011  env(token::mint(issuer, 1), txflags(tfTransferable));
+
5012  env.close();
+
5013 
+
5014  TER const offerCreateTER = tweakedFeatures[fixNFTokenNegOffer]
+
5015  ? static_cast<TER>(temBAD_AMOUNT)
+
5016  : static_cast<TER>(tesSUCCESS);
+
5017 
+
5018  // Make offers with negative amounts for the NFTs
+
5019  uint256 const sellNegXrpOfferIndex =
+
5020  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
5021  env(token::createOffer(issuer, nftID0, XRP(-2)),
+
5022  txflags(tfSellNFToken),
+
5023  ter(offerCreateTER));
+
5024  env.close();
+
5025 
+
5026  uint256 const sellNegIouOfferIndex =
+
5027  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
5028  env(token::createOffer(issuer, nftID1, gwXAU(-2)),
+
5029  txflags(tfSellNFToken),
+
5030  ter(offerCreateTER));
+
5031  env.close();
+
5032 
+
5033  uint256 const buyNegXrpOfferIndex =
+
5034  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
5035  env(token::createOffer(buyer, nftID0, XRP(-1)),
+
5036  token::owner(issuer),
+
5037  ter(offerCreateTER));
+
5038  env.close();
+
5039 
+
5040  uint256 const buyNegIouOfferIndex =
+
5041  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
5042  env(token::createOffer(buyer, nftID1, gwXAU(-1)),
+
5043  token::owner(issuer),
+
5044  ter(offerCreateTER));
+
5045  env.close();
+
5046 
+
5047  {
+
5048  // Now try to accept the offers.
+
5049  // 1. If fixNFTokenNegOffer is NOT enabled get tecINTERNAL.
+
5050  // 2. If fixNFTokenNegOffer IS enabled get tecOBJECT_NOT_FOUND.
+
5051  TER const offerAcceptTER = tweakedFeatures[fixNFTokenNegOffer]
+
5052  ? static_cast<TER>(tecOBJECT_NOT_FOUND)
+
5053  : static_cast<TER>(tecINTERNAL);
+
5054 
+
5055  // Sell offers.
+
5056  env(token::acceptSellOffer(buyer, sellNegXrpOfferIndex),
+
5057  ter(offerAcceptTER));
+
5058  env.close();
+
5059  env(token::acceptSellOffer(buyer, sellNegIouOfferIndex),
5060  ter(offerAcceptTER));
5061  env.close();
-
5062  }
-
5063  }
-
5064 
-
5065  // Test what happens if NFTokenOffers are created with negative amounts
-
5066  // and then fixNFTokenNegOffer goes live. What does an acceptOffer do?
-
5067  {
-
5068  Env env{
-
5069  *this,
-
5070  features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1};
-
5071 
-
5072  env.fund(XRP(1000000), issuer, buyer, gw);
-
5073  env.close();
-
5074 
-
5075  env(trust(issuer, gwXAU(2000)));
-
5076  env(trust(buyer, gwXAU(2000)));
-
5077  env.close();
-
5078 
-
5079  env(pay(gw, issuer, gwXAU(1000)));
-
5080  env(pay(gw, buyer, gwXAU(1000)));
-
5081  env.close();
-
5082 
-
5083  // Create an NFT that we'll make XRP offers for.
-
5084  uint256 const nftID0{
-
5085  token::getNextID(env, issuer, 0u, tfTransferable)};
-
5086  env(token::mint(issuer, 0), txflags(tfTransferable));
-
5087  env.close();
-
5088 
-
5089  // Create an NFT that we'll make IOU offers for.
-
5090  uint256 const nftID1{
-
5091  token::getNextID(env, issuer, 1u, tfTransferable)};
-
5092  env(token::mint(issuer, 1), txflags(tfTransferable));
-
5093  env.close();
-
5094 
-
5095  // Make offers with negative amounts for the NFTs
-
5096  uint256 const sellNegXrpOfferIndex =
-
5097  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
5098  env(token::createOffer(issuer, nftID0, XRP(-2)),
-
5099  txflags(tfSellNFToken));
+
5062 
+
5063  // Buy offers.
+
5064  env(token::acceptBuyOffer(issuer, buyNegXrpOfferIndex),
+
5065  ter(offerAcceptTER));
+
5066  env.close();
+
5067  env(token::acceptBuyOffer(issuer, buyNegIouOfferIndex),
+
5068  ter(offerAcceptTER));
+
5069  env.close();
+
5070  }
+
5071  {
+
5072  // 1. If fixNFTokenNegOffer is enabled get tecOBJECT_NOT_FOUND
+
5073  // 2. If it is not enabled, but fixNonFungibleTokensV1_2 is
+
5074  // enabled, get tecOBJECT_NOT_FOUND.
+
5075  // 3. If neither are enabled, get tesSUCCESS.
+
5076  TER const offerAcceptTER = tweakedFeatures[fixNFTokenNegOffer]
+
5077  ? static_cast<TER>(tecOBJECT_NOT_FOUND)
+
5078  : static_cast<TER>(tesSUCCESS);
+
5079 
+
5080  // Brokered offers.
+
5081  env(token::brokerOffers(
+
5082  gw, buyNegXrpOfferIndex, sellNegXrpOfferIndex),
+
5083  ter(offerAcceptTER));
+
5084  env.close();
+
5085  env(token::brokerOffers(
+
5086  gw, buyNegIouOfferIndex, sellNegIouOfferIndex),
+
5087  ter(offerAcceptTER));
+
5088  env.close();
+
5089  }
+
5090  }
+
5091 
+
5092  // Test what happens if NFTokenOffers are created with negative amounts
+
5093  // and then fixNFTokenNegOffer goes live. What does an acceptOffer do?
+
5094  {
+
5095  Env env{
+
5096  *this,
+
5097  features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1};
+
5098 
+
5099  env.fund(XRP(1000000), issuer, buyer, gw);
5100  env.close();
5101 
-
5102  uint256 const sellNegIouOfferIndex =
-
5103  keylet::nftoffer(issuer, env.seq(issuer)).key;
-
5104  env(token::createOffer(issuer, nftID1, gwXAU(-2)),
-
5105  txflags(tfSellNFToken));
-
5106  env.close();
-
5107 
-
5108  uint256 const buyNegXrpOfferIndex =
-
5109  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
5110  env(token::createOffer(buyer, nftID0, XRP(-1)),
-
5111  token::owner(issuer));
-
5112  env.close();
-
5113 
-
5114  uint256 const buyNegIouOfferIndex =
-
5115  keylet::nftoffer(buyer, env.seq(buyer)).key;
-
5116  env(token::createOffer(buyer, nftID1, gwXAU(-1)),
-
5117  token::owner(issuer));
-
5118  env.close();
-
5119 
-
5120  // Now the amendment passes.
-
5121  env.enableFeature(fixNFTokenNegOffer);
-
5122  env.close();
-
5123 
-
5124  // All attempts to accept the offers with negative amounts
-
5125  // should fail with temBAD_OFFER.
-
5126  env(token::acceptSellOffer(buyer, sellNegXrpOfferIndex),
-
5127  ter(temBAD_OFFER));
-
5128  env.close();
-
5129  env(token::acceptSellOffer(buyer, sellNegIouOfferIndex),
-
5130  ter(temBAD_OFFER));
-
5131  env.close();
-
5132 
-
5133  // Buy offers.
-
5134  env(token::acceptBuyOffer(issuer, buyNegXrpOfferIndex),
-
5135  ter(temBAD_OFFER));
-
5136  env.close();
-
5137  env(token::acceptBuyOffer(issuer, buyNegIouOfferIndex),
-
5138  ter(temBAD_OFFER));
+
5102  env(trust(issuer, gwXAU(2000)));
+
5103  env(trust(buyer, gwXAU(2000)));
+
5104  env.close();
+
5105 
+
5106  env(pay(gw, issuer, gwXAU(1000)));
+
5107  env(pay(gw, buyer, gwXAU(1000)));
+
5108  env.close();
+
5109 
+
5110  // Create an NFT that we'll make XRP offers for.
+
5111  uint256 const nftID0{
+
5112  token::getNextID(env, issuer, 0u, tfTransferable)};
+
5113  env(token::mint(issuer, 0), txflags(tfTransferable));
+
5114  env.close();
+
5115 
+
5116  // Create an NFT that we'll make IOU offers for.
+
5117  uint256 const nftID1{
+
5118  token::getNextID(env, issuer, 1u, tfTransferable)};
+
5119  env(token::mint(issuer, 1), txflags(tfTransferable));
+
5120  env.close();
+
5121 
+
5122  // Make offers with negative amounts for the NFTs
+
5123  uint256 const sellNegXrpOfferIndex =
+
5124  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
5125  env(token::createOffer(issuer, nftID0, XRP(-2)),
+
5126  txflags(tfSellNFToken));
+
5127  env.close();
+
5128 
+
5129  uint256 const sellNegIouOfferIndex =
+
5130  keylet::nftoffer(issuer, env.seq(issuer)).key;
+
5131  env(token::createOffer(issuer, nftID1, gwXAU(-2)),
+
5132  txflags(tfSellNFToken));
+
5133  env.close();
+
5134 
+
5135  uint256 const buyNegXrpOfferIndex =
+
5136  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
5137  env(token::createOffer(buyer, nftID0, XRP(-1)),
+
5138  token::owner(issuer));
5139  env.close();
5140 
-
5141  // Brokered offers.
-
5142  env(token::brokerOffers(
-
5143  gw, buyNegXrpOfferIndex, sellNegXrpOfferIndex),
-
5144  ter(temBAD_OFFER));
+
5141  uint256 const buyNegIouOfferIndex =
+
5142  keylet::nftoffer(buyer, env.seq(buyer)).key;
+
5143  env(token::createOffer(buyer, nftID1, gwXAU(-1)),
+
5144  token::owner(issuer));
5145  env.close();
-
5146  env(token::brokerOffers(
-
5147  gw, buyNegIouOfferIndex, sellNegIouOfferIndex),
-
5148  ter(temBAD_OFFER));
+
5146 
+
5147  // Now the amendment passes.
+
5148  env.enableFeature(fixNFTokenNegOffer);
5149  env.close();
-
5150  }
-
5151 
-
5152  // Test buy offers with a destination with and without
-
5153  // fixNFTokenNegOffer.
-
5154  for (auto const& tweakedFeatures :
-
5155  {features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1,
-
5156  features | fixNFTokenNegOffer})
-
5157  {
-
5158  Env env{*this, tweakedFeatures};
+
5150 
+
5151  // All attempts to accept the offers with negative amounts
+
5152  // should fail with temBAD_OFFER.
+
5153  env(token::acceptSellOffer(buyer, sellNegXrpOfferIndex),
+
5154  ter(temBAD_OFFER));
+
5155  env.close();
+
5156  env(token::acceptSellOffer(buyer, sellNegIouOfferIndex),
+
5157  ter(temBAD_OFFER));
+
5158  env.close();
5159 
-
5160  env.fund(XRP(1000000), issuer, buyer);
-
5161 
-
5162  // Create an NFT that we'll make offers for.
-
5163  uint256 const nftID{
-
5164  token::getNextID(env, issuer, 0u, tfTransferable)};
-
5165  env(token::mint(issuer, 0), txflags(tfTransferable));
+
5160  // Buy offers.
+
5161  env(token::acceptBuyOffer(issuer, buyNegXrpOfferIndex),
+
5162  ter(temBAD_OFFER));
+
5163  env.close();
+
5164  env(token::acceptBuyOffer(issuer, buyNegIouOfferIndex),
+
5165  ter(temBAD_OFFER));
5166  env.close();
5167 
-
5168  TER const offerCreateTER = tweakedFeatures[fixNFTokenNegOffer]
-
5169  ? static_cast<TER>(tesSUCCESS)
-
5170  : static_cast<TER>(temMALFORMED);
-
5171 
-
5172  env(token::createOffer(buyer, nftID, drops(1)),
-
5173  token::owner(issuer),
-
5174  token::destination(issuer),
-
5175  ter(offerCreateTER));
+
5168  // Brokered offers.
+
5169  env(token::brokerOffers(
+
5170  gw, buyNegXrpOfferIndex, sellNegXrpOfferIndex),
+
5171  ter(temBAD_OFFER));
+
5172  env.close();
+
5173  env(token::brokerOffers(
+
5174  gw, buyNegIouOfferIndex, sellNegIouOfferIndex),
+
5175  ter(temBAD_OFFER));
5176  env.close();
5177  }
-
5178  }
-
5179 
-
5180  void
-
5181  testIOUWithTransferFee(FeatureBitset features)
-
5182  {
-
5183  using namespace test::jtx;
-
5184 
-
5185  testcase("Payments with IOU transfer fees");
+
5178 
+
5179  // Test buy offers with a destination with and without
+
5180  // fixNFTokenNegOffer.
+
5181  for (auto const& tweakedFeatures :
+
5182  {features - fixNFTokenNegOffer - featureNonFungibleTokensV1_1,
+
5183  features | fixNFTokenNegOffer})
+
5184  {
+
5185  Env env{*this, tweakedFeatures};
5186 
-
5187  for (auto const& tweakedFeatures :
-
5188  {features - fixNonFungibleTokensV1_2,
-
5189  features | fixNonFungibleTokensV1_2})
-
5190  {
-
5191  Env env{*this, tweakedFeatures};
-
5192 
-
5193  Account const minter{"minter"};
-
5194  Account const secondarySeller{"seller"};
-
5195  Account const buyer{"buyer"};
-
5196  Account const gw{"gateway"};
-
5197  Account const broker{"broker"};
-
5198  IOU const gwXAU(gw["XAU"]);
-
5199  IOU const gwXPB(gw["XPB"]);
-
5200 
-
5201  env.fund(XRP(1000), gw, minter, secondarySeller, buyer, broker);
-
5202  env.close();
-
5203 
-
5204  env(trust(minter, gwXAU(2000)));
-
5205  env(trust(secondarySeller, gwXAU(2000)));
-
5206  env(trust(broker, gwXAU(10000)));
-
5207  env(trust(buyer, gwXAU(2000)));
-
5208  env(trust(buyer, gwXPB(2000)));
-
5209  env.close();
-
5210 
-
5211  // The IOU issuer has a 2% transfer rate
-
5212  env(rate(gw, 1.02));
-
5213  env.close();
-
5214 
-
5215  auto expectInitialState = [this,
-
5216  &env,
-
5217  &buyer,
-
5218  &minter,
-
5219  &secondarySeller,
-
5220  &broker,
-
5221  &gw,
-
5222  &gwXAU,
-
5223  &gwXPB]() {
-
5224  // Buyer should have XAU 1000, XPB 0
-
5225  // Minter should have XAU 0, XPB 0
-
5226  // Secondary seller should have XAU 0, XPB 0
-
5227  // Broker should have XAU 5000, XPB 0
-
5228  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(1000));
-
5229  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(0));
-
5230  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(0));
-
5231  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(0));
-
5232  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(0));
-
5233  BEAST_EXPECT(env.balance(secondarySeller, gwXPB) == gwXPB(0));
-
5234  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5000));
-
5235  BEAST_EXPECT(env.balance(broker, gwXPB) == gwXPB(0));
-
5236  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-1000));
-
5237  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(0));
-
5238  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(0));
-
5239  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(0));
-
5240  BEAST_EXPECT(
-
5241  env.balance(gw, secondarySeller["XAU"]) == gwXAU(0));
-
5242  BEAST_EXPECT(
-
5243  env.balance(gw, secondarySeller["XPB"]) == gwXPB(0));
-
5244  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5000));
-
5245  BEAST_EXPECT(env.balance(gw, broker["XPB"]) == gwXPB(0));
-
5246  };
-
5247 
-
5248  auto reinitializeTrustLineBalances = [&expectInitialState,
-
5249  &env,
-
5250  &buyer,
-
5251  &minter,
-
5252  &secondarySeller,
-
5253  &broker,
-
5254  &gw,
-
5255  &gwXAU,
-
5256  &gwXPB]() {
-
5257  if (auto const difference =
-
5258  gwXAU(1000) - env.balance(buyer, gwXAU);
-
5259  difference > gwXAU(0))
-
5260  env(pay(gw, buyer, difference));
-
5261  if (env.balance(buyer, gwXPB) > gwXPB(0))
-
5262  env(pay(buyer, gw, env.balance(buyer, gwXPB)));
-
5263  if (env.balance(minter, gwXAU) > gwXAU(0))
-
5264  env(pay(minter, gw, env.balance(minter, gwXAU)));
-
5265  if (env.balance(minter, gwXPB) > gwXPB(0))
-
5266  env(pay(minter, gw, env.balance(minter, gwXPB)));
-
5267  if (env.balance(secondarySeller, gwXAU) > gwXAU(0))
-
5268  env(
-
5269  pay(secondarySeller,
-
5270  gw,
-
5271  env.balance(secondarySeller, gwXAU)));
-
5272  if (env.balance(secondarySeller, gwXPB) > gwXPB(0))
-
5273  env(
-
5274  pay(secondarySeller,
-
5275  gw,
-
5276  env.balance(secondarySeller, gwXPB)));
-
5277  auto brokerDiff = gwXAU(5000) - env.balance(broker, gwXAU);
-
5278  if (brokerDiff > gwXAU(0))
-
5279  env(pay(gw, broker, brokerDiff));
-
5280  else if (brokerDiff < gwXAU(0))
-
5281  {
-
5282  brokerDiff.negate();
-
5283  env(pay(broker, gw, brokerDiff));
-
5284  }
-
5285  if (env.balance(broker, gwXPB) > gwXPB(0))
-
5286  env(pay(broker, gw, env.balance(broker, gwXPB)));
-
5287  env.close();
-
5288  expectInitialState();
-
5289  };
-
5290 
-
5291  auto mintNFT = [&env](Account const& minter, int transferFee = 0) {
-
5292  uint256 const nftID = token::getNextID(
-
5293  env, minter, 0, tfTransferable, transferFee);
-
5294  env(token::mint(minter),
-
5295  token::xferFee(transferFee),
-
5296  txflags(tfTransferable));
-
5297  env.close();
-
5298  return nftID;
-
5299  };
-
5300 
-
5301  auto createBuyOffer =
-
5302  [&env](
-
5303  Account const& offerer,
-
5304  Account const& owner,
-
5305  uint256 const& nftID,
-
5306  STAmount const& amount,
-
5307  std::optional<TER const> const terCode = {}) {
-
5308  uint256 const offerID =
-
5309  keylet::nftoffer(offerer, env.seq(offerer)).key;
-
5310  env(token::createOffer(offerer, nftID, amount),
-
5311  token::owner(owner),
-
5312  terCode ? ter(*terCode)
-
5313  : ter(static_cast<TER>(tesSUCCESS)));
-
5314  env.close();
-
5315  return offerID;
-
5316  };
+
5187  env.fund(XRP(1000000), issuer, buyer);
+
5188 
+
5189  // Create an NFT that we'll make offers for.
+
5190  uint256 const nftID{
+
5191  token::getNextID(env, issuer, 0u, tfTransferable)};
+
5192  env(token::mint(issuer, 0), txflags(tfTransferable));
+
5193  env.close();
+
5194 
+
5195  TER const offerCreateTER = tweakedFeatures[fixNFTokenNegOffer]
+
5196  ? static_cast<TER>(tesSUCCESS)
+
5197  : static_cast<TER>(temMALFORMED);
+
5198 
+
5199  env(token::createOffer(buyer, nftID, drops(1)),
+
5200  token::owner(issuer),
+
5201  token::destination(issuer),
+
5202  ter(offerCreateTER));
+
5203  env.close();
+
5204  }
+
5205  }
+
5206 
+
5207  void
+
5208  testIOUWithTransferFee(FeatureBitset features)
+
5209  {
+
5210  using namespace test::jtx;
+
5211 
+
5212  testcase("Payments with IOU transfer fees");
+
5213 
+
5214  for (auto const& tweakedFeatures :
+
5215  {features - fixNonFungibleTokensV1_2,
+
5216  features | fixNonFungibleTokensV1_2})
+
5217  {
+
5218  Env env{*this, tweakedFeatures};
+
5219 
+
5220  Account const minter{"minter"};
+
5221  Account const secondarySeller{"seller"};
+
5222  Account const buyer{"buyer"};
+
5223  Account const gw{"gateway"};
+
5224  Account const broker{"broker"};
+
5225  IOU const gwXAU(gw["XAU"]);
+
5226  IOU const gwXPB(gw["XPB"]);
+
5227 
+
5228  env.fund(XRP(1000), gw, minter, secondarySeller, buyer, broker);
+
5229  env.close();
+
5230 
+
5231  env(trust(minter, gwXAU(2000)));
+
5232  env(trust(secondarySeller, gwXAU(2000)));
+
5233  env(trust(broker, gwXAU(10000)));
+
5234  env(trust(buyer, gwXAU(2000)));
+
5235  env(trust(buyer, gwXPB(2000)));
+
5236  env.close();
+
5237 
+
5238  // The IOU issuer has a 2% transfer rate
+
5239  env(rate(gw, 1.02));
+
5240  env.close();
+
5241 
+
5242  auto expectInitialState = [this,
+
5243  &env,
+
5244  &buyer,
+
5245  &minter,
+
5246  &secondarySeller,
+
5247  &broker,
+
5248  &gw,
+
5249  &gwXAU,
+
5250  &gwXPB]() {
+
5251  // Buyer should have XAU 1000, XPB 0
+
5252  // Minter should have XAU 0, XPB 0
+
5253  // Secondary seller should have XAU 0, XPB 0
+
5254  // Broker should have XAU 5000, XPB 0
+
5255  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(1000));
+
5256  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(0));
+
5257  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(0));
+
5258  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(0));
+
5259  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(0));
+
5260  BEAST_EXPECT(env.balance(secondarySeller, gwXPB) == gwXPB(0));
+
5261  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5000));
+
5262  BEAST_EXPECT(env.balance(broker, gwXPB) == gwXPB(0));
+
5263  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-1000));
+
5264  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(0));
+
5265  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(0));
+
5266  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(0));
+
5267  BEAST_EXPECT(
+
5268  env.balance(gw, secondarySeller["XAU"]) == gwXAU(0));
+
5269  BEAST_EXPECT(
+
5270  env.balance(gw, secondarySeller["XPB"]) == gwXPB(0));
+
5271  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5000));
+
5272  BEAST_EXPECT(env.balance(gw, broker["XPB"]) == gwXPB(0));
+
5273  };
+
5274 
+
5275  auto reinitializeTrustLineBalances = [&expectInitialState,
+
5276  &env,
+
5277  &buyer,
+
5278  &minter,
+
5279  &secondarySeller,
+
5280  &broker,
+
5281  &gw,
+
5282  &gwXAU,
+
5283  &gwXPB]() {
+
5284  if (auto const difference =
+
5285  gwXAU(1000) - env.balance(buyer, gwXAU);
+
5286  difference > gwXAU(0))
+
5287  env(pay(gw, buyer, difference));
+
5288  if (env.balance(buyer, gwXPB) > gwXPB(0))
+
5289  env(pay(buyer, gw, env.balance(buyer, gwXPB)));
+
5290  if (env.balance(minter, gwXAU) > gwXAU(0))
+
5291  env(pay(minter, gw, env.balance(minter, gwXAU)));
+
5292  if (env.balance(minter, gwXPB) > gwXPB(0))
+
5293  env(pay(minter, gw, env.balance(minter, gwXPB)));
+
5294  if (env.balance(secondarySeller, gwXAU) > gwXAU(0))
+
5295  env(
+
5296  pay(secondarySeller,
+
5297  gw,
+
5298  env.balance(secondarySeller, gwXAU)));
+
5299  if (env.balance(secondarySeller, gwXPB) > gwXPB(0))
+
5300  env(
+
5301  pay(secondarySeller,
+
5302  gw,
+
5303  env.balance(secondarySeller, gwXPB)));
+
5304  auto brokerDiff = gwXAU(5000) - env.balance(broker, gwXAU);
+
5305  if (brokerDiff > gwXAU(0))
+
5306  env(pay(gw, broker, brokerDiff));
+
5307  else if (brokerDiff < gwXAU(0))
+
5308  {
+
5309  brokerDiff.negate();
+
5310  env(pay(broker, gw, brokerDiff));
+
5311  }
+
5312  if (env.balance(broker, gwXPB) > gwXPB(0))
+
5313  env(pay(broker, gw, env.balance(broker, gwXPB)));
+
5314  env.close();
+
5315  expectInitialState();
+
5316  };
5317 
-
5318  auto createSellOffer =
-
5319  [&env](
-
5320  Account const& offerer,
-
5321  uint256 const& nftID,
-
5322  STAmount const& amount,
-
5323  std::optional<TER const> const terCode = {}) {
-
5324  uint256 const offerID =
-
5325  keylet::nftoffer(offerer, env.seq(offerer)).key;
-
5326  env(token::createOffer(offerer, nftID, amount),
-
5327  txflags(tfSellNFToken),
-
5328  terCode ? ter(*terCode)
-
5329  : ter(static_cast<TER>(tesSUCCESS)));
-
5330  env.close();
-
5331  return offerID;
-
5332  };
-
5333 
-
5334  {
-
5335  // Buyer attempts to send 100% of their balance of an IOU
-
5336  // (sellside)
-
5337  reinitializeTrustLineBalances();
-
5338  auto const nftID = mintNFT(minter);
-
5339  auto const offerID =
-
5340  createSellOffer(minter, nftID, gwXAU(1000));
-
5341  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5342  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5343  : static_cast<TER>(tesSUCCESS);
-
5344  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
-
5345  env.close();
-
5346 
-
5347  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5348  expectInitialState();
-
5349  else
-
5350  {
-
5351  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5352  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
-
5353  BEAST_EXPECT(
-
5354  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5355  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
-
5356  }
-
5357  }
-
5358  {
-
5359  // Buyer attempts to send 100% of their balance of an IOU
-
5360  // (buyside)
-
5361  reinitializeTrustLineBalances();
-
5362  auto const nftID = mintNFT(minter);
-
5363  auto const offerID =
-
5364  createBuyOffer(buyer, minter, nftID, gwXAU(1000));
-
5365  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5366  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5367  : static_cast<TER>(tesSUCCESS);
-
5368  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
-
5369  env.close();
-
5370 
-
5371  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5372  expectInitialState();
-
5373  else
-
5374  {
-
5375  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5376  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
-
5377  BEAST_EXPECT(
-
5378  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5379  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
-
5380  }
-
5381  }
-
5382  {
-
5383  // Buyer attempts to send an amount less than 100% of their
-
5384  // balance of an IOU, but such that the addition of the transfer
-
5385  // fee would be greater than the buyer's balance (sellside)
-
5386  reinitializeTrustLineBalances();
-
5387  auto const nftID = mintNFT(minter);
-
5388  auto const offerID = createSellOffer(minter, nftID, gwXAU(995));
-
5389  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5390  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5391  : static_cast<TER>(tesSUCCESS);
-
5392  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
-
5393  env.close();
-
5394 
-
5395  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5396  expectInitialState();
-
5397  else
-
5398  {
-
5399  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(995));
-
5400  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-14.9));
-
5401  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-995));
-
5402  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(14.9));
-
5403  }
-
5404  }
-
5405  {
-
5406  // Buyer attempts to send an amount less than 100% of their
-
5407  // balance of an IOU, but such that the addition of the transfer
-
5408  // fee would be greater than the buyer's balance (buyside)
-
5409  reinitializeTrustLineBalances();
-
5410  auto const nftID = mintNFT(minter);
-
5411  auto const offerID =
-
5412  createBuyOffer(buyer, minter, nftID, gwXAU(995));
-
5413  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5414  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5415  : static_cast<TER>(tesSUCCESS);
-
5416  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
-
5417  env.close();
-
5418 
-
5419  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5420  expectInitialState();
-
5421  else
-
5422  {
-
5423  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(995));
-
5424  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-14.9));
-
5425  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-995));
-
5426  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(14.9));
-
5427  }
-
5428  }
-
5429  {
-
5430  // Buyer attempts to send an amount less than 100% of their
-
5431  // balance of an IOU with a transfer fee, and such that the
-
5432  // addition of the transfer fee is still less than their balance
-
5433  // (sellside)
-
5434  reinitializeTrustLineBalances();
-
5435  auto const nftID = mintNFT(minter);
-
5436  auto const offerID = createSellOffer(minter, nftID, gwXAU(900));
-
5437  env(token::acceptSellOffer(buyer, offerID));
-
5438  env.close();
-
5439 
-
5440  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
-
5441  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
-
5442  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
-
5443  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
-
5444  }
-
5445  {
-
5446  // Buyer attempts to send an amount less than 100% of their
-
5447  // balance of an IOU with a transfer fee, and such that the
-
5448  // addition of the transfer fee is still less than their balance
-
5449  // (buyside)
-
5450  reinitializeTrustLineBalances();
-
5451  auto const nftID = mintNFT(minter);
-
5452  auto const offerID =
-
5453  createBuyOffer(buyer, minter, nftID, gwXAU(900));
-
5454  env(token::acceptBuyOffer(minter, offerID));
-
5455  env.close();
-
5456 
-
5457  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
-
5458  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
-
5459  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
-
5460  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
-
5461  }
-
5462  {
-
5463  // Buyer attempts to send an amount less than 100% of their
-
5464  // balance of an IOU with a transfer fee, and such that the
-
5465  // addition of the transfer fee is equal than their balance
-
5466  // (sellside)
-
5467  reinitializeTrustLineBalances();
-
5468 
-
5469  // pay them an additional XAU 20 to cover transfer rate
-
5470  env(pay(gw, buyer, gwXAU(20)));
-
5471  env.close();
-
5472 
-
5473  auto const nftID = mintNFT(minter);
-
5474  auto const offerID =
-
5475  createSellOffer(minter, nftID, gwXAU(1000));
-
5476  env(token::acceptSellOffer(buyer, offerID));
-
5477  env.close();
-
5478 
-
5479  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5480  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
5481  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5482  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
-
5483  }
-
5484  {
-
5485  // Buyer attempts to send an amount less than 100% of their
-
5486  // balance of an IOU with a transfer fee, and such that the
-
5487  // addition of the transfer fee is equal than their balance
-
5488  // (buyside)
-
5489  reinitializeTrustLineBalances();
-
5490 
-
5491  // pay them an additional XAU 20 to cover transfer rate
-
5492  env(pay(gw, buyer, gwXAU(20)));
-
5493  env.close();
-
5494 
-
5495  auto const nftID = mintNFT(minter);
-
5496  auto const offerID =
-
5497  createBuyOffer(buyer, minter, nftID, gwXAU(1000));
-
5498  env(token::acceptBuyOffer(minter, offerID));
-
5499  env.close();
-
5500 
-
5501  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5502  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
5503  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5504  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
-
5505  }
-
5506  {
-
5507  // Gateway attempts to buy NFT with their own IOU - no
-
5508  // transfer fee is calculated here (sellside)
-
5509  reinitializeTrustLineBalances();
-
5510 
-
5511  auto const nftID = mintNFT(minter);
-
5512  auto const offerID =
-
5513  createSellOffer(minter, nftID, gwXAU(1000));
-
5514  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5515  ? static_cast<TER>(tesSUCCESS)
-
5516  : static_cast<TER>(tecINSUFFICIENT_FUNDS);
-
5517  env(token::acceptSellOffer(gw, offerID), ter(sellTER));
-
5518  env.close();
-
5519 
-
5520  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5521  {
-
5522  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5523  BEAST_EXPECT(
-
5524  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5525  }
-
5526  else
-
5527  expectInitialState();
-
5528  }
-
5529  {
-
5530  // Gateway attempts to buy NFT with their own IOU - no
-
5531  // transfer fee is calculated here (buyside)
-
5532  reinitializeTrustLineBalances();
-
5533 
-
5534  auto const nftID = mintNFT(minter);
-
5535  auto const offerTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5536  ? static_cast<TER>(tesSUCCESS)
-
5537  : static_cast<TER>(tecUNFUNDED_OFFER);
-
5538  auto const offerID =
-
5539  createBuyOffer(gw, minter, nftID, gwXAU(1000), {offerTER});
-
5540  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5541  ? static_cast<TER>(tesSUCCESS)
-
5542  : static_cast<TER>(tecOBJECT_NOT_FOUND);
-
5543  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
-
5544  env.close();
-
5545 
-
5546  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5547  {
-
5548  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
-
5549  BEAST_EXPECT(
-
5550  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
-
5551  }
-
5552  else
-
5553  expectInitialState();
-
5554  }
-
5555  {
-
5556  // Gateway attempts to buy NFT with their own IOU for more
-
5557  // than minter trusts (sellside)
-
5558  reinitializeTrustLineBalances();
-
5559  auto const nftID = mintNFT(minter);
-
5560  auto const offerID =
-
5561  createSellOffer(minter, nftID, gwXAU(5000));
-
5562  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5318  auto mintNFT = [&env](Account const& minter, int transferFee = 0) {
+
5319  uint256 const nftID = token::getNextID(
+
5320  env, minter, 0, tfTransferable, transferFee);
+
5321  env(token::mint(minter),
+
5322  token::xferFee(transferFee),
+
5323  txflags(tfTransferable));
+
5324  env.close();
+
5325  return nftID;
+
5326  };
+
5327 
+
5328  auto createBuyOffer =
+
5329  [&env](
+
5330  Account const& offerer,
+
5331  Account const& owner,
+
5332  uint256 const& nftID,
+
5333  STAmount const& amount,
+
5334  std::optional<TER const> const terCode = {}) {
+
5335  uint256 const offerID =
+
5336  keylet::nftoffer(offerer, env.seq(offerer)).key;
+
5337  env(token::createOffer(offerer, nftID, amount),
+
5338  token::owner(owner),
+
5339  terCode ? ter(*terCode)
+
5340  : ter(static_cast<TER>(tesSUCCESS)));
+
5341  env.close();
+
5342  return offerID;
+
5343  };
+
5344 
+
5345  auto createSellOffer =
+
5346  [&env](
+
5347  Account const& offerer,
+
5348  uint256 const& nftID,
+
5349  STAmount const& amount,
+
5350  std::optional<TER const> const terCode = {}) {
+
5351  uint256 const offerID =
+
5352  keylet::nftoffer(offerer, env.seq(offerer)).key;
+
5353  env(token::createOffer(offerer, nftID, amount),
+
5354  txflags(tfSellNFToken),
+
5355  terCode ? ter(*terCode)
+
5356  : ter(static_cast<TER>(tesSUCCESS)));
+
5357  env.close();
+
5358  return offerID;
+
5359  };
+
5360 
+
5361  {
+
5362  // Buyer attempts to send 100% of their balance of an IOU
+
5363  // (sellside)
+
5364  reinitializeTrustLineBalances();
+
5365  auto const nftID = mintNFT(minter);
+
5366  auto const offerID =
+
5367  createSellOffer(minter, nftID, gwXAU(1000));
+
5368  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5369  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5370  : static_cast<TER>(tesSUCCESS);
+
5371  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
+
5372  env.close();
+
5373 
+
5374  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5375  expectInitialState();
+
5376  else
+
5377  {
+
5378  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5379  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
+
5380  BEAST_EXPECT(
+
5381  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5382  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
+
5383  }
+
5384  }
+
5385  {
+
5386  // Buyer attempts to send 100% of their balance of an IOU
+
5387  // (buyside)
+
5388  reinitializeTrustLineBalances();
+
5389  auto const nftID = mintNFT(minter);
+
5390  auto const offerID =
+
5391  createBuyOffer(buyer, minter, nftID, gwXAU(1000));
+
5392  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5393  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5394  : static_cast<TER>(tesSUCCESS);
+
5395  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
+
5396  env.close();
+
5397 
+
5398  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5399  expectInitialState();
+
5400  else
+
5401  {
+
5402  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5403  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
+
5404  BEAST_EXPECT(
+
5405  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5406  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
+
5407  }
+
5408  }
+
5409  {
+
5410  // Buyer attempts to send an amount less than 100% of their
+
5411  // balance of an IOU, but such that the addition of the transfer
+
5412  // fee would be greater than the buyer's balance (sellside)
+
5413  reinitializeTrustLineBalances();
+
5414  auto const nftID = mintNFT(minter);
+
5415  auto const offerID = createSellOffer(minter, nftID, gwXAU(995));
+
5416  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5417  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5418  : static_cast<TER>(tesSUCCESS);
+
5419  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
+
5420  env.close();
+
5421 
+
5422  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5423  expectInitialState();
+
5424  else
+
5425  {
+
5426  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(995));
+
5427  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-14.9));
+
5428  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-995));
+
5429  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(14.9));
+
5430  }
+
5431  }
+
5432  {
+
5433  // Buyer attempts to send an amount less than 100% of their
+
5434  // balance of an IOU, but such that the addition of the transfer
+
5435  // fee would be greater than the buyer's balance (buyside)
+
5436  reinitializeTrustLineBalances();
+
5437  auto const nftID = mintNFT(minter);
+
5438  auto const offerID =
+
5439  createBuyOffer(buyer, minter, nftID, gwXAU(995));
+
5440  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5441  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5442  : static_cast<TER>(tesSUCCESS);
+
5443  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
+
5444  env.close();
+
5445 
+
5446  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5447  expectInitialState();
+
5448  else
+
5449  {
+
5450  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(995));
+
5451  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-14.9));
+
5452  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-995));
+
5453  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(14.9));
+
5454  }
+
5455  }
+
5456  {
+
5457  // Buyer attempts to send an amount less than 100% of their
+
5458  // balance of an IOU with a transfer fee, and such that the
+
5459  // addition of the transfer fee is still less than their balance
+
5460  // (sellside)
+
5461  reinitializeTrustLineBalances();
+
5462  auto const nftID = mintNFT(minter);
+
5463  auto const offerID = createSellOffer(minter, nftID, gwXAU(900));
+
5464  env(token::acceptSellOffer(buyer, offerID));
+
5465  env.close();
+
5466 
+
5467  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
+
5468  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
+
5469  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
+
5470  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
+
5471  }
+
5472  {
+
5473  // Buyer attempts to send an amount less than 100% of their
+
5474  // balance of an IOU with a transfer fee, and such that the
+
5475  // addition of the transfer fee is still less than their balance
+
5476  // (buyside)
+
5477  reinitializeTrustLineBalances();
+
5478  auto const nftID = mintNFT(minter);
+
5479  auto const offerID =
+
5480  createBuyOffer(buyer, minter, nftID, gwXAU(900));
+
5481  env(token::acceptBuyOffer(minter, offerID));
+
5482  env.close();
+
5483 
+
5484  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
+
5485  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
+
5486  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
+
5487  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
+
5488  }
+
5489  {
+
5490  // Buyer attempts to send an amount less than 100% of their
+
5491  // balance of an IOU with a transfer fee, and such that the
+
5492  // addition of the transfer fee is equal than their balance
+
5493  // (sellside)
+
5494  reinitializeTrustLineBalances();
+
5495 
+
5496  // pay them an additional XAU 20 to cover transfer rate
+
5497  env(pay(gw, buyer, gwXAU(20)));
+
5498  env.close();
+
5499 
+
5500  auto const nftID = mintNFT(minter);
+
5501  auto const offerID =
+
5502  createSellOffer(minter, nftID, gwXAU(1000));
+
5503  env(token::acceptSellOffer(buyer, offerID));
+
5504  env.close();
+
5505 
+
5506  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5507  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
5508  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5509  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
+
5510  }
+
5511  {
+
5512  // Buyer attempts to send an amount less than 100% of their
+
5513  // balance of an IOU with a transfer fee, and such that the
+
5514  // addition of the transfer fee is equal than their balance
+
5515  // (buyside)
+
5516  reinitializeTrustLineBalances();
+
5517 
+
5518  // pay them an additional XAU 20 to cover transfer rate
+
5519  env(pay(gw, buyer, gwXAU(20)));
+
5520  env.close();
+
5521 
+
5522  auto const nftID = mintNFT(minter);
+
5523  auto const offerID =
+
5524  createBuyOffer(buyer, minter, nftID, gwXAU(1000));
+
5525  env(token::acceptBuyOffer(minter, offerID));
+
5526  env.close();
+
5527 
+
5528  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5529  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
5530  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5531  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
+
5532  }
+
5533  {
+
5534  // Gateway attempts to buy NFT with their own IOU - no
+
5535  // transfer fee is calculated here (sellside)
+
5536  reinitializeTrustLineBalances();
+
5537 
+
5538  auto const nftID = mintNFT(minter);
+
5539  auto const offerID =
+
5540  createSellOffer(minter, nftID, gwXAU(1000));
+
5541  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5542  ? static_cast<TER>(tesSUCCESS)
+
5543  : static_cast<TER>(tecINSUFFICIENT_FUNDS);
+
5544  env(token::acceptSellOffer(gw, offerID), ter(sellTER));
+
5545  env.close();
+
5546 
+
5547  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5548  {
+
5549  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5550  BEAST_EXPECT(
+
5551  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5552  }
+
5553  else
+
5554  expectInitialState();
+
5555  }
+
5556  {
+
5557  // Gateway attempts to buy NFT with their own IOU - no
+
5558  // transfer fee is calculated here (buyside)
+
5559  reinitializeTrustLineBalances();
+
5560 
+
5561  auto const nftID = mintNFT(minter);
+
5562  auto const offerTER = tweakedFeatures[fixNonFungibleTokensV1_2]
5563  ? static_cast<TER>(tesSUCCESS)
-
5564  : static_cast<TER>(tecINSUFFICIENT_FUNDS);
-
5565  env(token::acceptSellOffer(gw, offerID), ter(sellTER));
-
5566  env.close();
-
5567 
-
5568  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5569  {
-
5570  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
-
5571  BEAST_EXPECT(
-
5572  env.balance(gw, minter["XAU"]) == gwXAU(-5000));
-
5573  }
-
5574  else
-
5575  expectInitialState();
-
5576  }
-
5577  {
-
5578  // Gateway attempts to buy NFT with their own IOU for more
-
5579  // than minter trusts (buyside)
-
5580  reinitializeTrustLineBalances();
-
5581 
-
5582  auto const nftID = mintNFT(minter);
-
5583  auto const offerTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5584  ? static_cast<TER>(tesSUCCESS)
-
5585  : static_cast<TER>(tecUNFUNDED_OFFER);
-
5586  auto const offerID =
-
5587  createBuyOffer(gw, minter, nftID, gwXAU(5000), {offerTER});
-
5588  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5589  ? static_cast<TER>(tesSUCCESS)
-
5590  : static_cast<TER>(tecOBJECT_NOT_FOUND);
-
5591  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
-
5592  env.close();
-
5593 
-
5594  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5595  {
-
5596  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
-
5597  BEAST_EXPECT(
-
5598  env.balance(gw, minter["XAU"]) == gwXAU(-5000));
-
5599  }
-
5600  else
-
5601  expectInitialState();
-
5602  }
-
5603  {
-
5604  // Gateway is the NFT minter and attempts to sell NFT for an
-
5605  // amount that would be greater than a balance if there were a
-
5606  // transfer fee calculated in this transaction. (sellside)
+
5564  : static_cast<TER>(tecUNFUNDED_OFFER);
+
5565  auto const offerID =
+
5566  createBuyOffer(gw, minter, nftID, gwXAU(1000), {offerTER});
+
5567  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5568  ? static_cast<TER>(tesSUCCESS)
+
5569  : static_cast<TER>(tecOBJECT_NOT_FOUND);
+
5570  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
+
5571  env.close();
+
5572 
+
5573  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5574  {
+
5575  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
+
5576  BEAST_EXPECT(
+
5577  env.balance(gw, minter["XAU"]) == gwXAU(-1000));
+
5578  }
+
5579  else
+
5580  expectInitialState();
+
5581  }
+
5582  {
+
5583  // Gateway attempts to buy NFT with their own IOU for more
+
5584  // than minter trusts (sellside)
+
5585  reinitializeTrustLineBalances();
+
5586  auto const nftID = mintNFT(minter);
+
5587  auto const offerID =
+
5588  createSellOffer(minter, nftID, gwXAU(5000));
+
5589  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5590  ? static_cast<TER>(tesSUCCESS)
+
5591  : static_cast<TER>(tecINSUFFICIENT_FUNDS);
+
5592  env(token::acceptSellOffer(gw, offerID), ter(sellTER));
+
5593  env.close();
+
5594 
+
5595  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5596  {
+
5597  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
+
5598  BEAST_EXPECT(
+
5599  env.balance(gw, minter["XAU"]) == gwXAU(-5000));
+
5600  }
+
5601  else
+
5602  expectInitialState();
+
5603  }
+
5604  {
+
5605  // Gateway attempts to buy NFT with their own IOU for more
+
5606  // than minter trusts (buyside)
5607  reinitializeTrustLineBalances();
-
5608  auto const nftID = mintNFT(gw);
-
5609  auto const offerID = createSellOffer(gw, nftID, gwXAU(1000));
-
5610  env(token::acceptSellOffer(buyer, offerID));
-
5611  env.close();
-
5612 
-
5613  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
5614  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
-
5615  }
-
5616  {
-
5617  // Gateway is the NFT minter and attempts to sell NFT for an
-
5618  // amount that would be greater than a balance if there were a
-
5619  // transfer fee calculated in this transaction. (buyside)
-
5620  reinitializeTrustLineBalances();
-
5621 
-
5622  auto const nftID = mintNFT(gw);
-
5623  auto const offerID =
-
5624  createBuyOffer(buyer, gw, nftID, gwXAU(1000));
-
5625  env(token::acceptBuyOffer(gw, offerID));
-
5626  env.close();
-
5627 
-
5628  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
-
5629  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
-
5630  }
-
5631  {
-
5632  // Gateway is the NFT minter and attempts to sell NFT for an
-
5633  // amount that is greater than a balance before transfer fees.
-
5634  // (sellside)
-
5635  reinitializeTrustLineBalances();
-
5636  auto const nftID = mintNFT(gw);
-
5637  auto const offerID = createSellOffer(gw, nftID, gwXAU(2000));
-
5638  env(token::acceptSellOffer(buyer, offerID),
-
5639  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
-
5640  env.close();
-
5641  expectInitialState();
+
5608 
+
5609  auto const nftID = mintNFT(minter);
+
5610  auto const offerTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5611  ? static_cast<TER>(tesSUCCESS)
+
5612  : static_cast<TER>(tecUNFUNDED_OFFER);
+
5613  auto const offerID =
+
5614  createBuyOffer(gw, minter, nftID, gwXAU(5000), {offerTER});
+
5615  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5616  ? static_cast<TER>(tesSUCCESS)
+
5617  : static_cast<TER>(tecOBJECT_NOT_FOUND);
+
5618  env(token::acceptBuyOffer(minter, offerID), ter(sellTER));
+
5619  env.close();
+
5620 
+
5621  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5622  {
+
5623  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
+
5624  BEAST_EXPECT(
+
5625  env.balance(gw, minter["XAU"]) == gwXAU(-5000));
+
5626  }
+
5627  else
+
5628  expectInitialState();
+
5629  }
+
5630  {
+
5631  // Gateway is the NFT minter and attempts to sell NFT for an
+
5632  // amount that would be greater than a balance if there were a
+
5633  // transfer fee calculated in this transaction. (sellside)
+
5634  reinitializeTrustLineBalances();
+
5635  auto const nftID = mintNFT(gw);
+
5636  auto const offerID = createSellOffer(gw, nftID, gwXAU(1000));
+
5637  env(token::acceptSellOffer(buyer, offerID));
+
5638  env.close();
+
5639 
+
5640  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
5641  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
5642  }
5643  {
5644  // Gateway is the NFT minter and attempts to sell NFT for an
-
5645  // amount that is greater than a balance before transfer fees.
-
5646  // (buyside)
+
5645  // amount that would be greater than a balance if there were a
+
5646  // transfer fee calculated in this transaction. (buyside)
5647  reinitializeTrustLineBalances();
-
5648  auto const nftID = mintNFT(gw);
-
5649  auto const offerID =
-
5650  createBuyOffer(buyer, gw, nftID, gwXAU(2000));
-
5651  env(token::acceptBuyOffer(gw, offerID),
-
5652  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
+
5648 
+
5649  auto const nftID = mintNFT(gw);
+
5650  auto const offerID =
+
5651  createBuyOffer(buyer, gw, nftID, gwXAU(1000));
+
5652  env(token::acceptBuyOffer(gw, offerID));
5653  env.close();
-
5654  expectInitialState();
-
5655  }
-
5656  {
-
5657  // Minter attempts to sell the token for XPB 10, which they
-
5658  // have no trust line for and buyer has none of (sellside).
-
5659  reinitializeTrustLineBalances();
-
5660  auto const nftID = mintNFT(minter);
-
5661  auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
-
5662  env(token::acceptSellOffer(buyer, offerID),
-
5663  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
-
5664  env.close();
-
5665  expectInitialState();
-
5666  }
-
5667  {
-
5668  // Minter attempts to sell the token for XPB 10, which they
-
5669  // have no trust line for and buyer has none of (buyside).
-
5670  reinitializeTrustLineBalances();
-
5671  auto const nftID = mintNFT(minter);
-
5672  auto const offerID = createBuyOffer(
-
5673  buyer,
-
5674  minter,
-
5675  nftID,
-
5676  gwXPB(10),
-
5677  {static_cast<TER>(tecUNFUNDED_OFFER)});
-
5678  env(token::acceptBuyOffer(minter, offerID),
-
5679  ter(static_cast<TER>(tecOBJECT_NOT_FOUND)));
+
5654 
+
5655  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
+
5656  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
+
5657  }
+
5658  {
+
5659  // Gateway is the NFT minter and attempts to sell NFT for an
+
5660  // amount that is greater than a balance before transfer fees.
+
5661  // (sellside)
+
5662  reinitializeTrustLineBalances();
+
5663  auto const nftID = mintNFT(gw);
+
5664  auto const offerID = createSellOffer(gw, nftID, gwXAU(2000));
+
5665  env(token::acceptSellOffer(buyer, offerID),
+
5666  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
+
5667  env.close();
+
5668  expectInitialState();
+
5669  }
+
5670  {
+
5671  // Gateway is the NFT minter and attempts to sell NFT for an
+
5672  // amount that is greater than a balance before transfer fees.
+
5673  // (buyside)
+
5674  reinitializeTrustLineBalances();
+
5675  auto const nftID = mintNFT(gw);
+
5676  auto const offerID =
+
5677  createBuyOffer(buyer, gw, nftID, gwXAU(2000));
+
5678  env(token::acceptBuyOffer(gw, offerID),
+
5679  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
5680  env.close();
5681  expectInitialState();
5682  }
5683  {
-
5684  // Minter attempts to sell the token for XPB 10 and the buyer
-
5685  // has it but the minter has no trust line. Trust line is
-
5686  // created as a result of the tx (sellside).
-
5687  reinitializeTrustLineBalances();
-
5688  env(pay(gw, buyer, gwXPB(100)));
-
5689  env.close();
-
5690 
-
5691  auto const nftID = mintNFT(minter);
-
5692  auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
-
5693  env(token::acceptSellOffer(buyer, offerID));
-
5694  env.close();
-
5695 
-
5696  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
-
5697  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
-
5698  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
-
5699  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
-
5700  }
-
5701  {
-
5702  // Minter attempts to sell the token for XPB 10 and the buyer
-
5703  // has it but the minter has no trust line. Trust line is
-
5704  // created as a result of the tx (buyside).
-
5705  reinitializeTrustLineBalances();
-
5706  env(pay(gw, buyer, gwXPB(100)));
+
5684  // Minter attempts to sell the token for XPB 10, which they
+
5685  // have no trust line for and buyer has none of (sellside).
+
5686  reinitializeTrustLineBalances();
+
5687  auto const nftID = mintNFT(minter);
+
5688  auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
+
5689  env(token::acceptSellOffer(buyer, offerID),
+
5690  ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
+
5691  env.close();
+
5692  expectInitialState();
+
5693  }
+
5694  {
+
5695  // Minter attempts to sell the token for XPB 10, which they
+
5696  // have no trust line for and buyer has none of (buyside).
+
5697  reinitializeTrustLineBalances();
+
5698  auto const nftID = mintNFT(minter);
+
5699  auto const offerID = createBuyOffer(
+
5700  buyer,
+
5701  minter,
+
5702  nftID,
+
5703  gwXPB(10),
+
5704  {static_cast<TER>(tecUNFUNDED_OFFER)});
+
5705  env(token::acceptBuyOffer(minter, offerID),
+
5706  ter(static_cast<TER>(tecOBJECT_NOT_FOUND)));
5707  env.close();
-
5708 
-
5709  auto const nftID = mintNFT(minter);
-
5710  auto const offerID =
-
5711  createBuyOffer(buyer, minter, nftID, gwXPB(10));
-
5712  env(token::acceptBuyOffer(minter, offerID));
-
5713  env.close();
-
5714 
-
5715  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
-
5716  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
-
5717  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
-
5718  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
-
5719  }
-
5720  {
-
5721  // There is a transfer fee on the NFT and buyer has exact
-
5722  // amount (sellside)
-
5723  reinitializeTrustLineBalances();
-
5724 
-
5725  // secondarySeller has to sell it because transfer fees only
-
5726  // happen on secondary sales
-
5727  auto const nftID = mintNFT(minter, 3000); // 3%
-
5728  auto const primaryOfferID =
-
5729  createSellOffer(minter, nftID, XRP(0));
-
5730  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
-
5731  env.close();
-
5732 
-
5733  // now we can do a secondary sale
-
5734  auto const offerID =
-
5735  createSellOffer(secondarySeller, nftID, gwXAU(1000));
-
5736  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5737  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5738  : static_cast<TER>(tesSUCCESS);
-
5739  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
+
5708  expectInitialState();
+
5709  }
+
5710  {
+
5711  // Minter attempts to sell the token for XPB 10 and the buyer
+
5712  // has it but the minter has no trust line. Trust line is
+
5713  // created as a result of the tx (sellside).
+
5714  reinitializeTrustLineBalances();
+
5715  env(pay(gw, buyer, gwXPB(100)));
+
5716  env.close();
+
5717 
+
5718  auto const nftID = mintNFT(minter);
+
5719  auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
+
5720  env(token::acceptSellOffer(buyer, offerID));
+
5721  env.close();
+
5722 
+
5723  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
+
5724  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
+
5725  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
+
5726  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
+
5727  }
+
5728  {
+
5729  // Minter attempts to sell the token for XPB 10 and the buyer
+
5730  // has it but the minter has no trust line. Trust line is
+
5731  // created as a result of the tx (buyside).
+
5732  reinitializeTrustLineBalances();
+
5733  env(pay(gw, buyer, gwXPB(100)));
+
5734  env.close();
+
5735 
+
5736  auto const nftID = mintNFT(minter);
+
5737  auto const offerID =
+
5738  createBuyOffer(buyer, minter, nftID, gwXPB(10));
+
5739  env(token::acceptBuyOffer(minter, offerID));
5740  env.close();
5741 
-
5742  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5743  expectInitialState();
-
5744  else
-
5745  {
-
5746  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(30));
-
5747  BEAST_EXPECT(
-
5748  env.balance(secondarySeller, gwXAU) == gwXAU(970));
-
5749  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
-
5750  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-30));
-
5751  BEAST_EXPECT(
-
5752  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-970));
-
5753  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
-
5754  }
-
5755  }
-
5756  {
-
5757  // There is a transfer fee on the NFT and buyer has exact
-
5758  // amount (buyside)
-
5759  reinitializeTrustLineBalances();
-
5760 
-
5761  // secondarySeller has to sell it because transfer fees only
-
5762  // happen on secondary sales
-
5763  auto const nftID = mintNFT(minter, 3000); // 3%
-
5764  auto const primaryOfferID =
-
5765  createSellOffer(minter, nftID, XRP(0));
-
5766  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5742  BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
+
5743  BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
+
5744  BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
+
5745  BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
+
5746  }
+
5747  {
+
5748  // There is a transfer fee on the NFT and buyer has exact
+
5749  // amount (sellside)
+
5750  reinitializeTrustLineBalances();
+
5751 
+
5752  // secondarySeller has to sell it because transfer fees only
+
5753  // happen on secondary sales
+
5754  auto const nftID = mintNFT(minter, 3000); // 3%
+
5755  auto const primaryOfferID =
+
5756  createSellOffer(minter, nftID, XRP(0));
+
5757  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5758  env.close();
+
5759 
+
5760  // now we can do a secondary sale
+
5761  auto const offerID =
+
5762  createSellOffer(secondarySeller, nftID, gwXAU(1000));
+
5763  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5764  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5765  : static_cast<TER>(tesSUCCESS);
+
5766  env(token::acceptSellOffer(buyer, offerID), ter(sellTER));
5767  env.close();
5768 
-
5769  // now we can do a secondary sale
-
5770  auto const offerID =
-
5771  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(1000));
-
5772  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
-
5773  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
-
5774  : static_cast<TER>(tesSUCCESS);
-
5775  env(token::acceptBuyOffer(secondarySeller, offerID),
-
5776  ter(sellTER));
-
5777  env.close();
-
5778 
-
5779  if (tweakedFeatures[fixNonFungibleTokensV1_2])
-
5780  expectInitialState();
-
5781  else
-
5782  {
-
5783  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(30));
-
5784  BEAST_EXPECT(
-
5785  env.balance(secondarySeller, gwXAU) == gwXAU(970));
-
5786  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
-
5787  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-30));
-
5788  BEAST_EXPECT(
-
5789  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-970));
-
5790  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
-
5791  }
-
5792  }
-
5793  {
-
5794  // There is a transfer fee on the NFT and buyer has enough
-
5795  // (sellside)
-
5796  reinitializeTrustLineBalances();
-
5797 
-
5798  // secondarySeller has to sell it because transfer fees only
-
5799  // happen on secondary sales
-
5800  auto const nftID = mintNFT(minter, 3000); // 3%
-
5801  auto const primaryOfferID =
-
5802  createSellOffer(minter, nftID, XRP(0));
-
5803  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5769  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5770  expectInitialState();
+
5771  else
+
5772  {
+
5773  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(30));
+
5774  BEAST_EXPECT(
+
5775  env.balance(secondarySeller, gwXAU) == gwXAU(970));
+
5776  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
+
5777  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-30));
+
5778  BEAST_EXPECT(
+
5779  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-970));
+
5780  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
+
5781  }
+
5782  }
+
5783  {
+
5784  // There is a transfer fee on the NFT and buyer has exact
+
5785  // amount (buyside)
+
5786  reinitializeTrustLineBalances();
+
5787 
+
5788  // secondarySeller has to sell it because transfer fees only
+
5789  // happen on secondary sales
+
5790  auto const nftID = mintNFT(minter, 3000); // 3%
+
5791  auto const primaryOfferID =
+
5792  createSellOffer(minter, nftID, XRP(0));
+
5793  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5794  env.close();
+
5795 
+
5796  // now we can do a secondary sale
+
5797  auto const offerID =
+
5798  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(1000));
+
5799  auto const sellTER = tweakedFeatures[fixNonFungibleTokensV1_2]
+
5800  ? static_cast<TER>(tecINSUFFICIENT_FUNDS)
+
5801  : static_cast<TER>(tesSUCCESS);
+
5802  env(token::acceptBuyOffer(secondarySeller, offerID),
+
5803  ter(sellTER));
5804  env.close();
5805 
-
5806  // now we can do a secondary sale
-
5807  auto const offerID =
-
5808  createSellOffer(secondarySeller, nftID, gwXAU(900));
-
5809  env(token::acceptSellOffer(buyer, offerID));
-
5810  env.close();
-
5811 
-
5812  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
-
5813  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
-
5814  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
-
5815  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
-
5816  BEAST_EXPECT(
-
5817  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
-
5818  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
+
5806  if (tweakedFeatures[fixNonFungibleTokensV1_2])
+
5807  expectInitialState();
+
5808  else
+
5809  {
+
5810  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(30));
+
5811  BEAST_EXPECT(
+
5812  env.balance(secondarySeller, gwXAU) == gwXAU(970));
+
5813  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(-20));
+
5814  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-30));
+
5815  BEAST_EXPECT(
+
5816  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-970));
+
5817  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(20));
+
5818  }
5819  }
5820  {
5821  // There is a transfer fee on the NFT and buyer has enough
-
5822  // (buyside)
+
5822  // (sellside)
5823  reinitializeTrustLineBalances();
5824 
5825  // secondarySeller has to sell it because transfer fees only
@@ -5903,284 +5903,818 @@ $(function() {
5832 
5833  // now we can do a secondary sale
5834  auto const offerID =
-
5835  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(900));
-
5836  env(token::acceptBuyOffer(secondarySeller, offerID));
+
5835  createSellOffer(secondarySeller, nftID, gwXAU(900));
+
5836  env(token::acceptSellOffer(buyer, offerID));
5837  env.close();
5838 
-
5839  // receives 3% of 900 - 27
-
5840  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
-
5841  // receives 97% of 900 - 873
-
5842  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
-
5843  // pays 900 plus 2% transfer fee on XAU - 918
-
5844  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
-
5845  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
-
5846  BEAST_EXPECT(
-
5847  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
-
5848  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
-
5849  }
-
5850  {
-
5851  // There is a broker fee on the NFT. XAU transfer fee is only
-
5852  // calculated from the buyer's output, not deducted from
-
5853  // broker fee.
-
5854  //
-
5855  // For a payment of 500 with a 2% IOU transfee fee and 100
-
5856  // broker fee:
-
5857  //
-
5858  // A) Total sale amount + IOU transfer fee is paid by buyer
-
5859  // (Buyer pays (1.02 * 500) = 510)
-
5860  // B) GW receives the additional IOU transfer fee
-
5861  // (GW receives 10 from buyer calculated above)
-
5862  // C) Broker receives broker fee (no IOU transfer fee)
-
5863  // (Broker receives 100 from buyer)
-
5864  // D) Seller receives balance (no IOU transfer fee)
-
5865  // (Seller receives (510 - 10 - 100) = 400)
-
5866  reinitializeTrustLineBalances();
-
5867 
-
5868  auto const nftID = mintNFT(minter);
-
5869  auto const sellOffer =
-
5870  createSellOffer(minter, nftID, gwXAU(300));
-
5871  auto const buyOffer =
-
5872  createBuyOffer(buyer, minter, nftID, gwXAU(500));
-
5873  env(token::brokerOffers(broker, buyOffer, sellOffer),
-
5874  token::brokerFee(gwXAU(100)));
-
5875  env.close();
-
5876 
-
5877  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(400));
-
5878  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
-
5879  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
-
5880  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-400));
-
5881  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
-
5882  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
-
5883  }
-
5884  {
-
5885  // There is broker and transfer fee on the NFT
-
5886  //
-
5887  // For a payment of 500 with a 2% IOU transfer fee, 3% NFT
-
5888  // transfer fee, and 100 broker fee:
-
5889  //
-
5890  // A) Total sale amount + IOU transfer fee is paid by buyer
-
5891  // (Buyer pays (1.02 * 500) = 510)
-
5892  // B) GW receives the additional IOU transfer fee
-
5893  // (GW receives 10 from buyer calculated above)
-
5894  // C) Broker receives broker fee (no IOU transfer fee)
-
5895  // (Broker receives 100 from buyer)
-
5896  // D) Minter receives transfer fee (no IOU transfer fee)
-
5897  // (Minter receives 0.03 * (510 - 10 - 100) = 12)
-
5898  // E) Seller receives balance (no IOU transfer fee)
-
5899  // (Seller receives (510 - 10 - 100 - 12) = 388)
-
5900  reinitializeTrustLineBalances();
-
5901 
-
5902  // secondarySeller has to sell it because transfer fees only
-
5903  // happen on secondary sales
-
5904  auto const nftID = mintNFT(minter, 3000); // 3%
-
5905  auto const primaryOfferID =
-
5906  createSellOffer(minter, nftID, XRP(0));
-
5907  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
-
5908  env.close();
-
5909 
-
5910  // now we can do a secondary sale
-
5911  auto const sellOffer =
-
5912  createSellOffer(secondarySeller, nftID, gwXAU(300));
-
5913  auto const buyOffer =
-
5914  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(500));
-
5915  env(token::brokerOffers(broker, buyOffer, sellOffer),
-
5916  token::brokerFee(gwXAU(100)));
-
5917  env.close();
-
5918 
-
5919  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(12));
-
5920  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
-
5921  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(388));
-
5922  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
-
5923  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-12));
-
5924  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
-
5925  BEAST_EXPECT(
-
5926  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-388));
-
5927  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
-
5928  }
-
5929  }
-
5930  }
-
5931 
-
5932  void
-
5933  testBrokeredSaleToSelf(FeatureBitset features)
-
5934  {
-
5935  // There was a bug that if an account had...
-
5936  //
-
5937  // 1. An NFToken, and
-
5938  // 2. An offer on the ledger to buy that same token, and
-
5939  // 3. Also an offer of the ledger to sell that same token,
-
5940  //
-
5941  // Then someone could broker the two offers. This would result in
-
5942  // the NFToken being bought and returned to the original owner and
-
5943  // the broker pocketing the profit.
-
5944  //
-
5945  // This unit test verifies that the fixNonFungibleTokensV1_2 amendment
-
5946  // fixes that bug.
-
5947  testcase("Brokered sale to self");
-
5948 
-
5949  using namespace test::jtx;
-
5950 
-
5951  Account const alice{"alice"};
-
5952  Account const bob{"bob"};
-
5953  Account const broker{"broker"};
-
5954 
-
5955  Env env{*this, features};
-
5956  env.fund(XRP(10000), alice, bob, broker);
-
5957  env.close();
+
5839  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
+
5840  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
+
5841  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
+
5842  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
+
5843  BEAST_EXPECT(
+
5844  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
+
5845  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
+
5846  }
+
5847  {
+
5848  // There is a transfer fee on the NFT and buyer has enough
+
5849  // (buyside)
+
5850  reinitializeTrustLineBalances();
+
5851 
+
5852  // secondarySeller has to sell it because transfer fees only
+
5853  // happen on secondary sales
+
5854  auto const nftID = mintNFT(minter, 3000); // 3%
+
5855  auto const primaryOfferID =
+
5856  createSellOffer(minter, nftID, XRP(0));
+
5857  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5858  env.close();
+
5859 
+
5860  // now we can do a secondary sale
+
5861  auto const offerID =
+
5862  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(900));
+
5863  env(token::acceptBuyOffer(secondarySeller, offerID));
+
5864  env.close();
+
5865 
+
5866  // receives 3% of 900 - 27
+
5867  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
+
5868  // receives 97% of 900 - 873
+
5869  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
+
5870  // pays 900 plus 2% transfer fee on XAU - 918
+
5871  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
+
5872  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
+
5873  BEAST_EXPECT(
+
5874  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
+
5875  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
+
5876  }
+
5877  {
+
5878  // There is a broker fee on the NFT. XAU transfer fee is only
+
5879  // calculated from the buyer's output, not deducted from
+
5880  // broker fee.
+
5881  //
+
5882  // For a payment of 500 with a 2% IOU transfee fee and 100
+
5883  // broker fee:
+
5884  //
+
5885  // A) Total sale amount + IOU transfer fee is paid by buyer
+
5886  // (Buyer pays (1.02 * 500) = 510)
+
5887  // B) GW receives the additional IOU transfer fee
+
5888  // (GW receives 10 from buyer calculated above)
+
5889  // C) Broker receives broker fee (no IOU transfer fee)
+
5890  // (Broker receives 100 from buyer)
+
5891  // D) Seller receives balance (no IOU transfer fee)
+
5892  // (Seller receives (510 - 10 - 100) = 400)
+
5893  reinitializeTrustLineBalances();
+
5894 
+
5895  auto const nftID = mintNFT(minter);
+
5896  auto const sellOffer =
+
5897  createSellOffer(minter, nftID, gwXAU(300));
+
5898  auto const buyOffer =
+
5899  createBuyOffer(buyer, minter, nftID, gwXAU(500));
+
5900  env(token::brokerOffers(broker, buyOffer, sellOffer),
+
5901  token::brokerFee(gwXAU(100)));
+
5902  env.close();
+
5903 
+
5904  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(400));
+
5905  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
+
5906  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
+
5907  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-400));
+
5908  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
+
5909  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
+
5910  }
+
5911  {
+
5912  // There is broker and transfer fee on the NFT
+
5913  //
+
5914  // For a payment of 500 with a 2% IOU transfer fee, 3% NFT
+
5915  // transfer fee, and 100 broker fee:
+
5916  //
+
5917  // A) Total sale amount + IOU transfer fee is paid by buyer
+
5918  // (Buyer pays (1.02 * 500) = 510)
+
5919  // B) GW receives the additional IOU transfer fee
+
5920  // (GW receives 10 from buyer calculated above)
+
5921  // C) Broker receives broker fee (no IOU transfer fee)
+
5922  // (Broker receives 100 from buyer)
+
5923  // D) Minter receives transfer fee (no IOU transfer fee)
+
5924  // (Minter receives 0.03 * (510 - 10 - 100) = 12)
+
5925  // E) Seller receives balance (no IOU transfer fee)
+
5926  // (Seller receives (510 - 10 - 100 - 12) = 388)
+
5927  reinitializeTrustLineBalances();
+
5928 
+
5929  // secondarySeller has to sell it because transfer fees only
+
5930  // happen on secondary sales
+
5931  auto const nftID = mintNFT(minter, 3000); // 3%
+
5932  auto const primaryOfferID =
+
5933  createSellOffer(minter, nftID, XRP(0));
+
5934  env(token::acceptSellOffer(secondarySeller, primaryOfferID));
+
5935  env.close();
+
5936 
+
5937  // now we can do a secondary sale
+
5938  auto const sellOffer =
+
5939  createSellOffer(secondarySeller, nftID, gwXAU(300));
+
5940  auto const buyOffer =
+
5941  createBuyOffer(buyer, secondarySeller, nftID, gwXAU(500));
+
5942  env(token::brokerOffers(broker, buyOffer, sellOffer),
+
5943  token::brokerFee(gwXAU(100)));
+
5944  env.close();
+
5945 
+
5946  BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(12));
+
5947  BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
+
5948  BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(388));
+
5949  BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
+
5950  BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-12));
+
5951  BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
+
5952  BEAST_EXPECT(
+
5953  env.balance(gw, secondarySeller["XAU"]) == gwXAU(-388));
+
5954  BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
+
5955  }
+
5956  }
+
5957  }
5958 
-
5959  // For this scenario to occur we need the following steps:
-
5960  //
-
5961  // 1. alice mints NFT.
-
5962  // 2. bob creates a buy offer for it for 5 XRP.
-
5963  // 3. alice decides to gift the NFT to bob for 0.
-
5964  // creating a sell offer (hopefully using a destination too)
-
5965  // 4. Bob accepts the sell offer, because it is better than
-
5966  // paying 5 XRP.
-
5967  // 5. At this point, bob has the NFT and still has their buy
-
5968  // offer from when they did not have the NFT! This is because
-
5969  // the order book is not cleared when an NFT changes hands.
-
5970  // 6. Now that Bob owns the NFT, he cannot create new buy offers.
-
5971  // However he still has one left over from when he did not own
-
5972  // it. He can create new sell offers and does.
-
5973  // 7. Now that bob has both a buy and a sell offer for the same NFT,
-
5974  // a broker can sell the NFT that bob owns to bob and pocket the
-
5975  // difference.
-
5976  uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
-
5977  env(token::mint(alice, 0u), txflags(tfTransferable));
-
5978  env.close();
-
5979 
-
5980  // Bob creates a buy offer for 5 XRP. Alice creates a sell offer
-
5981  // for 0 XRP.
-
5982  uint256 const bobBuyOfferIndex =
-
5983  keylet::nftoffer(bob, env.seq(bob)).key;
-
5984  env(token::createOffer(bob, nftId, XRP(5)), token::owner(alice));
+
5959  void
+
5960  testBrokeredSaleToSelf(FeatureBitset features)
+
5961  {
+
5962  // There was a bug that if an account had...
+
5963  //
+
5964  // 1. An NFToken, and
+
5965  // 2. An offer on the ledger to buy that same token, and
+
5966  // 3. Also an offer of the ledger to sell that same token,
+
5967  //
+
5968  // Then someone could broker the two offers. This would result in
+
5969  // the NFToken being bought and returned to the original owner and
+
5970  // the broker pocketing the profit.
+
5971  //
+
5972  // This unit test verifies that the fixNonFungibleTokensV1_2 amendment
+
5973  // fixes that bug.
+
5974  testcase("Brokered sale to self");
+
5975 
+
5976  using namespace test::jtx;
+
5977 
+
5978  Account const alice{"alice"};
+
5979  Account const bob{"bob"};
+
5980  Account const broker{"broker"};
+
5981 
+
5982  Env env{*this, features};
+
5983  env.fund(XRP(10000), alice, bob, broker);
+
5984  env.close();
5985 
-
5986  uint256 const aliceSellOfferIndex =
-
5987  keylet::nftoffer(alice, env.seq(alice)).key;
-
5988  env(token::createOffer(alice, nftId, XRP(0)),
-
5989  token::destination(bob),
-
5990  txflags(tfSellNFToken));
-
5991  env.close();
-
5992 
-
5993  // bob accepts alice's offer but forgets to remove the old buy offer.
-
5994  env(token::acceptSellOffer(bob, aliceSellOfferIndex));
-
5995  env.close();
-
5996 
-
5997  // Note that bob still has a buy offer on the books.
-
5998  BEAST_EXPECT(env.le(keylet::nftoffer(bobBuyOfferIndex)));
-
5999 
-
6000  // Bob creates a sell offer for the gift NFT from alice.
-
6001  uint256 const bobSellOfferIndex =
-
6002  keylet::nftoffer(bob, env.seq(bob)).key;
-
6003  env(token::createOffer(bob, nftId, XRP(4)), txflags(tfSellNFToken));
-
6004  env.close();
-
6005 
-
6006  // bob now has a buy offer and a sell offer on the books. A broker
-
6007  // spots this and swoops in to make a profit.
-
6008  BEAST_EXPECT(nftCount(env, bob) == 1);
-
6009  auto const bobsPriorBalance = env.balance(bob);
-
6010  auto const brokersPriorBalance = env.balance(broker);
-
6011  TER expectTer = features[fixNonFungibleTokensV1_2]
-
6012  ? TER(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER)
-
6013  : TER(tesSUCCESS);
-
6014  env(token::brokerOffers(broker, bobBuyOfferIndex, bobSellOfferIndex),
-
6015  token::brokerFee(XRP(1)),
-
6016  ter(expectTer));
-
6017  env.close();
-
6018 
-
6019  if (expectTer == tesSUCCESS)
-
6020  {
-
6021  // bob should still have the NFT from alice, but be XRP(1) poorer.
-
6022  // broker should be almost XRP(1) richer because they also paid a
-
6023  // transaction fee.
-
6024  BEAST_EXPECT(nftCount(env, bob) == 1);
-
6025  BEAST_EXPECT(env.balance(bob) == bobsPriorBalance - XRP(1));
-
6026  BEAST_EXPECT(
-
6027  env.balance(broker) ==
-
6028  brokersPriorBalance + XRP(1) - drops(10));
-
6029  }
-
6030  else
-
6031  {
-
6032  // A tec result was returned, so no state should change other
-
6033  // than the broker burning their transaction fee.
-
6034  BEAST_EXPECT(nftCount(env, bob) == 1);
-
6035  BEAST_EXPECT(env.balance(bob) == bobsPriorBalance);
-
6036  BEAST_EXPECT(
-
6037  env.balance(broker) == brokersPriorBalance - drops(10));
-
6038  }
-
6039  }
-
6040 
-
6041  void
-
6042  testWithFeats(FeatureBitset features)
-
6043  {
-
6044  testEnabled(features);
-
6045  testMintReserve(features);
-
6046  testMintMaxTokens(features);
-
6047  testMintInvalid(features);
-
6048  testBurnInvalid(features);
-
6049  testCreateOfferInvalid(features);
-
6050  testCancelOfferInvalid(features);
-
6051  testAcceptOfferInvalid(features);
-
6052  testMintFlagBurnable(features);
-
6053  testMintFlagOnlyXRP(features);
-
6054  testMintFlagCreateTrustLine(features);
-
6055  testMintFlagTransferable(features);
-
6056  testMintTransferFee(features);
-
6057  testMintTaxon(features);
-
6058  testMintURI(features);
-
6059  testCreateOfferDestination(features);
-
6060  testCreateOfferDestinationDisallowIncoming(features);
-
6061  testCreateOfferExpiration(features);
-
6062  testCancelOffers(features);
-
6063  testCancelTooManyOffers(features);
-
6064  testBrokeredAccept(features);
-
6065  testNFTokenOfferOwner(features);
-
6066  testNFTokenWithTickets(features);
-
6067  testNFTokenDeleteAccount(features);
-
6068  testNftXxxOffers(features);
-
6069  testFixNFTokenNegOffer(features);
-
6070  testIOUWithTransferFee(features);
-
6071  testBrokeredSaleToSelf(features);
-
6072  }
-
6073 
-
6074 public:
-
6075  void
-
6076  run() override
-
6077  {
-
6078  using namespace test::jtx;
-
6079  FeatureBitset const all{supported_amendments()};
-
6080  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
-
6081 
-
6082  testWithFeats(all - fixNFTDir - fixNonFungibleTokensV1_2);
-
6083  testWithFeats(all - disallowIncoming - fixNonFungibleTokensV1_2);
-
6084  testWithFeats(all - fixNonFungibleTokensV1_2);
-
6085  testWithFeats(all);
-
6086  }
-
6087 };
-
6088 
-
6089 BEAST_DEFINE_TESTSUITE_PRIO(NFToken, tx, ripple, 2);
-
6090 
-
6091 } // namespace ripple
+
5986  // For this scenario to occur we need the following steps:
+
5987  //
+
5988  // 1. alice mints NFT.
+
5989  // 2. bob creates a buy offer for it for 5 XRP.
+
5990  // 3. alice decides to gift the NFT to bob for 0.
+
5991  // creating a sell offer (hopefully using a destination too)
+
5992  // 4. Bob accepts the sell offer, because it is better than
+
5993  // paying 5 XRP.
+
5994  // 5. At this point, bob has the NFT and still has their buy
+
5995  // offer from when they did not have the NFT! This is because
+
5996  // the order book is not cleared when an NFT changes hands.
+
5997  // 6. Now that Bob owns the NFT, he cannot create new buy offers.
+
5998  // However he still has one left over from when he did not own
+
5999  // it. He can create new sell offers and does.
+
6000  // 7. Now that bob has both a buy and a sell offer for the same NFT,
+
6001  // a broker can sell the NFT that bob owns to bob and pocket the
+
6002  // difference.
+
6003  uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
+
6004  env(token::mint(alice, 0u), txflags(tfTransferable));
+
6005  env.close();
+
6006 
+
6007  // Bob creates a buy offer for 5 XRP. Alice creates a sell offer
+
6008  // for 0 XRP.
+
6009  uint256 const bobBuyOfferIndex =
+
6010  keylet::nftoffer(bob, env.seq(bob)).key;
+
6011  env(token::createOffer(bob, nftId, XRP(5)), token::owner(alice));
+
6012 
+
6013  uint256 const aliceSellOfferIndex =
+
6014  keylet::nftoffer(alice, env.seq(alice)).key;
+
6015  env(token::createOffer(alice, nftId, XRP(0)),
+
6016  token::destination(bob),
+
6017  txflags(tfSellNFToken));
+
6018  env.close();
+
6019 
+
6020  // bob accepts alice's offer but forgets to remove the old buy offer.
+
6021  env(token::acceptSellOffer(bob, aliceSellOfferIndex));
+
6022  env.close();
+
6023 
+
6024  // Note that bob still has a buy offer on the books.
+
6025  BEAST_EXPECT(env.le(keylet::nftoffer(bobBuyOfferIndex)));
+
6026 
+
6027  // Bob creates a sell offer for the gift NFT from alice.
+
6028  uint256 const bobSellOfferIndex =
+
6029  keylet::nftoffer(bob, env.seq(bob)).key;
+
6030  env(token::createOffer(bob, nftId, XRP(4)), txflags(tfSellNFToken));
+
6031  env.close();
+
6032 
+
6033  // bob now has a buy offer and a sell offer on the books. A broker
+
6034  // spots this and swoops in to make a profit.
+
6035  BEAST_EXPECT(nftCount(env, bob) == 1);
+
6036  auto const bobsPriorBalance = env.balance(bob);
+
6037  auto const brokersPriorBalance = env.balance(broker);
+
6038  TER expectTer = features[fixNonFungibleTokensV1_2]
+
6039  ? TER(tecCANT_ACCEPT_OWN_NFTOKEN_OFFER)
+
6040  : TER(tesSUCCESS);
+
6041  env(token::brokerOffers(broker, bobBuyOfferIndex, bobSellOfferIndex),
+
6042  token::brokerFee(XRP(1)),
+
6043  ter(expectTer));
+
6044  env.close();
+
6045 
+
6046  if (expectTer == tesSUCCESS)
+
6047  {
+
6048  // bob should still have the NFT from alice, but be XRP(1) poorer.
+
6049  // broker should be almost XRP(1) richer because they also paid a
+
6050  // transaction fee.
+
6051  BEAST_EXPECT(nftCount(env, bob) == 1);
+
6052  BEAST_EXPECT(env.balance(bob) == bobsPriorBalance - XRP(1));
+
6053  BEAST_EXPECT(
+
6054  env.balance(broker) ==
+
6055  brokersPriorBalance + XRP(1) - drops(10));
+
6056  }
+
6057  else
+
6058  {
+
6059  // A tec result was returned, so no state should change other
+
6060  // than the broker burning their transaction fee.
+
6061  BEAST_EXPECT(nftCount(env, bob) == 1);
+
6062  BEAST_EXPECT(env.balance(bob) == bobsPriorBalance);
+
6063  BEAST_EXPECT(
+
6064  env.balance(broker) == brokersPriorBalance - drops(10));
+
6065  }
+
6066  }
+
6067 
+
6068  void
+
6069  testFixNFTokenRemint(FeatureBitset features)
+
6070  {
+
6071  using namespace test::jtx;
+
6072 
+
6073  testcase("fixNFTokenRemint");
+
6074 
+
6075  // Returns the current ledger sequence
+
6076  auto openLedgerSeq = [](Env& env) { return env.current()->seq(); };
+
6077 
+
6078  // Close the ledger until the ledger sequence is large enough to delete
+
6079  // the account (no longer within <Sequence + 256>)
+
6080  // This is enforced by the featureDeletableAccounts amendment
+
6081  auto incLgrSeqForAcctDel = [&](Env& env, Account const& acct) {
+
6082  int const delta = [&]() -> int {
+
6083  if (env.seq(acct) + 255 > openLedgerSeq(env))
+
6084  return env.seq(acct) - openLedgerSeq(env) + 255;
+
6085  return 0;
+
6086  }();
+
6087  BEAST_EXPECT(delta >= 0);
+
6088  for (int i = 0; i < delta; ++i)
+
6089  env.close();
+
6090  BEAST_EXPECT(openLedgerSeq(env) == env.seq(acct) + 255);
+
6091  };
+
6092 
+
6093  // Close the ledger until the ledger sequence is no longer
+
6094  // within <FirstNFTokenSequence + MintedNFTokens + 256>.
+
6095  // This is enforced by the fixNFTokenRemint amendment.
+
6096  auto incLgrSeqForFixNftRemint = [&](Env& env, Account const& acct) {
+
6097  int delta = 0;
+
6098  auto const deletableLgrSeq =
+
6099  (*env.le(acct))[~sfFirstNFTokenSequence].value_or(0) +
+
6100  (*env.le(acct))[sfMintedNFTokens] + 255;
+
6101 
+
6102  if (deletableLgrSeq > openLedgerSeq(env))
+
6103  delta = deletableLgrSeq - openLedgerSeq(env);
+
6104 
+
6105  BEAST_EXPECT(delta >= 0);
+
6106  for (int i = 0; i < delta; ++i)
+
6107  env.close();
+
6108  BEAST_EXPECT(openLedgerSeq(env) == deletableLgrSeq);
+
6109  };
+
6110 
+
6111  // We check if NFTokenIDs can be duplicated by
+
6112  // re-creation of an account
+
6113  {
+
6114  Env env{*this, features};
+
6115  Account const alice("alice");
+
6116  Account const becky("becky");
+
6117 
+
6118  env.fund(XRP(10000), alice, becky);
+
6119  env.close();
+
6120 
+
6121  // alice mint and burn a NFT
+
6122  uint256 const prevNFTokenID = token::getNextID(env, alice, 0u);
+
6123  env(token::mint(alice));
+
6124  env.close();
+
6125  env(token::burn(alice, prevNFTokenID));
+
6126  env.close();
+
6127 
+
6128  // alice has minted 1 NFToken
+
6129  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 1);
+
6130 
+
6131  // Close enough ledgers to delete alice's account
+
6132  incLgrSeqForAcctDel(env, alice);
+
6133 
+
6134  // alice's account is deleted
+
6135  Keylet const aliceAcctKey{keylet::account(alice.id())};
+
6136  auto const acctDelFee{drops(env.current()->fees().increment)};
+
6137  env(acctdelete(alice, becky), fee(acctDelFee));
+
6138  env.close();
+
6139 
+
6140  // alice's account root is gone from the most recently
+
6141  // closed ledger and the current ledger.
+
6142  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6143  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6144 
+
6145  // Fund alice to re-create her account
+
6146  env.fund(XRP(10000), alice);
+
6147  env.close();
+
6148 
+
6149  // alice's account now exists and has minted 0 NFTokens
+
6150  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6151  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6152  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6153 
+
6154  // alice mints a NFT with same params as prevNFTokenID
+
6155  uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
6156  env(token::mint(alice));
+
6157  env.close();
+
6158 
+
6159  // burn the NFT to make sure alice owns remintNFTokenID
+
6160  env(token::burn(alice, remintNFTokenID));
+
6161  env.close();
+
6162 
+
6163  if (features[fixNFTokenRemint])
+
6164  // Check that two NFTs don't have the same ID
+
6165  BEAST_EXPECT(remintNFTokenID != prevNFTokenID);
+
6166  else
+
6167  // Check that two NFTs have the same ID
+
6168  BEAST_EXPECT(remintNFTokenID == prevNFTokenID);
+
6169  }
+
6170 
+
6171  // Test if the issuer account can be deleted after an authorized
+
6172  // minter mints and burns a batch of NFTokens.
+
6173  {
+
6174  Env env{*this, features};
+
6175  Account const alice("alice");
+
6176  Account const becky("becky");
+
6177  Account const minter{"minter"};
+
6178 
+
6179  env.fund(XRP(10000), alice, becky, minter);
+
6180  env.close();
+
6181 
+
6182  // alice sets minter as her authorized minter
+
6183  env(token::setMinter(alice, minter));
+
6184  env.close();
+
6185 
+
6186  // minter mints 500 NFTs for alice
+
6187  std::vector<uint256> nftIDs;
+
6188  nftIDs.reserve(500);
+
6189  for (int i = 0; i < 500; i++)
+
6190  {
+
6191  uint256 const nftokenID = token::getNextID(env, alice, 0u);
+
6192  nftIDs.push_back(nftokenID);
+
6193  env(token::mint(minter), token::issuer(alice));
+
6194  }
+
6195  env.close();
+
6196 
+
6197  // minter burns 500 NFTs
+
6198  for (auto const nftokenID : nftIDs)
+
6199  {
+
6200  env(token::burn(minter, nftokenID));
+
6201  }
+
6202  env.close();
+
6203 
+
6204  // Increment ledger sequence to the number that is
+
6205  // enforced by the featureDeletableAccounts amendment
+
6206  incLgrSeqForAcctDel(env, alice);
+
6207 
+
6208  // Verify that alice's account root is present.
+
6209  Keylet const aliceAcctKey{keylet::account(alice.id())};
+
6210  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6211  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6212 
+
6213  auto const acctDelFee{drops(env.current()->fees().increment)};
+
6214 
+
6215  if (!features[fixNFTokenRemint])
+
6216  {
+
6217  // alice's account can be successfully deleted.
+
6218  env(acctdelete(alice, becky), fee(acctDelFee));
+
6219  env.close();
+
6220  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6221 
+
6222  // Fund alice to re-create her account
+
6223  env.fund(XRP(10000), alice);
+
6224  env.close();
+
6225 
+
6226  // alice's account now exists and has minted 0 NFTokens
+
6227  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6228  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6229  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6230 
+
6231  // alice mints a NFT with same params as the first one before
+
6232  // the account delete.
+
6233  uint256 const remintNFTokenID =
+
6234  token::getNextID(env, alice, 0u);
+
6235  env(token::mint(alice));
+
6236  env.close();
+
6237 
+
6238  // burn the NFT to make sure alice owns remintNFTokenID
+
6239  env(token::burn(alice, remintNFTokenID));
+
6240  env.close();
+
6241 
+
6242  // The new NFT minted has the same ID as one of the NFTs
+
6243  // authorized minter minted for alice
+
6244  BEAST_EXPECT(
+
6245  std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) !=
+
6246  nftIDs.end());
+
6247  }
+
6248  else if (features[fixNFTokenRemint])
+
6249  {
+
6250  // alice tries to delete her account, but is unsuccessful.
+
6251  // Due to authorized minting, alice's account sequence does not
+
6252  // advance while minter mints NFTokens for her.
+
6253  // The new account deletion retriction <FirstNFTokenSequence +
+
6254  // MintedNFTokens + 256> enabled by this amendment will enforce
+
6255  // alice to wait for more ledgers to close before she can
+
6256  // delete her account, to prevent duplicate NFTokenIDs
+
6257  env(acctdelete(alice, becky),
+
6258  fee(acctDelFee),
+
6259  ter(tecTOO_SOON));
+
6260  env.close();
+
6261 
+
6262  // alice's account is still present
+
6263  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6264 
+
6265  // Close more ledgers until it is no longer within
+
6266  // <FirstNFTokenSequence + MintedNFTokens + 256>
+
6267  // to be able to delete alice's account
+
6268  incLgrSeqForFixNftRemint(env, alice);
+
6269 
+
6270  // alice's account is deleted
+
6271  env(acctdelete(alice, becky), fee(acctDelFee));
+
6272  env.close();
+
6273 
+
6274  // alice's account root is gone from the most recently
+
6275  // closed ledger and the current ledger.
+
6276  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6277  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6278 
+
6279  // Fund alice to re-create her account
+
6280  env.fund(XRP(10000), alice);
+
6281  env.close();
+
6282 
+
6283  // alice's account now exists and has minted 0 NFTokens
+
6284  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6285  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6286  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6287 
+
6288  // alice mints a NFT with same params as the first one before
+
6289  // the account delete.
+
6290  uint256 const remintNFTokenID =
+
6291  token::getNextID(env, alice, 0u);
+
6292  env(token::mint(alice));
+
6293  env.close();
+
6294 
+
6295  // burn the NFT to make sure alice owns remintNFTokenID
+
6296  env(token::burn(alice, remintNFTokenID));
+
6297  env.close();
+
6298 
+
6299  // The new NFT minted will not have the same ID
+
6300  // as any of the NFTs authorized minter minted
+
6301  BEAST_EXPECT(
+
6302  std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
6303  nftIDs.end());
+
6304  }
+
6305  }
+
6306 
+
6307  // When an account mints and burns a batch of NFTokens using tickets,
+
6308  // see if the the account can be deleted.
+
6309  {
+
6310  Env env{*this, features};
+
6311 
+
6312  Account const alice{"alice"};
+
6313  Account const becky{"becky"};
+
6314  env.fund(XRP(10000), alice, becky);
+
6315  env.close();
+
6316 
+
6317  // alice grab enough tickets for all of the following
+
6318  // transactions. Note that once the tickets are acquired alice's
+
6319  // account sequence number should not advance.
+
6320  std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
+
6321  env(ticket::create(alice, 100));
+
6322  env.close();
+
6323 
+
6324  BEAST_EXPECT(ticketCount(env, alice) == 100);
+
6325  BEAST_EXPECT(ownerCount(env, alice) == 100);
+
6326 
+
6327  // alice mints 50 NFTs using tickets
+
6328  std::vector<uint256> nftIDs;
+
6329  nftIDs.reserve(50);
+
6330  for (int i = 0; i < 50; i++)
+
6331  {
+
6332  nftIDs.push_back(token::getNextID(env, alice, 0u));
+
6333  env(token::mint(alice, 0u), ticket::use(aliceTicketSeq++));
+
6334  env.close();
+
6335  }
+
6336 
+
6337  // alice burns 50 NFTs using tickets
+
6338  for (auto const nftokenID : nftIDs)
+
6339  {
+
6340  env(token::burn(alice, nftokenID),
+
6341  ticket::use(aliceTicketSeq++));
+
6342  }
+
6343  env.close();
+
6344 
+
6345  BEAST_EXPECT(ticketCount(env, alice) == 0);
+
6346 
+
6347  // Increment ledger sequence to the number that is
+
6348  // enforced by the featureDeletableAccounts amendment
+
6349  incLgrSeqForAcctDel(env, alice);
+
6350 
+
6351  // Verify that alice's account root is present.
+
6352  Keylet const aliceAcctKey{keylet::account(alice.id())};
+
6353  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6354  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6355 
+
6356  auto const acctDelFee{drops(env.current()->fees().increment)};
+
6357 
+
6358  if (!features[fixNFTokenRemint])
+
6359  {
+
6360  // alice tries to delete her account, and is successful.
+
6361  env(acctdelete(alice, becky), fee(acctDelFee));
+
6362  env.close();
+
6363 
+
6364  // alice's account root is gone from the most recently
+
6365  // closed ledger and the current ledger.
+
6366  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6367  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6368 
+
6369  // Fund alice to re-create her account
+
6370  env.fund(XRP(10000), alice);
+
6371  env.close();
+
6372 
+
6373  // alice's account now exists and has minted 0 NFTokens
+
6374  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6375  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6376  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6377 
+
6378  // alice mints a NFT with same params as the first one before
+
6379  // the account delete.
+
6380  uint256 const remintNFTokenID =
+
6381  token::getNextID(env, alice, 0u);
+
6382  env(token::mint(alice));
+
6383  env.close();
+
6384 
+
6385  // burn the NFT to make sure alice owns remintNFTokenID
+
6386  env(token::burn(alice, remintNFTokenID));
+
6387  env.close();
+
6388 
+
6389  // The new NFT minted will have the same ID
+
6390  // as one of NFTs minted using tickets
+
6391  BEAST_EXPECT(
+
6392  std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) !=
+
6393  nftIDs.end());
+
6394  }
+
6395  else if (features[fixNFTokenRemint])
+
6396  {
+
6397  // alice tries to delete her account, but is unsuccessful.
+
6398  // Due to authorized minting, alice's account sequence does not
+
6399  // advance while minter mints NFTokens for her using tickets.
+
6400  // The new account deletion retriction <FirstNFTokenSequence +
+
6401  // MintedNFTokens + 256> enabled by this amendment will enforce
+
6402  // alice to wait for more ledgers to close before she can
+
6403  // delete her account, to prevent duplicate NFTokenIDs
+
6404  env(acctdelete(alice, becky),
+
6405  fee(acctDelFee),
+
6406  ter(tecTOO_SOON));
+
6407  env.close();
+
6408 
+
6409  // alice's account is still present
+
6410  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6411 
+
6412  // Close more ledgers until it is no longer within
+
6413  // <FirstNFTokenSequence + MintedNFTokens + 256>
+
6414  // to be able to delete alice's account
+
6415  incLgrSeqForFixNftRemint(env, alice);
+
6416 
+
6417  // alice's account is deleted
+
6418  env(acctdelete(alice, becky), fee(acctDelFee));
+
6419  env.close();
+
6420 
+
6421  // alice's account root is gone from the most recently
+
6422  // closed ledger and the current ledger.
+
6423  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6424  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6425 
+
6426  // Fund alice to re-create her account
+
6427  env.fund(XRP(10000), alice);
+
6428  env.close();
+
6429 
+
6430  // alice's account now exists and has minted 0 NFTokens
+
6431  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6432  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6433  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6434 
+
6435  // alice mints a NFT with same params as the first one before
+
6436  // the account delete.
+
6437  uint256 const remintNFTokenID =
+
6438  token::getNextID(env, alice, 0u);
+
6439  env(token::mint(alice));
+
6440  env.close();
+
6441 
+
6442  // burn the NFT to make sure alice owns remintNFTokenID
+
6443  env(token::burn(alice, remintNFTokenID));
+
6444  env.close();
+
6445 
+
6446  // The new NFT minted will not have the same ID
+
6447  // as any of the NFTs authorized minter minted using tickets
+
6448  BEAST_EXPECT(
+
6449  std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
6450  nftIDs.end());
+
6451  }
+
6452  }
+
6453  // If fixNFTokenRemint is enabled,
+
6454  // when an authorized minter mints and burns a batch of NFTokens using
+
6455  // tickets, issuer's account needs to wait a longer time before it can
+
6456  // deleted.
+
6457  // After the issuer's account is re-created and mints a NFT, it should
+
6458  // not have the same NFTokenID as the ones authorized minter minted.
+
6459  if (features[fixNFTokenRemint])
+
6460  {
+
6461  Env env{*this, features};
+
6462  Account const alice("alice");
+
6463  Account const becky("becky");
+
6464  Account const minter{"minter"};
+
6465 
+
6466  env.fund(XRP(10000), alice, becky, minter);
+
6467  env.close();
+
6468 
+
6469  // alice sets minter as her authorized minter
+
6470  env(token::setMinter(alice, minter));
+
6471  env.close();
+
6472 
+
6473  // minter creates 100 tickets
+
6474  std::uint32_t minterTicketSeq{env.seq(minter) + 1};
+
6475  env(ticket::create(minter, 100));
+
6476  env.close();
+
6477 
+
6478  BEAST_EXPECT(ticketCount(env, minter) == 100);
+
6479  BEAST_EXPECT(ownerCount(env, minter) == 100);
+
6480 
+
6481  // minter mints 50 NFTs for alice using tickets
+
6482  std::vector<uint256> nftIDs;
+
6483  nftIDs.reserve(50);
+
6484  for (int i = 0; i < 50; i++)
+
6485  {
+
6486  uint256 const nftokenID = token::getNextID(env, alice, 0u);
+
6487  nftIDs.push_back(nftokenID);
+
6488  env(token::mint(minter),
+
6489  token::issuer(alice),
+
6490  ticket::use(minterTicketSeq++));
+
6491  }
+
6492  env.close();
+
6493 
+
6494  // minter burns 50 NFTs using tickets
+
6495  for (auto const nftokenID : nftIDs)
+
6496  {
+
6497  env(token::burn(minter, nftokenID),
+
6498  ticket::use(minterTicketSeq++));
+
6499  }
+
6500  env.close();
+
6501 
+
6502  BEAST_EXPECT(ticketCount(env, minter) == 0);
+
6503 
+
6504  // Increment ledger sequence to the number that is
+
6505  // enforced by the featureDeletableAccounts amendment
+
6506  incLgrSeqForAcctDel(env, alice);
+
6507 
+
6508  // Verify that alice's account root is present.
+
6509  Keylet const aliceAcctKey{keylet::account(alice.id())};
+
6510  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6511  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6512 
+
6513  // alice tries to delete her account, but is unsuccessful.
+
6514  // Due to authorized minting, alice's account sequence does not
+
6515  // advance while minter mints NFTokens for her using tickets.
+
6516  // The new account deletion retriction <FirstNFTokenSequence +
+
6517  // MintedNFTokens + 256> enabled by this amendment will enforce
+
6518  // alice to wait for more ledgers to close before she can delete her
+
6519  // account, to prevent duplicate NFTokenIDs
+
6520  auto const acctDelFee{drops(env.current()->fees().increment)};
+
6521  env(acctdelete(alice, becky), fee(acctDelFee), ter(tecTOO_SOON));
+
6522  env.close();
+
6523 
+
6524  // alice's account is still present
+
6525  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6526 
+
6527  // Close more ledgers until it is no longer within
+
6528  // <FirstNFTokenSequence + MintedNFTokens + 256>
+
6529  // to be able to delete alice's account
+
6530  incLgrSeqForFixNftRemint(env, alice);
+
6531 
+
6532  // alice's account is deleted
+
6533  env(acctdelete(alice, becky), fee(acctDelFee));
+
6534  env.close();
+
6535 
+
6536  // alice's account root is gone from the most recently
+
6537  // closed ledger and the current ledger.
+
6538  BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
+
6539  BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
+
6540 
+
6541  // Fund alice to re-create her account
+
6542  env.fund(XRP(10000), alice);
+
6543  env.close();
+
6544 
+
6545  // alice's account now exists and has minted 0 NFTokens
+
6546  BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
+
6547  BEAST_EXPECT(env.current()->exists(aliceAcctKey));
+
6548  BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
+
6549 
+
6550  // The new NFT minted will not have the same ID
+
6551  // as any of the NFTs authorized minter minted using tickets
+
6552  uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
+
6553  env(token::mint(alice));
+
6554  env.close();
+
6555 
+
6556  // burn the NFT to make sure alice owns remintNFTokenID
+
6557  env(token::burn(alice, remintNFTokenID));
+
6558  env.close();
+
6559 
+
6560  // The new NFT minted will not have the same ID
+
6561  // as one of NFTs authorized minter minted using tickets
+
6562  BEAST_EXPECT(
+
6563  std::find(nftIDs.begin(), nftIDs.end(), remintNFTokenID) ==
+
6564  nftIDs.end());
+
6565  }
+
6566  }
+
6567 
+
6568  void
+
6569  testWithFeats(FeatureBitset features)
+
6570  {
+
6571  testEnabled(features);
+
6572  testMintReserve(features);
+
6573  testMintMaxTokens(features);
+
6574  testMintInvalid(features);
+
6575  testBurnInvalid(features);
+
6576  testCreateOfferInvalid(features);
+
6577  testCancelOfferInvalid(features);
+
6578  testAcceptOfferInvalid(features);
+
6579  testMintFlagBurnable(features);
+
6580  testMintFlagOnlyXRP(features);
+
6581  testMintFlagCreateTrustLine(features);
+
6582  testMintFlagTransferable(features);
+
6583  testMintTransferFee(features);
+
6584  testMintTaxon(features);
+
6585  testMintURI(features);
+
6586  testCreateOfferDestination(features);
+
6587  testCreateOfferDestinationDisallowIncoming(features);
+
6588  testCreateOfferExpiration(features);
+
6589  testCancelOffers(features);
+
6590  testCancelTooManyOffers(features);
+
6591  testBrokeredAccept(features);
+
6592  testNFTokenOfferOwner(features);
+
6593  testNFTokenWithTickets(features);
+
6594  testNFTokenDeleteAccount(features);
+
6595  testNftXxxOffers(features);
+
6596  testFixNFTokenNegOffer(features);
+
6597  testIOUWithTransferFee(features);
+
6598  testBrokeredSaleToSelf(features);
+
6599  testFixNFTokenRemint(features);
+
6600  }
+
6601 
+
6602 public:
+
6603  void
+
6604  run() override
+
6605  {
+
6606  using namespace test::jtx;
+
6607  FeatureBitset const all{supported_amendments()};
+
6608  FeatureBitset const fixNFTDir{fixNFTokenDirV1};
+
6609 
+
6610  testWithFeats(
+
6611  all - fixNFTDir - fixNonFungibleTokensV1_2 - fixNFTokenRemint);
+
6612  testWithFeats(
+
6613  all - disallowIncoming - fixNonFungibleTokensV1_2 -
+
6614  fixNFTokenRemint);
+
6615  testWithFeats(all - fixNonFungibleTokensV1_2 - fixNFTokenRemint);
+
6616  testWithFeats(all - fixNFTokenRemint);
+
6617  testWithFeats(all);
+
6618  }
+
6619 };
+
6620 
+
6621 BEAST_DEFINE_TESTSUITE_PRIO(NFToken, tx, ripple, 2);
+
6622 
+
6623 } // namespace ripple
-
void testNFTokenDeleteAccount(FeatureBitset features)
+
void testNFTokenDeleteAccount(FeatureBitset features)
@ tecUNFUNDED_OFFER
Definition: TER.h:248
constexpr std::uint16_t maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:81
const SF_UINT32 sfOwnerCount
const uint256 fixRemoveNFTokenAutoTrustLine
-
void testMintMaxTokens(FeatureBitset features)
-
void testBurnInvalid(FeatureBitset features)
+
const SF_UINT32 sfFirstNFTokenSequence
+
void testMintMaxTokens(FeatureBitset features)
+
void testBurnInvalid(FeatureBitset features)
@ tecOBJECT_NOT_FOUND
Definition: TER.h:290
@ tecFROZEN
Definition: TER.h:267
-
void testMintFlagBurnable(FeatureBitset features)
-
void testCreateOfferInvalid(FeatureBitset features)
+
void testMintFlagBurnable(FeatureBitset features)
+
void testCreateOfferInvalid(FeatureBitset features)
constexpr const std::uint32_t tfTransferable
Definition: TxFlags.h:130
const uint256 fixNFTokenNegOffer
static std::uint32_t nftCount(test::jtx::Env &env, test::jtx::Account const &acct)
+
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:38
STL class.
@ temBAD_OFFER
Definition: TER.h:90
static const std::uint64_t cMinValue
Definition: STAmount.h:66
void testMintReserve(FeatureBitset features)
-
void testCreateOfferExpiration(FeatureBitset features)
-
void testNFTokenOfferOwner(FeatureBitset features)
+
void testCreateOfferExpiration(FeatureBitset features)
+
void testNFTokenOfferOwner(FeatureBitset features)
const SF_VECTOR256 sfNFTokenOffers
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:289
@@ -6188,53 +6722,55 @@ $(function() {
@ all
Writable ledger view that accumulates state and tx changes.
Definition: OpenView.h:55
STL class.
+
T find(T... args)
T size(T... args)
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:634
const SF_UINT32 sfMintedNFTokens
Keylet nftoffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition: Indexes.cpp:355
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
STL class.
-
void testCancelTooManyOffers(FeatureBitset features)
+
void testCancelTooManyOffers(FeatureBitset features)
static const int cMinOffset
Definition: STAmount.h:62
static std::uint32_t ownerCount(test::jtx::Env const &env, test::jtx::Account const &acct)
@ tecCANT_ACCEPT_OWN_NFTOKEN_OFFER
Definition: TER.h:288
Taxon toTaxon(std::uint32_t i)
Definition: NFTokenUtils.h:40
BEAST_DEFINE_TESTSUITE_PRIO(NFToken, tx, ripple, 2)
-
void testMintFlagOnlyXRP(FeatureBitset features)
-
void testAcceptOfferInvalid(FeatureBitset features)
+
void testMintFlagOnlyXRP(FeatureBitset features)
+
void testAcceptOfferInvalid(FeatureBitset features)
const Json::StaticString jsonName
Definition: SField.h:136
T sort(T... args)
T clear(T... args)
static std::uint32_t ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)
-
void testIOUWithTransferFee(FeatureBitset features)
+
void testIOUWithTransferFee(FeatureBitset features)
+
const uint256 fixNFTokenRemint
T push_back(T... args)
-
void testCancelOffers(FeatureBitset features)
+
void testCancelOffers(FeatureBitset features)
uint256 key
Definition: Keylet.h:40
-
void testWithFeats(FeatureBitset features)
+
void testWithFeats(FeatureBitset features)
@ temINVALID_FLAG
Definition: TER.h:106
const FeatureBitset disallowIncoming
@ tecNFTOKEN_OFFER_TYPE_MISMATCH
Definition: TER.h:287
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
-
void testFixNFTokenNegOffer(FeatureBitset features)
+
void testFixNFTokenNegOffer(FeatureBitset features)
std::enable_if_t< std::is_integral< Integral >::value &&detail::is_engine< Engine >::value, Integral > rand_int(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
constexpr const std::uint32_t tfBurnable
Definition: TxFlags.h:127
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:133
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Definition: TER.h:165
const uint256 featureDisallowIncoming
const SF_ACCOUNT sfNFTokenMinter
-
void run() override
+
void run() override
@ none
-
void testNftXxxOffers(FeatureBitset features)
+
void testNftXxxOffers(FeatureBitset features)
static std::uint32_t mintedCount(test::jtx::Env const &env, test::jtx::Account const &issuer)
-
void testMintTaxon(FeatureBitset features)
+
void testMintTaxon(FeatureBitset features)
TERSubset< CanCvtToTER > TER
Definition: TER.h:565
T to_string(T... args)
std::enable_if_t<(std::is_same< Byte, unsigned char >::value||std::is_same< Byte, std::uint8_t >::value) &&detail::is_engine< Engine >::value, Byte > rand_byte(Engine &engine)
Return a random byte.
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
Definition: BasicConfig.h:313
const SF_UINT32 sfTicketCount
-
void testMintInvalid(FeatureBitset features)
+
void testMintInvalid(FeatureBitset features)
Definition: STAmount.h:45
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
@ tecINTERNAL
Definition: TER.h:274
@@ -6246,27 +6782,29 @@ $(function() {
static std::uint32_t burnedCount(test::jtx::Env const &env, test::jtx::Account const &issuer)
-
void testMintTransferFee(FeatureBitset features)
+
void testMintTransferFee(FeatureBitset features)
const uint256 fixNFTokenDirV1
@ temBAD_FEE
Definition: TER.h:87
constexpr std::size_t maxTokenURILength
The maximum length of a URI inside an NFT.
Definition: Protocol.h:84
-
void testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
-
void testMintFlagTransferable(FeatureBitset features)
+
void testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
+
@ tecTOO_SOON
Definition: TER.h:282
+
void testMintFlagTransferable(FeatureBitset features)
const SF_VL sfURI
-
void testCancelOfferInvalid(FeatureBitset features)
+
void testCancelOfferInvalid(FeatureBitset features)
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
const uint256 featureNonFungibleTokensV1
Value removeMember(const char *key)
Remove and return the named member.
Definition: json_value.cpp:907
@ tecNFTOKEN_BUY_SELL_MISMATCH
Definition: TER.h:286
@ tecINSUFFICIENT_PAYMENT
Definition: TER.h:291
-
void testBrokeredAccept(FeatureBitset features)
+
void testBrokeredAccept(FeatureBitset features)
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:111
@ tecNO_LINE
Definition: TER.h:265
@ tecEXPIRED
Definition: TER.h:278
@ lsfDisallowIncomingNFTokenOffer
@ temDISABLED
Definition: TER.h:109
std::uint32_t lastClose(test::jtx::Env &env)
+
void testFixNFTokenRemint(FeatureBitset features)
T begin(T... args)
T insert(T... args)
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:213
@@ -6277,10 +6815,10 @@ $(function() {
@ tecNO_PERMISSION
Definition: TER.h:269
Definition: Feature.h:113
void testEnabled(FeatureBitset features)
-
void testMintFlagCreateTrustLine(FeatureBitset features)
+
void testMintFlagCreateTrustLine(FeatureBitset features)
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:271
T empty(T... args)
-
void testNFTokenWithTickets(FeatureBitset features)
+
void testNFTokenWithTickets(FeatureBitset features)
std::shared_ptr< SLE const > read(Keylet const &k) const override
Return the state item associated with a key.
Definition: OpenView.cpp:171
constexpr const std::uint32_t tfTrustLine
Definition: TxFlags.h:129
@@ -6301,9 +6839,9 @@ $(function() {
const SF_UINT32 sfBurnedNFTokens
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:281
Taxon getTaxon(uint256 const &id)
Definition: NFTokenUtils.h:168
-
void testMintURI(FeatureBitset features)
+
void testMintURI(FeatureBitset features)
@ tesSUCCESS
Definition: TER.h:219
-
void testCreateOfferDestination(FeatureBitset features)
+
void testCreateOfferDestination(FeatureBitset features)
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition: TER.h:122
const SF_UINT256 sfNFTokenSellOffer
@@ -6314,7 +6852,7 @@ $(function() {
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
const SF_AMOUNT sfNFTokenBrokerFee
Represents a JSON value.
Definition: json_value.h:145
-
void testBrokeredSaleToSelf(FeatureBitset features)
+
void testBrokeredSaleToSelf(FeatureBitset features)
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
diff --git a/SField_8cpp_source.html b/SField_8cpp_source.html index a70a25041c..379f9dbdb5 100644 --- a/SField_8cpp_source.html +++ b/SField_8cpp_source.html @@ -221,276 +221,279 @@ $(function() {
150 CONSTRUCT_TYPED_SFIELD(sfBurnedNFTokens, "BurnedNFTokens", UINT32, 44);
151 CONSTRUCT_TYPED_SFIELD(sfHookStateCount, "HookStateCount", UINT32, 45);
152 CONSTRUCT_TYPED_SFIELD(sfEmitGeneration, "EmitGeneration", UINT32, 46);
-
153 
-
154 // 64-bit integers (common)
-
155 CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1);
-
156 CONSTRUCT_TYPED_SFIELD(sfIndexPrevious, "IndexPrevious", UINT64, 2);
-
157 CONSTRUCT_TYPED_SFIELD(sfBookNode, "BookNode", UINT64, 3);
-
158 CONSTRUCT_TYPED_SFIELD(sfOwnerNode, "OwnerNode", UINT64, 4);
-
159 CONSTRUCT_TYPED_SFIELD(sfBaseFee, "BaseFee", UINT64, 5);
-
160 CONSTRUCT_TYPED_SFIELD(sfExchangeRate, "ExchangeRate", UINT64, 6);
-
161 CONSTRUCT_TYPED_SFIELD(sfLowNode, "LowNode", UINT64, 7);
-
162 CONSTRUCT_TYPED_SFIELD(sfHighNode, "HighNode", UINT64, 8);
-
163 CONSTRUCT_TYPED_SFIELD(sfDestinationNode, "DestinationNode", UINT64, 9);
-
164 CONSTRUCT_TYPED_SFIELD(sfCookie, "Cookie", UINT64, 10);
-
165 CONSTRUCT_TYPED_SFIELD(sfServerVersion, "ServerVersion", UINT64, 11);
-
166 CONSTRUCT_TYPED_SFIELD(sfNFTokenOfferNode, "NFTokenOfferNode", UINT64, 12);
-
167 CONSTRUCT_TYPED_SFIELD(sfEmitBurden, "EmitBurden", UINT64, 13);
-
168 
-
169 // 64-bit integers (uncommon)
-
170 CONSTRUCT_TYPED_SFIELD(sfHookOn, "HookOn", UINT64, 16);
-
171 CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17);
-
172 CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18);
-
173 CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19);
-
174 
-
175 // 128-bit
-
176 CONSTRUCT_TYPED_SFIELD(sfEmailHash, "EmailHash", UINT128, 1);
+
153 // Three field values of 47, 48 and 49 are reserved for
+
154 // LockCount(Hooks), VoteWeight(AMM), DiscountedFee(AMM)
+
155 CONSTRUCT_TYPED_SFIELD(sfFirstNFTokenSequence, "FirstNFTokenSequence", UINT32, 50);
+
156 
+
157 // 64-bit integers (common)
+
158 CONSTRUCT_TYPED_SFIELD(sfIndexNext, "IndexNext", UINT64, 1);
+
159 CONSTRUCT_TYPED_SFIELD(sfIndexPrevious, "IndexPrevious", UINT64, 2);
+
160 CONSTRUCT_TYPED_SFIELD(sfBookNode, "BookNode", UINT64, 3);
+
161 CONSTRUCT_TYPED_SFIELD(sfOwnerNode, "OwnerNode", UINT64, 4);
+
162 CONSTRUCT_TYPED_SFIELD(sfBaseFee, "BaseFee", UINT64, 5);
+
163 CONSTRUCT_TYPED_SFIELD(sfExchangeRate, "ExchangeRate", UINT64, 6);
+
164 CONSTRUCT_TYPED_SFIELD(sfLowNode, "LowNode", UINT64, 7);
+
165 CONSTRUCT_TYPED_SFIELD(sfHighNode, "HighNode", UINT64, 8);
+
166 CONSTRUCT_TYPED_SFIELD(sfDestinationNode, "DestinationNode", UINT64, 9);
+
167 CONSTRUCT_TYPED_SFIELD(sfCookie, "Cookie", UINT64, 10);
+
168 CONSTRUCT_TYPED_SFIELD(sfServerVersion, "ServerVersion", UINT64, 11);
+
169 CONSTRUCT_TYPED_SFIELD(sfNFTokenOfferNode, "NFTokenOfferNode", UINT64, 12);
+
170 CONSTRUCT_TYPED_SFIELD(sfEmitBurden, "EmitBurden", UINT64, 13);
+
171 
+
172 // 64-bit integers (uncommon)
+
173 CONSTRUCT_TYPED_SFIELD(sfHookOn, "HookOn", UINT64, 16);
+
174 CONSTRUCT_TYPED_SFIELD(sfHookInstructionCount, "HookInstructionCount", UINT64, 17);
+
175 CONSTRUCT_TYPED_SFIELD(sfHookReturnCode, "HookReturnCode", UINT64, 18);
+
176 CONSTRUCT_TYPED_SFIELD(sfReferenceCount, "ReferenceCount", UINT64, 19);
177 
-
178 // 160-bit (common)
-
179 CONSTRUCT_TYPED_SFIELD(sfTakerPaysCurrency, "TakerPaysCurrency", UINT160, 1);
-
180 CONSTRUCT_TYPED_SFIELD(sfTakerPaysIssuer, "TakerPaysIssuer", UINT160, 2);
-
181 CONSTRUCT_TYPED_SFIELD(sfTakerGetsCurrency, "TakerGetsCurrency", UINT160, 3);
-
182 CONSTRUCT_TYPED_SFIELD(sfTakerGetsIssuer, "TakerGetsIssuer", UINT160, 4);
-
183 
-
184 // 256-bit (common)
-
185 CONSTRUCT_TYPED_SFIELD(sfLedgerHash, "LedgerHash", UINT256, 1);
-
186 CONSTRUCT_TYPED_SFIELD(sfParentHash, "ParentHash", UINT256, 2);
-
187 CONSTRUCT_TYPED_SFIELD(sfTransactionHash, "TransactionHash", UINT256, 3);
-
188 CONSTRUCT_TYPED_SFIELD(sfAccountHash, "AccountHash", UINT256, 4);
-
189 CONSTRUCT_TYPED_SFIELD(sfPreviousTxnID, "PreviousTxnID", UINT256, 5, SField::sMD_DeleteFinal);
-
190 CONSTRUCT_TYPED_SFIELD(sfLedgerIndex, "LedgerIndex", UINT256, 6);
-
191 CONSTRUCT_TYPED_SFIELD(sfWalletLocator, "WalletLocator", UINT256, 7);
-
192 CONSTRUCT_TYPED_SFIELD(sfRootIndex, "RootIndex", UINT256, 8, SField::sMD_Always);
-
193 CONSTRUCT_TYPED_SFIELD(sfAccountTxnID, "AccountTxnID", UINT256, 9);
-
194 CONSTRUCT_TYPED_SFIELD(sfNFTokenID, "NFTokenID", UINT256, 10);
-
195 CONSTRUCT_TYPED_SFIELD(sfEmitParentTxnID, "EmitParentTxnID", UINT256, 11);
-
196 CONSTRUCT_TYPED_SFIELD(sfEmitNonce, "EmitNonce", UINT256, 12);
-
197 CONSTRUCT_TYPED_SFIELD(sfEmitHookHash, "EmitHookHash", UINT256, 13);
-
198 
-
199 // 256-bit (uncommon)
-
200 CONSTRUCT_TYPED_SFIELD(sfBookDirectory, "BookDirectory", UINT256, 16);
-
201 CONSTRUCT_TYPED_SFIELD(sfInvoiceID, "InvoiceID", UINT256, 17);
-
202 CONSTRUCT_TYPED_SFIELD(sfNickname, "Nickname", UINT256, 18);
-
203 CONSTRUCT_TYPED_SFIELD(sfAmendment, "Amendment", UINT256, 19);
-
204 // 20 is currently unused
-
205 CONSTRUCT_TYPED_SFIELD(sfDigest, "Digest", UINT256, 21);
-
206 CONSTRUCT_TYPED_SFIELD(sfChannel, "Channel", UINT256, 22);
-
207 CONSTRUCT_TYPED_SFIELD(sfConsensusHash, "ConsensusHash", UINT256, 23);
-
208 CONSTRUCT_TYPED_SFIELD(sfCheckID, "CheckID", UINT256, 24);
-
209 CONSTRUCT_TYPED_SFIELD(sfValidatedHash, "ValidatedHash", UINT256, 25);
-
210 CONSTRUCT_TYPED_SFIELD(sfPreviousPageMin, "PreviousPageMin", UINT256, 26);
-
211 CONSTRUCT_TYPED_SFIELD(sfNextPageMin, "NextPageMin", UINT256, 27);
-
212 CONSTRUCT_TYPED_SFIELD(sfNFTokenBuyOffer, "NFTokenBuyOffer", UINT256, 28);
-
213 CONSTRUCT_TYPED_SFIELD(sfNFTokenSellOffer, "NFTokenSellOffer", UINT256, 29);
-
214 CONSTRUCT_TYPED_SFIELD(sfHookStateKey, "HookStateKey", UINT256, 30);
-
215 CONSTRUCT_TYPED_SFIELD(sfHookHash, "HookHash", UINT256, 31);
-
216 CONSTRUCT_TYPED_SFIELD(sfHookNamespace, "HookNamespace", UINT256, 32);
-
217 CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256, 33);
-
218 
-
219 // currency amount (common)
-
220 CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
-
221 CONSTRUCT_TYPED_SFIELD(sfBalance, "Balance", AMOUNT, 2);
-
222 CONSTRUCT_TYPED_SFIELD(sfLimitAmount, "LimitAmount", AMOUNT, 3);
-
223 CONSTRUCT_TYPED_SFIELD(sfTakerPays, "TakerPays", AMOUNT, 4);
-
224 CONSTRUCT_TYPED_SFIELD(sfTakerGets, "TakerGets", AMOUNT, 5);
-
225 CONSTRUCT_TYPED_SFIELD(sfLowLimit, "LowLimit", AMOUNT, 6);
-
226 CONSTRUCT_TYPED_SFIELD(sfHighLimit, "HighLimit", AMOUNT, 7);
-
227 CONSTRUCT_TYPED_SFIELD(sfFee, "Fee", AMOUNT, 8);
-
228 CONSTRUCT_TYPED_SFIELD(sfSendMax, "SendMax", AMOUNT, 9);
-
229 CONSTRUCT_TYPED_SFIELD(sfDeliverMin, "DeliverMin", AMOUNT, 10);
-
230 
-
231 // currency amount (uncommon)
-
232 CONSTRUCT_TYPED_SFIELD(sfMinimumOffer, "MinimumOffer", AMOUNT, 16);
-
233 CONSTRUCT_TYPED_SFIELD(sfRippleEscrow, "RippleEscrow", AMOUNT, 17);
-
234 CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18);
-
235 CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
-
236 
-
237 // Reserve 20 & 21 for Hooks
-
238 
-
239 // currency amount (fees)
-
240 CONSTRUCT_TYPED_SFIELD(sfBaseFeeDrops, "BaseFeeDrops", AMOUNT, 22);
-
241 CONSTRUCT_TYPED_SFIELD(sfReserveBaseDrops, "ReserveBaseDrops", AMOUNT, 23);
-
242 CONSTRUCT_TYPED_SFIELD(sfReserveIncrementDrops, "ReserveIncrementDrops", AMOUNT, 24);
-
243 
-
244 // variable length (common)
-
245 CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);
-
246 CONSTRUCT_TYPED_SFIELD(sfMessageKey, "MessageKey", VL, 2);
-
247 CONSTRUCT_TYPED_SFIELD(sfSigningPubKey, "SigningPubKey", VL, 3);
-
248 CONSTRUCT_TYPED_SFIELD(sfTxnSignature, "TxnSignature", VL, 4, SField::sMD_Default, SField::notSigning);
-
249 CONSTRUCT_TYPED_SFIELD(sfURI, "URI", VL, 5);
-
250 CONSTRUCT_TYPED_SFIELD(sfSignature, "Signature", VL, 6, SField::sMD_Default, SField::notSigning);
-
251 CONSTRUCT_TYPED_SFIELD(sfDomain, "Domain", VL, 7);
-
252 CONSTRUCT_TYPED_SFIELD(sfFundCode, "FundCode", VL, 8);
-
253 CONSTRUCT_TYPED_SFIELD(sfRemoveCode, "RemoveCode", VL, 9);
-
254 CONSTRUCT_TYPED_SFIELD(sfExpireCode, "ExpireCode", VL, 10);
-
255 CONSTRUCT_TYPED_SFIELD(sfCreateCode, "CreateCode", VL, 11);
-
256 CONSTRUCT_TYPED_SFIELD(sfMemoType, "MemoType", VL, 12);
-
257 CONSTRUCT_TYPED_SFIELD(sfMemoData, "MemoData", VL, 13);
-
258 CONSTRUCT_TYPED_SFIELD(sfMemoFormat, "MemoFormat", VL, 14);
-
259 
-
260 // variable length (uncommon)
-
261 CONSTRUCT_TYPED_SFIELD(sfFulfillment, "Fulfillment", VL, 16);
-
262 CONSTRUCT_TYPED_SFIELD(sfCondition, "Condition", VL, 17);
-
263 CONSTRUCT_TYPED_SFIELD(sfMasterSignature, "MasterSignature", VL, 18, SField::sMD_Default, SField::notSigning);
-
264 CONSTRUCT_TYPED_SFIELD(sfUNLModifyValidator, "UNLModifyValidator", VL, 19);
-
265 CONSTRUCT_TYPED_SFIELD(sfValidatorToDisable, "ValidatorToDisable", VL, 20);
-
266 CONSTRUCT_TYPED_SFIELD(sfValidatorToReEnable, "ValidatorToReEnable", VL, 21);
-
267 CONSTRUCT_TYPED_SFIELD(sfHookStateData, "HookStateData", VL, 22);
-
268 CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL, 23);
-
269 CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24);
-
270 CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25);
-
271 
-
272 // account
-
273 CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1);
-
274 CONSTRUCT_TYPED_SFIELD(sfOwner, "Owner", ACCOUNT, 2);
-
275 CONSTRUCT_TYPED_SFIELD(sfDestination, "Destination", ACCOUNT, 3);
-
276 CONSTRUCT_TYPED_SFIELD(sfIssuer, "Issuer", ACCOUNT, 4);
-
277 CONSTRUCT_TYPED_SFIELD(sfAuthorize, "Authorize", ACCOUNT, 5);
-
278 CONSTRUCT_TYPED_SFIELD(sfUnauthorize, "Unauthorize", ACCOUNT, 6);
-
279 // 7 is currently unused
-
280 CONSTRUCT_TYPED_SFIELD(sfRegularKey, "RegularKey", ACCOUNT, 8);
-
281 CONSTRUCT_TYPED_SFIELD(sfNFTokenMinter, "NFTokenMinter", ACCOUNT, 9);
-
282 CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT, 10);
-
283 
-
284 // account (uncommon)
-
285 CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
+
178 // 128-bit
+
179 CONSTRUCT_TYPED_SFIELD(sfEmailHash, "EmailHash", UINT128, 1);
+
180 
+
181 // 160-bit (common)
+
182 CONSTRUCT_TYPED_SFIELD(sfTakerPaysCurrency, "TakerPaysCurrency", UINT160, 1);
+
183 CONSTRUCT_TYPED_SFIELD(sfTakerPaysIssuer, "TakerPaysIssuer", UINT160, 2);
+
184 CONSTRUCT_TYPED_SFIELD(sfTakerGetsCurrency, "TakerGetsCurrency", UINT160, 3);
+
185 CONSTRUCT_TYPED_SFIELD(sfTakerGetsIssuer, "TakerGetsIssuer", UINT160, 4);
+
186 
+
187 // 256-bit (common)
+
188 CONSTRUCT_TYPED_SFIELD(sfLedgerHash, "LedgerHash", UINT256, 1);
+
189 CONSTRUCT_TYPED_SFIELD(sfParentHash, "ParentHash", UINT256, 2);
+
190 CONSTRUCT_TYPED_SFIELD(sfTransactionHash, "TransactionHash", UINT256, 3);
+
191 CONSTRUCT_TYPED_SFIELD(sfAccountHash, "AccountHash", UINT256, 4);
+
192 CONSTRUCT_TYPED_SFIELD(sfPreviousTxnID, "PreviousTxnID", UINT256, 5, SField::sMD_DeleteFinal);
+
193 CONSTRUCT_TYPED_SFIELD(sfLedgerIndex, "LedgerIndex", UINT256, 6);
+
194 CONSTRUCT_TYPED_SFIELD(sfWalletLocator, "WalletLocator", UINT256, 7);
+
195 CONSTRUCT_TYPED_SFIELD(sfRootIndex, "RootIndex", UINT256, 8, SField::sMD_Always);
+
196 CONSTRUCT_TYPED_SFIELD(sfAccountTxnID, "AccountTxnID", UINT256, 9);
+
197 CONSTRUCT_TYPED_SFIELD(sfNFTokenID, "NFTokenID", UINT256, 10);
+
198 CONSTRUCT_TYPED_SFIELD(sfEmitParentTxnID, "EmitParentTxnID", UINT256, 11);
+
199 CONSTRUCT_TYPED_SFIELD(sfEmitNonce, "EmitNonce", UINT256, 12);
+
200 CONSTRUCT_TYPED_SFIELD(sfEmitHookHash, "EmitHookHash", UINT256, 13);
+
201 
+
202 // 256-bit (uncommon)
+
203 CONSTRUCT_TYPED_SFIELD(sfBookDirectory, "BookDirectory", UINT256, 16);
+
204 CONSTRUCT_TYPED_SFIELD(sfInvoiceID, "InvoiceID", UINT256, 17);
+
205 CONSTRUCT_TYPED_SFIELD(sfNickname, "Nickname", UINT256, 18);
+
206 CONSTRUCT_TYPED_SFIELD(sfAmendment, "Amendment", UINT256, 19);
+
207 // 20 is currently unused
+
208 CONSTRUCT_TYPED_SFIELD(sfDigest, "Digest", UINT256, 21);
+
209 CONSTRUCT_TYPED_SFIELD(sfChannel, "Channel", UINT256, 22);
+
210 CONSTRUCT_TYPED_SFIELD(sfConsensusHash, "ConsensusHash", UINT256, 23);
+
211 CONSTRUCT_TYPED_SFIELD(sfCheckID, "CheckID", UINT256, 24);
+
212 CONSTRUCT_TYPED_SFIELD(sfValidatedHash, "ValidatedHash", UINT256, 25);
+
213 CONSTRUCT_TYPED_SFIELD(sfPreviousPageMin, "PreviousPageMin", UINT256, 26);
+
214 CONSTRUCT_TYPED_SFIELD(sfNextPageMin, "NextPageMin", UINT256, 27);
+
215 CONSTRUCT_TYPED_SFIELD(sfNFTokenBuyOffer, "NFTokenBuyOffer", UINT256, 28);
+
216 CONSTRUCT_TYPED_SFIELD(sfNFTokenSellOffer, "NFTokenSellOffer", UINT256, 29);
+
217 CONSTRUCT_TYPED_SFIELD(sfHookStateKey, "HookStateKey", UINT256, 30);
+
218 CONSTRUCT_TYPED_SFIELD(sfHookHash, "HookHash", UINT256, 31);
+
219 CONSTRUCT_TYPED_SFIELD(sfHookNamespace, "HookNamespace", UINT256, 32);
+
220 CONSTRUCT_TYPED_SFIELD(sfHookSetTxnID, "HookSetTxnID", UINT256, 33);
+
221 
+
222 // currency amount (common)
+
223 CONSTRUCT_TYPED_SFIELD(sfAmount, "Amount", AMOUNT, 1);
+
224 CONSTRUCT_TYPED_SFIELD(sfBalance, "Balance", AMOUNT, 2);
+
225 CONSTRUCT_TYPED_SFIELD(sfLimitAmount, "LimitAmount", AMOUNT, 3);
+
226 CONSTRUCT_TYPED_SFIELD(sfTakerPays, "TakerPays", AMOUNT, 4);
+
227 CONSTRUCT_TYPED_SFIELD(sfTakerGets, "TakerGets", AMOUNT, 5);
+
228 CONSTRUCT_TYPED_SFIELD(sfLowLimit, "LowLimit", AMOUNT, 6);
+
229 CONSTRUCT_TYPED_SFIELD(sfHighLimit, "HighLimit", AMOUNT, 7);
+
230 CONSTRUCT_TYPED_SFIELD(sfFee, "Fee", AMOUNT, 8);
+
231 CONSTRUCT_TYPED_SFIELD(sfSendMax, "SendMax", AMOUNT, 9);
+
232 CONSTRUCT_TYPED_SFIELD(sfDeliverMin, "DeliverMin", AMOUNT, 10);
+
233 
+
234 // currency amount (uncommon)
+
235 CONSTRUCT_TYPED_SFIELD(sfMinimumOffer, "MinimumOffer", AMOUNT, 16);
+
236 CONSTRUCT_TYPED_SFIELD(sfRippleEscrow, "RippleEscrow", AMOUNT, 17);
+
237 CONSTRUCT_TYPED_SFIELD(sfDeliveredAmount, "DeliveredAmount", AMOUNT, 18);
+
238 CONSTRUCT_TYPED_SFIELD(sfNFTokenBrokerFee, "NFTokenBrokerFee", AMOUNT, 19);
+
239 
+
240 // Reserve 20 & 21 for Hooks
+
241 
+
242 // currency amount (fees)
+
243 CONSTRUCT_TYPED_SFIELD(sfBaseFeeDrops, "BaseFeeDrops", AMOUNT, 22);
+
244 CONSTRUCT_TYPED_SFIELD(sfReserveBaseDrops, "ReserveBaseDrops", AMOUNT, 23);
+
245 CONSTRUCT_TYPED_SFIELD(sfReserveIncrementDrops, "ReserveIncrementDrops", AMOUNT, 24);
+
246 
+
247 // variable length (common)
+
248 CONSTRUCT_TYPED_SFIELD(sfPublicKey, "PublicKey", VL, 1);
+
249 CONSTRUCT_TYPED_SFIELD(sfMessageKey, "MessageKey", VL, 2);
+
250 CONSTRUCT_TYPED_SFIELD(sfSigningPubKey, "SigningPubKey", VL, 3);
+
251 CONSTRUCT_TYPED_SFIELD(sfTxnSignature, "TxnSignature", VL, 4, SField::sMD_Default, SField::notSigning);
+
252 CONSTRUCT_TYPED_SFIELD(sfURI, "URI", VL, 5);
+
253 CONSTRUCT_TYPED_SFIELD(sfSignature, "Signature", VL, 6, SField::sMD_Default, SField::notSigning);
+
254 CONSTRUCT_TYPED_SFIELD(sfDomain, "Domain", VL, 7);
+
255 CONSTRUCT_TYPED_SFIELD(sfFundCode, "FundCode", VL, 8);
+
256 CONSTRUCT_TYPED_SFIELD(sfRemoveCode, "RemoveCode", VL, 9);
+
257 CONSTRUCT_TYPED_SFIELD(sfExpireCode, "ExpireCode", VL, 10);
+
258 CONSTRUCT_TYPED_SFIELD(sfCreateCode, "CreateCode", VL, 11);
+
259 CONSTRUCT_TYPED_SFIELD(sfMemoType, "MemoType", VL, 12);
+
260 CONSTRUCT_TYPED_SFIELD(sfMemoData, "MemoData", VL, 13);
+
261 CONSTRUCT_TYPED_SFIELD(sfMemoFormat, "MemoFormat", VL, 14);
+
262 
+
263 // variable length (uncommon)
+
264 CONSTRUCT_TYPED_SFIELD(sfFulfillment, "Fulfillment", VL, 16);
+
265 CONSTRUCT_TYPED_SFIELD(sfCondition, "Condition", VL, 17);
+
266 CONSTRUCT_TYPED_SFIELD(sfMasterSignature, "MasterSignature", VL, 18, SField::sMD_Default, SField::notSigning);
+
267 CONSTRUCT_TYPED_SFIELD(sfUNLModifyValidator, "UNLModifyValidator", VL, 19);
+
268 CONSTRUCT_TYPED_SFIELD(sfValidatorToDisable, "ValidatorToDisable", VL, 20);
+
269 CONSTRUCT_TYPED_SFIELD(sfValidatorToReEnable, "ValidatorToReEnable", VL, 21);
+
270 CONSTRUCT_TYPED_SFIELD(sfHookStateData, "HookStateData", VL, 22);
+
271 CONSTRUCT_TYPED_SFIELD(sfHookReturnString, "HookReturnString", VL, 23);
+
272 CONSTRUCT_TYPED_SFIELD(sfHookParameterName, "HookParameterName", VL, 24);
+
273 CONSTRUCT_TYPED_SFIELD(sfHookParameterValue, "HookParameterValue", VL, 25);
+
274 
+
275 // account
+
276 CONSTRUCT_TYPED_SFIELD(sfAccount, "Account", ACCOUNT, 1);
+
277 CONSTRUCT_TYPED_SFIELD(sfOwner, "Owner", ACCOUNT, 2);
+
278 CONSTRUCT_TYPED_SFIELD(sfDestination, "Destination", ACCOUNT, 3);
+
279 CONSTRUCT_TYPED_SFIELD(sfIssuer, "Issuer", ACCOUNT, 4);
+
280 CONSTRUCT_TYPED_SFIELD(sfAuthorize, "Authorize", ACCOUNT, 5);
+
281 CONSTRUCT_TYPED_SFIELD(sfUnauthorize, "Unauthorize", ACCOUNT, 6);
+
282 // 7 is currently unused
+
283 CONSTRUCT_TYPED_SFIELD(sfRegularKey, "RegularKey", ACCOUNT, 8);
+
284 CONSTRUCT_TYPED_SFIELD(sfNFTokenMinter, "NFTokenMinter", ACCOUNT, 9);
+
285 CONSTRUCT_TYPED_SFIELD(sfEmitCallback, "EmitCallback", ACCOUNT, 10);
286 
-
287 // vector of 256-bit
-
288 CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
-
289 CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR256, 2);
-
290 CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3);
-
291 CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4);
-
292 
-
293 // path set
-
294 CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1);
+
287 // account (uncommon)
+
288 CONSTRUCT_TYPED_SFIELD(sfHookAccount, "HookAccount", ACCOUNT, 16);
+
289 
+
290 // vector of 256-bit
+
291 CONSTRUCT_TYPED_SFIELD(sfIndexes, "Indexes", VECTOR256, 1, SField::sMD_Never);
+
292 CONSTRUCT_TYPED_SFIELD(sfHashes, "Hashes", VECTOR256, 2);
+
293 CONSTRUCT_TYPED_SFIELD(sfAmendments, "Amendments", VECTOR256, 3);
+
294 CONSTRUCT_TYPED_SFIELD(sfNFTokenOffers, "NFTokenOffers", VECTOR256, 4);
295 
-
296 // inner object
-
297 // OBJECT/1 is reserved for end of object
-
298 CONSTRUCT_UNTYPED_SFIELD(sfTransactionMetaData, "TransactionMetaData", OBJECT, 2);
-
299 CONSTRUCT_UNTYPED_SFIELD(sfCreatedNode, "CreatedNode", OBJECT, 3);
-
300 CONSTRUCT_UNTYPED_SFIELD(sfDeletedNode, "DeletedNode", OBJECT, 4);
-
301 CONSTRUCT_UNTYPED_SFIELD(sfModifiedNode, "ModifiedNode", OBJECT, 5);
-
302 CONSTRUCT_UNTYPED_SFIELD(sfPreviousFields, "PreviousFields", OBJECT, 6);
-
303 CONSTRUCT_UNTYPED_SFIELD(sfFinalFields, "FinalFields", OBJECT, 7);
-
304 CONSTRUCT_UNTYPED_SFIELD(sfNewFields, "NewFields", OBJECT, 8);
-
305 CONSTRUCT_UNTYPED_SFIELD(sfTemplateEntry, "TemplateEntry", OBJECT, 9);
-
306 CONSTRUCT_UNTYPED_SFIELD(sfMemo, "Memo", OBJECT, 10);
-
307 CONSTRUCT_UNTYPED_SFIELD(sfSignerEntry, "SignerEntry", OBJECT, 11);
-
308 CONSTRUCT_UNTYPED_SFIELD(sfNFToken, "NFToken", OBJECT, 12);
-
309 CONSTRUCT_UNTYPED_SFIELD(sfEmitDetails, "EmitDetails", OBJECT, 13);
-
310 CONSTRUCT_UNTYPED_SFIELD(sfHook, "Hook", OBJECT, 14);
-
311 
-
312 // inner object (uncommon)
-
313 CONSTRUCT_UNTYPED_SFIELD(sfSigner, "Signer", OBJECT, 16);
-
314 // 17 has not been used yet
-
315 CONSTRUCT_UNTYPED_SFIELD(sfMajority, "Majority", OBJECT, 18);
-
316 CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidator, "DisabledValidator", OBJECT, 19);
-
317 CONSTRUCT_UNTYPED_SFIELD(sfEmittedTxn, "EmittedTxn", OBJECT, 20);
-
318 CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT, 21);
-
319 CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22);
-
320 CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23);
-
321 CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
-
322 
-
323 // array of objects
-
324 // ARRAY/1 is reserved for end of array
-
325 // 2 has never been used
-
326 CONSTRUCT_UNTYPED_SFIELD(sfSigners, "Signers", ARRAY, 3, SField::sMD_Default, SField::notSigning);
-
327 CONSTRUCT_UNTYPED_SFIELD(sfSignerEntries, "SignerEntries", ARRAY, 4);
-
328 CONSTRUCT_UNTYPED_SFIELD(sfTemplate, "Template", ARRAY, 5);
-
329 CONSTRUCT_UNTYPED_SFIELD(sfNecessary, "Necessary", ARRAY, 6);
-
330 CONSTRUCT_UNTYPED_SFIELD(sfSufficient, "Sufficient", ARRAY, 7);
-
331 CONSTRUCT_UNTYPED_SFIELD(sfAffectedNodes, "AffectedNodes", ARRAY, 8);
-
332 CONSTRUCT_UNTYPED_SFIELD(sfMemos, "Memos", ARRAY, 9);
-
333 CONSTRUCT_UNTYPED_SFIELD(sfNFTokens, "NFTokens", ARRAY, 10);
-
334 CONSTRUCT_UNTYPED_SFIELD(sfHooks, "Hooks", ARRAY, 11);
-
335 
-
336 // array of objects (uncommon)
-
337 CONSTRUCT_UNTYPED_SFIELD(sfMajorities, "Majorities", ARRAY, 16);
-
338 CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, 17);
-
339 CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
-
340 CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
-
341 CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
-
342 
-
343 // clang-format on
-
344 
-
345 #undef CONSTRUCT_TYPED_SFIELD
-
346 #undef CONSTRUCT_UNTYPED_SFIELD
+
296 // path set
+
297 CONSTRUCT_UNTYPED_SFIELD(sfPaths, "Paths", PATHSET, 1);
+
298 
+
299 // inner object
+
300 // OBJECT/1 is reserved for end of object
+
301 CONSTRUCT_UNTYPED_SFIELD(sfTransactionMetaData, "TransactionMetaData", OBJECT, 2);
+
302 CONSTRUCT_UNTYPED_SFIELD(sfCreatedNode, "CreatedNode", OBJECT, 3);
+
303 CONSTRUCT_UNTYPED_SFIELD(sfDeletedNode, "DeletedNode", OBJECT, 4);
+
304 CONSTRUCT_UNTYPED_SFIELD(sfModifiedNode, "ModifiedNode", OBJECT, 5);
+
305 CONSTRUCT_UNTYPED_SFIELD(sfPreviousFields, "PreviousFields", OBJECT, 6);
+
306 CONSTRUCT_UNTYPED_SFIELD(sfFinalFields, "FinalFields", OBJECT, 7);
+
307 CONSTRUCT_UNTYPED_SFIELD(sfNewFields, "NewFields", OBJECT, 8);
+
308 CONSTRUCT_UNTYPED_SFIELD(sfTemplateEntry, "TemplateEntry", OBJECT, 9);
+
309 CONSTRUCT_UNTYPED_SFIELD(sfMemo, "Memo", OBJECT, 10);
+
310 CONSTRUCT_UNTYPED_SFIELD(sfSignerEntry, "SignerEntry", OBJECT, 11);
+
311 CONSTRUCT_UNTYPED_SFIELD(sfNFToken, "NFToken", OBJECT, 12);
+
312 CONSTRUCT_UNTYPED_SFIELD(sfEmitDetails, "EmitDetails", OBJECT, 13);
+
313 CONSTRUCT_UNTYPED_SFIELD(sfHook, "Hook", OBJECT, 14);
+
314 
+
315 // inner object (uncommon)
+
316 CONSTRUCT_UNTYPED_SFIELD(sfSigner, "Signer", OBJECT, 16);
+
317 // 17 has not been used yet
+
318 CONSTRUCT_UNTYPED_SFIELD(sfMajority, "Majority", OBJECT, 18);
+
319 CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidator, "DisabledValidator", OBJECT, 19);
+
320 CONSTRUCT_UNTYPED_SFIELD(sfEmittedTxn, "EmittedTxn", OBJECT, 20);
+
321 CONSTRUCT_UNTYPED_SFIELD(sfHookExecution, "HookExecution", OBJECT, 21);
+
322 CONSTRUCT_UNTYPED_SFIELD(sfHookDefinition, "HookDefinition", OBJECT, 22);
+
323 CONSTRUCT_UNTYPED_SFIELD(sfHookParameter, "HookParameter", OBJECT, 23);
+
324 CONSTRUCT_UNTYPED_SFIELD(sfHookGrant, "HookGrant", OBJECT, 24);
+
325 
+
326 // array of objects
+
327 // ARRAY/1 is reserved for end of array
+
328 // 2 has never been used
+
329 CONSTRUCT_UNTYPED_SFIELD(sfSigners, "Signers", ARRAY, 3, SField::sMD_Default, SField::notSigning);
+
330 CONSTRUCT_UNTYPED_SFIELD(sfSignerEntries, "SignerEntries", ARRAY, 4);
+
331 CONSTRUCT_UNTYPED_SFIELD(sfTemplate, "Template", ARRAY, 5);
+
332 CONSTRUCT_UNTYPED_SFIELD(sfNecessary, "Necessary", ARRAY, 6);
+
333 CONSTRUCT_UNTYPED_SFIELD(sfSufficient, "Sufficient", ARRAY, 7);
+
334 CONSTRUCT_UNTYPED_SFIELD(sfAffectedNodes, "AffectedNodes", ARRAY, 8);
+
335 CONSTRUCT_UNTYPED_SFIELD(sfMemos, "Memos", ARRAY, 9);
+
336 CONSTRUCT_UNTYPED_SFIELD(sfNFTokens, "NFTokens", ARRAY, 10);
+
337 CONSTRUCT_UNTYPED_SFIELD(sfHooks, "Hooks", ARRAY, 11);
+
338 
+
339 // array of objects (uncommon)
+
340 CONSTRUCT_UNTYPED_SFIELD(sfMajorities, "Majorities", ARRAY, 16);
+
341 CONSTRUCT_UNTYPED_SFIELD(sfDisabledValidators, "DisabledValidators", ARRAY, 17);
+
342 CONSTRUCT_UNTYPED_SFIELD(sfHookExecutions, "HookExecutions", ARRAY, 18);
+
343 CONSTRUCT_UNTYPED_SFIELD(sfHookParameters, "HookParameters", ARRAY, 19);
+
344 CONSTRUCT_UNTYPED_SFIELD(sfHookGrants, "HookGrants", ARRAY, 20);
+
345 
+
346 // clang-format on
347 
-
348 #pragma pop_macro("CONSTRUCT_TYPED_SFIELD")
-
349 #pragma pop_macro("CONSTRUCT_UNTYPED_SFIELD")
+
348 #undef CONSTRUCT_TYPED_SFIELD
+
349 #undef CONSTRUCT_UNTYPED_SFIELD
350 
-
351 SField::SField(
-
352  private_access_tag_t,
-
353  SerializedTypeID tid,
-
354  int fv,
-
355  const char* fn,
-
356  int meta,
-
357  IsSigning signing)
-
358  : fieldCode(field_code(tid, fv))
-
359  , fieldType(tid)
-
360  , fieldValue(fv)
-
361  , fieldName(fn)
-
362  , fieldMeta(meta)
-
363  , fieldNum(++num)
-
364  , signingField(signing)
-
365  , jsonName(fieldName.c_str())
-
366 {
-
367  knownCodeToField[fieldCode] = this;
-
368 }
-
369 
-
370 SField::SField(private_access_tag_t, int fc)
-
371  : fieldCode(fc)
-
372  , fieldType(STI_UNKNOWN)
-
373  , fieldValue(0)
-
374  , fieldMeta(sMD_Never)
-
375  , fieldNum(++num)
-
376  , signingField(IsSigning::yes)
-
377  , jsonName(fieldName.c_str())
-
378 {
-
379  knownCodeToField[fieldCode] = this;
-
380 }
-
381 
-
382 SField const&
-
383 SField::getField(int code)
-
384 {
-
385  auto it = knownCodeToField.find(code);
-
386 
-
387  if (it != knownCodeToField.end())
-
388  {
-
389  return *(it->second);
-
390  }
-
391  return sfInvalid;
-
392 }
-
393 
-
394 int
-
395 SField::compare(SField const& f1, SField const& f2)
-
396 {
-
397  // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2
-
398  if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0))
-
399  return 0;
-
400 
-
401  if (f1.fieldCode < f2.fieldCode)
-
402  return -1;
+
351 #pragma pop_macro("CONSTRUCT_TYPED_SFIELD")
+
352 #pragma pop_macro("CONSTRUCT_UNTYPED_SFIELD")
+
353 
+
354 SField::SField(
+
355  private_access_tag_t,
+
356  SerializedTypeID tid,
+
357  int fv,
+
358  const char* fn,
+
359  int meta,
+
360  IsSigning signing)
+
361  : fieldCode(field_code(tid, fv))
+
362  , fieldType(tid)
+
363  , fieldValue(fv)
+
364  , fieldName(fn)
+
365  , fieldMeta(meta)
+
366  , fieldNum(++num)
+
367  , signingField(signing)
+
368  , jsonName(fieldName.c_str())
+
369 {
+
370  knownCodeToField[fieldCode] = this;
+
371 }
+
372 
+
373 SField::SField(private_access_tag_t, int fc)
+
374  : fieldCode(fc)
+
375  , fieldType(STI_UNKNOWN)
+
376  , fieldValue(0)
+
377  , fieldMeta(sMD_Never)
+
378  , fieldNum(++num)
+
379  , signingField(IsSigning::yes)
+
380  , jsonName(fieldName.c_str())
+
381 {
+
382  knownCodeToField[fieldCode] = this;
+
383 }
+
384 
+
385 SField const&
+
386 SField::getField(int code)
+
387 {
+
388  auto it = knownCodeToField.find(code);
+
389 
+
390  if (it != knownCodeToField.end())
+
391  {
+
392  return *(it->second);
+
393  }
+
394  return sfInvalid;
+
395 }
+
396 
+
397 int
+
398 SField::compare(SField const& f1, SField const& f2)
+
399 {
+
400  // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2
+
401  if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0))
+
402  return 0;
403 
-
404  if (f2.fieldCode < f1.fieldCode)
-
405  return 1;
+
404  if (f1.fieldCode < f2.fieldCode)
+
405  return -1;
406 
-
407  return 0;
-
408 }
+
407  if (f2.fieldCode < f1.fieldCode)
+
408  return 1;
409 
-
410 SField const&
-
411 SField::getField(std::string const& fieldName)
-
412 {
-
413  for (auto const& [_, f] : knownCodeToField)
-
414  {
-
415  (void)_;
-
416  if (f->fieldName == fieldName)
-
417  return *f;
-
418  }
-
419  return sfInvalid;
-
420 }
-
421 
-
422 } // namespace ripple
+
410  return 0;
+
411 }
+
412 
+
413 SField const&
+
414 SField::getField(std::string const& fieldName)
+
415 {
+
416  for (auto const& [_, f] : knownCodeToField)
+
417  {
+
418  (void)_;
+
419  if (f->fieldName == fieldName)
+
420  return *f;
+
421  }
+
422  return sfInvalid;
+
423 }
+
424 
+
425 } // namespace ripple
const SF_ACCOUNT sfHookAccount
const SF_UINT64 sfIndexNext
@@ -500,6 +503,7 @@ $(function() {
const SF_UINT32 sfPreviousTxnLgrSeq
const SF_UINT256 sfHookHash
const SF_UINT32 sfOwnerCount
+
const SF_UINT32 sfFirstNFTokenSequence
const SF_UINT16 sfSignerWeight
const SF_UINT16 sfHookApiVersion
const SField sfHooks
@@ -560,7 +564,7 @@ $(function() {
const SF_UINT32 sfCloseTime
const SF_UINT32 sfTicketSequence
const SF_UINT32 sfParentCloseTime
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
static std::map< int, SField const * > knownCodeToField
Definition: SField.h:266
const SF_UINT32 sfHookStateCount
const SF_UINT160 sfTakerGetsCurrency
@@ -688,7 +692,7 @@ $(function() {
const SF_VL sfSignature
const SF_AMOUNT sfBalance
const SField sfHook
-
static int compare(const SField &f1, const SField &f2)
Definition: SField.cpp:395
+
static int compare(const SField &f1, const SField &f2)
Definition: SField.cpp:398
const SF_UINT32 sfReferenceFeeUnits
const SF_UINT256 sfNextPageMin
const SF_UINT32 sfCancelAfter
diff --git a/SField_8h_source.html b/SField_8h_source.html index 81b90b1ffe..e53017db0e 100644 --- a/SField_8h_source.html +++ b/SField_8h_source.html @@ -459,195 +459,196 @@ $(function() {
400 extern SF_UINT32 const sfBurnedNFTokens;
401 extern SF_UINT32 const sfHookStateCount;
402 extern SF_UINT32 const sfEmitGeneration;
-
403 
-
404 // 64-bit integers (common)
-
405 extern SF_UINT64 const sfIndexNext;
-
406 extern SF_UINT64 const sfIndexPrevious;
-
407 extern SF_UINT64 const sfBookNode;
-
408 extern SF_UINT64 const sfOwnerNode;
-
409 extern SF_UINT64 const sfBaseFee;
-
410 extern SF_UINT64 const sfExchangeRate;
-
411 extern SF_UINT64 const sfLowNode;
-
412 extern SF_UINT64 const sfHighNode;
-
413 extern SF_UINT64 const sfDestinationNode;
-
414 extern SF_UINT64 const sfCookie;
-
415 extern SF_UINT64 const sfServerVersion;
-
416 extern SF_UINT64 const sfNFTokenOfferNode;
-
417 extern SF_UINT64 const sfEmitBurden;
-
418 
-
419 // 64-bit integers (uncommon)
-
420 extern SF_UINT64 const sfHookOn;
-
421 extern SF_UINT64 const sfHookInstructionCount;
-
422 extern SF_UINT64 const sfHookReturnCode;
-
423 extern SF_UINT64 const sfReferenceCount;
-
424 
-
425 // 128-bit
-
426 extern SF_UINT128 const sfEmailHash;
-
427 
-
428 // 160-bit (common)
-
429 extern SF_UINT160 const sfTakerPaysCurrency;
-
430 extern SF_UINT160 const sfTakerPaysIssuer;
-
431 extern SF_UINT160 const sfTakerGetsCurrency;
-
432 extern SF_UINT160 const sfTakerGetsIssuer;
-
433 
-
434 // 256-bit (common)
-
435 extern SF_UINT256 const sfLedgerHash;
-
436 extern SF_UINT256 const sfParentHash;
-
437 extern SF_UINT256 const sfTransactionHash;
-
438 extern SF_UINT256 const sfAccountHash;
-
439 extern SF_UINT256 const sfPreviousTxnID;
-
440 extern SF_UINT256 const sfLedgerIndex;
-
441 extern SF_UINT256 const sfWalletLocator;
-
442 extern SF_UINT256 const sfRootIndex;
-
443 extern SF_UINT256 const sfAccountTxnID;
-
444 extern SF_UINT256 const sfNFTokenID;
-
445 extern SF_UINT256 const sfEmitParentTxnID;
-
446 extern SF_UINT256 const sfEmitNonce;
-
447 extern SF_UINT256 const sfEmitHookHash;
-
448 
-
449 // 256-bit (uncommon)
-
450 extern SF_UINT256 const sfBookDirectory;
-
451 extern SF_UINT256 const sfInvoiceID;
-
452 extern SF_UINT256 const sfNickname;
-
453 extern SF_UINT256 const sfAmendment;
-
454 extern SF_UINT256 const sfDigest;
-
455 extern SF_UINT256 const sfChannel;
-
456 extern SF_UINT256 const sfConsensusHash;
-
457 extern SF_UINT256 const sfCheckID;
-
458 extern SF_UINT256 const sfValidatedHash;
-
459 extern SF_UINT256 const sfPreviousPageMin;
-
460 extern SF_UINT256 const sfNextPageMin;
-
461 extern SF_UINT256 const sfNFTokenBuyOffer;
-
462 extern SF_UINT256 const sfNFTokenSellOffer;
-
463 extern SF_UINT256 const sfHookStateKey;
-
464 extern SF_UINT256 const sfHookHash;
-
465 extern SF_UINT256 const sfHookNamespace;
-
466 extern SF_UINT256 const sfHookSetTxnID;
-
467 
-
468 // currency amount (common)
-
469 extern SF_AMOUNT const sfAmount;
-
470 extern SF_AMOUNT const sfBalance;
-
471 extern SF_AMOUNT const sfLimitAmount;
-
472 extern SF_AMOUNT const sfTakerPays;
-
473 extern SF_AMOUNT const sfTakerGets;
-
474 extern SF_AMOUNT const sfLowLimit;
-
475 extern SF_AMOUNT const sfHighLimit;
-
476 extern SF_AMOUNT const sfFee;
-
477 extern SF_AMOUNT const sfSendMax;
-
478 extern SF_AMOUNT const sfDeliverMin;
-
479 
-
480 // currency amount (uncommon)
-
481 extern SF_AMOUNT const sfMinimumOffer;
-
482 extern SF_AMOUNT const sfRippleEscrow;
-
483 extern SF_AMOUNT const sfDeliveredAmount;
-
484 extern SF_AMOUNT const sfNFTokenBrokerFee;
-
485 
-
486 // currency amount (fees)
-
487 extern SF_AMOUNT const sfBaseFeeDrops;
-
488 extern SF_AMOUNT const sfReserveBaseDrops;
-
489 extern SF_AMOUNT const sfReserveIncrementDrops;
-
490 
-
491 // variable length (common)
-
492 extern SF_VL const sfPublicKey;
-
493 extern SF_VL const sfMessageKey;
-
494 extern SF_VL const sfSigningPubKey;
-
495 extern SF_VL const sfTxnSignature;
-
496 extern SF_VL const sfURI;
-
497 extern SF_VL const sfSignature;
-
498 extern SF_VL const sfDomain;
-
499 extern SF_VL const sfFundCode;
-
500 extern SF_VL const sfRemoveCode;
-
501 extern SF_VL const sfExpireCode;
-
502 extern SF_VL const sfCreateCode;
-
503 extern SF_VL const sfMemoType;
-
504 extern SF_VL const sfMemoData;
-
505 extern SF_VL const sfMemoFormat;
-
506 
-
507 // variable length (uncommon)
-
508 extern SF_VL const sfFulfillment;
-
509 extern SF_VL const sfCondition;
-
510 extern SF_VL const sfMasterSignature;
-
511 extern SF_VL const sfUNLModifyValidator;
-
512 extern SF_VL const sfValidatorToDisable;
-
513 extern SF_VL const sfValidatorToReEnable;
-
514 extern SF_VL const sfHookStateData;
-
515 extern SF_VL const sfHookReturnString;
-
516 extern SF_VL const sfHookParameterName;
-
517 extern SF_VL const sfHookParameterValue;
-
518 
-
519 // account
-
520 extern SF_ACCOUNT const sfAccount;
-
521 extern SF_ACCOUNT const sfOwner;
-
522 extern SF_ACCOUNT const sfDestination;
-
523 extern SF_ACCOUNT const sfIssuer;
-
524 extern SF_ACCOUNT const sfAuthorize;
-
525 extern SF_ACCOUNT const sfUnauthorize;
-
526 extern SF_ACCOUNT const sfRegularKey;
-
527 extern SF_ACCOUNT const sfNFTokenMinter;
-
528 extern SF_ACCOUNT const sfEmitCallback;
-
529 
-
530 // account (uncommon)
-
531 extern SF_ACCOUNT const sfHookAccount;
-
532 
-
533 // path set
-
534 extern SField const sfPaths;
-
535 
-
536 // vector of 256-bit
-
537 extern SF_VECTOR256 const sfIndexes;
-
538 extern SF_VECTOR256 const sfHashes;
-
539 extern SF_VECTOR256 const sfAmendments;
-
540 extern SF_VECTOR256 const sfNFTokenOffers;
-
541 
-
542 // inner object
-
543 // OBJECT/1 is reserved for end of object
-
544 extern SField const sfTransactionMetaData;
-
545 extern SField const sfCreatedNode;
-
546 extern SField const sfDeletedNode;
-
547 extern SField const sfModifiedNode;
-
548 extern SField const sfPreviousFields;
-
549 extern SField const sfFinalFields;
-
550 extern SField const sfNewFields;
-
551 extern SField const sfTemplateEntry;
-
552 extern SField const sfMemo;
-
553 extern SField const sfSignerEntry;
-
554 extern SField const sfNFToken;
-
555 extern SField const sfEmitDetails;
-
556 extern SField const sfHook;
-
557 
-
558 extern SField const sfSigner;
-
559 extern SField const sfMajority;
-
560 extern SField const sfDisabledValidator;
-
561 extern SField const sfEmittedTxn;
-
562 extern SField const sfHookExecution;
-
563 extern SField const sfHookDefinition;
-
564 extern SField const sfHookParameter;
-
565 extern SField const sfHookGrant;
-
566 
-
567 // array of objects (common)
-
568 // ARRAY/1 is reserved for end of array
-
569 // extern SField const sfSigningAccounts; // Never been used.
-
570 extern SField const sfSigners;
-
571 extern SField const sfSignerEntries;
-
572 extern SField const sfTemplate;
-
573 extern SField const sfNecessary;
-
574 extern SField const sfSufficient;
-
575 extern SField const sfAffectedNodes;
-
576 extern SField const sfMemos;
-
577 extern SField const sfNFTokens;
-
578 extern SField const sfHooks;
-
579 
-
580 // array of objects (uncommon)
-
581 extern SField const sfMajorities;
-
582 extern SField const sfDisabledValidators;
-
583 extern SField const sfHookExecutions;
-
584 extern SField const sfHookParameters;
-
585 extern SField const sfHookGrants;
-
586 
-
587 //------------------------------------------------------------------------------
-
588 
-
589 } // namespace ripple
-
590 
-
591 #endif
+
403 extern SF_UINT32 const sfFirstNFTokenSequence;
+
404 
+
405 // 64-bit integers (common)
+
406 extern SF_UINT64 const sfIndexNext;
+
407 extern SF_UINT64 const sfIndexPrevious;
+
408 extern SF_UINT64 const sfBookNode;
+
409 extern SF_UINT64 const sfOwnerNode;
+
410 extern SF_UINT64 const sfBaseFee;
+
411 extern SF_UINT64 const sfExchangeRate;
+
412 extern SF_UINT64 const sfLowNode;
+
413 extern SF_UINT64 const sfHighNode;
+
414 extern SF_UINT64 const sfDestinationNode;
+
415 extern SF_UINT64 const sfCookie;
+
416 extern SF_UINT64 const sfServerVersion;
+
417 extern SF_UINT64 const sfNFTokenOfferNode;
+
418 extern SF_UINT64 const sfEmitBurden;
+
419 
+
420 // 64-bit integers (uncommon)
+
421 extern SF_UINT64 const sfHookOn;
+
422 extern SF_UINT64 const sfHookInstructionCount;
+
423 extern SF_UINT64 const sfHookReturnCode;
+
424 extern SF_UINT64 const sfReferenceCount;
+
425 
+
426 // 128-bit
+
427 extern SF_UINT128 const sfEmailHash;
+
428 
+
429 // 160-bit (common)
+
430 extern SF_UINT160 const sfTakerPaysCurrency;
+
431 extern SF_UINT160 const sfTakerPaysIssuer;
+
432 extern SF_UINT160 const sfTakerGetsCurrency;
+
433 extern SF_UINT160 const sfTakerGetsIssuer;
+
434 
+
435 // 256-bit (common)
+
436 extern SF_UINT256 const sfLedgerHash;
+
437 extern SF_UINT256 const sfParentHash;
+
438 extern SF_UINT256 const sfTransactionHash;
+
439 extern SF_UINT256 const sfAccountHash;
+
440 extern SF_UINT256 const sfPreviousTxnID;
+
441 extern SF_UINT256 const sfLedgerIndex;
+
442 extern SF_UINT256 const sfWalletLocator;
+
443 extern SF_UINT256 const sfRootIndex;
+
444 extern SF_UINT256 const sfAccountTxnID;
+
445 extern SF_UINT256 const sfNFTokenID;
+
446 extern SF_UINT256 const sfEmitParentTxnID;
+
447 extern SF_UINT256 const sfEmitNonce;
+
448 extern SF_UINT256 const sfEmitHookHash;
+
449 
+
450 // 256-bit (uncommon)
+
451 extern SF_UINT256 const sfBookDirectory;
+
452 extern SF_UINT256 const sfInvoiceID;
+
453 extern SF_UINT256 const sfNickname;
+
454 extern SF_UINT256 const sfAmendment;
+
455 extern SF_UINT256 const sfDigest;
+
456 extern SF_UINT256 const sfChannel;
+
457 extern SF_UINT256 const sfConsensusHash;
+
458 extern SF_UINT256 const sfCheckID;
+
459 extern SF_UINT256 const sfValidatedHash;
+
460 extern SF_UINT256 const sfPreviousPageMin;
+
461 extern SF_UINT256 const sfNextPageMin;
+
462 extern SF_UINT256 const sfNFTokenBuyOffer;
+
463 extern SF_UINT256 const sfNFTokenSellOffer;
+
464 extern SF_UINT256 const sfHookStateKey;
+
465 extern SF_UINT256 const sfHookHash;
+
466 extern SF_UINT256 const sfHookNamespace;
+
467 extern SF_UINT256 const sfHookSetTxnID;
+
468 
+
469 // currency amount (common)
+
470 extern SF_AMOUNT const sfAmount;
+
471 extern SF_AMOUNT const sfBalance;
+
472 extern SF_AMOUNT const sfLimitAmount;
+
473 extern SF_AMOUNT const sfTakerPays;
+
474 extern SF_AMOUNT const sfTakerGets;
+
475 extern SF_AMOUNT const sfLowLimit;
+
476 extern SF_AMOUNT const sfHighLimit;
+
477 extern SF_AMOUNT const sfFee;
+
478 extern SF_AMOUNT const sfSendMax;
+
479 extern SF_AMOUNT const sfDeliverMin;
+
480 
+
481 // currency amount (uncommon)
+
482 extern SF_AMOUNT const sfMinimumOffer;
+
483 extern SF_AMOUNT const sfRippleEscrow;
+
484 extern SF_AMOUNT const sfDeliveredAmount;
+
485 extern SF_AMOUNT const sfNFTokenBrokerFee;
+
486 
+
487 // currency amount (fees)
+
488 extern SF_AMOUNT const sfBaseFeeDrops;
+
489 extern SF_AMOUNT const sfReserveBaseDrops;
+
490 extern SF_AMOUNT const sfReserveIncrementDrops;
+
491 
+
492 // variable length (common)
+
493 extern SF_VL const sfPublicKey;
+
494 extern SF_VL const sfMessageKey;
+
495 extern SF_VL const sfSigningPubKey;
+
496 extern SF_VL const sfTxnSignature;
+
497 extern SF_VL const sfURI;
+
498 extern SF_VL const sfSignature;
+
499 extern SF_VL const sfDomain;
+
500 extern SF_VL const sfFundCode;
+
501 extern SF_VL const sfRemoveCode;
+
502 extern SF_VL const sfExpireCode;
+
503 extern SF_VL const sfCreateCode;
+
504 extern SF_VL const sfMemoType;
+
505 extern SF_VL const sfMemoData;
+
506 extern SF_VL const sfMemoFormat;
+
507 
+
508 // variable length (uncommon)
+
509 extern SF_VL const sfFulfillment;
+
510 extern SF_VL const sfCondition;
+
511 extern SF_VL const sfMasterSignature;
+
512 extern SF_VL const sfUNLModifyValidator;
+
513 extern SF_VL const sfValidatorToDisable;
+
514 extern SF_VL const sfValidatorToReEnable;
+
515 extern SF_VL const sfHookStateData;
+
516 extern SF_VL const sfHookReturnString;
+
517 extern SF_VL const sfHookParameterName;
+
518 extern SF_VL const sfHookParameterValue;
+
519 
+
520 // account
+
521 extern SF_ACCOUNT const sfAccount;
+
522 extern SF_ACCOUNT const sfOwner;
+
523 extern SF_ACCOUNT const sfDestination;
+
524 extern SF_ACCOUNT const sfIssuer;
+
525 extern SF_ACCOUNT const sfAuthorize;
+
526 extern SF_ACCOUNT const sfUnauthorize;
+
527 extern SF_ACCOUNT const sfRegularKey;
+
528 extern SF_ACCOUNT const sfNFTokenMinter;
+
529 extern SF_ACCOUNT const sfEmitCallback;
+
530 
+
531 // account (uncommon)
+
532 extern SF_ACCOUNT const sfHookAccount;
+
533 
+
534 // path set
+
535 extern SField const sfPaths;
+
536 
+
537 // vector of 256-bit
+
538 extern SF_VECTOR256 const sfIndexes;
+
539 extern SF_VECTOR256 const sfHashes;
+
540 extern SF_VECTOR256 const sfAmendments;
+
541 extern SF_VECTOR256 const sfNFTokenOffers;
+
542 
+
543 // inner object
+
544 // OBJECT/1 is reserved for end of object
+
545 extern SField const sfTransactionMetaData;
+
546 extern SField const sfCreatedNode;
+
547 extern SField const sfDeletedNode;
+
548 extern SField const sfModifiedNode;
+
549 extern SField const sfPreviousFields;
+
550 extern SField const sfFinalFields;
+
551 extern SField const sfNewFields;
+
552 extern SField const sfTemplateEntry;
+
553 extern SField const sfMemo;
+
554 extern SField const sfSignerEntry;
+
555 extern SField const sfNFToken;
+
556 extern SField const sfEmitDetails;
+
557 extern SField const sfHook;
+
558 
+
559 extern SField const sfSigner;
+
560 extern SField const sfMajority;
+
561 extern SField const sfDisabledValidator;
+
562 extern SField const sfEmittedTxn;
+
563 extern SField const sfHookExecution;
+
564 extern SField const sfHookDefinition;
+
565 extern SField const sfHookParameter;
+
566 extern SField const sfHookGrant;
+
567 
+
568 // array of objects (common)
+
569 // ARRAY/1 is reserved for end of array
+
570 // extern SField const sfSigningAccounts; // Never been used.
+
571 extern SField const sfSigners;
+
572 extern SField const sfSignerEntries;
+
573 extern SField const sfTemplate;
+
574 extern SField const sfNecessary;
+
575 extern SField const sfSufficient;
+
576 extern SField const sfAffectedNodes;
+
577 extern SField const sfMemos;
+
578 extern SField const sfNFTokens;
+
579 extern SField const sfHooks;
+
580 
+
581 // array of objects (uncommon)
+
582 extern SField const sfMajorities;
+
583 extern SField const sfDisabledValidators;
+
584 extern SField const sfHookExecutions;
+
585 extern SField const sfHookParameters;
+
586 extern SField const sfHookGrants;
+
587 
+
588 //------------------------------------------------------------------------------
+
589 
+
590 } // namespace ripple
+
591 
+
592 #endif
const SF_ACCOUNT sfHookAccount
const SF_UINT64 sfIndexNext
@@ -657,6 +658,7 @@ $(function() {
const SF_UINT32 sfPreviousTxnLgrSeq
const SF_UINT256 sfHookHash
const SF_UINT32 sfOwnerCount
+
const SF_UINT32 sfFirstNFTokenSequence
TypedField(TypedField &&u)
Definition: SField.h:280
const SF_UINT16 sfSignerWeight
const SF_UINT16 sfHookApiVersion
@@ -740,7 +742,7 @@ $(function() {
const SF_UINT32 sfCloseTime
const SF_UINT32 sfTicketSequence
const SF_UINT32 sfParentCloseTime
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
@ STI_ACCOUNT
Definition: SField.h:65
@ STI_ARRAY
Definition: SField.h:68
static std::map< int, SField const * > knownCodeToField
Definition: SField.h:266
@@ -890,7 +892,7 @@ $(function() {
const SF_VL sfSignature
const SF_AMOUNT sfBalance
const SField sfHook
-
static int compare(const SField &f1, const SField &f2)
Definition: SField.cpp:395
+
static int compare(const SField &f1, const SField &f2)
Definition: SField.cpp:398
const SF_UINT32 sfReferenceFeeUnits
Definition: STVector256.h:29
const SF_UINT256 sfNextPageMin
diff --git a/STArray_8cpp_source.html b/STArray_8cpp_source.html index 183d4a3977..f63e0acce0 100644 --- a/STArray_8cpp_source.html +++ b/STArray_8cpp_source.html @@ -273,7 +273,7 @@ $(function() {
SerializedTypeID
Definition: SField.h:52
void getFieldID(int &type, int &name)
Definition: Serializer.cpp:414
STBase * copy(std::size_t n, void *buf) const override
Definition: STArray.cpp:93
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
STArray()=default
@ STI_ARRAY
Definition: SField.h:68
T back(T... args)
diff --git a/STInteger_8cpp_source.html b/STInteger_8cpp_source.html index 4a2261bc18..5f54c3a2ef 100644 --- a/STInteger_8cpp_source.html +++ b/STInteger_8cpp_source.html @@ -290,7 +290,7 @@ $(function() {
Definition: Serializer.h:310
T to_chars(T... args)
Definition: SField.h:49
-
static LedgerFormats const & getInstance()
+
static LedgerFormats const & getInstance()
@ STI_UINT32
Definition: SField.h:59
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
bool transResultInfo(TER code, std::string &token, std::string &text)
Definition: TER.cpp:192
diff --git a/STLedgerEntry_8cpp_source.html b/STLedgerEntry_8cpp_source.html index 8a7e113fc4..12afae79bc 100644 --- a/STLedgerEntry_8cpp_source.html +++ b/STLedgerEntry_8cpp_source.html @@ -254,7 +254,7 @@ $(function() {
const SF_UINT256 sfPreviousTxnID
std::uint16_t getFieldU16(SField const &field) const
Definition: STObject.cpp:553
-
static LedgerFormats const & getInstance()
+
static LedgerFormats const & getInstance()
int getFieldIndex(SField const &field) const
Definition: STObject.cpp:357
Definition: STObject.h:51
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
diff --git a/STObject_8cpp_source.html b/STObject_8cpp_source.html index 12199754aa..f0c8f47824 100644 --- a/STObject_8cpp_source.html +++ b/STObject_8cpp_source.html @@ -942,7 +942,7 @@ $(function() {
@ soeREQUIRED
Definition: SOTemplate.h:35
Like std::vector<char> but better.
Definition: Buffer.h:35
STBase * move(std::size_t n, void *buf) override
Definition: STObject.cpp:67
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
@ STI_ARRAY
Definition: SField.h:68
T back(T... args)
void setFieldVL(SField const &field, Blob const &)
Definition: STObject.cpp:695
diff --git a/STObject__test_8cpp_source.html b/STObject__test_8cpp_source.html index bf1586901b..df5f11bc1d 100644 --- a/STObject__test_8cpp_source.html +++ b/STObject__test_8cpp_source.html @@ -825,7 +825,7 @@ $(function() {
@ soeREQUIRED
Definition: SOTemplate.h:35
Like std::vector<char> but better.
Definition: Buffer.h:35
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:31
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
void setFieldVL(SField const &field, Blob const &)
Definition: STObject.cpp:695
Blob getFieldVL(SField const &field) const
Definition: STObject.cpp:595
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
diff --git a/STParsedJSON_8cpp_source.html b/STParsedJSON_8cpp_source.html index 8b0c7977fe..7d5b237618 100644 --- a/STParsedJSON_8cpp_source.html +++ b/STParsedJSON_8cpp_source.html @@ -1091,7 +1091,7 @@ $(function() {
void push_back(STObject const &object)
Definition: STArray.h:212
static Json::Value too_deep(std::string const &object)
-
static const SField & getField(int fieldCode)
Definition: SField.cpp:383
+
static const SField & getField(int fieldCode)
Definition: SField.cpp:386
@ STI_ACCOUNT
Definition: SField.h:65
@ STI_ARRAY
Definition: SField.h:68
constexpr TERUnderlyingType TERtoInt(TELcodes v)
Definition: TER.h:298
@@ -1119,7 +1119,7 @@ $(function() {
T from_chars(T... args)
constexpr std::enable_if_t< std::is_unsigned< U >::value &&std::is_signed< S >::value, U > to_unsigned(S value)
-
static LedgerFormats const & getInstance()
+
static LedgerFormats const & getInstance()
T min(T... args)
@ STI_UINT32
Definition: SField.h:59
Definition: STObject.h:51
diff --git a/classripple_1_1DeleteAccount.html b/classripple_1_1DeleteAccount.html index 5e6122a11f..737fbde730 100644 --- a/classripple_1_1DeleteAccount.html +++ b/classripple_1_1DeleteAccount.html @@ -418,7 +418,7 @@ Static Private Member Functions

Implements ripple::Transactor.

-

Definition at line 267 of file DeleteAccount.cpp.

+

Definition at line 284 of file DeleteAccount.cpp.

diff --git a/classripple_1_1LedgerFormats.html b/classripple_1_1LedgerFormats.html index bb8dcb7b01..34d6af93e9 100644 --- a/classripple_1_1LedgerFormats.html +++ b/classripple_1_1LedgerFormats.html @@ -209,7 +209,7 @@ Private Attributes
-

Definition at line 273 of file LedgerFormats.cpp.

+

Definition at line 274 of file LedgerFormats.cpp.

diff --git a/classripple_1_1NFTokenBurn__test.html b/classripple_1_1NFTokenBurn__test.html index ff524c15df..0d634bb13d 100644 --- a/classripple_1_1NFTokenBurn__test.html +++ b/classripple_1_1NFTokenBurn__test.html @@ -328,7 +328,7 @@ Static Private Member Functions
-

Definition at line 519 of file NFTokenBurn_test.cpp.

+

Definition at line 527 of file NFTokenBurn_test.cpp.

@@ -356,7 +356,7 @@ Static Private Member Functions
-

Definition at line 774 of file NFTokenBurn_test.cpp.

+

Definition at line 782 of file NFTokenBurn_test.cpp.

@@ -383,7 +383,7 @@ Static Private Member Functions
-

Definition at line 783 of file NFTokenBurn_test.cpp.

+

Definition at line 791 of file NFTokenBurn_test.cpp.

diff --git a/classripple_1_1NFTokenDir__test.html b/classripple_1_1NFTokenDir__test.html index 47a1016c04..a330711c07 100644 --- a/classripple_1_1NFTokenDir__test.html +++ b/classripple_1_1NFTokenDir__test.html @@ -273,7 +273,7 @@ Private Member Functions
-

Definition at line 374 of file NFTokenDir_test.cpp.

+

Definition at line 380 of file NFTokenDir_test.cpp.

@@ -301,7 +301,7 @@ Private Member Functions
-

Definition at line 594 of file NFTokenDir_test.cpp.

+

Definition at line 606 of file NFTokenDir_test.cpp.

@@ -329,7 +329,7 @@ Private Member Functions
-

Definition at line 755 of file NFTokenDir_test.cpp.

+

Definition at line 773 of file NFTokenDir_test.cpp.

@@ -357,7 +357,7 @@ Private Member Functions
-

Definition at line 1063 of file NFTokenDir_test.cpp.

+

Definition at line 1087 of file NFTokenDir_test.cpp.

@@ -384,7 +384,7 @@ Private Member Functions
-

Definition at line 1074 of file NFTokenDir_test.cpp.

+

Definition at line 1098 of file NFTokenDir_test.cpp.

diff --git a/classripple_1_1NFToken__test-members.html b/classripple_1_1NFToken__test-members.html index 985d8c2be6..edfa0f332a 100644 --- a/classripple_1_1NFToken__test-members.html +++ b/classripple_1_1NFToken__test-members.html @@ -92,23 +92,24 @@ $(function() { testCreateOfferInvalid(FeatureBitset features)ripple::NFToken_testprivate testEnabled(FeatureBitset features)ripple::NFToken_testprivate testFixNFTokenNegOffer(FeatureBitset features)ripple::NFToken_testprivate - testIOUWithTransferFee(FeatureBitset features)ripple::NFToken_testprivate - testMintFlagBurnable(FeatureBitset features)ripple::NFToken_testprivate - testMintFlagCreateTrustLine(FeatureBitset features)ripple::NFToken_testprivate - testMintFlagOnlyXRP(FeatureBitset features)ripple::NFToken_testprivate - testMintFlagTransferable(FeatureBitset features)ripple::NFToken_testprivate - testMintInvalid(FeatureBitset features)ripple::NFToken_testprivate - testMintMaxTokens(FeatureBitset features)ripple::NFToken_testprivate - testMintReserve(FeatureBitset features)ripple::NFToken_testprivate - testMintTaxon(FeatureBitset features)ripple::NFToken_testprivate - testMintTransferFee(FeatureBitset features)ripple::NFToken_testprivate - testMintURI(FeatureBitset features)ripple::NFToken_testprivate - testNFTokenDeleteAccount(FeatureBitset features)ripple::NFToken_testprivate - testNFTokenOfferOwner(FeatureBitset features)ripple::NFToken_testprivate - testNFTokenWithTickets(FeatureBitset features)ripple::NFToken_testprivate - testNftXxxOffers(FeatureBitset features)ripple::NFToken_testprivate - testWithFeats(FeatureBitset features)ripple::NFToken_testprivate - ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)ripple::NFToken_testprivatestatic + testFixNFTokenRemint(FeatureBitset features)ripple::NFToken_testprivate + testIOUWithTransferFee(FeatureBitset features)ripple::NFToken_testprivate + testMintFlagBurnable(FeatureBitset features)ripple::NFToken_testprivate + testMintFlagCreateTrustLine(FeatureBitset features)ripple::NFToken_testprivate + testMintFlagOnlyXRP(FeatureBitset features)ripple::NFToken_testprivate + testMintFlagTransferable(FeatureBitset features)ripple::NFToken_testprivate + testMintInvalid(FeatureBitset features)ripple::NFToken_testprivate + testMintMaxTokens(FeatureBitset features)ripple::NFToken_testprivate + testMintReserve(FeatureBitset features)ripple::NFToken_testprivate + testMintTaxon(FeatureBitset features)ripple::NFToken_testprivate + testMintTransferFee(FeatureBitset features)ripple::NFToken_testprivate + testMintURI(FeatureBitset features)ripple::NFToken_testprivate + testNFTokenDeleteAccount(FeatureBitset features)ripple::NFToken_testprivate + testNFTokenOfferOwner(FeatureBitset features)ripple::NFToken_testprivate + testNFTokenWithTickets(FeatureBitset features)ripple::NFToken_testprivate + testNftXxxOffers(FeatureBitset features)ripple::NFToken_testprivate + testWithFeats(FeatureBitset features)ripple::NFToken_testprivate + ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)ripple::NFToken_testprivatestatic