xrpld
Loading...
Searching...
No Matches
NFToken_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/acctdelete.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/balance.h> // IWYU pragma: keep
8#include <test/jtx/check.h>
9#include <test/jtx/fee.h>
10#include <test/jtx/flags.h>
11#include <test/jtx/noop.h>
12#include <test/jtx/offer.h>
13#include <test/jtx/owners.h> // IWYU pragma: keep
14#include <test/jtx/pay.h>
15#include <test/jtx/rate.h>
16#include <test/jtx/ter.h>
17#include <test/jtx/ticket.h>
18#include <test/jtx/token.h>
19#include <test/jtx/trust.h>
20#include <test/jtx/txflags.h>
21
22#include <xrpl/basics/base_uint.h>
23#include <xrpl/basics/random.h>
24#include <xrpl/basics/strHex.h>
25#include <xrpl/beast/unit_test/suite.h>
26#include <xrpl/beast/utility/Journal.h>
27#include <xrpl/beast/utility/Zero.h>
28#include <xrpl/core/ServiceRegistry.h>
29#include <xrpl/json/json_value.h>
30#include <xrpl/json/to_string.h>
31#include <xrpl/ledger/OpenView.h>
32#include <xrpl/protocol/Feature.h>
33#include <xrpl/protocol/Indexes.h>
34#include <xrpl/protocol/Protocol.h>
35#include <xrpl/protocol/SField.h>
36#include <xrpl/protocol/TER.h>
37#include <xrpl/protocol/TxFlags.h>
38#include <xrpl/protocol/jss.h>
39#include <xrpl/protocol/nft.h>
40
41#include <algorithm>
42#include <cstddef>
43#include <cstdint>
44#include <functional>
45#include <initializer_list>
46#include <iterator>
47#include <memory>
48#include <optional>
49#include <set>
50#include <sstream>
51#include <string>
52#include <utility>
53#include <vector>
54
55namespace xrpl {
56
58{
59 // Helper function that returns the number of NFTs minted by an issuer.
60 static std::uint32_t
62 {
63 std::uint32_t ret{0};
64 if (auto const sleIssuer = env.le(issuer))
65 ret = sleIssuer->at(~sfMintedNFTokens).value_or(0);
66 return ret;
67 }
68
69 // Helper function that returns the number of an issuer's burned NFTs.
70 static std::uint32_t
72 {
73 std::uint32_t ret{0};
74 if (auto const sleIssuer = env.le(issuer))
75 ret = sleIssuer->at(~sfBurnedNFTokens).value_or(0);
76 return ret;
77 }
78
79 // Helper function that returns the number of nfts owned by an account.
80 static std::uint32_t
82 {
83 json::Value params;
84 params[jss::account] = acct.human();
85 params[jss::type] = "state";
86 json::Value nfts = env.rpc("json", "account_nfts", to_string(params));
87 return nfts[jss::result][jss::account_nfts].size();
88 };
89
90 // Helper function that returns the number of tickets held by an account.
91 static std::uint32_t
93 {
94 std::uint32_t ret{0};
95 if (auto const sleAcct = env.le(acct))
96 ret = sleAcct->at(~sfTicketCount).value_or(0);
97 return ret;
98 }
99
100 // Helper function returns the close time of the parent ledger.
101 static std::uint32_t
103 {
104 return env.current()->header().parentCloseTime.time_since_epoch().count();
105 }
106
107 void
109 {
110 testcase("Enabled");
111
112 using namespace test::jtx;
113 {
114 // If the NFT amendment is enabled all NFT-related
115 // facilities should be available.
116 Env env{*this, features};
117 Account const& master = env.master;
118
119 BEAST_EXPECT(ownerCount(env, master) == 0);
120 BEAST_EXPECT(mintedCount(env, master) == 0);
121 BEAST_EXPECT(burnedCount(env, master) == 0);
122
123 uint256 const nftId0{token::getNextID(env, env.master, 0u)};
124 env(token::mint(env.master, 0u));
125 env.close();
126 BEAST_EXPECT(ownerCount(env, master) == 1);
127 BEAST_EXPECT(mintedCount(env, master) == 1);
128 BEAST_EXPECT(burnedCount(env, master) == 0);
129
130 env(token::burn(env.master, nftId0));
131 env.close();
132 BEAST_EXPECT(ownerCount(env, master) == 0);
133 BEAST_EXPECT(mintedCount(env, master) == 1);
134 BEAST_EXPECT(burnedCount(env, master) == 1);
135
136 uint256 const nftId1{token::getNextID(env, env.master, 0u, tfTransferable)};
137 env(token::mint(env.master, 0u), Txflags(tfTransferable));
138 env.close();
139 BEAST_EXPECT(ownerCount(env, master) == 1);
140 BEAST_EXPECT(mintedCount(env, master) == 2);
141 BEAST_EXPECT(burnedCount(env, master) == 1);
142
143 Account const alice{"alice"};
144 env.fund(XRP(10000), alice);
145 env.close();
146 uint256 const aliceOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
147 env(token::createOffer(alice, nftId1, XRP(1000)), token::Owner(master));
148 env.close();
149
150 BEAST_EXPECT(ownerCount(env, master) == 1);
151 BEAST_EXPECT(mintedCount(env, master) == 2);
152 BEAST_EXPECT(burnedCount(env, master) == 1);
153
154 BEAST_EXPECT(ownerCount(env, alice) == 1);
155 BEAST_EXPECT(mintedCount(env, alice) == 0);
156 BEAST_EXPECT(burnedCount(env, alice) == 0);
157
158 env(token::acceptBuyOffer(master, aliceOfferIndex));
159 env.close();
160
161 BEAST_EXPECT(ownerCount(env, master) == 0);
162 BEAST_EXPECT(mintedCount(env, master) == 2);
163 BEAST_EXPECT(burnedCount(env, master) == 1);
164
165 BEAST_EXPECT(ownerCount(env, alice) == 1);
166 BEAST_EXPECT(mintedCount(env, alice) == 0);
167 BEAST_EXPECT(burnedCount(env, alice) == 0);
168 }
169 }
170
171 void
173 {
174 // Verify that the reserve behaves as expected for minting.
175 testcase("Mint reserve");
176
177 using namespace test::jtx;
178
179 Env env{*this, features};
180 Account const alice{"alice"};
181 Account const minter{"minter"};
182
183 // Fund alice and minter enough to exist, but not enough to meet
184 // the reserve for creating their first NFT.
185 auto const acctReserve = env.current()->fees().reserve;
186 auto const incReserve = env.current()->fees().increment;
187 auto const baseFee = env.current()->fees().base;
188
189 env.fund(acctReserve, alice, minter);
190 env.close();
191
192 BEAST_EXPECT(env.balance(alice) == acctReserve);
193 BEAST_EXPECT(env.balance(minter) == acctReserve);
194 BEAST_EXPECT(ownerCount(env, alice) == 0);
195 BEAST_EXPECT(ownerCount(env, minter) == 0);
196
197 // alice does not have enough XRP to cover the reserve for an NFT
198 // page.
199 env(token::mint(alice, 0u), Ter(tecINSUFFICIENT_RESERVE));
200 env.close();
201
202 BEAST_EXPECT(ownerCount(env, alice) == 0);
203 BEAST_EXPECT(mintedCount(env, alice) == 0);
204 BEAST_EXPECT(burnedCount(env, alice) == 0);
205
206 // Pay alice almost enough to make the reserve for an NFT page.
207 env(pay(env.master, alice, incReserve + drops(baseFee - 1)));
208 env.close();
209
210 // A lambda that checks alice's ownerCount, mintedCount, and
211 // burnedCount all in one fell swoop.
212 auto checkAliceOwnerMintedBurned = [&env, this, &alice](
213 std::uint32_t owners,
214 std::uint32_t minted,
215 std::uint32_t burned,
216 int line) {
217 auto oneCheck = [line, this](char const* type, std::uint32_t found, std::uint32_t exp) {
218 if (found == exp)
219 {
220 pass();
221 }
222 else
223 {
225 ss << "Wrong " << type << " count. Found: " << found << "; Expected: " << exp;
226 fail(ss.str(), __FILE__, line);
227 }
228 };
229 oneCheck("owner", ownerCount(env, alice), owners);
230 oneCheck("minted", mintedCount(env, alice), minted);
231 oneCheck("burned", burnedCount(env, alice), burned);
232 };
233
234 // alice still does not have enough XRP for the reserve of an NFT
235 // page.
236 env(token::mint(alice, 0u), Ter(tecINSUFFICIENT_RESERVE));
237 env.close();
238
239 checkAliceOwnerMintedBurned(0, 0, 0, __LINE__);
240
241 // Pay alice enough to make the reserve for an NFT page.
242 env(pay(env.master, alice, drops(baseFee + 1)));
243 env.close();
244
245 // Now alice can mint an NFT.
246 env(token::mint(alice));
247 env.close();
248
249 checkAliceOwnerMintedBurned(1, 1, 0, __LINE__);
250
251 // Alice should be able to mint an additional 31 NFTs without
252 // any additional reserve requirements.
253 for (int i = 1; i < 32; ++i)
254 {
255 env(token::mint(alice));
256 checkAliceOwnerMintedBurned(1, i + 1, 0, __LINE__);
257 }
258
259 // That NFT page is full. Creating an additional NFT page requires
260 // additional reserve.
261 env(token::mint(alice), Ter(tecINSUFFICIENT_RESERVE));
262 env.close();
263 checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
264
265 // Pay alice almost enough to make the reserve for an NFT page.
266 env(pay(env.master, alice, incReserve + drops(baseFee * 33 - 1)));
267 env.close();
268
269 // alice still does not have enough XRP for the reserve of an NFT
270 // page.
271 env(token::mint(alice), Ter(tecINSUFFICIENT_RESERVE));
272 env.close();
273 checkAliceOwnerMintedBurned(1, 32, 0, __LINE__);
274
275 // Pay alice enough to make the reserve for an NFT page.
276 env(pay(env.master, alice, drops(baseFee + 1)));
277 env.close();
278
279 // Now alice can mint an NFT.
280 env(token::mint(alice));
281 env.close();
282 checkAliceOwnerMintedBurned(2, 33, 0, __LINE__);
283
284 // alice burns the NFTs she created: check that pages consolidate
285 std::uint32_t seq = 0;
286
287 while (seq < 33)
288 {
289 env(token::burn(alice, token::getID(env, alice, 0, seq++)));
290 env.close();
291 checkAliceOwnerMintedBurned(((33 - seq) != 0u) ? 1 : 0, 33, seq, __LINE__);
292 }
293
294 // alice burns a non-existent NFT.
295 env(token::burn(alice, token::getID(env, alice, 197, 5)), Ter(tecNO_ENTRY));
296 env.close();
297 checkAliceOwnerMintedBurned(0, 33, 33, __LINE__);
298
299 // That was fun! Now let's see what happens when we let someone
300 // else mint NFTs on alice's behalf. alice gives permission to
301 // minter.
302 env(token::setMinter(alice, minter));
303 env.close();
304 BEAST_EXPECT(env.le(alice)->getAccountID(sfNFTokenMinter) == minter.id());
305
306 // A lambda that checks minter's and alice's ownerCount,
307 // mintedCount, and burnedCount all in one fell swoop.
308 auto checkMintersOwnerMintedBurned = [&env, this, &alice, &minter](
309 std::uint32_t aliceOwners,
310 std::uint32_t aliceMinted,
311 std::uint32_t aliceBurned,
312 std::uint32_t minterOwners,
313 std::uint32_t minterMinted,
314 std::uint32_t minterBurned,
315 int line) {
316 auto oneCheck =
317 [this](char const* type, std::uint32_t found, std::uint32_t exp, int line) {
318 if (found == exp)
319 {
320 pass();
321 }
322 else
323 {
325 ss << "Wrong " << type << " count. Found: " << found
326 << "; Expected: " << exp;
327 fail(ss.str(), __FILE__, line);
328 }
329 };
330 oneCheck("alice owner", ownerCount(env, alice), aliceOwners, line);
331 oneCheck("alice minted", mintedCount(env, alice), aliceMinted, line);
332 oneCheck("alice burned", burnedCount(env, alice), aliceBurned, line);
333 oneCheck("minter owner", ownerCount(env, minter), minterOwners, line);
334 oneCheck("minter minted", mintedCount(env, minter), minterMinted, line);
335 oneCheck("minter burned", burnedCount(env, minter), minterBurned, line);
336 };
337
338 std::uint32_t nftSeq = 33;
339
340 // Pay minter almost enough to make the reserve for an NFT page.
341 env(pay(env.master, minter, incReserve - drops(1)));
342 env.close();
343 checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
344
345 // minter still does not have enough XRP for the reserve of an NFT
346 // page. Just for grins (and code coverage), minter mints NFTs that
347 // include a URI.
348 env(token::mint(minter),
349 token::Issuer(alice),
350 token::Uri("uri"),
352 env.close();
353 checkMintersOwnerMintedBurned(0, 33, nftSeq, 0, 0, 0, __LINE__);
354
355 // Pay minter enough to make the reserve for an NFT page.
356 env(pay(env.master, minter, drops(baseFee + 1)));
357 env.close();
358
359 // Now minter can mint an NFT for alice.
360 env(token::mint(minter), token::Issuer(alice), token::Uri("uri"));
361 env.close();
362 checkMintersOwnerMintedBurned(0, 34, nftSeq, 1, 0, 0, __LINE__);
363
364 // Minter should be able to mint an additional 31 NFTs for alice
365 // without any additional reserve requirements.
366 for (int i = 1; i < 32; ++i)
367 {
368 env(token::mint(minter), token::Issuer(alice), token::Uri("uri"));
369 checkMintersOwnerMintedBurned(0, i + 34, nftSeq, 1, 0, 0, __LINE__);
370 }
371
372 // Pay minter almost enough for the reserve of an additional NFT
373 // page.
374 env(pay(env.master, minter, incReserve + drops(baseFee * 32 - 1)));
375 env.close();
376
377 // That NFT page is full. Creating an additional NFT page requires
378 // additional reserve.
379 env(token::mint(minter),
380 token::Issuer(alice),
381 token::Uri("uri"),
383 env.close();
384 checkMintersOwnerMintedBurned(0, 65, nftSeq, 1, 0, 0, __LINE__);
385
386 // Pay minter enough for the reserve of an additional NFT page.
387 env(pay(env.master, minter, drops(baseFee + 1)));
388 env.close();
389
390 // Now minter can mint an NFT.
391 env(token::mint(minter), token::Issuer(alice), token::Uri("uri"));
392 env.close();
393 checkMintersOwnerMintedBurned(0, 66, nftSeq, 2, 0, 0, __LINE__);
394
395 // minter burns the NFTs she created.
396 while (nftSeq < 65)
397 {
398 env(token::burn(minter, token::getID(env, alice, 0, nftSeq++)));
399 env.close();
400 checkMintersOwnerMintedBurned(
401 0, 66, nftSeq, ((65 - seq) != 0u) ? 1 : 0, 0, 0, __LINE__);
402 }
403
404 // minter has one more NFT to burn. Should take her owner count to
405 // 0.
406 env(token::burn(minter, token::getID(env, alice, 0, nftSeq++)));
407 env.close();
408 checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
409
410 // minter burns a non-existent NFT.
411 env(token::burn(minter, token::getID(env, alice, 2009, 3)), Ter(tecNO_ENTRY));
412 env.close();
413 checkMintersOwnerMintedBurned(0, 66, nftSeq, 0, 0, 0, __LINE__);
414 }
415
416 void
418 {
419 // Make sure that an account cannot cause the sfMintedNFTokens
420 // field to wrap by minting more than 0xFFFF'FFFF tokens.
421 testcase("Mint max tokens");
422
423 using namespace test::jtx;
424
425 Account const alice{"alice"};
426 Env env{*this, features};
427 env.fund(XRP(1000), alice);
428 env.close();
429
430 // We're going to hack the ledger in order to avoid generating
431 // 4 billion or so NFTs. Because we're hacking the ledger we
432 // need alice's account to have non-zero sfMintedNFTokens and
433 // sfBurnedNFTokens fields. This prevents an exception when the
434 // AccountRoot template is applied.
435 {
436 uint256 const nftId0{token::getNextID(env, alice, 0u)};
437 env(token::mint(alice, 0u));
438 env.close();
439
440 env(token::burn(alice, nftId0));
441 env.close();
442 }
443
444 // Note that we're bypassing almost all of the ledger's safety
445 // checks with this modify() call. If you call close() between
446 // here and the end of the test all the effort will be lost.
447 env.app().getOpenLedger().modify([&alice](OpenView& view, beast::Journal j) {
448 // Get the account root we want to hijack.
449 auto const sle = view.read(keylet::account(alice.id()));
450 if (!sle)
451 return false; // This would be really surprising!
452
453 // Just for sanity's sake we'll check that the current value
454 // of sfMintedNFTokens matches what we expect.
455 auto replacement = std::make_shared<SLE>(*sle, sle->key());
456 if (replacement->getFieldU32(sfMintedNFTokens) != 1)
457 return false; // Unexpected test conditions.
458
459 // Sequence number is generated by sfFirstNFTokenSequence +
460 // sfMintedNFTokens. We can replace the two fields with any
461 // numbers as long as they add up to the largest valid number.
462 // In our case, sfFirstNFTokenSequence is set to the largest
463 // valid number, and sfMintedNFTokens is set to zero.
464 (*replacement)[sfFirstNFTokenSequence] = 0xFFFF'FFFE;
465 (*replacement)[sfMintedNFTokens] = 0x0000'0000;
466 view.rawReplace(replacement);
467 return true;
468 });
469
470 // See whether alice is at the boundary that causes an error.
471 env(token::mint(alice, 0u), Ter(tesSUCCESS));
472 env(token::mint(alice, 0u), Ter(tecMAX_SEQUENCE_REACHED));
473 }
474
475 void
477 {
478 // Explore many of the invalid ways to mint an NFT.
479 testcase("Mint invalid");
480
481 using namespace test::jtx;
482
483 Env env{*this, features};
484 Account const alice{"alice"};
485 Account const minter{"minter"};
486
487 // Fund alice and minter enough to exist, but not enough to meet
488 // the reserve for creating their first NFT. Account reserve for unit
489 // tests is 200 XRP, not 20.
490 env.fund(XRP(200), alice, minter);
491 env.close();
492
493 env(token::mint(alice, 0u), Ter(tecINSUFFICIENT_RESERVE));
494 env.close();
495
496 // Fund alice enough to start minting NFTs.
497 env(pay(env.master, alice, XRP(1000)));
498 env.close();
499
500 //----------------------------------------------------------------------
501 // preflight
502
503 // Set a negative fee.
504 env(token::mint(alice, 0u), Fee(STAmount(10ull, true)), Ter(temBAD_FEE));
505
506 // Set an invalid flag.
507 env(token::mint(alice, 0u), Txflags(0x00008000), Ter(temINVALID_FLAG));
508
509 // Can't set a transfer fee if the NFT does not have the tfTRANSFERABLE
510 // flag set.
511 env(token::mint(alice, 0u), token::XferFee(kMaxTransferFee), Ter(temMALFORMED));
512
513 // Set a bad transfer fee.
514 env(token::mint(alice, 0u),
515 token::XferFee(kMaxTransferFee + 1),
516 Txflags(tfTransferable),
518
519 // Account can't also be issuer.
520 env(token::mint(alice, 0u), token::Issuer(alice), Ter(temMALFORMED));
521
522 // Invalid URI: zero length.
523 env(token::mint(alice, 0u), token::Uri(""), Ter(temMALFORMED));
524
525 // Invalid URI: too long.
526 env(token::mint(alice, 0u),
527 token::Uri(std::string(kMaxTokenUriLength + 1, 'q')),
528 Ter(temMALFORMED));
529
530 //----------------------------------------------------------------------
531 // preclaim
532
533 // Non-existent issuer.
534 env(token::mint(alice, 0u), token::Issuer(Account("demon")), Ter(tecNO_ISSUER));
535
536 //----------------------------------------------------------------------
537 // doApply
538
539 // Existent issuer, but not given minting permission
540 env(token::mint(minter, 0u), token::Issuer(alice), Ter(tecNO_PERMISSION));
541 }
542
543 void
545 {
546 // Explore many of the invalid ways to burn an NFT.
547 testcase("Burn invalid");
548
549 using namespace test::jtx;
550
551 Env env{*this, features};
552 Account const alice{"alice"};
553 Account const buyer{"buyer"};
554 Account const minter{"minter"};
555 Account const gw("gw");
556 IOU const gwAUD(gw["AUD"]);
557
558 // Fund alice and minter enough to exist and create an NFT, but not
559 // enough to meet the reserve for creating their first NFTOffer.
560 // Account reserve for unit tests is 200 XRP, not 20.
561 env.fund(XRP(250), alice, buyer, minter, gw);
562 env.close();
563 BEAST_EXPECT(ownerCount(env, alice) == 0);
564
565 uint256 const nftAlice0ID = token::getNextID(env, alice, 0, tfTransferable);
566 env(token::mint(alice, 0u), Txflags(tfTransferable));
567 env.close();
568 BEAST_EXPECT(ownerCount(env, alice) == 1);
569
570 //----------------------------------------------------------------------
571 // preflight
572
573 // Set a negative fee.
574 env(token::burn(alice, nftAlice0ID), Fee(STAmount(10ull, true)), Ter(temBAD_FEE));
575 env.close();
576 BEAST_EXPECT(ownerCount(env, alice) == 1);
577
578 // Set an invalid flag.
579 env(token::burn(alice, nftAlice0ID), Txflags(0x00008000), Ter(temINVALID_FLAG));
580 env.close();
581 BEAST_EXPECT(ownerCount(env, buyer) == 0);
582
583 //----------------------------------------------------------------------
584 // preclaim
585
586 // Try to burn a token that doesn't exist.
587 env(token::burn(alice, token::getID(env, alice, 0, 1)), Ter(tecNO_ENTRY));
588 env.close();
589 BEAST_EXPECT(ownerCount(env, buyer) == 0);
590
591 // Can't burn a token with many buy or sell offers. But that is
592 // verified in testManyNftOffers().
593
594 //----------------------------------------------------------------------
595 // doApply
596 }
597
598 void
600 {
601 testcase("Invalid NFT offer create");
602
603 using namespace test::jtx;
604
605 Env env{*this, features};
606 Account const alice{"alice"};
607 Account const buyer{"buyer"};
608 Account const gw("gw");
609 IOU const gwAUD(gw["AUD"]);
610
611 // Fund alice enough to exist and create an NFT, but not
612 // enough to meet the reserve for creating their first NFTOffer.
613 // Account reserve for unit tests is 200 XRP, not 20.
614 env.fund(XRP(250), alice, buyer, gw);
615 env.close();
616 BEAST_EXPECT(ownerCount(env, alice) == 0);
617
618 uint256 const nftAlice0ID = token::getNextID(env, alice, 0, tfTransferable, 10);
619 env(token::mint(alice, 0u), Txflags(tfTransferable), token::XferFee(10));
620 env.close();
621 BEAST_EXPECT(ownerCount(env, alice) == 1);
622
623 uint256 const nftXrpOnlyID = token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
624 env(token::mint(alice, 0), Txflags(tfOnlyXRP | tfTransferable));
625 env.close();
626 BEAST_EXPECT(ownerCount(env, alice) == 1);
627
628 uint256 const nftNoXferID = token::getNextID(env, alice, 0);
629 env(token::mint(alice, 0));
630 env.close();
631 BEAST_EXPECT(ownerCount(env, alice) == 1);
632
633 //----------------------------------------------------------------------
634 // preflight
635
636 // buyer burns a fee, so they no longer have enough XRP to cover the
637 // reserve for a token offer.
638 env(noop(buyer));
639 env.close();
640
641 // buyer tries to create an NFTokenOffer, but doesn't have the reserve.
642 env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
643 token::Owner(alice),
645 env.close();
646 BEAST_EXPECT(ownerCount(env, buyer) == 0);
647
648 // Set a negative fee.
649 env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
650 Fee(STAmount(10ull, true)),
651 Ter(temBAD_FEE));
652 env.close();
653 BEAST_EXPECT(ownerCount(env, buyer) == 0);
654
655 // Set an invalid flag.
656 env(token::createOffer(buyer, nftAlice0ID, XRP(1000)),
657 Txflags(0x00008000),
658 Ter(temINVALID_FLAG));
659 env.close();
660 BEAST_EXPECT(ownerCount(env, buyer) == 0);
661
662 // Set an invalid amount.
663 env(token::createOffer(buyer, nftXrpOnlyID, buyer["USD"](1)), Ter(temBAD_AMOUNT));
664 env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](0)), Ter(temBAD_AMOUNT));
665 env(token::createOffer(buyer, nftXrpOnlyID, drops(0)), Ter(temBAD_AMOUNT));
666 env.close();
667 BEAST_EXPECT(ownerCount(env, buyer) == 0);
668
669 // Set a bad expiration.
670 env(token::createOffer(buyer, nftAlice0ID, buyer["USD"](1)),
671 token::Expiration(0),
672 Ter(temBAD_EXPIRATION));
673 env.close();
674 BEAST_EXPECT(ownerCount(env, buyer) == 0);
675
676 // Invalid Owner field and tfSellToken flag relationships.
677 // A buy offer must specify the owner.
678 env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)), Ter(temMALFORMED));
679 env.close();
680 BEAST_EXPECT(ownerCount(env, buyer) == 0);
681
682 // A sell offer must not specify the owner; the owner is implicit.
683 env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
684 token::Owner(alice),
685 Txflags(tfSellNFToken),
686 Ter(temMALFORMED));
687 env.close();
688 BEAST_EXPECT(ownerCount(env, alice) == 1);
689
690 // An owner may not offer to buy their own token.
691 env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
692 token::Owner(alice),
693 Ter(temMALFORMED));
694 env.close();
695 BEAST_EXPECT(ownerCount(env, alice) == 1);
696
697 // The destination may not be the account submitting the transaction.
698 env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
699 token::Destination(alice),
700 Txflags(tfSellNFToken),
701 Ter(temMALFORMED));
702 env.close();
703 BEAST_EXPECT(ownerCount(env, alice) == 1);
704
705 // The destination must be an account already established in the ledger.
706 env(token::createOffer(alice, nftXrpOnlyID, XRP(1000)),
707 token::Destination(Account("demon")),
708 Txflags(tfSellNFToken),
709 Ter(tecNO_DST));
710 env.close();
711 BEAST_EXPECT(ownerCount(env, alice) == 1);
712
713 //----------------------------------------------------------------------
714 // preclaim
715
716 // The new NFTokenOffer may not have passed its expiration time.
717 env(token::createOffer(buyer, nftXrpOnlyID, XRP(1000)),
718 token::Owner(alice),
719 token::Expiration(lastClose(env)),
720 Ter(tecEXPIRED));
721 env.close();
722 BEAST_EXPECT(ownerCount(env, buyer) == 0);
723
724 // The nftID must be present in the ledger.
725 env(token::createOffer(buyer, token::getID(env, alice, 0, 1), XRP(1000)),
726 token::Owner(alice),
727 Ter(tecNO_ENTRY));
728 env.close();
729 BEAST_EXPECT(ownerCount(env, buyer) == 0);
730
731 // The nftID must be present in the ledger of a sell offer too.
732 env(token::createOffer(alice, token::getID(env, alice, 0, 1), XRP(1000)),
733 Txflags(tfSellNFToken),
734 Ter(tecNO_ENTRY));
735 env.close();
736 BEAST_EXPECT(ownerCount(env, buyer) == 0);
737
738 // buyer must have the funds to pay for their offer.
739 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
740 token::Owner(alice),
741 Ter(tecNO_LINE));
742 env.close();
743 BEAST_EXPECT(ownerCount(env, buyer) == 0);
744
745 env(trust(buyer, gwAUD(1000)));
746 env.close();
747 BEAST_EXPECT(ownerCount(env, buyer) == 1);
748 env.close();
749
750 // Issuer (alice) must have a trust line for the offered funds.
751 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
752 token::Owner(alice),
753 Ter(tecNO_LINE));
754 env.close();
755 BEAST_EXPECT(ownerCount(env, buyer) == 1);
756
757 // Give alice the needed trust line, but freeze it.
758 env(trust(gw, alice["AUD"](999), tfSetFreeze));
759 env.close();
760
761 // Issuer (alice) must have a trust line for the offered funds and
762 // the trust line may not be frozen.
763 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
764 token::Owner(alice),
765 Ter(tecFROZEN));
766 env.close();
767 BEAST_EXPECT(ownerCount(env, buyer) == 1);
768
769 // Unfreeze alice's trustline.
770 env(trust(gw, alice["AUD"](999), tfClearFreeze));
771 env.close();
772
773 // Can't transfer the NFT if the transferable flag is not set.
774 env(token::createOffer(buyer, nftNoXferID, gwAUD(1000)),
775 token::Owner(alice),
777 env.close();
778 BEAST_EXPECT(ownerCount(env, buyer) == 1);
779
780 // Give buyer the needed trust line, but freeze it.
781 env(trust(gw, buyer["AUD"](999), tfSetFreeze));
782 env.close();
783
784 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
785 token::Owner(alice),
786 Ter(tecFROZEN));
787 env.close();
788 BEAST_EXPECT(ownerCount(env, buyer) == 1);
789
790 // Unfreeze buyer's trust line, but buyer has no actual gwAUD.
791 // to cover the offer.
792 env(trust(gw, buyer["AUD"](999), tfClearFreeze));
793 env(trust(buyer, gwAUD(1000)));
794 env.close();
795
796 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
797 token::Owner(alice),
798 Ter(tecUNFUNDED_OFFER));
799 env.close();
800 BEAST_EXPECT(ownerCount(env, buyer) == 1); // the trust line.
801
802 //----------------------------------------------------------------------
803 // doApply
804
805 // Give buyer almost enough AUD to cover the offer...
806 env(pay(gw, buyer, gwAUD(999)));
807 env.close();
808
809 // However buyer doesn't have enough XRP to cover the reserve for
810 // an NFT offer.
811 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
812 token::Owner(alice),
814 env.close();
815 BEAST_EXPECT(ownerCount(env, buyer) == 1);
816
817 // Give buyer almost enough XRP to cover the reserve.
818 auto const baseFee = env.current()->fees().base;
819 env(pay(env.master, buyer, XRP(50) + drops(baseFee * 12 - 1)));
820 env.close();
821
822 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
823 token::Owner(alice),
825 env.close();
826 BEAST_EXPECT(ownerCount(env, buyer) == 1);
827
828 // Give buyer just enough XRP to cover the reserve for the offer.
829 env(pay(env.master, buyer, drops(baseFee + 1)));
830 env.close();
831
832 // We don't care whether the offer is fully funded until the offer is
833 // accepted. Success at last!
834 env(token::createOffer(buyer, nftAlice0ID, gwAUD(1000)),
835 token::Owner(alice),
836 Ter(tesSUCCESS));
837 env.close();
838 BEAST_EXPECT(ownerCount(env, buyer) == 2);
839 }
840
841 void
843 {
844 testcase("Invalid NFT offer cancel");
845
846 using namespace test::jtx;
847
848 Env env{*this, features};
849 Account const alice{"alice"};
850 Account const buyer{"buyer"};
851 Account const gw("gw");
852 IOU const gwAUD(gw["AUD"]);
853
854 env.fund(XRP(1000), alice, buyer, gw);
855 env.close();
856 BEAST_EXPECT(ownerCount(env, alice) == 0);
857
858 uint256 const nftAlice0ID = token::getNextID(env, alice, 0, tfTransferable);
859 env(token::mint(alice, 0u), Txflags(tfTransferable));
860 env.close();
861 BEAST_EXPECT(ownerCount(env, alice) == 1);
862
863 // This is the offer we'll try to cancel.
864 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
865 env(token::createOffer(buyer, nftAlice0ID, XRP(1)), token::Owner(alice), Ter(tesSUCCESS));
866 env.close();
867 BEAST_EXPECT(ownerCount(env, buyer) == 1);
868
869 //----------------------------------------------------------------------
870 // preflight
871
872 // Set a negative fee.
873 env(token::cancelOffer(buyer, {buyerOfferIndex}),
874 Fee(STAmount(10ull, true)),
875 Ter(temBAD_FEE));
876 env.close();
877 BEAST_EXPECT(ownerCount(env, buyer) == 1);
878
879 // Set an invalid flag.
880 env(token::cancelOffer(buyer, {buyerOfferIndex}),
881 Txflags(0x00008000),
882 Ter(temINVALID_FLAG));
883 env.close();
884 BEAST_EXPECT(ownerCount(env, buyer) == 1);
885
886 // Empty list of tokens to delete.
887 {
888 json::Value jv = token::cancelOffer(buyer);
889 jv[sfNFTokenOffers.jsonName] = json::ValueType::Array;
890 env(jv, Ter(temMALFORMED));
891 env.close();
892 BEAST_EXPECT(ownerCount(env, buyer) == 1);
893 }
894
895 // Only test this with fixCleanup3_2_0 enabled. Without the fix,
896 // an assert-enabled build can crash when Ledger::read() receives
897 // a zero-key offer ID.
898 if (features[fixCleanup3_2_0])
899 {
900 // Zero is not a valid offer ID.
901 env(token::cancelOffer(buyer, {uint256{}}), Ter(temMALFORMED));
902 env.close();
903 BEAST_EXPECT(ownerCount(env, buyer) == 1);
904
905 // List of offer IDs containing zero is invalid.
906 // craftedIndex is not a valid offer index but it is not zero.
907 auto const craftedIndex = keylet::nftokenOffer(gw, env.seq(gw)).key;
908 env(token::cancelOffer(buyer, {buyerOfferIndex, uint256{}, craftedIndex}),
909 Ter(temMALFORMED));
910 env.close();
911 BEAST_EXPECT(ownerCount(env, buyer) == 1);
912 }
913
914 // List of tokens to delete is too long.
915 {
916 std::vector<uint256> const offers(kMaxTokenOfferCancelCount + 1, buyerOfferIndex);
917
918 env(token::cancelOffer(buyer, offers), Ter(temMALFORMED));
919 env.close();
920 BEAST_EXPECT(ownerCount(env, buyer) == 1);
921 }
922
923 // Duplicate entries are not allowed in the list of offers to cancel.
924 env(token::cancelOffer(buyer, {buyerOfferIndex, buyerOfferIndex}), Ter(temMALFORMED));
925 env.close();
926 BEAST_EXPECT(ownerCount(env, buyer) == 1);
927
928 // Provide neither offers to cancel nor a root index.
929 env(token::cancelOffer(buyer), Ter(temMALFORMED));
930 env.close();
931 BEAST_EXPECT(ownerCount(env, buyer) == 1);
932
933 //----------------------------------------------------------------------
934 // preclaim
935
936 // Make a non-root directory that we can pass as a root index.
937 env(pay(env.master, gw, XRP(5000)));
938 env.close();
939 for (std::uint32_t i = 1; i < 34; ++i)
940 {
941 env(offer(gw, XRP(i), gwAUD(1)));
942 env.close();
943 }
944
945 {
946 // gw attempts to cancel a Check as through it is an NFTokenOffer.
947 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
948 env(check::create(gw, env.master, XRP(300)));
949 env.close();
950
951 env(token::cancelOffer(gw, {gwCheckId}), Ter(tecNO_PERMISSION));
952 env.close();
953
954 // Cancel the check so it doesn't mess up later tests.
955 env(check::cancel(gw, gwCheckId));
956 env.close();
957 }
958
959 // gw attempts to cancel an offer they don't have permission to cancel.
960 env(token::cancelOffer(gw, {buyerOfferIndex}), Ter(tecNO_PERMISSION));
961 env.close();
962 BEAST_EXPECT(ownerCount(env, buyer) == 1);
963
964 //----------------------------------------------------------------------
965 // doApply
966 //
967 // The tefBAD_LEDGER conditions are too hard to test.
968 // But let's see a successful offer cancel.
969 env(token::cancelOffer(buyer, {buyerOfferIndex}));
970 env.close();
971 BEAST_EXPECT(ownerCount(env, buyer) == 0);
972 }
973
974 void
976 {
977 testcase("Invalid NFT offer accept");
978
979 using namespace test::jtx;
980
981 Env env{*this, features};
982 Account const alice{"alice"};
983 Account const buyer{"buyer"};
984 Account const gw("gw");
985 IOU const gwAUD(gw["AUD"]);
986
987 env.fund(XRP(1000), alice, buyer, gw);
988 env.close();
989 BEAST_EXPECT(ownerCount(env, alice) == 0);
990 BEAST_EXPECT(ownerCount(env, buyer) == 0);
991
992 uint256 const nftAlice0ID = token::getNextID(env, alice, 0, tfTransferable);
993 env(token::mint(alice, 0u), Txflags(tfTransferable));
994 env.close();
995 uint8_t aliceCount = 1;
996 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
997
998 uint256 const nftXrpOnlyID = token::getNextID(env, alice, 0, tfOnlyXRP | tfTransferable);
999 env(token::mint(alice, 0), Txflags(tfOnlyXRP | tfTransferable));
1000 env.close();
1001 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1002
1003 uint256 const nftNoXferID = token::getNextID(env, alice, 0);
1004 env(token::mint(alice, 0));
1005 env.close();
1006 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1007
1008 // alice creates sell offers for her nfts.
1009 uint256 const plainOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1010 env(token::createOffer(alice, nftAlice0ID, XRP(10)), Txflags(tfSellNFToken));
1011 env.close();
1012 aliceCount++;
1013 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1014
1015 uint256 const audOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1016 env(token::createOffer(alice, nftAlice0ID, gwAUD(30)), Txflags(tfSellNFToken));
1017 env.close();
1018 aliceCount++;
1019 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1020
1021 uint256 const xrpOnlyOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1022 env(token::createOffer(alice, nftXrpOnlyID, XRP(20)), Txflags(tfSellNFToken));
1023 env.close();
1024 aliceCount++;
1025 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1026
1027 uint256 const noXferOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1028 env(token::createOffer(alice, nftNoXferID, XRP(30)), Txflags(tfSellNFToken));
1029 env.close();
1030 aliceCount++;
1031 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1032
1033 // alice creates a sell offer that will expire soon.
1034 uint256 const aliceExpOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1035 env(token::createOffer(alice, nftNoXferID, XRP(40)),
1036 Txflags(tfSellNFToken),
1037 token::Expiration(lastClose(env) + 5));
1038 env.close();
1039 aliceCount++;
1040 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1041
1042 // buyer creates a Buy offer that will expire soon.
1043 uint256 const buyerExpOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1044 env(token::createOffer(buyer, nftAlice0ID, XRP(40)),
1045 token::Owner(alice),
1046 token::Expiration(lastClose(env) + 5));
1047 env.close();
1048 uint8_t buyerCount = 1;
1049 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1050
1051 //----------------------------------------------------------------------
1052 // preflight
1053
1054 // Set a negative fee.
1055 env(token::acceptSellOffer(buyer, noXferOfferIndex),
1056 Fee(STAmount(10ull, true)),
1057 Ter(temBAD_FEE));
1058 env.close();
1059 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1060
1061 // Set an invalid flag.
1062 env(token::acceptSellOffer(buyer, noXferOfferIndex),
1063 Txflags(0x00008000),
1064 Ter(temINVALID_FLAG));
1065 env.close();
1066 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1067
1068 // Supply nether an sfNFTokenBuyOffer nor an sfNFTokenSellOffer field.
1069 {
1070 json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
1071 jv.removeMember(sfNFTokenSellOffer.jsonName);
1072 env(jv, Ter(temMALFORMED));
1073 env.close();
1074 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1075 }
1076
1077 // A buy offer may not contain a sfNFTokenBrokerFee field.
1078 {
1079 json::Value jv = token::acceptBuyOffer(buyer, noXferOfferIndex);
1080 jv[sfNFTokenBrokerFee.jsonName] = STAmount(500000).getJson(JsonOptions::Values::None);
1081 env(jv, Ter(temMALFORMED));
1082 env.close();
1083 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1084 }
1085
1086 // A sell offer may not contain a sfNFTokenBrokerFee field.
1087 {
1088 json::Value jv = token::acceptSellOffer(buyer, noXferOfferIndex);
1089 jv[sfNFTokenBrokerFee.jsonName] = STAmount(500000).getJson(JsonOptions::Values::None);
1090 env(jv, Ter(temMALFORMED));
1091 env.close();
1092 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1093 }
1094
1095 // A brokered offer may not contain a negative or zero brokerFee.
1096 env(token::brokerOffers(buyer, noXferOfferIndex, xrpOnlyOfferIndex),
1097 token::BrokerFee(gwAUD(0)),
1098 Ter(temMALFORMED));
1099 env.close();
1100 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1101
1102 //----------------------------------------------------------------------
1103 // preclaim
1104
1105 // The buy offer must be non-zero.
1106 env(token::acceptBuyOffer(buyer, beast::kZero), Ter(tecOBJECT_NOT_FOUND));
1107 env.close();
1108 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1109
1110 // The buy offer must be present in the ledger.
1111 uint256 const missingOfferIndex = keylet::nftokenOffer(alice, 1).key;
1112 env(token::acceptBuyOffer(buyer, missingOfferIndex), Ter(tecOBJECT_NOT_FOUND));
1113 env.close();
1114 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1115
1116 // The buy offer must not have expired.
1117 // NOTE: this is only a preclaim check with the fixCleanup3_1_3 amendment disabled.
1118 env(token::acceptBuyOffer(alice, buyerExpOfferIndex), Ter(tecEXPIRED));
1119 env.close();
1120 if (features[fixCleanup3_1_3])
1121 {
1122 buyerCount--;
1123 BEAST_EXPECT(!env.closed()->exists(keylet::nftokenOffer(buyerExpOfferIndex)));
1124 }
1125 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1126
1127 // The sell offer must be non-zero.
1128 env(token::acceptSellOffer(buyer, beast::kZero), Ter(tecOBJECT_NOT_FOUND));
1129 env.close();
1130 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1131
1132 // The sell offer must be present in the ledger.
1133 env(token::acceptSellOffer(buyer, missingOfferIndex), Ter(tecOBJECT_NOT_FOUND));
1134 env.close();
1135 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1136
1137 // The sell offer must not have expired.
1138 // NOTE: this is only a preclaim check with the fixCleanup3_1_3 amendment disabled.
1139 env(token::acceptSellOffer(buyer, aliceExpOfferIndex), Ter(tecEXPIRED));
1140
1141 env.close();
1142 // Alice's count is decremented by one when the expired offer is
1143 // removed.
1144 if (features[fixCleanup3_1_3])
1145 {
1146 aliceCount--;
1147 BEAST_EXPECT(!env.closed()->exists(keylet::nftokenOffer(aliceExpOfferIndex)));
1148 }
1149 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1150 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1151
1152 //----------------------------------------------------------------------
1153 // preclaim brokered
1154
1155 // alice and buyer need trustlines before buyer can to create an
1156 // offer for gwAUD.
1157 env(trust(alice, gwAUD(1000)));
1158 env(trust(buyer, gwAUD(1000)));
1159 env.close();
1160 env(pay(gw, buyer, gwAUD(30)));
1161 env.close();
1162 aliceCount++;
1163 buyerCount++;
1164 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1165 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1166
1167 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1168 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1169
1170 // We're about to exercise offer brokering, so we need
1171 // corresponding buy and sell offers.
1172 {
1173 // buyer creates a buy offer for one of alice's nfts.
1174 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1175 env(token::createOffer(buyer, nftAlice0ID, gwAUD(29)), token::Owner(alice));
1176 env.close();
1177 buyerCount++;
1178 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1179
1180 // gw attempts to broker offers that are not for the same token.
1181 env(token::brokerOffers(gw, buyerOfferIndex, xrpOnlyOfferIndex),
1183 env.close();
1184 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1185
1186 // gw attempts to broker offers that are not for the same currency.
1187 env(token::brokerOffers(gw, buyerOfferIndex, plainOfferIndex),
1189 env.close();
1190 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1191
1192 // In a brokered offer, the buyer must offer greater than or
1193 // equal to the selling price.
1194 env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
1196 env.close();
1197 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1198
1199 // Remove buyer's offer.
1200 env(token::cancelOffer(buyer, {buyerOfferIndex}));
1201 env.close();
1202 buyerCount--;
1203 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1204 }
1205 {
1206 // buyer creates a buy offer for one of alice's nfts.
1207 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1208 env(token::createOffer(buyer, nftAlice0ID, gwAUD(31)), token::Owner(alice));
1209 env.close();
1210 buyerCount++;
1211 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1212
1213 // Broker sets their fee in a denomination other than the one
1214 // used by the offers
1215 env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
1216 token::BrokerFee(XRP(40)),
1218 env.close();
1219 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1220
1221 // Broker fee way too big.
1222 env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
1223 token::BrokerFee(gwAUD(31)),
1225 env.close();
1226 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1227
1228 // Broker fee is smaller, but still too big once the offer
1229 // seller's minimum is taken into account.
1230 env(token::brokerOffers(gw, buyerOfferIndex, audOfferIndex),
1231 token::BrokerFee(gwAUD(1.5)),
1233 env.close();
1234 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1235
1236 // Remove buyer's offer.
1237 env(token::cancelOffer(buyer, {buyerOfferIndex}));
1238 env.close();
1239 buyerCount--;
1240 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1241 }
1242 //----------------------------------------------------------------------
1243 // preclaim buy
1244 {
1245 // buyer creates a buy offer for one of alice's nfts.
1246 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1247 env(token::createOffer(buyer, nftAlice0ID, gwAUD(30)), token::Owner(alice));
1248 env.close();
1249 buyerCount++;
1250 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1251
1252 // Don't accept a buy offer if the sell flag is set.
1253 env(token::acceptBuyOffer(buyer, plainOfferIndex), Ter(tecNFTOKEN_OFFER_TYPE_MISMATCH));
1254 env.close();
1255 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1256
1257 // An account can't accept its own offer.
1258 env(token::acceptBuyOffer(buyer, buyerOfferIndex),
1260 env.close();
1261 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1262
1263 // An offer acceptor must have enough funds to pay for the offer.
1264 env(pay(buyer, gw, gwAUD(30)));
1265 env.close();
1266 BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
1267 env(token::acceptBuyOffer(alice, buyerOfferIndex), Ter(tecINSUFFICIENT_FUNDS));
1268 env.close();
1269 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1270
1271 // alice gives her NFT to gw, so alice no longer owns nftAlice0.
1272 {
1273 uint256 const offerIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1274 env(token::createOffer(alice, nftAlice0ID, XRP(0)), Txflags(tfSellNFToken));
1275 env.close();
1276 env(token::acceptSellOffer(gw, offerIndex));
1277 env.close();
1278 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1279 }
1280 env(pay(gw, buyer, gwAUD(30)));
1281 env.close();
1282
1283 // alice can't accept a buy offer for an NFT she no longer owns.
1284 env(token::acceptBuyOffer(alice, buyerOfferIndex), Ter(tecNO_PERMISSION));
1285 env.close();
1286 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1287
1288 // Remove buyer's offer.
1289 env(token::cancelOffer(buyer, {buyerOfferIndex}));
1290 env.close();
1291 buyerCount--;
1292 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1293 }
1294 //----------------------------------------------------------------------
1295 // preclaim sell
1296 {
1297 // buyer creates a buy offer for one of alice's nfts.
1298 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1299 env(token::createOffer(buyer, nftXrpOnlyID, XRP(30)), token::Owner(alice));
1300 env.close();
1301 buyerCount++;
1302 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1303
1304 // Don't accept a sell offer without the sell flag set.
1305 env(token::acceptSellOffer(alice, buyerOfferIndex),
1307 env.close();
1308 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1309
1310 // An account can't accept its own offer.
1311 env(token::acceptSellOffer(alice, plainOfferIndex),
1313 env.close();
1314 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1315
1316 // The seller must currently be in possession of the token they
1317 // are selling. alice gave nftAlice0ID to gw.
1318 env(token::acceptSellOffer(buyer, plainOfferIndex), Ter(tecNO_PERMISSION));
1319 env.close();
1320 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1321
1322 // gw gives nftAlice0ID back to alice. That allows us to check
1323 // buyer attempting to accept one of alice's offers with
1324 // insufficient funds.
1325 {
1326 uint256 const offerIndex = keylet::nftokenOffer(gw, env.seq(gw)).key;
1327 env(token::createOffer(gw, nftAlice0ID, XRP(0)), Txflags(tfSellNFToken));
1328 env.close();
1329 env(token::acceptSellOffer(alice, offerIndex));
1330 env.close();
1331 BEAST_EXPECT(ownerCount(env, alice) == aliceCount);
1332 }
1333 env(pay(buyer, gw, gwAUD(30)));
1334 env.close();
1335 BEAST_EXPECT(env.balance(buyer, gwAUD) == gwAUD(0));
1336 env(token::acceptSellOffer(buyer, audOfferIndex), Ter(tecINSUFFICIENT_FUNDS));
1337 env.close();
1338 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
1339 }
1340
1341 //----------------------------------------------------------------------
1342 // doApply
1343 //
1344 // As far as I can see kNone of the failure modes are accessible as
1345 // long as the preflight and preclaim conditions are met.
1346 }
1347
1348 void
1350 {
1351 // Exercise NFTs with flagBurnable set and not set.
1352 testcase("Mint flagBurnable");
1353
1354 using namespace test::jtx;
1355
1356 Env env{*this, features};
1357 Account const alice{"alice"};
1358 Account const buyer{"buyer"};
1359 Account const minter1{"minter1"};
1360 Account const minter2{"minter2"};
1361
1362 env.fund(XRP(1000), alice, buyer, minter1, minter2);
1363 env.close();
1364 BEAST_EXPECT(ownerCount(env, alice) == 0);
1365
1366 // alice selects minter as her minter.
1367 env(token::setMinter(alice, minter1));
1368 env.close();
1369
1370 // A lambda that...
1371 // 1. creates an alice nft
1372 // 2. minted by minter and
1373 // 3. transfers that nft to buyer.
1374 auto nftToBuyer = [&env, &alice, &minter1, &buyer](std::uint32_t flags) {
1375 uint256 const nftID{token::getNextID(env, alice, 0u, flags)};
1376 env(token::mint(minter1, 0u), token::Issuer(alice), Txflags(flags));
1377 env.close();
1378
1379 uint256 const offerIndex = keylet::nftokenOffer(minter1, env.seq(minter1)).key;
1380 env(token::createOffer(minter1, nftID, XRP(0)), Txflags(tfSellNFToken));
1381 env.close();
1382
1383 env(token::acceptSellOffer(buyer, offerIndex));
1384 env.close();
1385
1386 return nftID;
1387 };
1388
1389 // An NFT without flagBurnable can only be burned by its owner.
1390 {
1391 uint256 const noBurnID = nftToBuyer(0);
1392 env(token::burn(alice, noBurnID), token::Owner(buyer), Ter(tecNO_PERMISSION));
1393 env.close();
1394 env(token::burn(minter1, noBurnID), token::Owner(buyer), Ter(tecNO_PERMISSION));
1395 env.close();
1396 env(token::burn(minter2, noBurnID), token::Owner(buyer), Ter(tecNO_PERMISSION));
1397 env.close();
1398
1399 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1400 env(token::burn(buyer, noBurnID), token::Owner(buyer));
1401 env.close();
1402 BEAST_EXPECT(ownerCount(env, buyer) == 0);
1403 }
1404 // An NFT with flagBurnable can be burned by the issuer.
1405 {
1406 uint256 const burnableID = nftToBuyer(tfBurnable);
1407 env(token::burn(minter2, burnableID), token::Owner(buyer), Ter(tecNO_PERMISSION));
1408 env.close();
1409
1410 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1411 env(token::burn(alice, burnableID), token::Owner(buyer));
1412 env.close();
1413 BEAST_EXPECT(ownerCount(env, buyer) == 0);
1414 }
1415 // An NFT with flagBurnable can be burned by the owner.
1416 {
1417 uint256 const burnableID = nftToBuyer(tfBurnable);
1418 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1419 env(token::burn(buyer, burnableID));
1420 env.close();
1421 BEAST_EXPECT(ownerCount(env, buyer) == 0);
1422 }
1423 // An NFT with flagBurnable can be burned by the minter.
1424 {
1425 uint256 const burnableID = nftToBuyer(tfBurnable);
1426 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1427 env(token::burn(buyer, burnableID), token::Owner(buyer));
1428 env.close();
1429 BEAST_EXPECT(ownerCount(env, buyer) == 0);
1430 }
1431 // An nft with flagBurnable may be burned by the issuers' minter,
1432 // who may not be the original minter.
1433 {
1434 uint256 const burnableID = nftToBuyer(tfBurnable);
1435 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1436
1437 env(token::setMinter(alice, minter2));
1438 env.close();
1439
1440 // minter1 is no longer alice's minter, so no longer has
1441 // permission to burn alice's nfts.
1442 env(token::burn(minter1, burnableID), token::Owner(buyer), Ter(tecNO_PERMISSION));
1443 env.close();
1444 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1445
1446 // minter2, however, can burn alice's nfts.
1447 env(token::burn(minter2, burnableID), token::Owner(buyer));
1448 env.close();
1449 BEAST_EXPECT(ownerCount(env, buyer) == 0);
1450 }
1451 }
1452
1453 void
1455 {
1456 // Exercise NFTs with flagOnlyXRP set and not set.
1457 testcase("Mint flagOnlyXRP");
1458
1459 using namespace test::jtx;
1460
1461 Env env{*this, features};
1462 Account const alice{"alice"};
1463 Account const buyer{"buyer"};
1464 Account const gw("gw");
1465 IOU const gwAUD(gw["AUD"]);
1466
1467 // Set trust lines so alice and buyer can use gwAUD.
1468 env.fund(XRP(1000), alice, buyer, gw);
1469 env.close();
1470 env(trust(alice, gwAUD(1000)));
1471 env(trust(buyer, gwAUD(1000)));
1472 env.close();
1473 env(pay(gw, buyer, gwAUD(100)));
1474
1475 // Don't set flagOnlyXRP and offers can be made with IOUs.
1476 {
1477 uint256 const nftIOUsOkayID{token::getNextID(env, alice, 0u, tfTransferable)};
1478 env(token::mint(alice, 0u), Txflags(tfTransferable));
1479 env.close();
1480
1481 BEAST_EXPECT(ownerCount(env, alice) == 2);
1482 uint256 const aliceOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1483 env(token::createOffer(alice, nftIOUsOkayID, gwAUD(50)), Txflags(tfSellNFToken));
1484 env.close();
1485 BEAST_EXPECT(ownerCount(env, alice) == 3);
1486
1487 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1488 uint256 const buyerOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
1489 env(token::createOffer(buyer, nftIOUsOkayID, gwAUD(50)), token::Owner(alice));
1490 env.close();
1491 BEAST_EXPECT(ownerCount(env, buyer) == 2);
1492
1493 // Cancel the two offers just to be tidy.
1494 env(token::cancelOffer(alice, {aliceOfferIndex}));
1495 env(token::cancelOffer(buyer, {buyerOfferIndex}));
1496 env.close();
1497 BEAST_EXPECT(ownerCount(env, alice) == 2);
1498 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1499
1500 // Also burn alice's nft.
1501 env(token::burn(alice, nftIOUsOkayID));
1502 env.close();
1503 BEAST_EXPECT(ownerCount(env, alice) == 1);
1504 }
1505
1506 // Set flagOnlyXRP and offers using IOUs are rejected.
1507 {
1508 uint256 const nftOnlyXrpID{
1509 token::getNextID(env, alice, 0u, tfOnlyXRP | tfTransferable)};
1510 env(token::mint(alice, 0u), Txflags(tfOnlyXRP | tfTransferable));
1511 env.close();
1512
1513 BEAST_EXPECT(ownerCount(env, alice) == 2);
1514 env(token::createOffer(alice, nftOnlyXrpID, gwAUD(50)),
1515 Txflags(tfSellNFToken),
1516 Ter(temBAD_AMOUNT));
1517 env.close();
1518 BEAST_EXPECT(ownerCount(env, alice) == 2);
1519
1520 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1521 env(token::createOffer(buyer, nftOnlyXrpID, gwAUD(50)),
1522 token::Owner(alice),
1523 Ter(temBAD_AMOUNT));
1524 env.close();
1525 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1526
1527 // However offers for XRP are okay.
1528 BEAST_EXPECT(ownerCount(env, alice) == 2);
1529 env(token::createOffer(alice, nftOnlyXrpID, XRP(60)), Txflags(tfSellNFToken));
1530 env.close();
1531 BEAST_EXPECT(ownerCount(env, alice) == 3);
1532
1533 BEAST_EXPECT(ownerCount(env, buyer) == 1);
1534 env(token::createOffer(buyer, nftOnlyXrpID, XRP(60)), token::Owner(alice));
1535 env.close();
1536 BEAST_EXPECT(ownerCount(env, buyer) == 2);
1537 }
1538 }
1539
1540 void
1542 {
1543 // Exercise NFTs with flagCreateTrustLines set and not set.
1544 testcase("Mint flagCreateTrustLines");
1545
1546 using namespace test::jtx;
1547
1548 Account const alice{"alice"};
1549 Account const becky{"becky"};
1550 Account const cheri{"cheri"};
1551 Account const gw("gw");
1552 IOU const gwAUD(gw["AUD"]);
1553 IOU const gwCAD(gw["CAD"]);
1554 IOU const gwEUR(gw["EUR"]);
1555
1556 // The behavior of this test changes dramatically based on the
1557 // presence (or absence) of the fixRemoveNFTokenAutoTrustLine
1558 // amendment. So we test both cases here.
1559 for (auto const& tweakedFeatures :
1560 {features - fixRemoveNFTokenAutoTrustLine, features | fixRemoveNFTokenAutoTrustLine})
1561 {
1562 Env env{*this, tweakedFeatures};
1563 env.fund(XRP(1000), alice, becky, cheri, gw);
1564 env.close();
1565
1566 // Set trust lines so becky and cheri can use gw's currency.
1567 env(trust(becky, gwAUD(1000)));
1568 env(trust(cheri, gwAUD(1000)));
1569 env(trust(becky, gwCAD(1000)));
1570 env(trust(cheri, gwCAD(1000)));
1571 env(trust(becky, gwEUR(1000)));
1572 env(trust(cheri, gwEUR(1000)));
1573 env.close();
1574 env(pay(gw, becky, gwAUD(500)));
1575 env(pay(gw, becky, gwCAD(500)));
1576 env(pay(gw, becky, gwEUR(500)));
1577 env(pay(gw, cheri, gwAUD(500)));
1578 env(pay(gw, cheri, gwCAD(500)));
1579 env.close();
1580
1581 // An nft without flagCreateTrustLines but with a non-zero transfer
1582 // fee will not allow creating offers that use IOUs for payment.
1583 for (std::uint32_t const xferFee : {0, 1})
1584 {
1585 uint256 const nftNoAutoTrustID{
1586 token::getNextID(env, alice, 0u, tfTransferable, xferFee)};
1587 env(token::mint(alice, 0u), token::XferFee(xferFee), Txflags(tfTransferable));
1588 env.close();
1589
1590 // becky buys the nft for 1 drop.
1591 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1592 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)), token::Owner(alice));
1593 env.close();
1594 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
1595 env.close();
1596
1597 // becky attempts to sell the nft for AUD.
1598 TER const createOfferTER = (xferFee != 0u) ? TER(tecNO_LINE) : TER(tesSUCCESS);
1599 uint256 const beckyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1600 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
1601 Txflags(tfSellNFToken),
1602 Ter(createOfferTER));
1603 env.close();
1604
1605 // cheri offers to buy the nft for CAD.
1606 uint256 const cheriOfferIndex = keylet::nftokenOffer(cheri, env.seq(cheri)).key;
1607 env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
1608 token::Owner(becky),
1609 Ter(createOfferTER));
1610 env.close();
1611
1612 // To keep things tidy, cancel the offers.
1613 env(token::cancelOffer(becky, {beckyOfferIndex}));
1614 env(token::cancelOffer(cheri, {cheriOfferIndex}));
1615 env.close();
1616 }
1617 // An nft with flagCreateTrustLines but with a non-zero transfer
1618 // fee allows transfers using IOUs for payment.
1619 {
1620 std::uint16_t const transferFee = 10000; // 10%
1621
1622 uint256 const nftAutoTrustID{
1623 token::getNextID(env, alice, 0u, tfTransferable | tfTrustLine, transferFee)};
1624
1625 // If the fixRemoveNFTokenAutoTrustLine amendment is active
1626 // then this transaction fails.
1627 {
1628 TER const mintTER = tweakedFeatures[fixRemoveNFTokenAutoTrustLine]
1629 ? static_cast<TER>(temINVALID_FLAG)
1630 : static_cast<TER>(tesSUCCESS);
1631
1632 env(token::mint(alice, 0u),
1633 token::XferFee(transferFee),
1634 Txflags(tfTransferable | tfTrustLine),
1635 Ter(mintTER));
1636 env.close();
1637
1638 // If fixRemoveNFTokenAutoTrustLine is active the rest
1639 // of this test falls on its face.
1640 if (tweakedFeatures[fixRemoveNFTokenAutoTrustLine])
1641 break;
1642 }
1643 // becky buys the nft for 1 drop.
1644 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1645 env(token::createOffer(becky, nftAutoTrustID, drops(1)), token::Owner(alice));
1646 env.close();
1647 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
1648 env.close();
1649
1650 // becky sells the nft for AUD.
1651 uint256 const beckySellOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1652 env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)), Txflags(tfSellNFToken));
1653 env.close();
1654 env(token::acceptSellOffer(cheri, beckySellOfferIndex));
1655 env.close();
1656
1657 // alice should now have a trust line for gwAUD.
1658 BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
1659
1660 // becky buys the nft back for CAD.
1661 uint256 const beckyBuyBackOfferIndex =
1662 keylet::nftokenOffer(becky, env.seq(becky)).key;
1663 env(token::createOffer(becky, nftAutoTrustID, gwCAD(50)), token::Owner(cheri));
1664 env.close();
1665 env(token::acceptBuyOffer(cheri, beckyBuyBackOfferIndex));
1666 env.close();
1667
1668 // alice should now have a trust line for gwAUD and gwCAD.
1669 BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(10));
1670 BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(5));
1671 }
1672 // Now that alice has trust lines preestablished, an nft without
1673 // flagCreateTrustLines will work for preestablished trust lines.
1674 {
1675 std::uint16_t const transferFee = 5000; // 5%
1676 uint256 const nftNoAutoTrustID{
1677 token::getNextID(env, alice, 0u, tfTransferable, transferFee)};
1678 env(token::mint(alice, 0u), token::XferFee(transferFee), Txflags(tfTransferable));
1679 env.close();
1680
1681 // alice sells the nft using AUD.
1682 uint256 const aliceSellOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1683 env(token::createOffer(alice, nftNoAutoTrustID, gwAUD(200)),
1684 Txflags(tfSellNFToken));
1685 env.close();
1686 env(token::acceptSellOffer(cheri, aliceSellOfferIndex));
1687 env.close();
1688
1689 // alice should now have AUD(210):
1690 // o 200 for this sale and
1691 // o 10 for the previous sale's fee.
1692 BEAST_EXPECT(env.balance(alice, gwAUD) == gwAUD(210));
1693
1694 // cheri can't sell the NFT for EUR, but can for CAD.
1695 env(token::createOffer(cheri, nftNoAutoTrustID, gwEUR(50)),
1696 Txflags(tfSellNFToken),
1697 Ter(tecNO_LINE));
1698 env.close();
1699 uint256 const cheriSellOfferIndex = keylet::nftokenOffer(cheri, env.seq(cheri)).key;
1700 env(token::createOffer(cheri, nftNoAutoTrustID, gwCAD(100)),
1701 Txflags(tfSellNFToken));
1702 env.close();
1703 env(token::acceptSellOffer(becky, cheriSellOfferIndex));
1704 env.close();
1705
1706 // alice should now have CAD(10):
1707 // o 5 from this sale's fee and
1708 // o 5 for the previous sale's fee.
1709 BEAST_EXPECT(env.balance(alice, gwCAD) == gwCAD(10));
1710 }
1711 }
1712 }
1713
1714 void
1716 {
1717 // Exercise NFTs with flagTransferable set and not set.
1718 testcase("Mint flagTransferable");
1719
1720 using namespace test::jtx;
1721
1722 Env env{*this, features};
1723
1724 Account const alice{"alice"};
1725 Account const becky{"becky"};
1726 Account const minter{"minter"};
1727
1728 env.fund(XRP(1000), alice, becky, minter);
1729 env.close();
1730
1731 // First try an nft made by alice without flagTransferable set.
1732 {
1733 BEAST_EXPECT(ownerCount(env, alice) == 0);
1734 uint256 const nftAliceNoTransferID{token::getNextID(env, alice, 0u)};
1735 env(token::mint(alice, 0u), token::XferFee(0));
1736 env.close();
1737 BEAST_EXPECT(ownerCount(env, alice) == 1);
1738
1739 // becky tries to offer to buy alice's nft.
1740 BEAST_EXPECT(ownerCount(env, becky) == 0);
1741 env(token::createOffer(becky, nftAliceNoTransferID, XRP(20)),
1742 token::Owner(alice),
1744
1745 // alice offers to sell the nft and becky accepts the offer.
1746 uint256 const aliceSellOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1747 env(token::createOffer(alice, nftAliceNoTransferID, XRP(20)), Txflags(tfSellNFToken));
1748 env.close();
1749 env(token::acceptSellOffer(becky, aliceSellOfferIndex));
1750 env.close();
1751 BEAST_EXPECT(ownerCount(env, alice) == 0);
1752 BEAST_EXPECT(ownerCount(env, becky) == 1);
1753
1754 // becky tries to offer the nft for sale.
1755 env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
1756 Txflags(tfSellNFToken),
1758 env.close();
1759 BEAST_EXPECT(ownerCount(env, alice) == 0);
1760 BEAST_EXPECT(ownerCount(env, becky) == 1);
1761
1762 // becky tries to offer the nft for sale with alice as the
1763 // destination. That also doesn't work.
1764 env(token::createOffer(becky, nftAliceNoTransferID, XRP(21)),
1765 Txflags(tfSellNFToken),
1766 token::Destination(alice),
1768 env.close();
1769 BEAST_EXPECT(ownerCount(env, alice) == 0);
1770 BEAST_EXPECT(ownerCount(env, becky) == 1);
1771
1772 // alice offers to buy the nft back from becky. becky accepts
1773 // the offer.
1774 uint256 const aliceBuyOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1775 env(token::createOffer(alice, nftAliceNoTransferID, XRP(22)), token::Owner(becky));
1776 env.close();
1777 env(token::acceptBuyOffer(becky, aliceBuyOfferIndex));
1778 env.close();
1779 BEAST_EXPECT(ownerCount(env, alice) == 1);
1780 BEAST_EXPECT(ownerCount(env, becky) == 0);
1781
1782 // alice burns her nft so accounting is simpler below.
1783 env(token::burn(alice, nftAliceNoTransferID));
1784 env.close();
1785 BEAST_EXPECT(ownerCount(env, alice) == 0);
1786 BEAST_EXPECT(ownerCount(env, becky) == 0);
1787 }
1788 // Try an nft minted by minter for alice without flagTransferable set.
1789 {
1790 env(token::setMinter(alice, minter));
1791 env.close();
1792
1793 BEAST_EXPECT(ownerCount(env, minter) == 0);
1794 uint256 const nftMinterNoTransferID{token::getNextID(env, alice, 0u)};
1795 env(token::mint(minter), token::Issuer(alice));
1796 env.close();
1797 BEAST_EXPECT(ownerCount(env, minter) == 1);
1798
1799 // becky tries to offer to buy minter's nft.
1800 BEAST_EXPECT(ownerCount(env, becky) == 0);
1801 env(token::createOffer(becky, nftMinterNoTransferID, XRP(20)),
1802 token::Owner(minter),
1804 env.close();
1805 BEAST_EXPECT(ownerCount(env, becky) == 0);
1806
1807 // alice removes authorization of minter.
1808 env(token::clearMinter(alice));
1809 env.close();
1810
1811 // minter tries to offer their nft for sale.
1812 BEAST_EXPECT(ownerCount(env, minter) == 1);
1813 env(token::createOffer(minter, nftMinterNoTransferID, XRP(21)),
1814 Txflags(tfSellNFToken),
1816 env.close();
1817 BEAST_EXPECT(ownerCount(env, minter) == 1);
1818
1819 // Let enough ledgers pass that old transactions are no longer
1820 // retried, then alice gives authorization back to minter.
1821 for (int i = 0; i < 10; ++i)
1822 env.close();
1823
1824 env(token::setMinter(alice, minter));
1825 env.close();
1826 BEAST_EXPECT(ownerCount(env, minter) == 1);
1827
1828 // minter successfully offers their nft for sale.
1829 BEAST_EXPECT(ownerCount(env, minter) == 1);
1830 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
1831 env(token::createOffer(minter, nftMinterNoTransferID, XRP(22)), Txflags(tfSellNFToken));
1832 env.close();
1833 BEAST_EXPECT(ownerCount(env, minter) == 2);
1834
1835 // alice removes authorization of minter so we can see whether
1836 // minter's pre-existing offer still works.
1837 env(token::clearMinter(alice));
1838 env.close();
1839
1840 // becky buys minter's nft even though minter is no longer alice's
1841 // official minter.
1842 BEAST_EXPECT(ownerCount(env, becky) == 0);
1843 env(token::acceptSellOffer(becky, minterSellOfferIndex));
1844 env.close();
1845 BEAST_EXPECT(ownerCount(env, becky) == 1);
1846 BEAST_EXPECT(ownerCount(env, minter) == 0);
1847
1848 // becky attempts to sell the nft.
1849 env(token::createOffer(becky, nftMinterNoTransferID, XRP(23)),
1850 Txflags(tfSellNFToken),
1852 env.close();
1853
1854 // Since minter is not, at the moment, alice's official minter
1855 // they cannot create an offer to buy the nft they minted.
1856 BEAST_EXPECT(ownerCount(env, minter) == 0);
1857 env(token::createOffer(minter, nftMinterNoTransferID, XRP(24)),
1858 token::Owner(becky),
1860 env.close();
1861 BEAST_EXPECT(ownerCount(env, minter) == 0);
1862
1863 // alice can create an offer to buy the nft.
1864 BEAST_EXPECT(ownerCount(env, alice) == 0);
1865 uint256 const aliceBuyOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1866 env(token::createOffer(alice, nftMinterNoTransferID, XRP(25)), token::Owner(becky));
1867 env.close();
1868 BEAST_EXPECT(ownerCount(env, alice) == 1);
1869
1870 // Let enough ledgers pass that old transactions are no longer
1871 // retried, then alice gives authorization back to minter.
1872 for (int i = 0; i < 10; ++i)
1873 env.close();
1874
1875 env(token::setMinter(alice, minter));
1876 env.close();
1877
1878 // Now minter can create an offer to buy the nft.
1879 BEAST_EXPECT(ownerCount(env, minter) == 0);
1880 uint256 const minterBuyOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
1881 env(token::createOffer(minter, nftMinterNoTransferID, XRP(26)), token::Owner(becky));
1882 env.close();
1883 BEAST_EXPECT(ownerCount(env, minter) == 1);
1884
1885 // alice removes authorization of minter so we can see whether
1886 // minter's pre-existing buy offer still works.
1887 env(token::clearMinter(alice));
1888 env.close();
1889
1890 // becky accepts minter's sell offer.
1891 BEAST_EXPECT(ownerCount(env, minter) == 1);
1892 BEAST_EXPECT(ownerCount(env, becky) == 1);
1893 env(token::acceptBuyOffer(becky, minterBuyOfferIndex));
1894 env.close();
1895 BEAST_EXPECT(ownerCount(env, minter) == 1);
1896 BEAST_EXPECT(ownerCount(env, becky) == 0);
1897 BEAST_EXPECT(ownerCount(env, alice) == 1);
1898
1899 // minter burns their nft and alice cancels her offer so the
1900 // next tests can start with a clean slate.
1901 env(token::burn(minter, nftMinterNoTransferID), Ter(tesSUCCESS));
1902 env.close();
1903 env(token::cancelOffer(alice, {aliceBuyOfferIndex}));
1904 env.close();
1905 BEAST_EXPECT(ownerCount(env, alice) == 0);
1906 BEAST_EXPECT(ownerCount(env, becky) == 0);
1907 BEAST_EXPECT(ownerCount(env, minter) == 0);
1908 }
1909 // nfts with flagTransferable set should be buyable and salable
1910 // by anybody.
1911 {
1912 BEAST_EXPECT(ownerCount(env, alice) == 0);
1913 uint256 const nftAliceID{token::getNextID(env, alice, 0u, tfTransferable)};
1914 env(token::mint(alice, 0u), Txflags(tfTransferable));
1915 env.close();
1916 BEAST_EXPECT(ownerCount(env, alice) == 1);
1917
1918 // Both alice and becky can make offers for alice's nft.
1919 uint256 const aliceSellOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
1920 env(token::createOffer(alice, nftAliceID, XRP(20)), Txflags(tfSellNFToken));
1921 env.close();
1922 BEAST_EXPECT(ownerCount(env, alice) == 2);
1923
1924 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1925 env(token::createOffer(becky, nftAliceID, XRP(21)), token::Owner(alice));
1926 env.close();
1927 BEAST_EXPECT(ownerCount(env, alice) == 2);
1928
1929 // becky accepts alice's sell offer.
1930 env(token::acceptSellOffer(becky, aliceSellOfferIndex));
1931 env.close();
1932 BEAST_EXPECT(ownerCount(env, alice) == 0);
1933 BEAST_EXPECT(ownerCount(env, becky) == 2);
1934
1935 // becky offers to sell the nft.
1936 uint256 const beckySellOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
1937 env(token::createOffer(becky, nftAliceID, XRP(22)), Txflags(tfSellNFToken));
1938 env.close();
1939 BEAST_EXPECT(ownerCount(env, alice) == 0);
1940 BEAST_EXPECT(ownerCount(env, becky) == 3);
1941
1942 // minter buys the nft (even though minter is not currently
1943 // alice's minter).
1944 env(token::acceptSellOffer(minter, beckySellOfferIndex));
1945 env.close();
1946 BEAST_EXPECT(ownerCount(env, alice) == 0);
1947 BEAST_EXPECT(ownerCount(env, becky) == 1);
1948 BEAST_EXPECT(ownerCount(env, minter) == 1);
1949
1950 // minter offers to sell the nft.
1951 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
1952 env(token::createOffer(minter, nftAliceID, XRP(23)), Txflags(tfSellNFToken));
1953 env.close();
1954 BEAST_EXPECT(ownerCount(env, alice) == 0);
1955 BEAST_EXPECT(ownerCount(env, becky) == 1);
1956 BEAST_EXPECT(ownerCount(env, minter) == 2);
1957
1958 // alice buys back the nft.
1959 env(token::acceptSellOffer(alice, minterSellOfferIndex));
1960 env.close();
1961 BEAST_EXPECT(ownerCount(env, alice) == 1);
1962 BEAST_EXPECT(ownerCount(env, becky) == 1);
1963 BEAST_EXPECT(ownerCount(env, minter) == 0);
1964
1965 // Remember the buy offer that becky made for alice's token way
1966 // back when? It's still in the ledger, and alice accepts it.
1967 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
1968 env.close();
1969 BEAST_EXPECT(ownerCount(env, alice) == 0);
1970 BEAST_EXPECT(ownerCount(env, becky) == 1);
1971 BEAST_EXPECT(ownerCount(env, minter) == 0);
1972
1973 // Just for tidiness, becky burns the token before shutting
1974 // things down.
1975 env(token::burn(becky, nftAliceID));
1976 env.close();
1977 BEAST_EXPECT(ownerCount(env, alice) == 0);
1978 BEAST_EXPECT(ownerCount(env, becky) == 0);
1979 BEAST_EXPECT(ownerCount(env, minter) == 0);
1980 }
1981 }
1982
1983 void
1985 {
1986 // Exercise NFTs with and without a transferFee.
1987 testcase("Mint transferFee");
1988
1989 using namespace test::jtx;
1990
1991 Env env{*this, features};
1992 auto const baseFee = env.current()->fees().base;
1993
1994 Account const alice{"alice"};
1995 Account const becky{"becky"};
1996 Account const carol{"carol"};
1997 Account const minter{"minter"};
1998 Account const gw{"gw"};
1999 IOU const gwXAU(gw["XAU"]);
2000
2001 env.fund(XRP(1000), alice, becky, carol, minter, gw);
2002 env.close();
2003
2004 env(trust(alice, gwXAU(2000)));
2005 env(trust(becky, gwXAU(2000)));
2006 env(trust(carol, gwXAU(2000)));
2007 env(trust(minter, gwXAU(2000)));
2008 env.close();
2009 env(pay(gw, alice, gwXAU(1000)));
2010 env(pay(gw, becky, gwXAU(1000)));
2011 env(pay(gw, carol, gwXAU(1000)));
2012 env(pay(gw, minter, gwXAU(1000)));
2013 env.close();
2014
2015 // Giving alice a minter helps us see if transfer rates are affected
2016 // by that.
2017 env(token::setMinter(alice, minter));
2018 env.close();
2019
2020 // If there is no transferFee, then alice gets nothing for the
2021 // transfer.
2022 {
2023 BEAST_EXPECT(ownerCount(env, alice) == 1);
2024 BEAST_EXPECT(ownerCount(env, becky) == 1);
2025 BEAST_EXPECT(ownerCount(env, carol) == 1);
2026 BEAST_EXPECT(ownerCount(env, minter) == 1);
2027
2028 uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable);
2029 env(token::mint(alice), Txflags(tfTransferable));
2030 env.close();
2031
2032 // Becky buys the nft for XAU(10). Check balances.
2033 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2034 env(token::createOffer(becky, nftID, gwXAU(10)), token::Owner(alice));
2035 env.close();
2036 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2037 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2038
2039 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
2040 env.close();
2041 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
2042 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
2043
2044 // becky sells nft to carol. alice's balance should not change.
2045 uint256 const beckySellOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2046 env(token::createOffer(becky, nftID, gwXAU(10)), Txflags(tfSellNFToken));
2047 env.close();
2048 env(token::acceptSellOffer(carol, beckySellOfferIndex));
2049 env.close();
2050 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
2051 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2052 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
2053
2054 // minter buys nft from carol. alice's balance should not change.
2055 uint256 const minterBuyOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2056 env(token::createOffer(minter, nftID, gwXAU(10)), token::Owner(carol));
2057 env.close();
2058 env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
2059 env.close();
2060 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
2061 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2062 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
2063 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
2064
2065 // minter sells the nft to alice. gwXAU balances should finish
2066 // where they started.
2067 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2068 env(token::createOffer(minter, nftID, gwXAU(10)), Txflags(tfSellNFToken));
2069 env.close();
2070 env(token::acceptSellOffer(alice, minterSellOfferIndex));
2071 env.close();
2072 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2073 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2074 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
2075 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
2076
2077 // alice burns the nft to make later tests easier to think about.
2078 env(token::burn(alice, nftID));
2079 env.close();
2080 BEAST_EXPECT(ownerCount(env, alice) == 1);
2081 BEAST_EXPECT(ownerCount(env, becky) == 1);
2082 BEAST_EXPECT(ownerCount(env, carol) == 1);
2083 BEAST_EXPECT(ownerCount(env, minter) == 1);
2084 }
2085
2086 // Set the smallest possible transfer fee.
2087 {
2088 // An nft with a transfer fee of 1 basis point.
2089 uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable, 1);
2090 env(token::mint(alice), Txflags(tfTransferable), token::XferFee(1));
2091 env.close();
2092
2093 // Becky buys the nft for XAU(10). Check balances.
2094 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2095 env(token::createOffer(becky, nftID, gwXAU(10)), token::Owner(alice));
2096 env.close();
2097 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2098 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2099
2100 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
2101 env.close();
2102 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
2103 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
2104
2105 // becky sells nft to carol. alice's balance goes up.
2106 uint256 const beckySellOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2107 env(token::createOffer(becky, nftID, gwXAU(10)), Txflags(tfSellNFToken));
2108 env.close();
2109 env(token::acceptSellOffer(carol, beckySellOfferIndex));
2110 env.close();
2111
2112 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0001));
2113 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
2114 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
2115
2116 // minter buys nft from carol. alice's balance goes up.
2117 uint256 const minterBuyOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2118 env(token::createOffer(minter, nftID, gwXAU(10)), token::Owner(carol));
2119 env.close();
2120 env(token::acceptBuyOffer(carol, minterBuyOfferIndex));
2121 env.close();
2122
2123 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010.0002));
2124 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
2125 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
2126 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(990));
2127
2128 // minter sells the nft to alice. Because alice is part of the
2129 // transaction no transfer fee is removed.
2130 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2131 env(token::createOffer(minter, nftID, gwXAU(10)), Txflags(tfSellNFToken));
2132 env.close();
2133 env(token::acceptSellOffer(alice, minterSellOfferIndex));
2134 env.close();
2135 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000.0002));
2136 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(999.9999));
2137 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(999.9999));
2138 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
2139
2140 // alice pays to becky and carol so subsequent tests are easier
2141 // to think about.
2142 env(pay(alice, becky, gwXAU(0.0001)));
2143 env(pay(alice, carol, gwXAU(0.0001)));
2144 env.close();
2145
2146 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2147 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2148 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
2149 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
2150
2151 // alice burns the nft to make later tests easier to think about.
2152 env(token::burn(alice, nftID));
2153 env.close();
2154 BEAST_EXPECT(ownerCount(env, alice) == 1);
2155 BEAST_EXPECT(ownerCount(env, becky) == 1);
2156 BEAST_EXPECT(ownerCount(env, carol) == 1);
2157 BEAST_EXPECT(ownerCount(env, minter) == 1);
2158 }
2159
2160 // Set the largest allowed transfer fee.
2161 {
2162 // A transfer fee greater than 50% is not allowed.
2163 env(token::mint(alice),
2164 Txflags(tfTransferable),
2165 token::XferFee(kMaxTransferFee + 1),
2167 env.close();
2168
2169 // Make an nft with a transfer fee of 50%.
2170 uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable, kMaxTransferFee);
2171 env(token::mint(alice), Txflags(tfTransferable), token::XferFee(kMaxTransferFee));
2172 env.close();
2173
2174 // Becky buys the nft for XAU(10). Check balances.
2175 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2176 env(token::createOffer(becky, nftID, gwXAU(10)), token::Owner(alice));
2177 env.close();
2178 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2179 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2180
2181 env(token::acceptBuyOffer(alice, beckyBuyOfferIndex));
2182 env.close();
2183 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1010));
2184 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(990));
2185
2186 // becky sells nft to minter. alice's balance goes up.
2187 uint256 const beckySellOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2188 env(token::createOffer(becky, nftID, gwXAU(100)), Txflags(tfSellNFToken));
2189 env.close();
2190 env(token::acceptSellOffer(minter, beckySellOfferIndex));
2191 env.close();
2192
2193 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1060));
2194 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
2195 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
2196
2197 // carol buys nft from minter. alice's balance goes up.
2198 uint256 const carolBuyOfferIndex = keylet::nftokenOffer(carol, env.seq(carol)).key;
2199 env(token::createOffer(carol, nftID, gwXAU(10)), token::Owner(minter));
2200 env.close();
2201 env(token::acceptBuyOffer(minter, carolBuyOfferIndex));
2202 env.close();
2203
2204 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1065));
2205 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
2206 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
2207 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(990));
2208
2209 // carol sells the nft to alice. Because alice is part of the
2210 // transaction no transfer fee is removed.
2211 uint256 const carolSellOfferIndex = keylet::nftokenOffer(carol, env.seq(carol)).key;
2212 env(token::createOffer(carol, nftID, gwXAU(10)), Txflags(tfSellNFToken));
2213 env.close();
2214 env(token::acceptSellOffer(alice, carolSellOfferIndex));
2215 env.close();
2216
2217 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1055));
2218 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1040));
2219 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(905));
2220 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
2221
2222 // rebalance so subsequent tests are easier to think about.
2223 env(pay(alice, minter, gwXAU(55)));
2224 env(pay(becky, minter, gwXAU(40)));
2225 env.close();
2226 BEAST_EXPECT(env.balance(alice, gwXAU) == gwXAU(1000));
2227 BEAST_EXPECT(env.balance(becky, gwXAU) == gwXAU(1000));
2228 BEAST_EXPECT(env.balance(carol, gwXAU) == gwXAU(1000));
2229 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
2230
2231 // alice burns the nft to make later tests easier to think about.
2232 env(token::burn(alice, nftID));
2233 env.close();
2234 BEAST_EXPECT(ownerCount(env, alice) == 1);
2235 BEAST_EXPECT(ownerCount(env, becky) == 1);
2236 BEAST_EXPECT(ownerCount(env, carol) == 1);
2237 BEAST_EXPECT(ownerCount(env, minter) == 1);
2238 }
2239
2240 // See the impact of rounding when the nft is sold for small amounts
2241 // of drops.
2242 {
2243 // An nft with a transfer fee of 1 basis point.
2244 uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable, 1);
2245 env(token::mint(alice), Txflags(tfTransferable), token::XferFee(1));
2246 env.close();
2247
2248 // minter buys the nft for XRP(1). Since the transfer involves
2249 // alice there should be no transfer fee.
2250 STAmount aliceBalance = env.balance(alice);
2251 STAmount minterBalance = env.balance(minter);
2252 uint256 const minterBuyOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2253 env(token::createOffer(minter, nftID, XRP(1)), token::Owner(alice));
2254 env.close();
2255 env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
2256 env.close();
2257 aliceBalance += XRP(1) - baseFee;
2258 minterBalance -= XRP(1) + baseFee;
2259 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2260 BEAST_EXPECT(env.balance(minter) == minterBalance);
2261
2262 // minter sells to carol. The payment is just small enough that
2263 // alice does not get any transfer fee.
2264 auto pmt = drops(50000);
2265 STAmount carolBalance = env.balance(carol);
2266 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2267 env(token::createOffer(minter, nftID, pmt), Txflags(tfSellNFToken));
2268 env.close();
2269 env(token::acceptSellOffer(carol, minterSellOfferIndex));
2270 env.close();
2271 minterBalance += pmt - baseFee;
2272 carolBalance -= pmt + baseFee;
2273 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2274 BEAST_EXPECT(env.balance(minter) == minterBalance);
2275 BEAST_EXPECT(env.balance(carol) == carolBalance);
2276
2277 // carol sells to becky. This is the smallest amount to pay for a
2278 // transfer that enables a transfer fee of 1 basis point.
2279 STAmount beckyBalance = env.balance(becky);
2280 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2281 pmt = drops(50001);
2282 env(token::createOffer(becky, nftID, pmt), token::Owner(carol));
2283 env.close();
2284 env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
2285 env.close();
2286 carolBalance += pmt - drops(1) - baseFee;
2287 beckyBalance -= pmt + baseFee;
2288 aliceBalance += drops(1);
2289
2290 BEAST_EXPECT(env.balance(alice) == aliceBalance);
2291 BEAST_EXPECT(env.balance(minter) == minterBalance);
2292 BEAST_EXPECT(env.balance(carol) == carolBalance);
2293 BEAST_EXPECT(env.balance(becky) == beckyBalance);
2294 }
2295
2296 // See the impact of rounding when the nft is sold for small amounts
2297 // of an IOU.
2298 {
2299 // An nft with a transfer fee of 1 basis point.
2300 uint256 const nftID = token::getNextID(env, alice, 0u, tfTransferable, 1);
2301 env(token::mint(alice), Txflags(tfTransferable), token::XferFee(1));
2302 env.close();
2303
2304 // Due to the floating point nature of IOUs we need to
2305 // significantly reduce the gwXAU balances of our accounts prior
2306 // to the iou transfer. Otherwise no transfers will happen.
2307 env(pay(alice, gw, env.balance(alice, gwXAU)));
2308 env(pay(minter, gw, env.balance(minter, gwXAU)));
2309 env(pay(becky, gw, env.balance(becky, gwXAU)));
2310 env.close();
2311
2312 STAmount const startXAUBalance(gwXAU, STAmount::kMinValue, STAmount::kMinOffset + 5);
2313 env(pay(gw, alice, startXAUBalance));
2314 env(pay(gw, minter, startXAUBalance));
2315 env(pay(gw, becky, startXAUBalance));
2316 env.close();
2317
2318 // Here is the smallest expressible gwXAU amount.
2319 STAmount const tinyXAU(gwXAU, STAmount::kMinValue, STAmount::kMinOffset);
2320
2321 // minter buys the nft for tinyXAU. Since the transfer involves
2322 // alice there should be no transfer fee.
2323 STAmount aliceBalance = env.balance(alice, gwXAU);
2324 STAmount minterBalance = env.balance(minter, gwXAU);
2325 uint256 const minterBuyOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2326 env(token::createOffer(minter, nftID, tinyXAU), token::Owner(alice));
2327 env.close();
2328 env(token::acceptBuyOffer(alice, minterBuyOfferIndex));
2329 env.close();
2330 aliceBalance += tinyXAU;
2331 minterBalance -= tinyXAU;
2332 BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
2333 BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
2334
2335 // minter sells to carol.
2336 STAmount carolBalance = env.balance(carol, gwXAU);
2337 uint256 const minterSellOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2338 env(token::createOffer(minter, nftID, tinyXAU), Txflags(tfSellNFToken));
2339 env.close();
2340 env(token::acceptSellOffer(carol, minterSellOfferIndex));
2341 env.close();
2342
2343 minterBalance += tinyXAU;
2344 carolBalance -= tinyXAU;
2345 // tiny XAU is so small that alice does not get a transfer fee.
2346 BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
2347 BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
2348 BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
2349
2350 // carol sells to becky. This is the smallest gwXAU amount
2351 // to pay for a transfer that enables a transfer fee of 1.
2352 STAmount const cheapNFT(gwXAU, STAmount::kMinValue, STAmount::kMinOffset + 5);
2353
2354 STAmount beckyBalance = env.balance(becky, gwXAU);
2355 uint256 const beckyBuyOfferIndex = keylet::nftokenOffer(becky, env.seq(becky)).key;
2356 env(token::createOffer(becky, nftID, cheapNFT), token::Owner(carol));
2357 env.close();
2358 env(token::acceptBuyOffer(carol, beckyBuyOfferIndex));
2359 env.close();
2360
2361 aliceBalance += tinyXAU;
2362 beckyBalance -= cheapNFT;
2363 carolBalance += cheapNFT - tinyXAU;
2364 BEAST_EXPECT(env.balance(alice, gwXAU) == aliceBalance);
2365 BEAST_EXPECT(env.balance(minter, gwXAU) == minterBalance);
2366 BEAST_EXPECT(env.balance(carol, gwXAU) == carolBalance);
2367 BEAST_EXPECT(env.balance(becky, gwXAU) == beckyBalance);
2368 }
2369 }
2370
2371 void
2373 {
2374 // Exercise the NFT taxon field.
2375 testcase("Mint taxon");
2376
2377 using namespace test::jtx;
2378
2379 Env env{*this, features};
2380
2381 Account const alice{"alice"};
2382 Account const becky{"becky"};
2383
2384 env.fund(XRP(1000), alice, becky);
2385 env.close();
2386
2387 // The taxon field is incorporated straight into the NFT ID. So
2388 // tests only need to operate on NFT IDs; we don't need to generate
2389 // any transactions.
2390
2391 // The taxon value should be recoverable from the NFT ID.
2392 {
2393 uint256 const nftID = token::getNextID(env, alice, 0u);
2394 BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon(0));
2395 }
2396
2397 // Make sure the full range of taxon values work. We just tried
2398 // the minimum. Now try the largest.
2399 {
2400 uint256 const nftID = token::getNextID(env, alice, 0xFFFFFFFFu);
2401 BEAST_EXPECT(nft::getTaxon(nftID) == nft::toTaxon((0xFFFFFFFF)));
2402 }
2403
2404 // Do some touch testing to show that the taxon is recoverable no
2405 // matter what else changes around it in the nft ID.
2406 {
2407 std::uint32_t const taxon = randInt<std::uint32_t>();
2408 for (int i = 0; i < 10; ++i)
2409 {
2410 // lambda to produce a useful message on error.
2411 auto check = [this](std::uint32_t taxon, uint256 const& nftID) {
2412 nft::Taxon const gotTaxon = nft::getTaxon(nftID);
2413 if (nft::toTaxon(taxon) == gotTaxon)
2414 {
2415 pass();
2416 }
2417 else
2418 {
2420 ss << "Taxon recovery failed from nftID " << to_string(nftID)
2421 << ". Expected: " << taxon << "; got: " << gotTaxon;
2422 fail(ss.str());
2423 }
2424 };
2425
2426 uint256 const nftAliceID = token::getID(
2427 env,
2428 alice,
2429 taxon,
2433 check(taxon, nftAliceID);
2434
2435 uint256 const nftBeckyID = token::getID(
2436 env,
2437 becky,
2438 taxon,
2442 check(taxon, nftBeckyID);
2443 }
2444 }
2445 }
2446
2447 void
2449 {
2450 // Exercise the NFT URI field.
2451 // 1. Create a number of NFTs with and without URIs.
2452 // 2. Retrieve the NFTs from the server.
2453 // 3. Make sure the right URI is attached to each NFT.
2454 testcase("Mint URI");
2455
2456 using namespace test::jtx;
2457
2458 Env env{*this, features};
2459
2460 Account const alice{"alice"};
2461 Account const becky{"becky"};
2462
2463 env.fund(XRP(10000), alice, becky);
2464 env.close();
2465
2466 // lambda that returns a randomly generated string which fits
2467 // the constraints of a URI. Empty strings may be returned.
2468 // In the empty string case do not add the URI to the nft.
2469 auto randURI = []() {
2470 std::string ret;
2471
2472 // About 20% of the returned strings should be empty
2473 if (randInt(4) == 0)
2474 return ret;
2475
2476 std::size_t const strLen = randInt(256);
2477 ret.reserve(strLen);
2478 for (std::size_t i = 0; i < strLen; ++i)
2479 ret.push_back(randByte());
2480
2481 return ret;
2482 };
2483
2484 // Make a list of URIs that we'll put in nfts.
2485 struct Entry
2486 {
2487 std::string uri;
2488 std::uint32_t taxon;
2489
2490 Entry(std::string uri, std::uint32_t taxon) : uri(std::move(uri)), taxon(taxon)
2491 {
2492 }
2493 };
2494
2495 std::vector<Entry> entries;
2496 entries.reserve(100);
2497 for (std::size_t i = 0; i < 100; ++i)
2498 entries.emplace_back(randURI(), randInt<std::uint32_t>());
2499
2500 // alice creates nfts using entries.
2501 for (Entry const& entry : entries)
2502 {
2503 if (entry.uri.empty())
2504 {
2505 env(token::mint(alice, entry.taxon));
2506 }
2507 else
2508 {
2509 env(token::mint(alice, entry.taxon), token::Uri(entry.uri));
2510 }
2511 env.close();
2512 }
2513
2514 // Recover alice's nfts from the ledger.
2515 json::Value aliceNFTs = [&env, &alice]() {
2516 json::Value params;
2517 params[jss::account] = alice.human();
2518 params[jss::type] = "state";
2519 return env.rpc("json", "account_nfts", to_string(params));
2520 }();
2521
2522 // Verify that the returned NFTs match what we sent.
2523 json::Value& nfts = aliceNFTs[jss::result][jss::account_nfts];
2524 if (!BEAST_EXPECT(nfts.size() == entries.size()))
2525 return;
2526
2527 // Sort the returned NFTs by nft_serial so the are in the same order
2528 // as entries.
2529 std::vector<json::Value> sortedNFTs;
2530 sortedNFTs.reserve(nfts.size());
2531 for (std::size_t i = 0; i < nfts.size(); ++i)
2532 sortedNFTs.push_back(nfts[i]);
2534 sortedNFTs,
2535
2536 [](json::Value const& lhs, json::Value const& rhs) {
2537 return lhs[jss::nft_serial] < rhs[jss::nft_serial];
2538 });
2539
2540 for (std::size_t i = 0; i < entries.size(); ++i)
2541 {
2542 Entry const& entry = entries[i];
2543 json::Value const& ret = sortedNFTs[i];
2544 BEAST_EXPECT(entry.taxon == ret[sfNFTokenTaxon.jsonName]);
2545 if (entry.uri.empty())
2546 {
2547 BEAST_EXPECT(!ret.isMember(sfURI.jsonName));
2548 }
2549 else
2550 {
2551 BEAST_EXPECT(strHex(entry.uri) == ret[sfURI.jsonName]);
2552 }
2553 }
2554 }
2555
2556 void
2558 {
2559 // Explore the OfferCreate Destination field.
2560 testcase("Create offer destination");
2561
2562 using namespace test::jtx;
2563
2564 Env env{*this, features};
2565
2566 Account const issuer{"issuer"};
2567 Account const minter{"minter"};
2568 Account const buyer{"buyer"};
2569 Account const broker{"broker"};
2570
2571 env.fund(XRP(1000), issuer, minter, buyer, broker);
2572
2573 // We want to explore how issuers vs minters fits into the permission
2574 // scheme. So issuer issues and minter mints.
2575 env(token::setMinter(issuer, minter));
2576 env.close();
2577
2578 uint256 const nftokenID = token::getNextID(env, issuer, 0, tfTransferable);
2579 env(token::mint(minter, 0), token::Issuer(issuer), Txflags(tfTransferable));
2580 env.close();
2581
2582 // Test how adding a Destination field to an offer affects permissions
2583 // for canceling offers.
2584 {
2585 uint256 const offerMinterToIssuer = keylet::nftokenOffer(minter, env.seq(minter)).key;
2586 env(token::createOffer(minter, nftokenID, drops(1)),
2587 token::Destination(issuer),
2588 Txflags(tfSellNFToken));
2589
2590 uint256 const offerMinterToBuyer = keylet::nftokenOffer(minter, env.seq(minter)).key;
2591 env(token::createOffer(minter, nftokenID, drops(1)),
2592 token::Destination(buyer),
2593 Txflags(tfSellNFToken));
2594
2595 uint256 const offerIssuerToMinter = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
2596 env(token::createOffer(issuer, nftokenID, drops(1)),
2597 token::Owner(minter),
2598 token::Destination(minter));
2599
2600 uint256 const offerIssuerToBuyer = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
2601 env(token::createOffer(issuer, nftokenID, drops(1)),
2602 token::Owner(minter),
2603 token::Destination(buyer));
2604
2605 env.close();
2606 BEAST_EXPECT(ownerCount(env, issuer) == 2);
2607 BEAST_EXPECT(ownerCount(env, minter) == 3);
2608 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2609
2610 // Test who gets to cancel the offers. Anyone outside of the
2611 // offer-owner/destination pair should not be able to cancel the
2612 // offers.
2613 //
2614 // Note that issuer does not have any special permissions regarding
2615 // offer cancellation. issuer cannot cancel an offer for an
2616 // NFToken they issued.
2617 env(token::cancelOffer(issuer, {offerMinterToBuyer}), Ter(tecNO_PERMISSION));
2618 env(token::cancelOffer(buyer, {offerMinterToIssuer}), Ter(tecNO_PERMISSION));
2619 env(token::cancelOffer(buyer, {offerIssuerToMinter}), Ter(tecNO_PERMISSION));
2620 env(token::cancelOffer(minter, {offerIssuerToBuyer}), Ter(tecNO_PERMISSION));
2621 env.close();
2622 BEAST_EXPECT(ownerCount(env, issuer) == 2);
2623 BEAST_EXPECT(ownerCount(env, minter) == 3);
2624 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2625
2626 // Both the offer creator and and destination should be able to
2627 // cancel the offers.
2628 env(token::cancelOffer(buyer, {offerMinterToBuyer}));
2629 env(token::cancelOffer(minter, {offerMinterToIssuer}));
2630 env(token::cancelOffer(buyer, {offerIssuerToBuyer}));
2631 env(token::cancelOffer(issuer, {offerIssuerToMinter}));
2632 env.close();
2633 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2634 BEAST_EXPECT(ownerCount(env, minter) == 1);
2635 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2636 }
2637
2638 // Test how adding a Destination field to a sell offer affects
2639 // accepting that offer.
2640 {
2641 uint256 const offerMinterSellsToBuyer =
2642 keylet::nftokenOffer(minter, env.seq(minter)).key;
2643 env(token::createOffer(minter, nftokenID, drops(1)),
2644 token::Destination(buyer),
2645 Txflags(tfSellNFToken));
2646 env.close();
2647 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2648 BEAST_EXPECT(ownerCount(env, minter) == 2);
2649 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2650
2651 // issuer cannot accept a sell offer where they are not the
2652 // destination.
2653 env(token::acceptSellOffer(issuer, offerMinterSellsToBuyer), Ter(tecNO_PERMISSION));
2654 env.close();
2655 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2656 BEAST_EXPECT(ownerCount(env, minter) == 2);
2657 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2658
2659 // However buyer can accept the sell offer.
2660 env(token::acceptSellOffer(buyer, offerMinterSellsToBuyer));
2661 env.close();
2662 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2663 BEAST_EXPECT(ownerCount(env, minter) == 0);
2664 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2665 }
2666
2667 // Test how adding a Destination field to a buy offer affects
2668 // accepting that offer.
2669 {
2670 uint256 const offerMinterBuysFromBuyer =
2671 keylet::nftokenOffer(minter, env.seq(minter)).key;
2672 env(token::createOffer(minter, nftokenID, drops(1)),
2673 token::Owner(buyer),
2674 token::Destination(buyer));
2675 env.close();
2676 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2677 BEAST_EXPECT(ownerCount(env, minter) == 1);
2678 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2679
2680 // issuer cannot accept a buy offer where they are the
2681 // destination.
2682 env(token::acceptBuyOffer(issuer, offerMinterBuysFromBuyer), Ter(tecNO_PERMISSION));
2683 env.close();
2684 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2685 BEAST_EXPECT(ownerCount(env, minter) == 1);
2686 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2687
2688 // Buyer accepts minter's offer.
2689 env(token::acceptBuyOffer(buyer, offerMinterBuysFromBuyer));
2690 env.close();
2691 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2692 BEAST_EXPECT(ownerCount(env, minter) == 1);
2693 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2694
2695 // If a destination other than the NFToken owner is set, that
2696 // destination must act as a broker. The NFToken owner may not
2697 // simply accept the offer.
2698 uint256 const offerBuyerBuysFromMinter =
2699 keylet::nftokenOffer(buyer, env.seq(buyer)).key;
2700 env(token::createOffer(buyer, nftokenID, drops(1)),
2701 token::Owner(minter),
2702 token::Destination(broker));
2703 env.close();
2704 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2705 BEAST_EXPECT(ownerCount(env, minter) == 1);
2706 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2707
2708 env(token::acceptBuyOffer(minter, offerBuyerBuysFromMinter), Ter(tecNO_PERMISSION));
2709 env.close();
2710
2711 // Clean up the unused offer.
2712 env(token::cancelOffer(buyer, {offerBuyerBuysFromMinter}));
2713 env.close();
2714 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2715 BEAST_EXPECT(ownerCount(env, minter) == 1);
2716 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2717 }
2718
2719 // Show that a sell offer's Destination can broker that sell offer
2720 // to another account.
2721 {
2722 uint256 const offerMinterToBroker = keylet::nftokenOffer(minter, env.seq(minter)).key;
2723 env(token::createOffer(minter, nftokenID, drops(1)),
2724 token::Destination(broker),
2725 Txflags(tfSellNFToken));
2726
2727 uint256 const offerBuyerToMinter = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
2728 env(token::createOffer(buyer, nftokenID, drops(1)), token::Owner(minter));
2729
2730 env.close();
2731 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2732 BEAST_EXPECT(ownerCount(env, minter) == 2);
2733 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2734
2735 {
2736 // issuer cannot broker the offers, because they are not the
2737 // Destination.
2738 env(token::brokerOffers(issuer, offerBuyerToMinter, offerMinterToBroker),
2739 Ter(tecNO_PERMISSION));
2740 env.close();
2741 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2742 BEAST_EXPECT(ownerCount(env, minter) == 2);
2743 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2744 }
2745
2746 // Since broker is the sell offer's destination, they can broker
2747 // the two offers.
2748 env(token::brokerOffers(broker, offerBuyerToMinter, offerMinterToBroker));
2749 env.close();
2750 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2751 BEAST_EXPECT(ownerCount(env, minter) == 0);
2752 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2753 }
2754
2755 // Show that brokered mode cannot complete a transfer where the
2756 // Destination doesn't match, but can complete if the Destination
2757 // does match.
2758 {
2759 uint256 const offerBuyerToMinter = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
2760 env(token::createOffer(buyer, nftokenID, drops(1)),
2761 token::Destination(minter),
2762 Txflags(tfSellNFToken));
2763
2764 uint256 const offerMinterToBuyer = keylet::nftokenOffer(minter, env.seq(minter)).key;
2765 env(token::createOffer(minter, nftokenID, drops(1)), token::Owner(buyer));
2766
2767 uint256 const offerIssuerToBuyer = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
2768 env(token::createOffer(issuer, nftokenID, drops(1)), token::Owner(buyer));
2769
2770 env.close();
2771 BEAST_EXPECT(ownerCount(env, issuer) == 1);
2772 BEAST_EXPECT(ownerCount(env, minter) == 1);
2773 BEAST_EXPECT(ownerCount(env, buyer) == 2);
2774
2775 {
2776 // Cannot broker offers when the sell destination is not the
2777 // buyer.
2778 env(token::brokerOffers(broker, offerIssuerToBuyer, offerBuyerToMinter),
2779 Ter(tecNO_PERMISSION));
2780 env.close();
2781
2782 BEAST_EXPECT(ownerCount(env, issuer) == 1);
2783 BEAST_EXPECT(ownerCount(env, minter) == 1);
2784 BEAST_EXPECT(ownerCount(env, buyer) == 2);
2785
2786 env(token::brokerOffers(broker, offerMinterToBuyer, offerBuyerToMinter),
2787 Ter(tecNO_PERMISSION));
2788 env.close();
2789
2790 // Buyer is successful with acceptOffer.
2791 env(token::acceptBuyOffer(buyer, offerMinterToBuyer));
2792 env.close();
2793
2794 // Clean out the unconsumed offer.
2795 env(token::cancelOffer(buyer, {offerBuyerToMinter}));
2796 env.close();
2797
2798 BEAST_EXPECT(ownerCount(env, issuer) == 1);
2799 BEAST_EXPECT(ownerCount(env, minter) == 1);
2800 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2801
2802 // Clean out the unconsumed offer.
2803 env(token::cancelOffer(issuer, {offerIssuerToBuyer}));
2804 env.close();
2805 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2806 BEAST_EXPECT(ownerCount(env, minter) == 1);
2807 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2808 return;
2809 }
2810 }
2811
2812 // Show that if a buy and a sell offer both have the same destination,
2813 // then that destination can broker the offers.
2814 {
2815 uint256 const offerMinterToBroker = keylet::nftokenOffer(minter, env.seq(minter)).key;
2816 env(token::createOffer(minter, nftokenID, drops(1)),
2817 token::Destination(broker),
2818 Txflags(tfSellNFToken));
2819
2820 uint256 const offerBuyerToBroker = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
2821 env(token::createOffer(buyer, nftokenID, drops(1)),
2822 token::Owner(minter),
2823 token::Destination(broker));
2824
2825 {
2826 // Cannot broker offers when the sell destination is not the
2827 // buyer or the broker.
2828 env(token::brokerOffers(issuer, offerBuyerToBroker, offerMinterToBroker),
2829 Ter(tecNO_PERMISSION));
2830 env.close();
2831 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2832 BEAST_EXPECT(ownerCount(env, minter) == 2);
2833 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2834 }
2835
2836 // Broker is successful if they are the destination of both offers.
2837 env(token::brokerOffers(broker, offerBuyerToBroker, offerMinterToBroker));
2838 env.close();
2839 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2840 BEAST_EXPECT(ownerCount(env, minter) == 0);
2841 BEAST_EXPECT(ownerCount(env, buyer) == 1);
2842 }
2843 }
2844
2845 void
2847 {
2848 testcase("Create offer destination disallow incoming");
2849
2850 using namespace test::jtx;
2851
2852 Env env{*this, features};
2853
2854 Account const issuer{"issuer"};
2855 Account const minter{"minter"};
2856 Account const buyer{"buyer"};
2857 Account const alice{"alice"};
2858
2859 env.fund(XRP(1000), issuer, minter, buyer, alice);
2860
2861 env(token::setMinter(issuer, minter));
2862 env.close();
2863
2864 uint256 const nftokenID = token::getNextID(env, issuer, 0, tfTransferable);
2865 env(token::mint(minter, 0), token::Issuer(issuer), Txflags(tfTransferable));
2866 env.close();
2867
2868 // enable flag
2869 env(fset(buyer, asfDisallowIncomingNFTokenOffer));
2870 env.close();
2871
2872 // a sell offer from the minter to the buyer should be rejected
2873 {
2874 env(token::createOffer(minter, nftokenID, drops(1)),
2875 token::Destination(buyer),
2876 Txflags(tfSellNFToken),
2877 Ter(tecNO_PERMISSION));
2878 env.close();
2879 BEAST_EXPECT(ownerCount(env, issuer) == 0);
2880 BEAST_EXPECT(ownerCount(env, minter) == 1);
2881 BEAST_EXPECT(ownerCount(env, buyer) == 0);
2882 }
2883
2884 // disable the flag
2885 env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
2886 env.close();
2887
2888 // create offer (allowed now) then cancel
2889 {
2890 uint256 const offerIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2891
2892 env(token::createOffer(minter, nftokenID, drops(1)),
2893 token::Destination(buyer),
2894 Txflags(tfSellNFToken));
2895 env.close();
2896
2897 env(token::cancelOffer(minter, {offerIndex}));
2898 env.close();
2899 }
2900
2901 // create offer, enable flag, then cancel
2902 {
2903 uint256 const offerIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2904
2905 env(token::createOffer(minter, nftokenID, drops(1)),
2906 token::Destination(buyer),
2907 Txflags(tfSellNFToken));
2908 env.close();
2909
2910 env(fset(buyer, asfDisallowIncomingNFTokenOffer));
2911 env.close();
2912
2913 env(token::cancelOffer(minter, {offerIndex}));
2914 env.close();
2915
2916 env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
2917 env.close();
2918 }
2919
2920 // create offer then transfer
2921 {
2922 uint256 const offerIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
2923
2924 env(token::createOffer(minter, nftokenID, drops(1)),
2925 token::Destination(buyer),
2926 Txflags(tfSellNFToken));
2927 env.close();
2928
2929 env(token::acceptSellOffer(buyer, offerIndex));
2930 env.close();
2931 }
2932
2933 // buyer now owns the token
2934
2935 // enable flag again
2936 env(fset(buyer, asfDisallowIncomingNFTokenOffer));
2937 env.close();
2938
2939 // a random offer to buy the token
2940 {
2941 env(token::createOffer(alice, nftokenID, drops(1)),
2942 token::Owner(buyer),
2943 Ter(tecNO_PERMISSION));
2944 env.close();
2945 }
2946
2947 // minter offer to buy the token
2948 {
2949 env(token::createOffer(minter, nftokenID, drops(1)),
2950 token::Owner(buyer),
2951 Ter(tecNO_PERMISSION));
2952 env.close();
2953 }
2954
2955 // minter mint and offer to buyer
2956 if (features[featureNFTokenMintOffer])
2957 {
2958 // enable flag
2959 env(fset(buyer, asfDisallowIncomingNFTokenOffer));
2960 // a sell offer from the minter to the buyer should be rejected
2961 env(token::mint(minter),
2962 token::Amount(drops(1)),
2963 token::Destination(buyer),
2964 Ter(tecNO_PERMISSION));
2965 env.close();
2966
2967 // disable flag
2968 env(fclear(buyer, asfDisallowIncomingNFTokenOffer));
2969 env(token::mint(minter), token::Amount(drops(1)), token::Destination(buyer));
2970 env.close();
2971 }
2972 }
2973
2974 void
2976 {
2977 // Explore the OfferCreate Expiration field.
2978 testcase("Create offer expiration");
2979
2980 using namespace test::jtx;
2981
2982 Env env{*this, features};
2983
2984 Account const issuer{"issuer"};
2985 Account const minter{"minter"};
2986 Account const buyer{"buyer"};
2987
2988 env.fund(XRP(1000), issuer, minter, buyer);
2989
2990 // We want to explore how issuers vs minters fits into the permission
2991 // scheme. So issuer issues and minter mints.
2992 env(token::setMinter(issuer, minter));
2993 env.close();
2994
2995 uint256 const nftokenID0 = token::getNextID(env, issuer, 0, tfTransferable);
2996 env(token::mint(minter, 0), token::Issuer(issuer), Txflags(tfTransferable));
2997 env.close();
2998
2999 uint256 const nftokenID1 = token::getNextID(env, issuer, 0, tfTransferable);
3000 env(token::mint(minter, 0), token::Issuer(issuer), Txflags(tfTransferable));
3001 env.close();
3002 uint8_t issuerCount = 0, minterCount = 0, buyerCount = 0;
3003
3004 // Test how adding an Expiration field to an offer affects permissions
3005 // for cancelling offers.
3006 {
3007 std::uint32_t const expiration = lastClose(env) + 25;
3008
3009 uint256 const offerMinterToIssuer = keylet::nftokenOffer(minter, env.seq(minter)).key;
3010 env(token::createOffer(minter, nftokenID0, drops(1)),
3011 token::Destination(issuer),
3012 token::Expiration(expiration),
3013 Txflags(tfSellNFToken));
3014
3015 uint256 const offerMinterToAnyone = keylet::nftokenOffer(minter, env.seq(minter)).key;
3016 env(token::createOffer(minter, nftokenID0, drops(1)),
3017 token::Expiration(expiration),
3018 Txflags(tfSellNFToken));
3019
3020 uint256 const offerIssuerToMinter = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
3021 env(token::createOffer(issuer, nftokenID0, drops(1)),
3022 token::Owner(minter),
3023 token::Expiration(expiration));
3024
3025 uint256 const offerBuyerToMinter = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3026 env(token::createOffer(buyer, nftokenID0, drops(1)),
3027 token::Owner(minter),
3028 token::Expiration(expiration));
3029 env.close();
3030 issuerCount = 1;
3031 minterCount = 3;
3032 buyerCount = 1;
3033 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3034 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3035 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3036
3037 // Test who gets to cancel the offers. Anyone outside of the
3038 // offer-owner/destination pair should not be able to cancel
3039 // unexpired offers.
3040 //
3041 // Note that these are tec responses, so these transactions will
3042 // not be retried by the ledger.
3043 env(token::cancelOffer(issuer, {offerMinterToAnyone}), Ter(tecNO_PERMISSION));
3044 env(token::cancelOffer(buyer, {offerIssuerToMinter}), Ter(tecNO_PERMISSION));
3045 env.close();
3046 BEAST_EXPECT(lastClose(env) < expiration);
3047 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3048 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3049 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3050
3051 // The offer creator can cancel their own unexpired offer.
3052 env(token::cancelOffer(minter, {offerMinterToAnyone}));
3053 minterCount--;
3054
3055 // The destination of a sell offer can cancel the NFT owner's
3056 // unexpired offer.
3057 env(token::cancelOffer(issuer, {offerMinterToIssuer}));
3058 minterCount--;
3059
3060 // Close enough ledgers to get past the expiration.
3061 while (lastClose(env) < expiration)
3062 env.close();
3063
3064 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3065 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3066 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3067
3068 // Anyone can cancel expired offers.
3069 env(token::cancelOffer(issuer, {offerBuyerToMinter}));
3070 buyerCount--;
3071 env(token::cancelOffer(buyer, {offerIssuerToMinter}));
3072 issuerCount--;
3073 env.close();
3074 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3075 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3076 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3077 }
3078 // Show that:
3079 // 1. An unexpired sell offer with an expiration can be accepted.
3080 // 2. An expired sell offer cannot be accepted and remains
3081 // in ledger after the accept fails.
3082 {
3083 std::uint32_t const expiration = lastClose(env) + 25;
3084
3085 uint256 const offer0 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3086 env(token::createOffer(minter, nftokenID0, drops(1)),
3087 token::Expiration(expiration),
3088 Txflags(tfSellNFToken));
3089 minterCount++;
3090
3091 uint256 const offer1 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3092 env(token::createOffer(minter, nftokenID1, drops(1)),
3093 token::Expiration(expiration),
3094 Txflags(tfSellNFToken));
3095 minterCount++;
3096 env.close();
3097 BEAST_EXPECT(lastClose(env) < expiration);
3098 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3099 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3100 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3101
3102 // Anyone can accept an unexpired sell offer.
3103 env(token::acceptSellOffer(buyer, offer0));
3104 minterCount--;
3105 buyerCount++;
3106
3107 // Close enough ledgers to get past the expiration.
3108 while (lastClose(env) < expiration)
3109 env.close();
3110
3111 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3112 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3113 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3114
3115 // No one can accept an expired sell offer.
3116 env(token::acceptSellOffer(buyer, offer1), Ter(tecEXPIRED));
3117
3118 // With fixCleanup3_1_3 amendment, the first accept
3119 // attempt deletes the expired offer. Without the amendment,
3120 // the offer remains and we can try to accept it again.
3121 if (features[fixCleanup3_1_3])
3122 {
3123 // After amendment: offer was deleted by first accept attempt
3124 minterCount--;
3125 env(token::acceptSellOffer(issuer, offer1), Ter(tecOBJECT_NOT_FOUND));
3126 }
3127 else
3128 {
3129 // Before amendment: offer still exists, second accept also
3130 // fails
3131 env(token::acceptSellOffer(issuer, offer1), Ter(tecEXPIRED));
3132 }
3133 env.close();
3134
3135 // Check if the expired sell offer behavior matches amendment status
3136 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3137 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3138 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3139
3140 if (!features[fixCleanup3_1_3])
3141 {
3142 // Before amendment: expired offer still exists and needs to be
3143 // cancelled
3144 env(token::cancelOffer(issuer, {offer1}));
3145 env.close();
3146 minterCount--;
3147 }
3148 // Ensure that owner counts are correct with and without the
3149 // amendment
3150 BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
3151 BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
3152 BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
3153
3154 // Transfer nftokenID0 back to minter so we start the next test in
3155 // a simple place.
3156 uint256 const offerSellBack = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3157 env(token::createOffer(buyer, nftokenID0, XRP(0)),
3158 Txflags(tfSellNFToken),
3159 token::Destination(minter));
3160 env.close();
3161 env(token::acceptSellOffer(minter, offerSellBack));
3162 buyerCount--;
3163 env.close();
3164 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3165 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3166 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3167 }
3168 // Show that:
3169 // 1. An unexpired buy offer with an expiration can be accepted.
3170 // 2. An expired buy offer cannot be accepted and remains
3171 // in ledger after the accept fails.
3172 {
3173 std::uint32_t const expiration = lastClose(env) + 25;
3174
3175 uint256 const offer0 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3176 env(token::createOffer(buyer, nftokenID0, drops(1)),
3177 token::Owner(minter),
3178 token::Expiration(expiration));
3179 buyerCount++;
3180
3181 uint256 const offer1 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3182 env(token::createOffer(buyer, nftokenID1, drops(1)),
3183 token::Owner(minter),
3184 token::Expiration(expiration));
3185 buyerCount++;
3186 env.close();
3187 BEAST_EXPECT(lastClose(env) < expiration);
3188 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3189 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3190 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3191
3192 // An unexpired buy offer can be accepted.
3193 env(token::acceptBuyOffer(minter, offer0));
3194
3195 // Close enough ledgers to get past the expiration.
3196 while (lastClose(env) < expiration)
3197 env.close();
3198
3199 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3200 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3201 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3202
3203 // An expired buy offer cannot be accepted.
3204 env(token::acceptBuyOffer(minter, offer1), Ter(tecEXPIRED));
3205
3206 // With fixCleanup3_1_3 amendment, the first accept
3207 // attempt deletes the expired offer. Without the amendment,
3208 // the offer remains and we can try to accept it again.
3209 if (features[fixCleanup3_1_3])
3210 {
3211 // After amendment: offer was deleted by first accept attempt
3212 buyerCount--;
3213 env(token::acceptBuyOffer(issuer, offer1), Ter(tecOBJECT_NOT_FOUND));
3214 }
3215 else
3216 {
3217 // Before amendment: offer still exists, second accept also
3218 // fails
3219 env(token::acceptBuyOffer(issuer, offer1), Ter(tecEXPIRED));
3220 }
3221 env.close();
3222
3223 // Check if the expired buy offer behavior matches amendment status
3224 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3225 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3226 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3227
3228 if (!features[fixCleanup3_1_3])
3229 {
3230 // Before amendment: expired offer still exists and can be
3231 // cancelled
3232 env(token::cancelOffer(issuer, {offer1}));
3233 env.close();
3234 buyerCount--;
3235 }
3236 // Ensure that owner counts are the same with and without the
3237 // amendment
3238 BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
3239 BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
3240 BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
3241
3242 // Transfer nftokenID0 back to minter so we start the next test in
3243 // a simple place.
3244 uint256 const offerSellBack = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3245 env(token::createOffer(buyer, nftokenID0, XRP(0)),
3246 Txflags(tfSellNFToken),
3247 token::Destination(minter));
3248 env.close();
3249 env(token::acceptSellOffer(minter, offerSellBack));
3250 env.close();
3251 buyerCount--;
3252 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3253 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3254 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3255 }
3256 // Show that in brokered mode:
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;
3262
3263 uint256 const sellOffer0 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3264 env(token::createOffer(minter, nftokenID0, drops(1)),
3265 token::Expiration(expiration),
3266 Txflags(tfSellNFToken));
3267 minterCount++;
3268
3269 uint256 const sellOffer1 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3270 env(token::createOffer(minter, nftokenID1, drops(1)),
3271 token::Expiration(expiration),
3272 Txflags(tfSellNFToken));
3273 minterCount++;
3274
3275 uint256 const buyOffer0 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3276 env(token::createOffer(buyer, nftokenID0, drops(1)), token::Owner(minter));
3277 buyerCount++;
3278
3279 uint256 const buyOffer1 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3280 env(token::createOffer(buyer, nftokenID1, drops(1)), token::Owner(minter));
3281 buyerCount++;
3282
3283 env.close();
3284 BEAST_EXPECT(lastClose(env) < expiration);
3285 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3286 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3287 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3288
3289 // An unexpired offer can be brokered.
3290 env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
3291 minterCount--;
3292
3293 // Close enough ledgers to get past the expiration.
3294 while (lastClose(env) < expiration)
3295 env.close();
3296
3297 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3298 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3299 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3300
3301 // If the sell offer is expired it cannot be brokered.
3302 env(token::brokerOffers(issuer, buyOffer1, sellOffer1), Ter(tecEXPIRED));
3303 env.close();
3304
3305 if (features[fixCleanup3_1_3])
3306 {
3307 // With amendment: expired offers are deleted
3308 minterCount--;
3309 }
3310
3311 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3312 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3313 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3314
3315 if (features[fixCleanup3_1_3])
3316 {
3317 // The buy offer was deleted, so no need to cancel it
3318 // The sell offer still exists, so we can cancel it
3319 env(token::cancelOffer(buyer, {buyOffer1}));
3320 buyerCount--;
3321 }
3322 else
3323 {
3324 // Anyone can cancel the expired offers
3325 env(token::cancelOffer(buyer, {buyOffer1, sellOffer1}));
3326 minterCount--;
3327 buyerCount--;
3328 }
3329 env.close();
3330 // Ensure that owner counts are the same with and without the
3331 // amendment
3332 BEAST_EXPECT(ownerCount(env, issuer) == 0 && issuerCount == 0);
3333 BEAST_EXPECT(ownerCount(env, minter) == 1 && minterCount == 1);
3334 BEAST_EXPECT(ownerCount(env, buyer) == 1 && buyerCount == 1);
3335
3336 // Transfer nftokenID0 back to minter so we start the next test in
3337 // a simple place.
3338 uint256 const offerSellBack = keylet::nftokenOffer(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 buyerCount--;
3346 BEAST_EXPECT(ownerCount(env, issuer) == issuerCount);
3347 BEAST_EXPECT(ownerCount(env, minter) == minterCount);
3348 BEAST_EXPECT(ownerCount(env, buyer) == buyerCount);
3349 }
3350 // Show that in brokered mode:
3351 // 1. An unexpired buy offer with an expiration can be accepted.
3352 // 2. An expired buy offer cannot be accepted and remains
3353 // in ledger after the accept fails.
3354 {
3355 std::uint32_t const expiration = lastClose(env) + 25;
3356
3357 uint256 const sellOffer0 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3358 env(token::createOffer(minter, nftokenID0, drops(1)), Txflags(tfSellNFToken));
3359
3360 uint256 const sellOffer1 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3361 env(token::createOffer(minter, nftokenID1, drops(1)), Txflags(tfSellNFToken));
3362
3363 uint256 const buyOffer0 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3364 env(token::createOffer(buyer, nftokenID0, drops(1)),
3365 token::Expiration(expiration),
3366 token::Owner(minter));
3367
3368 uint256 const buyOffer1 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3369 env(token::createOffer(buyer, nftokenID1, drops(1)),
3370 token::Expiration(expiration),
3371 token::Owner(minter));
3372
3373 env.close();
3374 BEAST_EXPECT(lastClose(env) < expiration);
3375 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3376 BEAST_EXPECT(ownerCount(env, minter) == 3);
3377 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3378
3379 // An unexpired offer can be brokered.
3380 env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
3381
3382 // Close enough ledgers to get past the expiration.
3383 while (lastClose(env) < expiration)
3384 env.close();
3385
3386 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3387 BEAST_EXPECT(ownerCount(env, minter) == 2);
3388 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3389
3390 env(token::brokerOffers(issuer, buyOffer1, sellOffer1), Ter(tecEXPIRED));
3391 env.close();
3392
3393 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3394 if (features[fixCleanup3_1_3])
3395 {
3396 // After amendment: expired offers were deleted during broker
3397 // attempt
3398 BEAST_EXPECT(ownerCount(env, minter) == 2);
3399 BEAST_EXPECT(ownerCount(env, buyer) == 1);
3400 // The buy offer was deleted, so no need to cancel it
3401 // The sell offer still exists, so we can cancel it
3402 env(token::cancelOffer(minter, {sellOffer1}));
3403 }
3404 else
3405 {
3406 // Before amendment: expired offers still exist in ledger
3407 BEAST_EXPECT(ownerCount(env, minter) == 2);
3408 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3409 // Anyone can cancel the expired offers
3410 env(token::cancelOffer(minter, {buyOffer1, sellOffer1}));
3411 }
3412 env.close();
3413 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3414 BEAST_EXPECT(ownerCount(env, minter) == 1);
3415 BEAST_EXPECT(ownerCount(env, buyer) == 1);
3416
3417 // Transfer nftokenID0 back to minter so we start the next test in
3418 // a simple place.
3419 uint256 const offerSellBack = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3420 env(token::createOffer(buyer, nftokenID0, XRP(0)),
3421 Txflags(tfSellNFToken),
3422 token::Destination(minter));
3423 env.close();
3424 env(token::acceptSellOffer(minter, offerSellBack));
3425 env.close();
3426 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3427 BEAST_EXPECT(ownerCount(env, minter) == 1);
3428 BEAST_EXPECT(ownerCount(env, buyer) == 0);
3429 }
3430 // Show that in brokered mode:
3431 // 1. An unexpired buy/sell offer pair with an expiration can be
3432 // accepted.
3433 // 2. An expired buy/sell offer pair cannot be accepted and they
3434 // remain in ledger after the accept fails.
3435 {
3436 std::uint32_t const expiration = lastClose(env) + 25;
3437
3438 uint256 const sellOffer0 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3439 env(token::createOffer(minter, nftokenID0, drops(1)),
3440 token::Expiration(expiration),
3441 Txflags(tfSellNFToken));
3442
3443 uint256 const sellOffer1 = keylet::nftokenOffer(minter, env.seq(minter)).key;
3444 env(token::createOffer(minter, nftokenID1, drops(1)),
3445 token::Expiration(expiration),
3446 Txflags(tfSellNFToken));
3447
3448 uint256 const buyOffer0 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3449 env(token::createOffer(buyer, nftokenID0, drops(1)),
3450 token::Expiration(expiration),
3451 token::Owner(minter));
3452
3453 uint256 const buyOffer1 = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3454 env(token::createOffer(buyer, nftokenID1, drops(1)),
3455 token::Expiration(expiration),
3456 token::Owner(minter));
3457
3458 env.close();
3459 BEAST_EXPECT(lastClose(env) < expiration);
3460 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3461 BEAST_EXPECT(ownerCount(env, minter) == 3);
3462 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3463
3464 // Unexpired offers can be brokered.
3465 env(token::brokerOffers(issuer, buyOffer0, sellOffer0));
3466
3467 // Close enough ledgers to get past the expiration.
3468 while (lastClose(env) < expiration)
3469 env.close();
3470
3471 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3472 BEAST_EXPECT(ownerCount(env, minter) == 2);
3473 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3474
3475 env(token::brokerOffers(issuer, buyOffer1, sellOffer1), Ter(tecEXPIRED));
3476 env.close();
3477
3478 // The expired offers are still in the ledger.
3479 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3480 if (!features[fixCleanup3_1_3])
3481 {
3482 // Before amendment: expired offers still exist in ledger
3483 BEAST_EXPECT(ownerCount(env, minter) == 2);
3484 BEAST_EXPECT(ownerCount(env, buyer) == 2);
3485 // Anyone can cancel the expired offers
3486 env(token::cancelOffer(issuer, {buyOffer1, sellOffer1}));
3487 }
3488 env.close();
3489 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3490 BEAST_EXPECT(ownerCount(env, minter) == 1);
3491 BEAST_EXPECT(ownerCount(env, buyer) == 1);
3492
3493 // Transfer nftokenID0 back to minter so we start the next test in
3494 // a simple place.
3495 uint256 const offerSellBack = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3496 env(token::createOffer(buyer, nftokenID0, XRP(0)),
3497 Txflags(tfSellNFToken),
3498 token::Destination(minter));
3499 env.close();
3500 env(token::acceptSellOffer(minter, offerSellBack));
3501 env.close();
3502 BEAST_EXPECT(ownerCount(env, issuer) == 0);
3503 BEAST_EXPECT(ownerCount(env, minter) == 1);
3504 BEAST_EXPECT(ownerCount(env, buyer) == 0);
3505 }
3506 }
3507
3508 void
3510 {
3511 // Look at offer canceling.
3512 testcase("Cancel offers");
3513
3514 using namespace test::jtx;
3515
3516 Env env{*this, features};
3517
3518 Account const alice("alice");
3519 Account const becky("becky");
3520 Account const minter("minter");
3521 env.fund(XRP(50000), alice, becky, minter);
3522 env.close();
3523
3524 // alice has a minter to see if minters have offer canceling permission.
3525 env(token::setMinter(alice, minter));
3526 env.close();
3527
3528 uint256 const nftokenID = token::getNextID(env, alice, 0, tfTransferable);
3529 env(token::mint(alice, 0), Txflags(tfTransferable));
3530 env.close();
3531
3532 // Anyone can cancel an expired offer.
3533 uint256 const expiredOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
3534
3535 env(token::createOffer(alice, nftokenID, XRP(1000)),
3536 Txflags(tfSellNFToken),
3537 token::Expiration(lastClose(env) + 13));
3538 env.close();
3539
3540 // The offer has not expired yet, so becky can't cancel it now.
3541 BEAST_EXPECT(ownerCount(env, alice) == 2);
3542 env(token::cancelOffer(becky, {expiredOfferIndex}), Ter(tecNO_PERMISSION));
3543 env.close();
3544
3545 // Close a couple of ledgers and advance the time. Then becky
3546 // should be able to cancel the (now) expired offer.
3547 env.close();
3548 env.close();
3549 env(token::cancelOffer(becky, {expiredOfferIndex}));
3550 env.close();
3551 BEAST_EXPECT(ownerCount(env, alice) == 1);
3552
3553 // Create a couple of offers with a destination. Those offers
3554 // should be cancellable by the creator and the destination.
3555 uint256 const dest1OfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
3556
3557 env(token::createOffer(alice, nftokenID, XRP(1000)),
3558 token::Destination(becky),
3559 Txflags(tfSellNFToken));
3560 env.close();
3561 BEAST_EXPECT(ownerCount(env, alice) == 2);
3562
3563 // Minter can't cancel that offer, but becky (the destination) can.
3564 env(token::cancelOffer(minter, {dest1OfferIndex}), Ter(tecNO_PERMISSION));
3565 env.close();
3566 BEAST_EXPECT(ownerCount(env, alice) == 2);
3567
3568 env(token::cancelOffer(becky, {dest1OfferIndex}));
3569 env.close();
3570 BEAST_EXPECT(ownerCount(env, alice) == 1);
3571
3572 // alice can cancel her own offer, even if becky is the destination.
3573 uint256 const dest2OfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
3574
3575 env(token::createOffer(alice, nftokenID, XRP(1000)),
3576 token::Destination(becky),
3577 Txflags(tfSellNFToken));
3578 env.close();
3579 BEAST_EXPECT(ownerCount(env, alice) == 2);
3580
3581 env(token::cancelOffer(alice, {dest2OfferIndex}));
3582 env.close();
3583 BEAST_EXPECT(ownerCount(env, alice) == 1);
3584
3585 // The issuer has no special permissions regarding offer cancellation.
3586 // Minter creates a token with alice as issuer. alice cannot cancel
3587 // minter's offer.
3588 uint256 const mintersNFTokenID = token::getNextID(env, alice, 0, tfTransferable);
3589 env(token::mint(minter, 0), token::Issuer(alice), Txflags(tfTransferable));
3590 env.close();
3591
3592 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
3593
3594 env(token::createOffer(minter, mintersNFTokenID, XRP(1000)), Txflags(tfSellNFToken));
3595 env.close();
3596 BEAST_EXPECT(ownerCount(env, minter) == 2);
3597
3598 // Nobody other than minter should be able to cancel minter's offer.
3599 env(token::cancelOffer(alice, {minterOfferIndex}), Ter(tecNO_PERMISSION));
3600 env(token::cancelOffer(becky, {minterOfferIndex}), Ter(tecNO_PERMISSION));
3601 env.close();
3602 BEAST_EXPECT(ownerCount(env, minter) == 2);
3603
3604 env(token::cancelOffer(minter, {minterOfferIndex}));
3605 env.close();
3606 BEAST_EXPECT(ownerCount(env, minter) == 1);
3607 }
3608
3609 void
3611 {
3612 // Look at the case where too many offers are passed in a cancel.
3613 testcase("Cancel too many offers");
3614
3615 using namespace test::jtx;
3616
3617 Env env{*this, features};
3618
3619 // We want to maximize the metadata from a cancel offer transaction to
3620 // make sure we don't hit metadata limits. The way we'll do that is:
3621 //
3622 // 1. Generate twice as many separate funded accounts as we have
3623 // offers.
3624 // 2.
3625 // a. One of these accounts mints an NFT with a full URL.
3626 // b. The other account makes an offer that will expire soon.
3627 // 3. After all of these offers have expired, cancel all of the
3628 // expired offers in a single transaction.
3629 //
3630 // I can't think of any way to increase the metadata beyond this,
3631 // but I'm open to ideas.
3632 Account const alice("alice");
3633 env.fund(XRP(1000), alice);
3634 env.close();
3635
3636 std::string const uri(kMaxTokenUriLength, '?');
3637 std::vector<uint256> offerIndexes;
3638 offerIndexes.reserve(kMaxTokenOfferCancelCount + 1);
3639 for (uint32_t i = 0; i < kMaxTokenOfferCancelCount + 1; ++i)
3640 {
3641 Account const nftAcct(std::string("nftAcct") + std::to_string(i));
3642 Account const offerAcct(std::string("offerAcct") + std::to_string(i));
3643 env.fund(XRP(1000), nftAcct, offerAcct);
3644 env.close();
3645
3646 uint256 const nftokenID = token::getNextID(env, nftAcct, 0, tfTransferable);
3647 env(token::mint(nftAcct, 0), token::Uri(uri), Txflags(tfTransferable));
3648 env.close();
3649
3650 offerIndexes.push_back(keylet::nftokenOffer(offerAcct, env.seq(offerAcct)).key);
3651 env(token::createOffer(offerAcct, nftokenID, drops(1)),
3652 token::Owner(nftAcct),
3653 token::Expiration(lastClose(env) + 5));
3654 env.close();
3655 }
3656
3657 // Close the ledger so the last of the offers expire.
3658 env.close();
3659
3660 // All offers should be in the ledger.
3661 for (uint256 const& offerIndex : offerIndexes)
3662 {
3663 BEAST_EXPECT(env.le(keylet::nftokenOffer(offerIndex)));
3664 }
3665
3666 // alice attempts to cancel all of the expired offers. There is one
3667 // too many so the request fails.
3668 env(token::cancelOffer(alice, offerIndexes), Ter(temMALFORMED));
3669 env.close();
3670
3671 // However alice can cancel just one of the offers.
3672 env(token::cancelOffer(alice, {offerIndexes.back()}));
3673 env.close();
3674
3675 // Verify that offer is gone from the ledger.
3676 BEAST_EXPECT(!env.le(keylet::nftokenOffer(offerIndexes.back())));
3677 offerIndexes.pop_back();
3678
3679 // But alice adds a sell offer to the list...
3680 {
3681 uint256 const nftokenID = token::getNextID(env, alice, 0, tfTransferable);
3682 env(token::mint(alice, 0), token::Uri(uri), Txflags(tfTransferable));
3683 env.close();
3684
3685 offerIndexes.push_back(keylet::nftokenOffer(alice, env.seq(alice)).key);
3686 env(token::createOffer(alice, nftokenID, drops(1)), Txflags(tfSellNFToken));
3687 env.close();
3688
3689 // alice's owner count should now to 2 for the nft and the offer.
3690 BEAST_EXPECT(ownerCount(env, alice) == 2);
3691
3692 // Because alice added the sell offer there are still too many
3693 // offers in the list to cancel.
3694 env(token::cancelOffer(alice, offerIndexes), Ter(temMALFORMED));
3695 env.close();
3696
3697 // alice burns her nft which removes the nft and the offer.
3698 env(token::burn(alice, nftokenID));
3699 env.close();
3700
3701 // If alice's owner count is zero we can see that the offer
3702 // and nft are both gone.
3703 BEAST_EXPECT(ownerCount(env, alice) == 0);
3704 offerIndexes.pop_back();
3705 }
3706
3707 // Now there are few enough offers in the list that they can all
3708 // be cancelled in a single transaction.
3709 env(token::cancelOffer(alice, offerIndexes));
3710 env.close();
3711
3712 // Verify that remaining offers are gone from the ledger.
3713 for (uint256 const& offerIndex : offerIndexes)
3714 {
3715 BEAST_EXPECT(!env.le(keylet::nftokenOffer(offerIndex)));
3716 }
3717 }
3718
3719 void
3721 {
3722 // Look at the case where too many offers are passed in a cancel.
3723 testcase("Brokered NFT offer accept");
3724
3725 using namespace test::jtx;
3726
3727 {
3728 Env env{*this, features};
3729 auto const baseFee = env.current()->fees().base;
3730
3731 // The most important thing to explore here is the way funds are
3732 // assigned from the buyer to...
3733 // o the Seller,
3734 // o the Broker, and
3735 // o the Issuer (in the case of a transfer fee).
3736
3737 Account const issuer{"issuer"};
3738 Account const minter{"minter"};
3739 Account const buyer{"buyer"};
3740 Account const broker{"broker"};
3741 Account const gw{"gw"};
3742 IOU const gwXAU(gw["XAU"]);
3743
3744 env.fund(XRP(1000), issuer, minter, buyer, broker, gw);
3745 env.close();
3746
3747 env(trust(issuer, gwXAU(2000)));
3748 env(trust(minter, gwXAU(2000)));
3749 env(trust(buyer, gwXAU(2000)));
3750 env(trust(broker, gwXAU(2000)));
3751 env.close();
3752
3753 env(token::setMinter(issuer, minter));
3754 env.close();
3755
3756 // Lambda to check owner count of all accounts is one.
3757 auto checkOwnerCountIsOne =
3758 [this, &env](
3760 int line) {
3761 for (Account const& acct : accounts)
3762 {
3763 if (std::uint32_t const ownerCount = test::jtx::ownerCount(env, acct);
3764 ownerCount != 1)
3765 {
3767 ss << "Account " << acct.human() << " expected ownerCount == 1. Got "
3768 << ownerCount;
3769 fail(ss.str(), __FILE__, line);
3770 }
3771 }
3772 };
3773
3774 // Lambda that mints an NFT and returns the nftID.
3775 auto mintNFT = [&env, &issuer, &minter](std::uint16_t xferFee = 0) {
3776 uint256 const nftID = token::getNextID(env, issuer, 0, tfTransferable, xferFee);
3777 env(token::mint(minter, 0),
3778 token::Issuer(issuer),
3779 token::XferFee(xferFee),
3780 Txflags(tfTransferable));
3781 env.close();
3782 return nftID;
3783 };
3784
3785 // o Seller is selling for zero XRP.
3786 // o Broker charges no fee.
3787 // o No transfer fee.
3788 //
3789 // Since minter is selling for zero the currency must be XRP.
3790 {
3791 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3792
3793 uint256 const nftID = mintNFT();
3794
3795 // minter creates their offer.
3796 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
3797 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
3798 env.close();
3799
3800 // buyer creates their offer. Note: a buy offer can never
3801 // offer zero.
3802 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3803 env(token::createOffer(buyer, nftID, XRP(1)), token::Owner(minter));
3804 env.close();
3805
3806 auto const minterBalance = env.balance(minter);
3807 auto const buyerBalance = env.balance(buyer);
3808 auto const brokerBalance = env.balance(broker);
3809 auto const issuerBalance = env.balance(issuer);
3810
3811 // Broker charges no brokerFee.
3812 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex));
3813 env.close();
3814
3815 // Note that minter's XRP balance goes up even though they
3816 // requested XRP(0).
3817 BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(1));
3818 BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
3819 BEAST_EXPECT(env.balance(broker) == brokerBalance - baseFee);
3820 BEAST_EXPECT(env.balance(issuer) == issuerBalance);
3821
3822 // Burn the NFT so the next test starts with a clean state.
3823 env(token::burn(buyer, nftID));
3824 env.close();
3825 }
3826
3827 // o Seller is selling for zero XRP.
3828 // o Broker charges a fee.
3829 // o No transfer fee.
3830 //
3831 // Since minter is selling for zero the currency must be XRP.
3832 {
3833 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3834
3835 uint256 const nftID = mintNFT();
3836
3837 // minter creates their offer.
3838 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
3839 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
3840 env.close();
3841
3842 // buyer creates their offer. Note: a buy offer can never
3843 // offer zero.
3844 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3845 env(token::createOffer(buyer, nftID, XRP(1)), token::Owner(minter));
3846 env.close();
3847
3848 // Broker attempts to charge a 1.1 XRP brokerFee and fails.
3849 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
3850 token::BrokerFee(XRP(1.1)),
3852 env.close();
3853
3854 auto const minterBalance = env.balance(minter);
3855 auto const buyerBalance = env.balance(buyer);
3856 auto const brokerBalance = env.balance(broker);
3857 auto const issuerBalance = env.balance(issuer);
3858
3859 // Broker charges a 0.5 XRP brokerFee.
3860 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
3861 token::BrokerFee(XRP(0.5)));
3862 env.close();
3863
3864 // Note that minter's XRP balance goes up even though they
3865 // requested XRP(0).
3866 BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
3867 BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
3868 BEAST_EXPECT(env.balance(broker) == brokerBalance + XRP(0.5) - baseFee);
3869 BEAST_EXPECT(env.balance(issuer) == issuerBalance);
3870
3871 // Burn the NFT so the next test starts with a clean state.
3872 env(token::burn(buyer, nftID));
3873 env.close();
3874 }
3875
3876 // o Seller is selling for zero XRP.
3877 // o Broker charges no fee.
3878 // o 50% transfer fee.
3879 //
3880 // Since minter is selling for zero the currency must be XRP.
3881 {
3882 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3883
3884 uint256 const nftID = mintNFT(kMaxTransferFee);
3885
3886 // minter creates their offer.
3887 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
3888 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
3889 env.close();
3890
3891 // buyer creates their offer. Note: a buy offer can never
3892 // offer zero.
3893 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3894 env(token::createOffer(buyer, nftID, XRP(1)), token::Owner(minter));
3895 env.close();
3896
3897 auto const minterBalance = env.balance(minter);
3898 auto const buyerBalance = env.balance(buyer);
3899 auto const brokerBalance = env.balance(broker);
3900 auto const issuerBalance = env.balance(issuer);
3901
3902 // Broker charges no brokerFee.
3903 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex));
3904 env.close();
3905
3906 // Note that minter's XRP balance goes up even though they
3907 // requested XRP(0).
3908 BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.5));
3909 BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
3910 BEAST_EXPECT(env.balance(broker) == brokerBalance - baseFee);
3911 BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.5));
3912
3913 // Burn the NFT so the next test starts with a clean state.
3914 env(token::burn(buyer, nftID));
3915 env.close();
3916 }
3917
3918 // o Seller is selling for zero XRP.
3919 // o Broker charges 0.5 XRP.
3920 // o 50% transfer fee.
3921 //
3922 // Since minter is selling for zero the currency must be XRP.
3923 {
3924 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3925
3926 uint256 const nftID = mintNFT(kMaxTransferFee);
3927
3928 // minter creates their offer.
3929 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
3930 env(token::createOffer(minter, nftID, XRP(0)), Txflags(tfSellNFToken));
3931 env.close();
3932
3933 // buyer creates their offer. Note: a buy offer can never
3934 // offer zero.
3935 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
3936 env(token::createOffer(buyer, nftID, XRP(1)), token::Owner(minter));
3937 env.close();
3938
3939 auto const minterBalance = env.balance(minter);
3940 auto const buyerBalance = env.balance(buyer);
3941 auto const brokerBalance = env.balance(broker);
3942 auto const issuerBalance = env.balance(issuer);
3943
3944 // Broker charges a 0.75 XRP brokerFee.
3945 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
3946 token::BrokerFee(XRP(0.75)));
3947 env.close();
3948
3949 // Note that, with a 50% transfer fee, issuer gets 1/2 of what's
3950 // left _after_ broker takes their fee. minter gets the
3951 // remainder after both broker and minter take their cuts
3952 BEAST_EXPECT(env.balance(minter) == minterBalance + XRP(0.125));
3953 BEAST_EXPECT(env.balance(buyer) == buyerBalance - XRP(1));
3954 BEAST_EXPECT(env.balance(broker) == brokerBalance + XRP(0.75) - baseFee);
3955 BEAST_EXPECT(env.balance(issuer) == issuerBalance + XRP(0.125));
3956
3957 // Burn the NFT so the next test starts with a clean state.
3958 env(token::burn(buyer, nftID));
3959 env.close();
3960 }
3961
3962 // Lambda to set the balance of all passed in accounts to
3963 // gwXAU(amount).
3964 auto setXAUBalance =
3965 [this, &gw, &gwXAU, &env](
3967 int amount,
3968 int line) {
3969 for (Account const& acct : accounts)
3970 {
3971 auto const xauAmt = gwXAU(amount);
3972 auto const balance = env.balance(acct, gwXAU);
3973 if (balance < xauAmt)
3974 {
3975 env(pay(gw, acct, xauAmt - balance));
3976 env.close();
3977 }
3978 else if (balance > xauAmt)
3979 {
3980 env(pay(acct, gw, balance - xauAmt));
3981 env.close();
3982 }
3983 if (env.balance(acct, gwXAU) != xauAmt)
3984 {
3986 ss << "Unable to set " << acct.human() << " account balance to gwXAU("
3987 << amount << ")";
3988 this->fail(ss.str(), __FILE__, line);
3989 }
3990 }
3991 };
3992
3993 // The buyer and seller have identical amounts and there is no
3994 // transfer fee.
3995 {
3996 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
3997 setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
3998
3999 uint256 const nftID = mintNFT();
4000
4001 // minter creates their offer.
4002 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
4003 env(token::createOffer(minter, nftID, gwXAU(1000)), Txflags(tfSellNFToken));
4004 env.close();
4005
4006 {
4007 // buyer creates an offer for more XAU than they currently
4008 // own.
4009 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4010 env(token::createOffer(buyer, nftID, gwXAU(1001)), token::Owner(minter));
4011 env.close();
4012
4013 // broker attempts to broker the offers but cannot.
4014 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4016 env.close();
4017
4018 // Cancel buyer's bad offer so the next test starts in a
4019 // clean state.
4020 env(token::cancelOffer(buyer, {buyOfferIndex}));
4021 env.close();
4022 }
4023 {
4024 // buyer creates an offer for less that what minter is
4025 // asking.
4026 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4027 env(token::createOffer(buyer, nftID, gwXAU(999)), token::Owner(minter));
4028 env.close();
4029
4030 // broker attempts to broker the offers but cannot.
4031 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4033 env.close();
4034
4035 // Cancel buyer's bad offer so the next test starts in a
4036 // clean state.
4037 env(token::cancelOffer(buyer, {buyOfferIndex}));
4038 env.close();
4039 }
4040
4041 // buyer creates a large enough offer.
4042 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4043 env(token::createOffer(buyer, nftID, gwXAU(1000)), token::Owner(minter));
4044 env.close();
4045
4046 // Broker attempts to charge a brokerFee but cannot.
4047 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4048 token::BrokerFee(gwXAU(0.1)),
4050 env.close();
4051
4052 // broker charges no brokerFee and succeeds.
4053 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex));
4054 env.close();
4055
4056 BEAST_EXPECT(ownerCount(env, issuer) == 1);
4057 BEAST_EXPECT(ownerCount(env, minter) == 1);
4058 BEAST_EXPECT(ownerCount(env, buyer) == 2);
4059 BEAST_EXPECT(ownerCount(env, broker) == 1);
4060 BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1000));
4061 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(2000));
4062 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
4063 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1000));
4064
4065 // Burn the NFT so the next test starts with a clean state.
4066 env(token::burn(buyer, nftID));
4067 env.close();
4068 }
4069
4070 // seller offers more than buyer is asking.
4071 // There are both transfer and broker fees.
4072 {
4073 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
4074 setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
4075
4076 uint256 const nftID = mintNFT(kMaxTransferFee);
4077
4078 // minter creates their offer.
4079 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
4080 env(token::createOffer(minter, nftID, gwXAU(900)), Txflags(tfSellNFToken));
4081 env.close();
4082 {
4083 // buyer creates an offer for more XAU than they currently
4084 // own.
4085 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4086 env(token::createOffer(buyer, nftID, gwXAU(1001)), token::Owner(minter));
4087 env.close();
4088
4089 // broker attempts to broker the offers but cannot.
4090 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4092 env.close();
4093
4094 // Cancel buyer's bad offer so the next test starts in a
4095 // clean state.
4096 env(token::cancelOffer(buyer, {buyOfferIndex}));
4097 env.close();
4098 }
4099 {
4100 // buyer creates an offer for less that what minter is
4101 // asking.
4102 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4103 env(token::createOffer(buyer, nftID, gwXAU(899)), token::Owner(minter));
4104 env.close();
4105
4106 // broker attempts to broker the offers but cannot.
4107 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4109 env.close();
4110
4111 // Cancel buyer's bad offer so the next test starts in a
4112 // clean state.
4113 env(token::cancelOffer(buyer, {buyOfferIndex}));
4114 env.close();
4115 }
4116 // buyer creates a large enough offer.
4117 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4118 env(token::createOffer(buyer, nftID, gwXAU(1000)), token::Owner(minter));
4119 env.close();
4120
4121 // Broker attempts to charge a brokerFee larger than the
4122 // difference between the two offers but cannot.
4123 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4124 token::BrokerFee(gwXAU(101)),
4126 env.close();
4127
4128 // broker charges the full difference between the two offers and
4129 // succeeds.
4130 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4131 token::BrokerFee(gwXAU(100)));
4132 env.close();
4133
4134 BEAST_EXPECT(ownerCount(env, issuer) == 1);
4135 BEAST_EXPECT(ownerCount(env, minter) == 1);
4136 BEAST_EXPECT(ownerCount(env, buyer) == 2);
4137 BEAST_EXPECT(ownerCount(env, broker) == 1);
4138 BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1450));
4139 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1450));
4140 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
4141 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1100));
4142
4143 // Burn the NFT so the next test starts with a clean state.
4144 env(token::burn(buyer, nftID));
4145 env.close();
4146 }
4147 // seller offers more than buyer is asking.
4148 // There are both transfer and broker fees, but broker takes less
4149 // than the maximum.
4150 {
4151 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
4152 setXAUBalance({issuer, minter, buyer, broker}, 1000, __LINE__);
4153
4154 uint256 const nftID = mintNFT(kMaxTransferFee / 2); // 25%
4155
4156 // minter creates their offer.
4157 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
4158 env(token::createOffer(minter, nftID, gwXAU(900)), Txflags(tfSellNFToken));
4159 env.close();
4160
4161 // buyer creates a large enough offer.
4162 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4163 env(token::createOffer(buyer, nftID, gwXAU(1000)), token::Owner(minter));
4164 env.close();
4165
4166 // broker charges half difference between the two offers and
4167 // succeeds. 25% of the remaining difference goes to issuer.
4168 // The rest goes to minter.
4169 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4170 token::BrokerFee(gwXAU(50)));
4171 env.close();
4172
4173 BEAST_EXPECT(ownerCount(env, issuer) == 1);
4174 BEAST_EXPECT(ownerCount(env, minter) == 1);
4175 BEAST_EXPECT(ownerCount(env, buyer) == 2);
4176 BEAST_EXPECT(ownerCount(env, broker) == 1);
4177 BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
4178 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
4179 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
4180 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(1050));
4181
4182 // Burn the NFT so the next test starts with a clean state.
4183 env(token::burn(buyer, nftID));
4184 env.close();
4185 }
4186 // Broker has a balance less than the seller offer
4187 {
4188 checkOwnerCountIsOne({issuer, minter, buyer, broker}, __LINE__);
4189 setXAUBalance({issuer, minter, buyer}, 1000, __LINE__);
4190 setXAUBalance({broker}, 500, __LINE__);
4191 uint256 const nftID = mintNFT(kMaxTransferFee / 2); // 25%
4192
4193 // minter creates their offer.
4194 uint256 const minterOfferIndex = keylet::nftokenOffer(minter, env.seq(minter)).key;
4195 env(token::createOffer(minter, nftID, gwXAU(900)), Txflags(tfSellNFToken));
4196 env.close();
4197
4198 // buyer creates a large enough offer.
4199 uint256 const buyOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4200 env(token::createOffer(buyer, nftID, gwXAU(1000)), token::Owner(minter));
4201 env.close();
4202
4203 env(token::brokerOffers(broker, buyOfferIndex, minterOfferIndex),
4204 token::BrokerFee(gwXAU(50)));
4205 env.close();
4206 BEAST_EXPECT(ownerCount(env, issuer) == 1);
4207 BEAST_EXPECT(ownerCount(env, minter) == 1);
4208 BEAST_EXPECT(ownerCount(env, buyer) == 2);
4209 BEAST_EXPECT(ownerCount(env, broker) == 1);
4210 BEAST_EXPECT(env.balance(issuer, gwXAU) == gwXAU(1237.5));
4211 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1712.5));
4212 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
4213 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(550));
4214
4215 // Burn the NFT so the next test starts with a clean state.
4216 env(token::burn(buyer, nftID));
4217 env.close();
4218 }
4219 }
4220 }
4221
4222 void
4224 {
4225 // Verify the Owner field of an offer behaves as expected.
4226 testcase("NFToken offer owner");
4227
4228 using namespace test::jtx;
4229
4230 Env env{*this, features};
4231
4232 Account const issuer{"issuer"};
4233 Account const buyer1{"buyer1"};
4234 Account const buyer2{"buyer2"};
4235 env.fund(XRP(10000), issuer, buyer1, buyer2);
4236 env.close();
4237
4238 // issuer creates an NFT.
4239 uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
4240 env(token::mint(issuer, 0u), Txflags(tfTransferable));
4241 env.close();
4242
4243 // Prove that issuer now owns nftId.
4244 BEAST_EXPECT(nftCount(env, issuer) == 1);
4245 BEAST_EXPECT(nftCount(env, buyer1) == 0);
4246 BEAST_EXPECT(nftCount(env, buyer2) == 0);
4247
4248 // Both buyer1 and buyer2 create buy offers for nftId.
4249 uint256 const buyer1OfferIndex = keylet::nftokenOffer(buyer1, env.seq(buyer1)).key;
4250 env(token::createOffer(buyer1, nftId, XRP(100)), token::Owner(issuer));
4251 uint256 const buyer2OfferIndex = keylet::nftokenOffer(buyer2, env.seq(buyer2)).key;
4252 env(token::createOffer(buyer2, nftId, XRP(100)), token::Owner(issuer));
4253 env.close();
4254
4255 // Lambda that counts the number of buy offers for a given NFT.
4256 auto nftBuyOfferCount = [&env](uint256 const& nftId) -> std::size_t {
4257 // We know that in this case not very many offers will be
4258 // returned, so we skip the marker stuff.
4259 json::Value params;
4260 params[jss::nft_id] = to_string(nftId);
4261 json::Value buyOffers = env.rpc("json", "nft_buy_offers", to_string(params));
4262
4263 if (buyOffers.isMember(jss::result) && buyOffers[jss::result].isMember(jss::offers))
4264 return buyOffers[jss::result][jss::offers].size();
4265
4266 return 0;
4267 };
4268
4269 // Show there are two buy offers for nftId.
4270 BEAST_EXPECT(nftBuyOfferCount(nftId) == 2);
4271
4272 // issuer accepts buyer1's offer.
4273 env(token::acceptBuyOffer(issuer, buyer1OfferIndex));
4274 env.close();
4275
4276 // Prove that buyer1 now owns nftId.
4277 BEAST_EXPECT(nftCount(env, issuer) == 0);
4278 BEAST_EXPECT(nftCount(env, buyer1) == 1);
4279 BEAST_EXPECT(nftCount(env, buyer2) == 0);
4280
4281 // buyer1's offer was consumed, but buyer2's offer is still in the
4282 // ledger.
4283 BEAST_EXPECT(nftBuyOfferCount(nftId) == 1);
4284
4285 // buyer1 can now accept buyer2's offer, even though buyer2's
4286 // NFTokenCreateOffer transaction specified the NFT Owner as issuer.
4287 env(token::acceptBuyOffer(buyer1, buyer2OfferIndex));
4288 env.close();
4289
4290 // Prove that buyer2 now owns nftId.
4291 BEAST_EXPECT(nftCount(env, issuer) == 0);
4292 BEAST_EXPECT(nftCount(env, buyer1) == 0);
4293 BEAST_EXPECT(nftCount(env, buyer2) == 1);
4294
4295 // All of the NFTokenOffers are now consumed.
4296 BEAST_EXPECT(nftBuyOfferCount(nftId) == 0);
4297 }
4298
4299 void
4301 {
4302 // Make sure all NFToken transactions work with tickets.
4303 testcase("NFToken transactions with tickets");
4304
4305 using namespace test::jtx;
4306
4307 Env env{*this, features};
4308
4309 Account const issuer{"issuer"};
4310 Account const buyer{"buyer"};
4311 env.fund(XRP(10000), issuer, buyer);
4312 env.close();
4313
4314 // issuer and buyer grab enough tickets for all of the following
4315 // transactions. Note that once the tickets are acquired issuer's
4316 // and buyer's account sequence numbers should not advance.
4317 std::uint32_t issuerTicketSeq{env.seq(issuer) + 1};
4318 env(ticket::create(issuer, 10));
4319 env.close();
4320 std::uint32_t const issuerSeq{env.seq(issuer)};
4321 BEAST_EXPECT(ticketCount(env, issuer) == 10);
4322
4323 std::uint32_t buyerTicketSeq{env.seq(buyer) + 1};
4324 env(ticket::create(buyer, 10));
4325 env.close();
4326 std::uint32_t const buyerSeq{env.seq(buyer)};
4327 BEAST_EXPECT(ticketCount(env, buyer) == 10);
4328
4329 // NFTokenMint
4330 BEAST_EXPECT(ownerCount(env, issuer) == 10);
4331 uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
4332 env(token::mint(issuer, 0u), Txflags(tfTransferable), ticket::Use(issuerTicketSeq++));
4333 env.close();
4334 BEAST_EXPECT(ownerCount(env, issuer) == 10);
4335 BEAST_EXPECT(ticketCount(env, issuer) == 9);
4336
4337 // NFTokenCreateOffer
4338 BEAST_EXPECT(ownerCount(env, buyer) == 10);
4339 uint256 const offerIndex0 = keylet::nftokenOffer(buyer, buyerTicketSeq).key;
4340 env(token::createOffer(buyer, nftId, XRP(1)),
4341 token::Owner(issuer),
4342 ticket::Use(buyerTicketSeq++));
4343 env.close();
4344 BEAST_EXPECT(ownerCount(env, buyer) == 10);
4345 BEAST_EXPECT(ticketCount(env, buyer) == 9);
4346
4347 // NFTokenCancelOffer
4348 env(token::cancelOffer(buyer, {offerIndex0}), ticket::Use(buyerTicketSeq++));
4349 env.close();
4350 BEAST_EXPECT(ownerCount(env, buyer) == 8);
4351 BEAST_EXPECT(ticketCount(env, buyer) == 8);
4352
4353 // NFTokenCreateOffer. buyer tries again.
4354 uint256 const offerIndex1 = keylet::nftokenOffer(buyer, buyerTicketSeq).key;
4355 env(token::createOffer(buyer, nftId, XRP(2)),
4356 token::Owner(issuer),
4357 ticket::Use(buyerTicketSeq++));
4358 env.close();
4359 BEAST_EXPECT(ownerCount(env, buyer) == 8);
4360 BEAST_EXPECT(ticketCount(env, buyer) == 7);
4361
4362 // NFTokenAcceptOffer. issuer accepts buyer's offer.
4363 env(token::acceptBuyOffer(issuer, offerIndex1), ticket::Use(issuerTicketSeq++));
4364 env.close();
4365 BEAST_EXPECT(ownerCount(env, issuer) == 8);
4366 BEAST_EXPECT(ownerCount(env, buyer) == 8);
4367 BEAST_EXPECT(ticketCount(env, issuer) == 8);
4368
4369 // NFTokenBurn. buyer burns the token they just bought.
4370 env(token::burn(buyer, nftId), ticket::Use(buyerTicketSeq++));
4371 env.close();
4372 BEAST_EXPECT(ownerCount(env, issuer) == 8);
4373 BEAST_EXPECT(ownerCount(env, buyer) == 6);
4374 BEAST_EXPECT(ticketCount(env, buyer) == 6);
4375
4376 // Verify that the account sequence numbers did not advance.
4377 BEAST_EXPECT(env.seq(issuer) == issuerSeq);
4378 BEAST_EXPECT(env.seq(buyer) == buyerSeq);
4379 }
4380
4381 void
4383 {
4384 // Account deletion rules with NFTs:
4385 // 1. An account holding one or more NFT offers may be deleted.
4386 // 2. An NFT issuer with any NFTs they have issued still in the
4387 // ledger may not be deleted.
4388 // 3. An account holding one or more NFTs may not be deleted.
4389 testcase("NFToken delete account");
4390
4391 using namespace test::jtx;
4392
4393 Env env{*this, features};
4394
4395 Account const issuer{"issuer"};
4396 Account const minter{"minter"};
4397 Account const becky{"becky"};
4398 Account const carla{"carla"};
4399 Account const daria{"daria"};
4400
4401 env.fund(XRP(10000), issuer, minter, becky, carla, daria);
4402 env.close();
4403
4404 // Allow enough ledgers to pass so any of these accounts can be deleted.
4405 for (int i = 0; i < 300; ++i)
4406 env.close();
4407
4408 env(token::setMinter(issuer, minter));
4409 env.close();
4410
4411 uint256 const nftId{token::getNextID(env, issuer, 0u, tfTransferable)};
4412 env(token::mint(minter, 0u), token::Issuer(issuer), Txflags(tfTransferable));
4413 env.close();
4414
4415 // At the moment issuer and minter cannot delete themselves.
4416 // o issuer has an issued NFT in the ledger.
4417 // o minter owns an NFT.
4418 env(acctdelete(issuer, daria), Fee(XRP(50)), Ter(tecHAS_OBLIGATIONS));
4419 env(acctdelete(minter, daria), Fee(XRP(50)), Ter(tecHAS_OBLIGATIONS));
4420 env.close();
4421
4422 // Let enough ledgers pass so the account delete transactions are
4423 // not retried.
4424 for (int i = 0; i < 15; ++i)
4425 env.close();
4426
4427 // becky and carla create offers for minter's NFT.
4428 env(token::createOffer(becky, nftId, XRP(2)), token::Owner(minter));
4429 env.close();
4430
4431 uint256 const carlaOfferIndex = keylet::nftokenOffer(carla, env.seq(carla)).key;
4432 env(token::createOffer(carla, nftId, XRP(3)), token::Owner(minter));
4433 env.close();
4434
4435 // It should be possible for becky to delete herself, even though
4436 // becky has an active NFT offer.
4437 env(acctdelete(becky, daria), Fee(XRP(50)));
4438 env.close();
4439
4440 // minter accepts carla's offer.
4441 env(token::acceptBuyOffer(minter, carlaOfferIndex));
4442 env.close();
4443
4444 // Now it should be possible for minter to delete themselves since
4445 // they no longer own an NFT.
4446 env(acctdelete(minter, daria), Fee(XRP(50)));
4447 env.close();
4448
4449 // 1. issuer cannot delete themselves because they issued an NFT that
4450 // is still in the ledger.
4451 // 2. carla owns an NFT, so she cannot delete herself.
4452 env(acctdelete(issuer, daria), Fee(XRP(50)), Ter(tecHAS_OBLIGATIONS));
4453 env(acctdelete(carla, daria), Fee(XRP(50)), Ter(tecHAS_OBLIGATIONS));
4454 env.close();
4455
4456 // Let enough ledgers pass so the account delete transactions are
4457 // not retried.
4458 for (int i = 0; i < 15; ++i)
4459 env.close();
4460
4461 // carla burns her NFT. Since issuer's NFT is no longer in the
4462 // ledger, both issuer and carla can delete themselves.
4463 env(token::burn(carla, nftId));
4464 env.close();
4465
4466 env(acctdelete(issuer, daria), Fee(XRP(50)));
4467 env(acctdelete(carla, daria), Fee(XRP(50)));
4468 env.close();
4469 }
4470
4471 void
4473 {
4474 testcase("nft_buy_offers and nft_sell_offers");
4475
4476 // The default limit on returned NFToken offers is 250, so we need
4477 // to produce more than 250 offers of each kind in order to exercise
4478 // the marker.
4479
4480 // Fortunately there's nothing in the rules that says an account
4481 // can't hold more than one offer for the same NFT. So we only
4482 // need two accounts to generate the necessary offers.
4483 using namespace test::jtx;
4484
4485 Env env{*this, features};
4486
4487 Account const issuer{"issuer"};
4488 Account const buyer{"buyer"};
4489
4490 // A lot of offers requires a lot for reserve.
4491 env.fund(XRP(1000000), issuer, buyer);
4492 env.close();
4493
4494 // Create an NFT that we'll make offers for.
4495 uint256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
4496 env(token::mint(issuer, 0), Txflags(tfTransferable));
4497 env.close();
4498
4499 // A lambda that validates nft_XXX_offers query responses.
4500 auto checkOffers = [this, &env, &nftID](
4501 char const* request,
4502 int expectCount,
4503 int expectMarkerCount,
4504 int line) {
4505 int markerCount = 0;
4507 std::string marker;
4508
4509 // The do/while collects results until no marker is returned.
4510 do
4511 {
4512 json::Value nftOffers = [&env, &nftID, &request, &marker]() {
4513 json::Value params;
4514 params[jss::nft_id] = to_string(nftID);
4515
4516 if (!marker.empty())
4517 params[jss::marker] = marker;
4518 return env.rpc("json", request, to_string(params));
4519 }();
4520
4521 // If there are no offers for the NFT we get an error
4522 if (expectCount == 0)
4523 {
4524 if (expect(
4525 nftOffers.isMember(jss::result), "expected \"result\"", __FILE__, line))
4526 {
4527 if (expect(
4528 nftOffers[jss::result].isMember(jss::error),
4529 "expected \"error\"",
4530 __FILE__,
4531 line))
4532 {
4533 expect(
4534 nftOffers[jss::result][jss::error].asString() == "objectNotFound",
4535 "expected \"objectNotFound\"",
4536 __FILE__,
4537 line);
4538 }
4539 }
4540 break;
4541 }
4542
4543 marker.clear();
4544 if (expect(nftOffers.isMember(jss::result), "expected \"result\"", __FILE__, line))
4545 {
4546 json::Value& result = nftOffers[jss::result];
4547
4548 if (result.isMember(jss::marker))
4549 {
4550 ++markerCount;
4551 marker = result[jss::marker].asString();
4552 }
4553
4554 if (expect(result.isMember(jss::offers), "expected \"offers\"", __FILE__, line))
4555 {
4556 json::Value& someOffers = result[jss::offers];
4557 for (std::size_t i = 0; i < someOffers.size(); ++i)
4558 allOffers.append(someOffers[i]);
4559 }
4560 }
4561 } while (!marker.empty());
4562
4563 // Verify the contents of allOffers makes sense.
4564 expect(
4565 allOffers.size() == expectCount, "Unexpected returned offer count", __FILE__, line);
4566 expect(markerCount == expectMarkerCount, "Unexpected marker count", __FILE__, line);
4567 std::optional<int> globalFlags;
4568 std::set<std::string> offerIndexes;
4569 std::set<std::string> amounts;
4570 for (json::Value const& offer : allOffers)
4571 {
4572 // The flags on all found offers should be the same.
4573 if (!globalFlags)
4574 globalFlags = offer[jss::flags].asInt();
4575
4576 expect(
4577 *globalFlags == offer[jss::flags].asInt(),
4578 "Inconsistent flags returned",
4579 __FILE__,
4580 line);
4581
4582 // The test conditions should produce unique indexes and
4583 // amounts for all offers.
4584 offerIndexes.insert(offer[jss::nft_offer_index].asString());
4585 amounts.insert(offer[jss::amount].asString());
4586 }
4587
4588 expect(
4589 offerIndexes.size() == expectCount, "Duplicate indexes returned?", __FILE__, line);
4590 expect(amounts.size() == expectCount, "Duplicate amounts returned?", __FILE__, line);
4591 };
4592
4593 // There are no sell offers.
4594 checkOffers("nft_sell_offers", 0, 0, __LINE__);
4595
4596 // A lambda that generates sell offers.
4597 STAmount sellPrice = XRP(0);
4598 auto makeSellOffers = [&env, &issuer, &nftID, &sellPrice](STAmount const& limit) {
4599 // Save a little test time by not closing too often.
4600 int offerCount = 0;
4601 while (sellPrice < limit)
4602 {
4603 sellPrice += XRP(1);
4604 env(token::createOffer(issuer, nftID, sellPrice), Txflags(tfSellNFToken));
4605 if (++offerCount % 10 == 0)
4606 env.close();
4607 }
4608 env.close();
4609 };
4610
4611 // There is one sell offer.
4612 makeSellOffers(XRP(1));
4613 checkOffers("nft_sell_offers", 1, 0, __LINE__);
4614
4615 // There are 250 sell offers.
4616 makeSellOffers(XRP(250));
4617 checkOffers("nft_sell_offers", 250, 0, __LINE__);
4618
4619 // There are 251 sell offers.
4620 makeSellOffers(XRP(251));
4621 checkOffers("nft_sell_offers", 251, 1, __LINE__);
4622
4623 // There are 500 sell offers.
4624 makeSellOffers(XRP(500));
4625 checkOffers("nft_sell_offers", 500, 1, __LINE__);
4626
4627 // There are 501 sell offers.
4628 makeSellOffers(XRP(501));
4629 checkOffers("nft_sell_offers", 501, 2, __LINE__);
4630
4631 // There are no buy offers.
4632 checkOffers("nft_buy_offers", 0, 0, __LINE__);
4633
4634 // A lambda that generates buy offers.
4635 STAmount buyPrice = XRP(0);
4636 auto makeBuyOffers = [&env, &buyer, &issuer, &nftID, &buyPrice](STAmount const& limit) {
4637 // Save a little test time by not closing too often.
4638 int offerCount = 0;
4639 while (buyPrice < limit)
4640 {
4641 buyPrice += XRP(1);
4642 env(token::createOffer(buyer, nftID, buyPrice), token::Owner(issuer));
4643 if (++offerCount % 10 == 0)
4644 env.close();
4645 }
4646 env.close();
4647 };
4648
4649 // There is one buy offer;
4650 makeBuyOffers(XRP(1));
4651 checkOffers("nft_buy_offers", 1, 0, __LINE__);
4652
4653 // There are 250 buy offers.
4654 makeBuyOffers(XRP(250));
4655 checkOffers("nft_buy_offers", 250, 0, __LINE__);
4656
4657 // There are 251 buy offers.
4658 makeBuyOffers(XRP(251));
4659 checkOffers("nft_buy_offers", 251, 1, __LINE__);
4660
4661 // There are 500 buy offers.
4662 makeBuyOffers(XRP(500));
4663 checkOffers("nft_buy_offers", 500, 1, __LINE__);
4664
4665 // There are 501 buy offers.
4666 makeBuyOffers(XRP(501));
4667 checkOffers("nft_buy_offers", 501, 2, __LINE__);
4668 }
4669
4670 void
4672 {
4673 using namespace test::jtx;
4674
4675 testcase("NFTokenNegOffer");
4676
4677 Account const issuer{"issuer"};
4678 Account const buyer{"buyer"};
4679 Account const gw{"gw"};
4680 IOU const gwXAU(gw["XAU"]);
4681
4682 {
4683 Env env{*this, features};
4684
4685 env.fund(XRP(1000000), issuer, buyer, gw);
4686 env.close();
4687
4688 env(trust(issuer, gwXAU(2000)));
4689 env(trust(buyer, gwXAU(2000)));
4690 env.close();
4691
4692 env(pay(gw, issuer, gwXAU(1000)));
4693 env(pay(gw, buyer, gwXAU(1000)));
4694 env.close();
4695
4696 // Create an NFT that we'll make XRP offers for.
4697 uint256 const nftID0{token::getNextID(env, issuer, 0u, tfTransferable)};
4698 env(token::mint(issuer, 0), Txflags(tfTransferable));
4699 env.close();
4700
4701 // Create an NFT that we'll make IOU offers for.
4702 uint256 const nftID1{token::getNextID(env, issuer, 1u, tfTransferable)};
4703 env(token::mint(issuer, 1), Txflags(tfTransferable));
4704 env.close();
4705
4706 TER const offerCreateTER = temBAD_AMOUNT;
4707
4708 // Make offers with negative amounts for the NFTs
4709 uint256 const sellNegXrpOfferIndex = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
4710 env(token::createOffer(issuer, nftID0, XRP(-2)),
4711 Txflags(tfSellNFToken),
4712 Ter(offerCreateTER));
4713 env.close();
4714
4715 uint256 const sellNegIouOfferIndex = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
4716 env(token::createOffer(issuer, nftID1, gwXAU(-2)),
4717 Txflags(tfSellNFToken),
4718 Ter(offerCreateTER));
4719 env.close();
4720
4721 uint256 const buyNegXrpOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4722 env(token::createOffer(buyer, nftID0, XRP(-1)),
4723 token::Owner(issuer),
4724 Ter(offerCreateTER));
4725 env.close();
4726
4727 uint256 const buyNegIouOfferIndex = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
4728 env(token::createOffer(buyer, nftID1, gwXAU(-1)),
4729 token::Owner(issuer),
4730 Ter(offerCreateTER));
4731 env.close();
4732
4733 {
4734 // Now try to accept the offers.
4735 TER const offerAcceptTER = tecOBJECT_NOT_FOUND;
4736
4737 // Sell offers.
4738 env(token::acceptSellOffer(buyer, sellNegXrpOfferIndex), Ter(offerAcceptTER));
4739 env.close();
4740 env(token::acceptSellOffer(buyer, sellNegIouOfferIndex), Ter(offerAcceptTER));
4741 env.close();
4742
4743 // Buy offers.
4744 env(token::acceptBuyOffer(issuer, buyNegXrpOfferIndex), Ter(offerAcceptTER));
4745 env.close();
4746 env(token::acceptBuyOffer(issuer, buyNegIouOfferIndex), Ter(offerAcceptTER));
4747 env.close();
4748 }
4749 {
4750 TER const offerAcceptTER = tecOBJECT_NOT_FOUND;
4751
4752 // Brokered offers.
4753 env(token::brokerOffers(gw, buyNegXrpOfferIndex, sellNegXrpOfferIndex),
4754 Ter(offerAcceptTER));
4755 env.close();
4756 env(token::brokerOffers(gw, buyNegIouOfferIndex, sellNegIouOfferIndex),
4757 Ter(offerAcceptTER));
4758 env.close();
4759 }
4760 }
4761
4762 {
4763 // Test buy offers with a destination.
4764 Env env{*this, features};
4765
4766 env.fund(XRP(1000000), issuer, buyer);
4767
4768 // Create an NFT that we'll make offers for.
4769 uint256 const nftID{token::getNextID(env, issuer, 0u, tfTransferable)};
4770 env(token::mint(issuer, 0), Txflags(tfTransferable));
4771 env.close();
4772
4773 TER const offerCreateTER = tesSUCCESS;
4774
4775 env(token::createOffer(buyer, nftID, drops(1)),
4776 token::Owner(issuer),
4777 token::Destination(issuer),
4778 Ter(offerCreateTER));
4779 env.close();
4780 }
4781 }
4782
4783 void
4785 {
4786 using namespace test::jtx;
4787
4788 testcase("Payments with IOU transfer fees");
4789
4790 {
4791 Env env{*this, features};
4792
4793 Account const minter{"minter"};
4794 Account const secondarySeller{"seller"};
4795 Account const buyer{"buyer"};
4796 Account const gw{"gateway"};
4797 Account const broker{"broker"};
4798 IOU const gwXAU(gw["XAU"]);
4799 IOU const gwXPB(gw["XPB"]);
4800
4801 env.fund(XRP(1000), gw, minter, secondarySeller, buyer, broker);
4802 env.close();
4803
4804 env(trust(minter, gwXAU(2000)));
4805 env(trust(secondarySeller, gwXAU(2000)));
4806 env(trust(broker, gwXAU(10000)));
4807 env(trust(buyer, gwXAU(2000)));
4808 env(trust(buyer, gwXPB(2000)));
4809 env.close();
4810
4811 // The IOU issuer has a 2% transfer rate
4812 env(rate(gw, 1.02));
4813 env.close();
4814
4815 auto expectInitialState =
4816 [this, &env, &buyer, &minter, &secondarySeller, &broker, &gw, &gwXAU, &gwXPB]() {
4817 // Buyer should have XAU 1000, XPB 0
4818 // Minter should have XAU 0, XPB 0
4819 // Secondary seller should have XAU 0, XPB 0
4820 // Broker should have XAU 5000, XPB 0
4821 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(1000));
4822 BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(0));
4823 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(0));
4824 BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(0));
4825 BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(0));
4826 BEAST_EXPECT(env.balance(secondarySeller, gwXPB) == gwXPB(0));
4827 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5000));
4828 BEAST_EXPECT(env.balance(broker, gwXPB) == gwXPB(0));
4829 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-1000));
4830 BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(0));
4831 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(0));
4832 BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(0));
4833 BEAST_EXPECT(env.balance(gw, secondarySeller["XAU"]) == gwXAU(0));
4834 BEAST_EXPECT(env.balance(gw, secondarySeller["XPB"]) == gwXPB(0));
4835 BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5000));
4836 BEAST_EXPECT(env.balance(gw, broker["XPB"]) == gwXPB(0));
4837 };
4838
4839 auto reinitializeTrustLineBalances = [&expectInitialState,
4840 &env,
4841 &buyer,
4842 &minter,
4843 &secondarySeller,
4844 &broker,
4845 &gw,
4846 &gwXAU,
4847 &gwXPB]() {
4848 if (auto const difference = gwXAU(1000) - env.balance(buyer, gwXAU);
4849 difference > gwXAU(0))
4850 env(pay(gw, buyer, difference));
4851 if (env.balance(buyer, gwXPB) > gwXPB(0))
4852 env(pay(buyer, gw, env.balance(buyer, gwXPB)));
4853 if (env.balance(minter, gwXAU) > gwXAU(0))
4854 env(pay(minter, gw, env.balance(minter, gwXAU)));
4855 if (env.balance(minter, gwXPB) > gwXPB(0))
4856 env(pay(minter, gw, env.balance(minter, gwXPB)));
4857 if (env.balance(secondarySeller, gwXAU) > gwXAU(0))
4858 env(pay(secondarySeller, gw, env.balance(secondarySeller, gwXAU)));
4859 if (env.balance(secondarySeller, gwXPB) > gwXPB(0))
4860 env(pay(secondarySeller, gw, env.balance(secondarySeller, gwXPB)));
4861 auto brokerDiff = gwXAU(5000) - env.balance(broker, gwXAU);
4862 if (brokerDiff > gwXAU(0))
4863 {
4864 env(pay(gw, broker, brokerDiff));
4865 }
4866 else if (brokerDiff < gwXAU(0))
4867 {
4868 brokerDiff.negate();
4869 env(pay(broker, gw, brokerDiff));
4870 }
4871 if (env.balance(broker, gwXPB) > gwXPB(0))
4872 env(pay(broker, gw, env.balance(broker, gwXPB)));
4873 env.close();
4874 expectInitialState();
4875 };
4876
4877 auto mintNFT = [&env](Account const& minter, int transferFee = 0) {
4878 uint256 const nftID = token::getNextID(env, minter, 0, tfTransferable, transferFee);
4879 env(token::mint(minter), token::XferFee(transferFee), Txflags(tfTransferable));
4880 env.close();
4881 return nftID;
4882 };
4883
4884 auto createBuyOffer = [&env](
4885 Account const& offerer,
4886 Account const& owner,
4887 uint256 const& nftID,
4888 STAmount const& amount,
4889 std::optional<TER const> const terCode = {}) {
4890 uint256 const offerID = keylet::nftokenOffer(offerer, env.seq(offerer)).key;
4891 env(token::createOffer(offerer, nftID, amount),
4892 token::Owner(owner),
4893 terCode ? Ter(*terCode) : Ter(static_cast<TER>(tesSUCCESS)));
4894 env.close();
4895 return offerID;
4896 };
4897
4898 auto createSellOffer = [&env](
4899 Account const& offerer,
4900 uint256 const& nftID,
4901 STAmount const& amount,
4902 std::optional<TER const> const terCode = {}) {
4903 uint256 const offerID = keylet::nftokenOffer(offerer, env.seq(offerer)).key;
4904 env(token::createOffer(offerer, nftID, amount),
4905 Txflags(tfSellNFToken),
4906 terCode ? Ter(*terCode) : Ter(static_cast<TER>(tesSUCCESS)));
4907 env.close();
4908 return offerID;
4909 };
4910
4911 {
4912 // Buyer attempts to send 100% of their balance of an IOU
4913 // (sellside)
4914 reinitializeTrustLineBalances();
4915 auto const nftID = mintNFT(minter);
4916 auto const offerID = createSellOffer(minter, nftID, gwXAU(1000));
4917 TER const sellTER = tecINSUFFICIENT_FUNDS;
4918 env(token::acceptSellOffer(buyer, offerID), Ter(sellTER));
4919 env.close();
4920
4921 expectInitialState();
4922 }
4923 {
4924 // Buyer attempts to send 100% of their balance of an IOU
4925 // (buyside)
4926 reinitializeTrustLineBalances();
4927 auto const nftID = mintNFT(minter);
4928 auto const offerID = createBuyOffer(buyer, minter, nftID, gwXAU(1000));
4929 TER const sellTER = tecINSUFFICIENT_FUNDS;
4930 env(token::acceptBuyOffer(minter, offerID), Ter(sellTER));
4931 env.close();
4932
4933 expectInitialState();
4934 }
4935 {
4936 // Buyer attempts to send an amount less than 100% of their
4937 // balance of an IOU, but such that the addition of the transfer
4938 // fee would be greater than the buyer's balance (sellside)
4939 reinitializeTrustLineBalances();
4940 auto const nftID = mintNFT(minter);
4941 auto const offerID = createSellOffer(minter, nftID, gwXAU(995));
4942 TER const sellTER = tecINSUFFICIENT_FUNDS;
4943 env(token::acceptSellOffer(buyer, offerID), Ter(sellTER));
4944 env.close();
4945
4946 expectInitialState();
4947 }
4948 {
4949 // Buyer attempts to send an amount less than 100% of their
4950 // balance of an IOU, but such that the addition of the transfer
4951 // fee would be greater than the buyer's balance (buyside)
4952 reinitializeTrustLineBalances();
4953 auto const nftID = mintNFT(minter);
4954 auto const offerID = createBuyOffer(buyer, minter, nftID, gwXAU(995));
4955 TER const sellTER = tecINSUFFICIENT_FUNDS;
4956 env(token::acceptBuyOffer(minter, offerID), Ter(sellTER));
4957 env.close();
4958
4959 expectInitialState();
4960 }
4961 {
4962 // Buyer attempts to send an amount less than 100% of their
4963 // balance of an IOU with a transfer fee, and such that the
4964 // addition of the transfer fee is still less than their balance
4965 // (sellside)
4966 reinitializeTrustLineBalances();
4967 auto const nftID = mintNFT(minter);
4968 auto const offerID = createSellOffer(minter, nftID, gwXAU(900));
4969 env(token::acceptSellOffer(buyer, offerID));
4970 env.close();
4971
4972 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
4973 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
4974 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
4975 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
4976 }
4977 {
4978 // Buyer attempts to send an amount less than 100% of their
4979 // balance of an IOU with a transfer fee, and such that the
4980 // addition of the transfer fee is still less than their balance
4981 // (buyside)
4982 reinitializeTrustLineBalances();
4983 auto const nftID = mintNFT(minter);
4984 auto const offerID = createBuyOffer(buyer, minter, nftID, gwXAU(900));
4985 env(token::acceptBuyOffer(minter, offerID));
4986 env.close();
4987
4988 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(900));
4989 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
4990 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-900));
4991 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
4992 }
4993 {
4994 // Buyer attempts to send an amount less than 100% of their
4995 // balance of an IOU with a transfer fee, and such that the
4996 // addition of the transfer fee is equal than their balance
4997 // (sellside)
4998 reinitializeTrustLineBalances();
4999
5000 // pay them an additional XAU 20 to cover transfer rate
5001 env(pay(gw, buyer, gwXAU(20)));
5002 env.close();
5003
5004 auto const nftID = mintNFT(minter);
5005 auto const offerID = createSellOffer(minter, nftID, gwXAU(1000));
5006 env(token::acceptSellOffer(buyer, offerID));
5007 env.close();
5008
5009 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
5010 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
5011 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
5012 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
5013 }
5014 {
5015 // Buyer attempts to send an amount less than 100% of their
5016 // balance of an IOU with a transfer fee, and such that the
5017 // addition of the transfer fee is equal than their balance
5018 // (buyside)
5019 reinitializeTrustLineBalances();
5020
5021 // pay them an additional XAU 20 to cover transfer rate
5022 env(pay(gw, buyer, gwXAU(20)));
5023 env.close();
5024
5025 auto const nftID = mintNFT(minter);
5026 auto const offerID = createBuyOffer(buyer, minter, nftID, gwXAU(1000));
5027 env(token::acceptBuyOffer(minter, offerID));
5028 env.close();
5029
5030 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
5031 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
5032 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
5033 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
5034 }
5035 {
5036 // Gateway attempts to buy NFT with their own IOU - no
5037 // transfer fee is calculated here (sellside)
5038 reinitializeTrustLineBalances();
5039
5040 auto const nftID = mintNFT(minter);
5041 auto const offerID = createSellOffer(minter, nftID, gwXAU(1000));
5042 TER const sellTER = tesSUCCESS;
5043 env(token::acceptSellOffer(gw, offerID), Ter(sellTER));
5044 env.close();
5045
5046 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
5047 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
5048 }
5049 {
5050 // Gateway attempts to buy NFT with their own IOU - no
5051 // transfer fee is calculated here (buyside)
5052 reinitializeTrustLineBalances();
5053
5054 auto const nftID = mintNFT(minter);
5055 TER const offerTER = tesSUCCESS;
5056 auto const offerID = createBuyOffer(gw, minter, nftID, gwXAU(1000), {offerTER});
5057 TER const sellTER = tesSUCCESS;
5058 env(token::acceptBuyOffer(minter, offerID), Ter(sellTER));
5059 env.close();
5060
5061 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(1000));
5062 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-1000));
5063 }
5064 {
5065 // Gateway attempts to buy NFT with their own IOU for more
5066 // than minter trusts (sellside)
5067 reinitializeTrustLineBalances();
5068 auto const nftID = mintNFT(minter);
5069 auto const offerID = createSellOffer(minter, nftID, gwXAU(5000));
5070 TER const sellTER = tesSUCCESS;
5071 env(token::acceptSellOffer(gw, offerID), Ter(sellTER));
5072 env.close();
5073
5074 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
5075 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-5000));
5076 }
5077 {
5078 // Gateway attempts to buy NFT with their own IOU for more
5079 // than minter trusts (buyside)
5080 reinitializeTrustLineBalances();
5081
5082 auto const nftID = mintNFT(minter);
5083 TER const offerTER = tesSUCCESS;
5084 auto const offerID = createBuyOffer(gw, minter, nftID, gwXAU(5000), {offerTER});
5085 TER const sellTER = tesSUCCESS;
5086 env(token::acceptBuyOffer(minter, offerID), Ter(sellTER));
5087 env.close();
5088
5089 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(5000));
5090 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-5000));
5091 }
5092 {
5093 // Gateway is the NFT minter and attempts to sell NFT for an
5094 // amount that would be greater than a balance if there were a
5095 // transfer fee calculated in this transaction. (sellside)
5096 reinitializeTrustLineBalances();
5097 auto const nftID = mintNFT(gw);
5098 auto const offerID = createSellOffer(gw, nftID, gwXAU(1000));
5099 env(token::acceptSellOffer(buyer, offerID));
5100 env.close();
5101
5102 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
5103 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
5104 }
5105 {
5106 // Gateway is the NFT minter and attempts to sell NFT for an
5107 // amount that would be greater than a balance if there were a
5108 // transfer fee calculated in this transaction. (buyside)
5109 reinitializeTrustLineBalances();
5110
5111 auto const nftID = mintNFT(gw);
5112 auto const offerID = createBuyOffer(buyer, gw, nftID, gwXAU(1000));
5113 env(token::acceptBuyOffer(gw, offerID));
5114 env.close();
5115
5116 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(0));
5117 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(0));
5118 }
5119 {
5120 // Gateway is the NFT minter and attempts to sell NFT for an
5121 // amount that is greater than a balance before transfer fees.
5122 // (sellside)
5123 reinitializeTrustLineBalances();
5124 auto const nftID = mintNFT(gw);
5125 auto const offerID = createSellOffer(gw, nftID, gwXAU(2000));
5126 env(token::acceptSellOffer(buyer, offerID),
5127 Ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
5128 env.close();
5129 expectInitialState();
5130 }
5131 {
5132 // Gateway is the NFT minter and attempts to sell NFT for an
5133 // amount that is greater than a balance before transfer fees.
5134 // (buyside)
5135 reinitializeTrustLineBalances();
5136 auto const nftID = mintNFT(gw);
5137 auto const offerID = createBuyOffer(buyer, gw, nftID, gwXAU(2000));
5138 env(token::acceptBuyOffer(gw, offerID),
5139 Ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
5140 env.close();
5141 expectInitialState();
5142 }
5143 {
5144 // Minter attempts to sell the token for XPB 10, which they
5145 // have no trust line for and buyer has kNone of (sellside).
5146 reinitializeTrustLineBalances();
5147 auto const nftID = mintNFT(minter);
5148 auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
5149 env(token::acceptSellOffer(buyer, offerID),
5150 Ter(static_cast<TER>(tecINSUFFICIENT_FUNDS)));
5151 env.close();
5152 expectInitialState();
5153 }
5154 {
5155 // Minter attempts to sell the token for XPB 10, which they
5156 // have no trust line for and buyer has kNone of (buyside).
5157 reinitializeTrustLineBalances();
5158 auto const nftID = mintNFT(minter);
5159 auto const offerID = createBuyOffer(
5160 buyer, minter, nftID, gwXPB(10), {static_cast<TER>(tecUNFUNDED_OFFER)});
5161 env(token::acceptBuyOffer(minter, offerID),
5162 Ter(static_cast<TER>(tecOBJECT_NOT_FOUND)));
5163 env.close();
5164 expectInitialState();
5165 }
5166 {
5167 // Minter attempts to sell the token for XPB 10 and the buyer
5168 // has it but the minter has no trust line. Trust line is
5169 // created as a result of the tx (sellside).
5170 reinitializeTrustLineBalances();
5171 env(pay(gw, buyer, gwXPB(100)));
5172 env.close();
5173
5174 auto const nftID = mintNFT(minter);
5175 auto const offerID = createSellOffer(minter, nftID, gwXPB(10));
5176 env(token::acceptSellOffer(buyer, offerID));
5177 env.close();
5178
5179 BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
5180 BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
5181 BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
5182 BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
5183 }
5184 {
5185 // Minter attempts to sell the token for XPB 10 and the buyer
5186 // has it but the minter has no trust line. Trust line is
5187 // created as a result of the tx (buyside).
5188 reinitializeTrustLineBalances();
5189 env(pay(gw, buyer, gwXPB(100)));
5190 env.close();
5191
5192 auto const nftID = mintNFT(minter);
5193 auto const offerID = createBuyOffer(buyer, minter, nftID, gwXPB(10));
5194 env(token::acceptBuyOffer(minter, offerID));
5195 env.close();
5196
5197 BEAST_EXPECT(env.balance(minter, gwXPB) == gwXPB(10));
5198 BEAST_EXPECT(env.balance(buyer, gwXPB) == gwXPB(89.8));
5199 BEAST_EXPECT(env.balance(gw, minter["XPB"]) == gwXPB(-10));
5200 BEAST_EXPECT(env.balance(gw, buyer["XPB"]) == gwXPB(-89.8));
5201 }
5202 {
5203 // There is a transfer fee on the NFT and buyer has exact
5204 // amount (sellside)
5205 reinitializeTrustLineBalances();
5206
5207 // secondarySeller has to sell it because transfer fees only
5208 // happen on secondary sales
5209 auto const nftID = mintNFT(minter, 3000); // 3%
5210 auto const primaryOfferID = createSellOffer(minter, nftID, XRP(0));
5211 env(token::acceptSellOffer(secondarySeller, primaryOfferID));
5212 env.close();
5213
5214 // now we can do a secondary sale
5215 auto const offerID = createSellOffer(secondarySeller, nftID, gwXAU(1000));
5216 TER const sellTER = tecINSUFFICIENT_FUNDS;
5217 env(token::acceptSellOffer(buyer, offerID), Ter(sellTER));
5218 env.close();
5219
5220 expectInitialState();
5221 }
5222 {
5223 // There is a transfer fee on the NFT and buyer has exact
5224 // amount (buyside)
5225 reinitializeTrustLineBalances();
5226
5227 // secondarySeller has to sell it because transfer fees only
5228 // happen on secondary sales
5229 auto const nftID = mintNFT(minter, 3000); // 3%
5230 auto const primaryOfferID = createSellOffer(minter, nftID, XRP(0));
5231 env(token::acceptSellOffer(secondarySeller, primaryOfferID));
5232 env.close();
5233
5234 // now we can do a secondary sale
5235 auto const offerID = createBuyOffer(buyer, secondarySeller, nftID, gwXAU(1000));
5236 TER const sellTER = tecINSUFFICIENT_FUNDS;
5237 env(token::acceptBuyOffer(secondarySeller, offerID), Ter(sellTER));
5238 env.close();
5239
5240 expectInitialState();
5241 }
5242 {
5243 // There is a transfer fee on the NFT and buyer has enough
5244 // (sellside)
5245 reinitializeTrustLineBalances();
5246
5247 // secondarySeller has to sell it because transfer fees only
5248 // happen on secondary sales
5249 auto const nftID = mintNFT(minter, 3000); // 3%
5250 auto const primaryOfferID = createSellOffer(minter, nftID, XRP(0));
5251 env(token::acceptSellOffer(secondarySeller, primaryOfferID));
5252 env.close();
5253
5254 // now we can do a secondary sale
5255 auto const offerID = createSellOffer(secondarySeller, nftID, gwXAU(900));
5256 env(token::acceptSellOffer(buyer, offerID));
5257 env.close();
5258
5259 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
5260 BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
5261 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
5262 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
5263 BEAST_EXPECT(env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
5264 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
5265 }
5266 {
5267 // There is a transfer fee on the NFT and buyer has enough
5268 // (buyside)
5269 reinitializeTrustLineBalances();
5270
5271 // secondarySeller has to sell it because transfer fees only
5272 // happen on secondary sales
5273 auto const nftID = mintNFT(minter, 3000); // 3%
5274 auto const primaryOfferID = createSellOffer(minter, nftID, XRP(0));
5275 env(token::acceptSellOffer(secondarySeller, primaryOfferID));
5276 env.close();
5277
5278 // now we can do a secondary sale
5279 auto const offerID = createBuyOffer(buyer, secondarySeller, nftID, gwXAU(900));
5280 env(token::acceptBuyOffer(secondarySeller, offerID));
5281 env.close();
5282
5283 // receives 3% of 900 - 27
5284 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(27));
5285 // receives 97% of 900 - 873
5286 BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(873));
5287 // pays 900 plus 2% transfer fee on XAU - 918
5288 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(82));
5289 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-27));
5290 BEAST_EXPECT(env.balance(gw, secondarySeller["XAU"]) == gwXAU(-873));
5291 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-82));
5292 }
5293 {
5294 // There is a broker fee on the NFT. XAU transfer fee is only
5295 // calculated from the buyer's output, not deducted from
5296 // broker fee.
5297 //
5298 // For a payment of 500 with a 2% IOU transfee fee and 100
5299 // broker fee:
5300 //
5301 // A) Total sale amount + IOU transfer fee is paid by buyer
5302 // (Buyer pays (1.02 * 500) = 510)
5303 // B) GW receives the additional IOU transfer fee
5304 // (GW receives 10 from buyer calculated above)
5305 // C) Broker receives broker fee (no IOU transfer fee)
5306 // (Broker receives 100 from buyer)
5307 // D) Seller receives balance (no IOU transfer fee)
5308 // (Seller receives (510 - 10 - 100) = 400)
5309 reinitializeTrustLineBalances();
5310
5311 auto const nftID = mintNFT(minter);
5312 auto const sellOffer = createSellOffer(minter, nftID, gwXAU(300));
5313 auto const buyOffer = createBuyOffer(buyer, minter, nftID, gwXAU(500));
5314 env(token::brokerOffers(broker, buyOffer, sellOffer), token::BrokerFee(gwXAU(100)));
5315 env.close();
5316
5317 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(400));
5318 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
5319 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
5320 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-400));
5321 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
5322 BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
5323 }
5324 {
5325 // There is broker and transfer fee on the NFT
5326 //
5327 // For a payment of 500 with a 2% IOU transfer fee, 3% NFT
5328 // transfer fee, and 100 broker fee:
5329 //
5330 // A) Total sale amount + IOU transfer fee is paid by buyer
5331 // (Buyer pays (1.02 * 500) = 510)
5332 // B) GW receives the additional IOU transfer fee
5333 // (GW receives 10 from buyer calculated above)
5334 // C) Broker receives broker fee (no IOU transfer fee)
5335 // (Broker receives 100 from buyer)
5336 // D) Minter receives transfer fee (no IOU transfer fee)
5337 // (Minter receives 0.03 * (510 - 10 - 100) = 12)
5338 // E) Seller receives balance (no IOU transfer fee)
5339 // (Seller receives (510 - 10 - 100 - 12) = 388)
5340 reinitializeTrustLineBalances();
5341
5342 // secondarySeller has to sell it because transfer fees only
5343 // happen on secondary sales
5344 auto const nftID = mintNFT(minter, 3000); // 3%
5345 auto const primaryOfferID = createSellOffer(minter, nftID, XRP(0));
5346 env(token::acceptSellOffer(secondarySeller, primaryOfferID));
5347 env.close();
5348
5349 // now we can do a secondary sale
5350 auto const sellOffer = createSellOffer(secondarySeller, nftID, gwXAU(300));
5351 auto const buyOffer = createBuyOffer(buyer, secondarySeller, nftID, gwXAU(500));
5352 env(token::brokerOffers(broker, buyOffer, sellOffer), token::BrokerFee(gwXAU(100)));
5353 env.close();
5354
5355 BEAST_EXPECT(env.balance(minter, gwXAU) == gwXAU(12));
5356 BEAST_EXPECT(env.balance(buyer, gwXAU) == gwXAU(490));
5357 BEAST_EXPECT(env.balance(secondarySeller, gwXAU) == gwXAU(388));
5358 BEAST_EXPECT(env.balance(broker, gwXAU) == gwXAU(5100));
5359 BEAST_EXPECT(env.balance(gw, minter["XAU"]) == gwXAU(-12));
5360 BEAST_EXPECT(env.balance(gw, buyer["XAU"]) == gwXAU(-490));
5361 BEAST_EXPECT(env.balance(gw, secondarySeller["XAU"]) == gwXAU(-388));
5362 BEAST_EXPECT(env.balance(gw, broker["XAU"]) == gwXAU(-5100));
5363 }
5364 }
5365 }
5366
5367 void
5369 {
5370 // There was a bug that if an account had...
5371 //
5372 // 1. An NFToken, and
5373 // 2. An offer on the ledger to buy that same token, and
5374 // 3. Also an offer of the ledger to sell that same token,
5375 //
5376 // Then someone could broker the two offers. This would result in
5377 // the NFToken being bought and returned to the original owner and
5378 // the broker pocketing the profit.
5379 //
5380 testcase("Brokered sale to self");
5381
5382 using namespace test::jtx;
5383
5384 Account const alice{"alice"};
5385 Account const bob{"bob"};
5386 Account const broker{"broker"};
5387
5388 Env env{*this, features};
5389 auto const baseFee = env.current()->fees().base;
5390 env.fund(XRP(10000), alice, bob, broker);
5391 env.close();
5392
5393 // For this scenario to occur we need the following steps:
5394 //
5395 // 1. alice mints NFT.
5396 // 2. bob creates a buy offer for it for 5 XRP.
5397 // 3. alice decides to gift the NFT to bob for 0.
5398 // creating a sell offer (hopefully using a destination too)
5399 // 4. Bob accepts the sell offer, because it is better than
5400 // paying 5 XRP.
5401 // 5. At this point, bob has the NFT and still has their buy
5402 // offer from when they did not have the NFT! This is because
5403 // the order book is not cleared when an NFT changes hands.
5404 // 6. Now that Bob owns the NFT, he cannot create new buy offers.
5405 // However he still has one left over from when he did not own
5406 // it. He can create new sell offers and does.
5407 // 7. Now that bob has both a buy and a sell offer for the same NFT,
5408 // a broker can sell the NFT that bob owns to bob and pocket the
5409 // difference.
5410 uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
5411 env(token::mint(alice, 0u), Txflags(tfTransferable));
5412 env.close();
5413
5414 // Bob creates a buy offer for 5 XRP. Alice creates a sell offer
5415 // for 0 XRP.
5416 uint256 const bobBuyOfferIndex = keylet::nftokenOffer(bob, env.seq(bob)).key;
5417 env(token::createOffer(bob, nftId, XRP(5)), token::Owner(alice));
5418
5419 uint256 const aliceSellOfferIndex = keylet::nftokenOffer(alice, env.seq(alice)).key;
5420 env(token::createOffer(alice, nftId, XRP(0)),
5421 token::Destination(bob),
5422 Txflags(tfSellNFToken));
5423 env.close();
5424
5425 // bob accepts alice's offer but forgets to remove the old buy offer.
5426 env(token::acceptSellOffer(bob, aliceSellOfferIndex));
5427 env.close();
5428
5429 // Note that bob still has a buy offer on the books.
5430 BEAST_EXPECT(env.le(keylet::nftokenOffer(bobBuyOfferIndex)));
5431
5432 // Bob creates a sell offer for the gift NFT from alice.
5433 uint256 const bobSellOfferIndex = keylet::nftokenOffer(bob, env.seq(bob)).key;
5434 env(token::createOffer(bob, nftId, XRP(4)), Txflags(tfSellNFToken));
5435 env.close();
5436
5437 // bob now has a buy offer and a sell offer on the books. A broker
5438 // spots this and swoops in to make a profit.
5439 BEAST_EXPECT(nftCount(env, bob) == 1);
5440 auto const bobPriorBalance = env.balance(bob);
5441 auto const brokerPriorBalance = env.balance(broker);
5442 env(token::brokerOffers(broker, bobBuyOfferIndex, bobSellOfferIndex),
5443 token::BrokerFee(XRP(1)),
5445 env.close();
5446
5447 // A tec result was returned, so no state should change other
5448 // than the broker burning their transaction fee.
5449 BEAST_EXPECT(nftCount(env, bob) == 1);
5450 BEAST_EXPECT(env.balance(bob) == bobPriorBalance);
5451 BEAST_EXPECT(env.balance(broker) == brokerPriorBalance - baseFee);
5452 }
5453
5454 void
5456 {
5457 using namespace test::jtx;
5458
5459 testcase("NFTokenRemint");
5460
5461 // Returns the current ledger sequence
5462 auto openLedgerSeq = [](Env& env) { return env.current()->seq(); };
5463
5464 // Close the ledger until the ledger sequence is large enough to delete
5465 // the account (no longer within <Sequence + 256>)
5466 auto incLgrSeqForAcctDel = [&](Env& env, Account const& acct) {
5467 int const delta = [&]() -> int {
5468 if (env.seq(acct) + 255 > openLedgerSeq(env))
5469 return env.seq(acct) - openLedgerSeq(env) + 255;
5470 return 0;
5471 }();
5472 BEAST_EXPECT(delta >= 0);
5473 for (int i = 0; i < delta; ++i)
5474 env.close();
5475 BEAST_EXPECT(openLedgerSeq(env) == env.seq(acct) + 255);
5476 };
5477
5478 // Close the ledger until the ledger sequence is no longer
5479 // within <FirstNFTokenSequence + MintedNFTokens + 256>.
5480 auto incLgrSeqForFixNftRemint = [&](Env& env, Account const& acct) {
5481 int delta = 0;
5482 auto const deletableLgrSeq = (*env.le(acct))[~sfFirstNFTokenSequence].value_or(0) +
5483 (*env.le(acct))[sfMintedNFTokens] + 255;
5484
5485 if (deletableLgrSeq > openLedgerSeq(env))
5486 delta = deletableLgrSeq - openLedgerSeq(env);
5487
5488 BEAST_EXPECT(delta >= 0);
5489 for (int i = 0; i < delta; ++i)
5490 env.close();
5491 BEAST_EXPECT(openLedgerSeq(env) == deletableLgrSeq);
5492 };
5493
5494 // We check if NFTokenIDs can be duplicated by
5495 // re-creation of an account
5496 {
5497 Env env{*this, features};
5498 Account const alice("alice");
5499 Account const becky("becky");
5500
5501 env.fund(XRP(10000), alice, becky);
5502 env.close();
5503
5504 // alice mint and burn a NFT
5505 uint256 const prevNFTokenID = token::getNextID(env, alice, 0u);
5506 env(token::mint(alice));
5507 env.close();
5508 env(token::burn(alice, prevNFTokenID));
5509 env.close();
5510
5511 // alice has minted 1 NFToken
5512 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 1);
5513
5514 // Close enough ledgers to delete alice's account
5515 incLgrSeqForAcctDel(env, alice);
5516
5517 // alice's account is deleted
5518 Keylet const aliceAcctKey{keylet::account(alice.id())};
5519 auto const acctDelFee{drops(env.current()->fees().increment)};
5520 env(acctdelete(alice, becky), Fee(acctDelFee));
5521 env.close();
5522
5523 // alice's account root is gone from the most recently
5524 // closed ledger and the current ledger.
5525 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
5526 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
5527
5528 // Fund alice to re-create her account
5529 env.fund(XRP(10000), alice);
5530 env.close();
5531
5532 // alice's account now exists and has minted 0 NFTokens
5533 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5534 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5535 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
5536
5537 // alice mints a NFT with same params as prevNFTokenID
5538 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
5539 env(token::mint(alice));
5540 env.close();
5541
5542 // burn the NFT to make sure alice owns remintNFTokenID
5543 env(token::burn(alice, remintNFTokenID));
5544 env.close();
5545
5546 // Check that two NFTs don't have the same ID
5547 BEAST_EXPECT(remintNFTokenID != prevNFTokenID);
5548 }
5549
5550 // Test if the issuer account can be deleted after an authorized
5551 // minter mints and burns a batch of NFTokens.
5552 {
5553 Env env{*this, features};
5554 Account const alice("alice");
5555 Account const becky("becky");
5556 Account const minter{"minter"};
5557
5558 env.fund(XRP(10000), alice, becky, minter);
5559 env.close();
5560
5561 // alice sets minter as her authorized minter
5562 env(token::setMinter(alice, minter));
5563 env.close();
5564
5565 // minter mints 500 NFTs for alice
5566 std::vector<uint256> nftIDs;
5567 nftIDs.reserve(500);
5568 for (int i = 0; i < 500; i++)
5569 {
5570 uint256 const nftokenID = token::getNextID(env, alice, 0u);
5571 nftIDs.push_back(nftokenID);
5572 env(token::mint(minter), token::Issuer(alice));
5573 }
5574 env.close();
5575
5576 // minter burns 500 NFTs
5577 for (auto const nftokenID : nftIDs)
5578 {
5579 env(token::burn(minter, nftokenID));
5580 }
5581 env.close();
5582
5583 incLgrSeqForAcctDel(env, alice);
5584
5585 // Verify that alice's account root is present.
5586 Keylet const aliceAcctKey{keylet::account(alice.id())};
5587 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5588 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5589
5590 auto const acctDelFee{drops(env.current()->fees().increment)};
5591
5592 // alice tries to delete her account, but is unsuccessful.
5593 // Due to authorized minting, alice's account sequence does not
5594 // advance while minter mints NFTokens for her.
5595 // The new account deletion restriction <FirstNFTokenSequence +
5596 // MintedNFTokens + 256> enabled by this amendment will enforce
5597 // alice to wait for more ledgers to close before she can
5598 // delete her account, to prevent duplicate NFTokenIDs
5599 env(acctdelete(alice, becky), Fee(acctDelFee), Ter(tecTOO_SOON));
5600 env.close();
5601
5602 // alice's account is still present
5603 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5604
5605 // Close more ledgers until it is no longer within
5606 // <FirstNFTokenSequence + MintedNFTokens + 256>
5607 // to be able to delete alice's account
5608 incLgrSeqForFixNftRemint(env, alice);
5609
5610 // alice's account is deleted
5611 env(acctdelete(alice, becky), Fee(acctDelFee));
5612 env.close();
5613
5614 // alice's account root is gone from the most recently
5615 // closed ledger and the current ledger.
5616 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
5617 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
5618
5619 // Fund alice to re-create her account
5620 env.fund(XRP(10000), alice);
5621 env.close();
5622
5623 // alice's account now exists and has minted 0 NFTokens
5624 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5625 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5626 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
5627
5628 // alice mints a NFT with same params as the first one before
5629 // the account delete.
5630 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
5631 env(token::mint(alice));
5632 env.close();
5633
5634 // burn the NFT to make sure alice owns remintNFTokenID
5635 env(token::burn(alice, remintNFTokenID));
5636 env.close();
5637
5638 // The new NFT minted will not have the same ID
5639 // as any of the NFTs authorized minter minted
5640 BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end());
5641 }
5642
5643 // When an account mints and burns a batch of NFTokens using tickets,
5644 // see if the account can be deleted.
5645 {
5646 Env env{*this, features};
5647
5648 Account const alice{"alice"};
5649 Account const becky{"becky"};
5650 env.fund(XRP(10000), alice, becky);
5651 env.close();
5652
5653 // alice grab enough tickets for all of the following
5654 // transactions. Note that once the tickets are acquired alice's
5655 // account sequence number should not advance.
5656 std::uint32_t aliceTicketSeq{env.seq(alice) + 1};
5657 env(ticket::create(alice, 100));
5658 env.close();
5659
5660 BEAST_EXPECT(ticketCount(env, alice) == 100);
5661 BEAST_EXPECT(ownerCount(env, alice) == 100);
5662
5663 // alice mints 50 NFTs using tickets
5664 std::vector<uint256> nftIDs;
5665 nftIDs.reserve(50);
5666 for (int i = 0; i < 50; i++)
5667 {
5668 nftIDs.push_back(token::getNextID(env, alice, 0u));
5669 env(token::mint(alice, 0u), ticket::Use(aliceTicketSeq++));
5670 env.close();
5671 }
5672
5673 // alice burns 50 NFTs using tickets
5674 for (auto const nftokenID : nftIDs)
5675 {
5676 env(token::burn(alice, nftokenID), ticket::Use(aliceTicketSeq++));
5677 }
5678 env.close();
5679
5680 BEAST_EXPECT(ticketCount(env, alice) == 0);
5681
5682 incLgrSeqForAcctDel(env, alice);
5683
5684 // Verify that alice's account root is present.
5685 Keylet const aliceAcctKey{keylet::account(alice.id())};
5686 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5687 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5688
5689 auto const acctDelFee{drops(env.current()->fees().increment)};
5690
5691 // alice tries to delete her account, but is unsuccessful.
5692 // Due to authorized minting, alice's account sequence does not
5693 // advance while minter mints NFTokens for her using tickets.
5694 // The new account deletion restriction <FirstNFTokenSequence +
5695 // MintedNFTokens + 256> enabled by this amendment will enforce
5696 // alice to wait for more ledgers to close before she can
5697 // delete her account, to prevent duplicate NFTokenIDs
5698 env(acctdelete(alice, becky), Fee(acctDelFee), Ter(tecTOO_SOON));
5699 env.close();
5700
5701 // alice's account is still present
5702 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5703
5704 // Close more ledgers until it is no longer within
5705 // <FirstNFTokenSequence + MintedNFTokens + 256>
5706 // to be able to delete alice's account
5707 incLgrSeqForFixNftRemint(env, alice);
5708
5709 // alice's account is deleted
5710 env(acctdelete(alice, becky), Fee(acctDelFee));
5711 env.close();
5712
5713 // alice's account root is gone from the most recently
5714 // closed ledger and the current ledger.
5715 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
5716 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
5717
5718 // Fund alice to re-create her account
5719 env.fund(XRP(10000), alice);
5720 env.close();
5721
5722 // alice's account now exists and has minted 0 NFTokens
5723 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5724 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5725 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
5726
5727 // alice mints a NFT with same params as the first one before
5728 // the account delete.
5729 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
5730 env(token::mint(alice));
5731 env.close();
5732
5733 // burn the NFT to make sure alice owns remintNFTokenID
5734 env(token::burn(alice, remintNFTokenID));
5735 env.close();
5736
5737 // The new NFT minted will not have the same ID
5738 // as any of the NFTs authorized minter minted using tickets
5739 BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end());
5740 }
5741 // When an authorized minter mints and burns a batch of NFTokens using
5742 // tickets, issuer's account needs to wait a longer time before it can
5743 // be deleted.
5744 // After the issuer's account is re-created and mints a NFT, it should
5745 // not have the same NFTokenID as the ones authorized minter minted.
5746 Env env{*this, features};
5747 Account const alice("alice");
5748 Account const becky("becky");
5749 Account const minter{"minter"};
5750
5751 env.fund(XRP(10000), alice, becky, minter);
5752 env.close();
5753
5754 // alice sets minter as her authorized minter
5755 env(token::setMinter(alice, minter));
5756 env.close();
5757
5758 // minter creates 100 tickets
5759 std::uint32_t minterTicketSeq{env.seq(minter) + 1};
5760 env(ticket::create(minter, 100));
5761 env.close();
5762
5763 BEAST_EXPECT(ticketCount(env, minter) == 100);
5764 BEAST_EXPECT(ownerCount(env, minter) == 100);
5765
5766 // minter mints 50 NFTs for alice using tickets
5767 std::vector<uint256> nftIDs;
5768 nftIDs.reserve(50);
5769 for (int i = 0; i < 50; i++)
5770 {
5771 uint256 const nftokenID = token::getNextID(env, alice, 0u);
5772 nftIDs.push_back(nftokenID);
5773 env(token::mint(minter), token::Issuer(alice), ticket::Use(minterTicketSeq++));
5774 }
5775 env.close();
5776
5777 // minter burns 50 NFTs using tickets
5778 for (auto const nftokenID : nftIDs)
5779 {
5780 env(token::burn(minter, nftokenID), ticket::Use(minterTicketSeq++));
5781 }
5782 env.close();
5783
5784 BEAST_EXPECT(ticketCount(env, minter) == 0);
5785
5786 incLgrSeqForAcctDel(env, alice);
5787
5788 // Verify that alice's account root is present.
5789 Keylet const aliceAcctKey{keylet::account(alice.id())};
5790 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5791 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5792
5793 // alice tries to delete her account, but is unsuccessful.
5794 // Due to authorized minting, alice's account sequence does not
5795 // advance while minter mints NFTokens for her using tickets.
5796 // The new account deletion restriction <FirstNFTokenSequence +
5797 // MintedNFTokens + 256> enabled by this amendment will enforce
5798 // alice to wait for more ledgers to close before she can delete her
5799 // account, to prevent duplicate NFTokenIDs
5800 auto const acctDelFee{drops(env.current()->fees().increment)};
5801 env(acctdelete(alice, becky), Fee(acctDelFee), Ter(tecTOO_SOON));
5802 env.close();
5803
5804 // alice's account is still present
5805 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5806
5807 // Close more ledgers until it is no longer within
5808 // <FirstNFTokenSequence + MintedNFTokens + 256>
5809 // to be able to delete alice's account
5810 incLgrSeqForFixNftRemint(env, alice);
5811
5812 // alice's account is deleted
5813 env(acctdelete(alice, becky), Fee(acctDelFee));
5814 env.close();
5815
5816 // alice's account root is gone from the most recently
5817 // closed ledger and the current ledger.
5818 BEAST_EXPECT(!env.closed()->exists(aliceAcctKey));
5819 BEAST_EXPECT(!env.current()->exists(aliceAcctKey));
5820
5821 // Fund alice to re-create her account
5822 env.fund(XRP(10000), alice);
5823 env.close();
5824
5825 // alice's account now exists and has minted 0 NFTokens
5826 BEAST_EXPECT(env.closed()->exists(aliceAcctKey));
5827 BEAST_EXPECT(env.current()->exists(aliceAcctKey));
5828 BEAST_EXPECT((*env.le(alice))[sfMintedNFTokens] == 0);
5829
5830 // The new NFT minted will not have the same ID
5831 // as any of the NFTs authorized minter minted using tickets
5832 uint256 const remintNFTokenID = token::getNextID(env, alice, 0u);
5833 env(token::mint(alice));
5834 env.close();
5835
5836 // burn the NFT to make sure alice owns remintNFTokenID
5837 env(token::burn(alice, remintNFTokenID));
5838 env.close();
5839
5840 // The new NFT minted will not have the same ID
5841 // as one of NFTs authorized minter minted using tickets
5842 BEAST_EXPECT(std::ranges::find(nftIDs, remintNFTokenID) == nftIDs.end());
5843 }
5844
5845 void
5847 {
5848 testcase("NFTokenMint with Create NFTokenOffer");
5849
5850 using namespace test::jtx;
5851
5852 if (!features[featureNFTokenMintOffer])
5853 {
5854 Env env{*this, features};
5855 Account const alice("alice");
5856 Account const buyer("buyer");
5857
5858 env.fund(XRP(10000), alice, buyer);
5859 env.close();
5860
5861 env(token::mint(alice), token::Amount(XRP(10000)), Ter(temDISABLED));
5862 env.close();
5863
5864 env(token::mint(alice), token::Destination("buyer"), Ter(temDISABLED));
5865 env.close();
5866
5867 env(token::mint(alice), token::Expiration(lastClose(env) + 25), Ter(temDISABLED));
5868 env.close();
5869
5870 return;
5871 }
5872
5873 // The remaining tests assume featureNFTokenMintOffer is enabled.
5874 {
5875 Env env{*this, features};
5876 auto const baseFee = env.current()->fees().base;
5877 Account const alice("alice");
5878 Account const buyer{"buyer"};
5879 Account const gw("gw");
5880 Account const issuer("issuer");
5881 Account const minter("minter");
5882 Account const bob("bob");
5883 IOU const gwAUD(gw["AUD"]);
5884
5885 env.fund(XRP(10000), alice, buyer, gw, issuer, minter);
5886 env.close();
5887
5888 {
5889 // Destination field specified but Amount field not specified
5890 env(token::mint(alice), token::Destination(buyer), Ter(temMALFORMED));
5891 env.close();
5892 BEAST_EXPECT(ownerCount(env, alice) == 0);
5893
5894 // Expiration field specified but Amount field not specified
5895 env(token::mint(alice), token::Expiration(lastClose(env) + 25), Ter(temMALFORMED));
5896 env.close();
5897 BEAST_EXPECT(ownerCount(env, buyer) == 0);
5898 }
5899
5900 {
5901 // The destination may not be the account submitting the
5902 // transaction.
5903 env(token::mint(alice),
5904 token::Amount(XRP(1000)),
5905 token::Destination(alice),
5906 Ter(temMALFORMED));
5907 env.close();
5908 BEAST_EXPECT(ownerCount(env, alice) == 0);
5909
5910 // The destination must be an account already established in the
5911 // ledger.
5912 env(token::mint(alice),
5913 token::Amount(XRP(1000)),
5914 token::Destination(Account("demon")),
5915 Ter(tecNO_DST));
5916 env.close();
5917 BEAST_EXPECT(ownerCount(env, alice) == 0);
5918 }
5919
5920 {
5921 // Set a bad expiration.
5922 env(token::mint(alice),
5923 token::Amount(XRP(1000)),
5924 token::Expiration(0),
5925 Ter(temBAD_EXPIRATION));
5926 env.close();
5927 BEAST_EXPECT(ownerCount(env, alice) == 0);
5928
5929 // The new NFTokenOffer may not have passed its expiration time.
5930 env(token::mint(alice),
5931 token::Amount(XRP(1000)),
5932 token::Expiration(lastClose(env)),
5933 Ter(tecEXPIRED));
5934 env.close();
5935 BEAST_EXPECT(ownerCount(env, alice) == 0);
5936 }
5937
5938 {
5939 // Set an invalid amount.
5940 env(token::mint(alice),
5941 token::Amount(buyer["USD"](1)),
5942 Txflags(tfOnlyXRP),
5943 Ter(temBAD_AMOUNT));
5944 env(token::mint(alice), token::Amount(buyer["USD"](0)), Ter(temBAD_AMOUNT));
5945 env.close();
5946 BEAST_EXPECT(ownerCount(env, alice) == 0);
5947
5948 // Issuer (alice) must have a trust line for the offered funds.
5949 env(token::mint(alice),
5950 token::Amount(gwAUD(1000)),
5951 Txflags(tfTransferable),
5952 token::XferFee(10),
5953 Ter(tecNO_LINE));
5954 env.close();
5955 BEAST_EXPECT(ownerCount(env, alice) == 0);
5956
5957 // If the IOU issuer and the NFToken issuer are the same,
5958 // then that issuer does not need a trust line to accept their
5959 // fee.
5960 env(token::mint(gw),
5961 token::Amount(gwAUD(1000)),
5962 Txflags(tfTransferable),
5963 token::XferFee(10));
5964 env.close();
5965
5966 // Give alice the needed trust line, but freeze it.
5967 env(trust(gw, alice["AUD"](999), tfSetFreeze));
5968 env.close();
5969
5970 // Issuer (alice) must have a trust line for the offered funds
5971 // and the trust line may not be frozen.
5972 env(token::mint(alice),
5973 token::Amount(gwAUD(1000)),
5974 Txflags(tfTransferable),
5975 token::XferFee(10),
5976 Ter(tecFROZEN));
5977 env.close();
5978 BEAST_EXPECT(ownerCount(env, alice) == 0);
5979
5980 // Seller (alice) must have a trust line may not be frozen.
5981 env(token::mint(alice), token::Amount(gwAUD(1000)), Ter(tecFROZEN));
5982 env.close();
5983 BEAST_EXPECT(ownerCount(env, alice) == 0);
5984
5985 // Unfreeze alice's trustline.
5986 env(trust(gw, alice["AUD"](999), tfClearFreeze));
5987 env.close();
5988 }
5989
5990 {
5991 // check reserve
5992 auto const acctReserve = env.current()->fees().reserve;
5993 auto const incReserve = env.current()->fees().increment;
5994
5995 env.fund(acctReserve + incReserve, bob);
5996 env.close();
5997
5998 // doesn't have reserve for 2 objects (NFTokenPage, Offer)
5999 env(token::mint(bob), token::Amount(XRP(0)), Ter(tecINSUFFICIENT_RESERVE));
6000 env.close();
6001
6002 // have reserve for NFTokenPage, Offer
6003 env(pay(env.master, bob, incReserve + drops(baseFee)));
6004 env.close();
6005 env(token::mint(bob), token::Amount(XRP(0)));
6006 env.close();
6007
6008 // doesn't have reserve for Offer
6009 env(pay(env.master, bob, drops(baseFee)));
6010 env.close();
6011 env(token::mint(bob), token::Amount(XRP(0)), Ter(tecINSUFFICIENT_RESERVE));
6012 env.close();
6013
6014 // have reserve for Offer
6015 env(pay(env.master, bob, incReserve + drops(baseFee)));
6016 env.close();
6017 env(token::mint(bob), token::Amount(XRP(0)));
6018 env.close();
6019 }
6020
6021 // Amount field specified
6022 BEAST_EXPECT(ownerCount(env, alice) == 0);
6023 env(token::mint(alice), token::Amount(XRP(10)));
6024 BEAST_EXPECT(ownerCount(env, alice) == 2);
6025 env.close();
6026
6027 // Amount field and Destination field, Expiration field specified
6028 env(token::mint(alice),
6029 token::Amount(XRP(10)),
6030 token::Destination(buyer),
6031 token::Expiration(lastClose(env) + 25));
6032 env.close();
6033
6034 // With TransferFee field
6035 env(trust(alice, gwAUD(1000)));
6036 env.close();
6037 env(token::mint(alice),
6038 token::Amount(gwAUD(1)),
6039 token::Destination(buyer),
6040 token::Expiration(lastClose(env) + 25),
6041 Txflags(tfTransferable),
6042 token::XferFee(10));
6043 env.close();
6044
6045 // Can be canceled by the issuer.
6046 env(token::mint(alice),
6047 token::Amount(XRP(10)),
6048 token::Destination(buyer),
6049 token::Expiration(lastClose(env) + 25));
6050 uint256 const offerAliceSellsToBuyer = keylet::nftokenOffer(alice, env.seq(alice)).key;
6051 env(token::cancelOffer(alice, {offerAliceSellsToBuyer}));
6052 env.close();
6053
6054 // Can be canceled by the buyer.
6055 env(token::mint(buyer),
6056 token::Amount(XRP(10)),
6057 token::Destination(alice),
6058 token::Expiration(lastClose(env) + 25));
6059 uint256 const offerBuyerSellsToAlice = keylet::nftokenOffer(buyer, env.seq(buyer)).key;
6060 env(token::cancelOffer(alice, {offerBuyerSellsToAlice}));
6061 env.close();
6062
6063 env(token::setMinter(issuer, minter));
6064 env.close();
6065
6066 // Minter will have offer not issuer
6067 BEAST_EXPECT(ownerCount(env, minter) == 0);
6068 BEAST_EXPECT(ownerCount(env, issuer) == 0);
6069 env(token::mint(minter), token::Issuer(issuer), token::Amount(drops(1)));
6070 env.close();
6071 BEAST_EXPECT(ownerCount(env, minter) == 2);
6072 BEAST_EXPECT(ownerCount(env, issuer) == 0);
6073 }
6074
6075 Env env{*this, features};
6076 Account const alice("alice");
6077
6078 env.fund(XRP(1000000), alice);
6079
6080 TER const offerCreateTER = temBAD_AMOUNT;
6081
6082 // Make offers with negative amounts for the NFTs
6083 env(token::mint(alice), token::Amount(XRP(-2)), Ter(offerCreateTER));
6084 env.close();
6085 }
6086
6087 void
6089 {
6090 // `nftoken_id` is added in the `tx` response for NFTokenMint and
6091 // NFTokenAcceptOffer.
6092 //
6093 // `nftoken_ids` is added in the `tx` response for NFTokenCancelOffer
6094 //
6095 // `offer_id` is added in the `tx` response for NFTokenCreateOffer
6096 //
6097 // The values of these fields are dependent on the NFTokenID/OfferID
6098 // changed in its corresponding transaction. We want to validate each
6099 // transaction to make sure the synthetic fields hold the right values.
6100
6101 testcase("Test synthetic fields from JSON response");
6102
6103 using namespace test::jtx;
6104
6105 Account const alice{"alice"};
6106 Account const bob{"bob"};
6107 Account const broker{"broker"};
6108
6109 Env env{*this, features};
6110 env.fund(XRP(10000), alice, bob, broker);
6111 env.close();
6112
6113 // Verify `nftoken_id` value equals to the NFTokenID that was
6114 // changed in the most recent NFTokenMint or NFTokenAcceptOffer
6115 // transaction
6116 auto verifyNFTokenID = [&](uint256 const& actualNftID) {
6117 // Get the hash for the most recent transaction.
6118 std::string const txHash{
6119 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
6120
6121 env.close();
6122 json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
6123
6124 // Expect nftokens_id field
6125 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_id)))
6126 return;
6127
6128 // Check the value of NFT ID in the meta with the
6129 // actual value
6130 uint256 nftID;
6131 BEAST_EXPECT(nftID.parseHex(meta[jss::nftoken_id].asString()));
6132 BEAST_EXPECT(nftID == actualNftID);
6133 };
6134
6135 // Verify `nftoken_ids` value equals to the NFTokenIDs that were
6136 // changed in the most recent NFTokenCancelOffer transaction
6137 auto verifyNFTokenIDsInCancelOffer = [&](std::vector<uint256> actualNftIDs) {
6138 // Get the hash for the most recent transaction.
6139 std::string const txHash{
6140 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
6141
6142 env.close();
6143 json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
6144
6145 // Expect nftokens_ids field and verify the values
6146 if (!BEAST_EXPECT(meta.isMember(jss::nftoken_ids)))
6147 return;
6148
6149 // Convert NFT IDs from json::Value to uint256
6150 std::vector<uint256> metaIDs;
6152 meta[jss::nftoken_ids].begin(),
6153 meta[jss::nftoken_ids].end(),
6154 std::back_inserter(metaIDs),
6155 [this](json::Value id) {
6156 uint256 nftID;
6157 BEAST_EXPECT(nftID.parseHex(id.asString()));
6158 return nftID;
6159 });
6160
6161 // Sort both array to prepare for comparison
6162 std::ranges::sort(metaIDs);
6163 std::ranges::sort(actualNftIDs);
6164
6165 // Make sure the expect number of NFTs is correct
6166 BEAST_EXPECT(metaIDs.size() == actualNftIDs.size());
6167
6168 // Check the value of NFT ID in the meta with the
6169 // actual values
6170 for (size_t i = 0; i < metaIDs.size(); ++i)
6171 BEAST_EXPECT(metaIDs[i] == actualNftIDs[i]);
6172 };
6173
6174 // Verify `offer_id` value equals to the offerID that was
6175 // changed in the most recent NFTokenCreateOffer tx
6176 auto verifyNFTokenOfferID = [&](uint256 const& offerID) {
6177 // Get the hash for the most recent transaction.
6178 std::string const txHash{
6179 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
6180
6181 env.close();
6182 json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
6183
6184 // Expect offer_id field and verify the value
6185 if (!BEAST_EXPECT(meta.isMember(jss::offer_id)))
6186 return;
6187
6188 uint256 metaOfferID;
6189 BEAST_EXPECT(metaOfferID.parseHex(meta[jss::offer_id].asString()));
6190 BEAST_EXPECT(metaOfferID == offerID);
6191 };
6192
6193 // Check new fields in tx meta when for all NFTtransactions
6194 {
6195 // Alice mints 2 NFTs
6196 // Verify the NFTokenIDs are correct in the NFTokenMint tx meta
6197 uint256 const nftId1{token::getNextID(env, alice, 0u, tfTransferable)};
6198 env(token::mint(alice, 0u), Txflags(tfTransferable));
6199 env.close();
6200 verifyNFTokenID(nftId1);
6201
6202 uint256 const nftId2{token::getNextID(env, alice, 0u, tfTransferable)};
6203 env(token::mint(alice, 0u), Txflags(tfTransferable));
6204 env.close();
6205 verifyNFTokenID(nftId2);
6206
6207 // Alice creates one sell offer for each NFT
6208 // Verify the offer indexes are correct in the NFTokenCreateOffer tx
6209 // meta
6210 uint256 const aliceOfferIndex1 = keylet::nftokenOffer(alice, env.seq(alice)).key;
6211 env(token::createOffer(alice, nftId1, drops(1)), Txflags(tfSellNFToken));
6212 env.close();
6213 verifyNFTokenOfferID(aliceOfferIndex1);
6214
6215 uint256 const aliceOfferIndex2 = keylet::nftokenOffer(alice, env.seq(alice)).key;
6216 env(token::createOffer(alice, nftId2, drops(1)), Txflags(tfSellNFToken));
6217 env.close();
6218 verifyNFTokenOfferID(aliceOfferIndex2);
6219
6220 // Alice cancels two offers she created
6221 // Verify the NFTokenIDs are correct in the NFTokenCancelOffer tx
6222 // meta
6223 env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2}));
6224 env.close();
6225 verifyNFTokenIDsInCancelOffer({nftId1, nftId2});
6226
6227 // Bobs creates a buy offer for nftId1
6228 // Verify the offer id is correct in the NFTokenCreateOffer tx meta
6229 auto const bobBuyOfferIndex = keylet::nftokenOffer(bob, env.seq(bob)).key;
6230 env(token::createOffer(bob, nftId1, drops(1)), token::Owner(alice));
6231 env.close();
6232 verifyNFTokenOfferID(bobBuyOfferIndex);
6233
6234 // Alice accepts bob's buy offer
6235 // Verify the NFTokenID is correct in the NFTokenAcceptOffer tx meta
6236 env(token::acceptBuyOffer(alice, bobBuyOfferIndex));
6237 env.close();
6238 verifyNFTokenID(nftId1);
6239 }
6240
6241 // Check `nftoken_ids` in brokered mode
6242 {
6243 // Alice mints a NFT
6244 uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
6245 env(token::mint(alice, 0u), Txflags(tfTransferable));
6246 env.close();
6247 verifyNFTokenID(nftId);
6248
6249 // Alice creates sell offer and set broker as destination
6250 uint256 const offerAliceToBroker = keylet::nftokenOffer(alice, env.seq(alice)).key;
6251 env(token::createOffer(alice, nftId, drops(1)),
6252 token::Destination(broker),
6253 Txflags(tfSellNFToken));
6254 env.close();
6255 verifyNFTokenOfferID(offerAliceToBroker);
6256
6257 // Bob creates buy offer
6258 uint256 const offerBobToBroker = keylet::nftokenOffer(bob, env.seq(bob)).key;
6259 env(token::createOffer(bob, nftId, drops(1)), token::Owner(alice));
6260 env.close();
6261 verifyNFTokenOfferID(offerBobToBroker);
6262
6263 // Check NFTokenID meta for NFTokenAcceptOffer in brokered mode
6264 env(token::brokerOffers(broker, offerBobToBroker, offerAliceToBroker));
6265 env.close();
6266 verifyNFTokenID(nftId);
6267 }
6268
6269 // Check if there are no duplicate nft id in Cancel transactions where
6270 // multiple offers are cancelled for the same NFT
6271 {
6272 // Alice mints a NFT
6273 uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
6274 env(token::mint(alice, 0u), Txflags(tfTransferable));
6275 env.close();
6276 verifyNFTokenID(nftId);
6277
6278 // Alice creates 2 sell offers for the same NFT
6279 uint256 const aliceOfferIndex1 = keylet::nftokenOffer(alice, env.seq(alice)).key;
6280 env(token::createOffer(alice, nftId, drops(1)), Txflags(tfSellNFToken));
6281 env.close();
6282 verifyNFTokenOfferID(aliceOfferIndex1);
6283
6284 uint256 const aliceOfferIndex2 = keylet::nftokenOffer(alice, env.seq(alice)).key;
6285 env(token::createOffer(alice, nftId, drops(1)), Txflags(tfSellNFToken));
6286 env.close();
6287 verifyNFTokenOfferID(aliceOfferIndex2);
6288
6289 // Make sure the metadata only has 1 nft id, since both offers are
6290 // for the same nft
6291 env(token::cancelOffer(alice, {aliceOfferIndex1, aliceOfferIndex2}));
6292 env.close();
6293 verifyNFTokenIDsInCancelOffer({nftId});
6294 }
6295
6296 if (features[featureNFTokenMintOffer])
6297 {
6298 uint256 const aliceMintWithOfferIndex1 =
6299 keylet::nftokenOffer(alice, env.seq(alice)).key;
6300 env(token::mint(alice), token::Amount(XRP(0)));
6301 env.close();
6302 verifyNFTokenOfferID(aliceMintWithOfferIndex1);
6303 }
6304 }
6305
6306 void
6308 {
6309 testcase("Test buyer reserve when accepting an offer");
6310
6311 using namespace test::jtx;
6312
6313 // Lambda that mints an NFT and then creates a sell offer
6314 auto mintAndCreateSellOffer =
6315 [](test::jtx::Env& env, test::jtx::Account const& acct, STAmount const amt) -> uint256 {
6316 // acct mints a NFT
6317 uint256 const nftId{token::getNextID(env, acct, 0u, tfTransferable)};
6318 env(token::mint(acct, 0u), Txflags(tfTransferable));
6319 env.close();
6320
6321 // acct makes an sell offer
6322 uint256 const sellOfferIndex = keylet::nftokenOffer(acct, env.seq(acct)).key;
6323 env(token::createOffer(acct, nftId, amt), Txflags(tfSellNFToken));
6324 env.close();
6325
6326 return sellOfferIndex;
6327 };
6328
6329 // Test the behaviors when the buyer makes an accept offer, both before
6330 // and after enabling the amendment. Exercises the precise number of
6331 // reserve in drops that's required to accept the offer
6332 {
6333 Account const alice{"alice"};
6334 Account const bob{"bob"};
6335
6336 Env env{*this, features};
6337 auto const acctReserve = env.current()->fees().reserve;
6338 auto const incReserve = env.current()->fees().increment;
6339 auto const baseFee = env.current()->fees().base;
6340
6341 env.fund(XRP(10000), alice);
6342 env.close();
6343
6344 // Bob is funded with minimum XRP reserve
6345 env.fund(acctReserve, bob);
6346 env.close();
6347
6348 // alice mints an NFT and create a sell offer for 0 XRP
6349 auto const sellOfferIndex = mintAndCreateSellOffer(env, alice, XRP(0));
6350
6351 // Bob owns no object
6352 BEAST_EXPECT(ownerCount(env, bob) == 0);
6353
6354 // bob can no longer accept the offer unless
6355 // there is enough reserve. A detail to note is that NFTs(sell
6356 // offer) will not allow one to go below the reserve requirement,
6357 // because buyer's balance is computed after the transaction fee is
6358 // deducted. This means that the reserve requirement will be `base
6359 // fee` drops higher than normal.
6360
6361 // Bob is not able to accept the offer with only the account
6362 // reserve (200,000,000 drops)
6363 env(token::acceptSellOffer(bob, sellOfferIndex), Ter(tecINSUFFICIENT_RESERVE));
6364 env.close();
6365
6366 // after prev transaction, Bob owns `200M - base fee` drops due
6367 // to burnt tx fee
6368
6369 BEAST_EXPECT(ownerCount(env, bob) == 0);
6370
6371 // Send bob an kIncrement reserve and base fee (to make up for
6372 // the transaction fee burnt from the prev failed tx) Bob now
6373 // owns 250,000,000 drops
6374 env(pay(env.master, bob, incReserve + drops(baseFee)));
6375 env.close();
6376
6377 // However, this transaction will still fail because the reserve
6378 // requirement is `base fee` drops higher
6379 env(token::acceptSellOffer(bob, sellOfferIndex), Ter(tecINSUFFICIENT_RESERVE));
6380 env.close();
6381
6382 // Send bob `base fee * 2` drops
6383 // Bob now owns `250M + base fee` drops
6384 env(pay(env.master, bob, drops(baseFee * 2)));
6385 env.close();
6386
6387 // Bob is now able to accept the offer
6388 env(token::acceptSellOffer(bob, sellOfferIndex));
6389 env.close();
6390
6391 BEAST_EXPECT(ownerCount(env, bob) == 1);
6392 }
6393
6394 // Now exercise the scenario when the buyer accepts
6395 // many sell offers
6396 {
6397 Account const alice{"alice"};
6398 Account const bob{"bob"};
6399
6400 Env env{*this, features};
6401 auto const acctReserve = env.current()->fees().reserve;
6402 auto const incReserve = env.current()->fees().increment;
6403
6404 env.fund(XRP(10000), alice);
6405 env.close();
6406
6407 env.fund(acctReserve + XRP(1), bob);
6408 env.close();
6409
6410 // alice mints the first NFT and creates a sell offer for 0 XRP
6411 auto const sellOfferIndex1 = mintAndCreateSellOffer(env, alice, XRP(0));
6412
6413 // Bob cannot accept this offer because he doesn't have the
6414 // reserve for the NFT
6415 env(token::acceptSellOffer(bob, sellOfferIndex1), Ter(tecINSUFFICIENT_RESERVE));
6416 env.close();
6417
6418 // Give bob enough reserve
6419 env(pay(env.master, bob, drops(incReserve)));
6420 env.close();
6421
6422 BEAST_EXPECT(ownerCount(env, bob) == 0);
6423
6424 // Bob now owns his first NFT
6425 env(token::acceptSellOffer(bob, sellOfferIndex1));
6426 env.close();
6427
6428 BEAST_EXPECT(ownerCount(env, bob) == 1);
6429
6430 // alice now mints 31 more NFTs and creates an offer for each
6431 // NFT, then sells to bob
6432 for (size_t i = 0; i < 31; i++)
6433 {
6434 // alice mints an NFT and creates a sell offer for 0 XRP
6435 auto const sellOfferIndex = mintAndCreateSellOffer(env, alice, XRP(0));
6436
6437 // Bob can accept the offer because the new NFT is stored in
6438 // an existing NFTokenPage so no new reserve is required
6439 env(token::acceptSellOffer(bob, sellOfferIndex));
6440 env.close();
6441 }
6442
6443 BEAST_EXPECT(ownerCount(env, bob) == 1);
6444
6445 // alice now mints the 33rd NFT and creates an sell offer for 0
6446 // XRP
6447 auto const sellOfferIndex33 = mintAndCreateSellOffer(env, alice, XRP(0));
6448
6449 // Bob fails to accept this NFT because he does not have enough
6450 // reserve for a new NFTokenPage
6451 env(token::acceptSellOffer(bob, sellOfferIndex33), Ter(tecINSUFFICIENT_RESERVE));
6452 env.close();
6453
6454 // Send bob incremental reserve
6455 env(pay(env.master, bob, drops(incReserve)));
6456 env.close();
6457
6458 // Bob now has enough reserve to accept the offer and now
6459 // owns one more NFTokenPage
6460 env(token::acceptSellOffer(bob, sellOfferIndex33));
6461 env.close();
6462
6463 BEAST_EXPECT(ownerCount(env, bob) == 2);
6464 }
6465
6466 // Test the behavior when the seller accepts a buy offer.
6467 {
6468 Account const alice{"alice"};
6469 Account const bob{"bob"};
6470
6471 Env env{*this, features};
6472 auto const acctReserve = env.current()->fees().reserve;
6473 auto const incReserve = env.current()->fees().increment;
6474 auto const baseFee = env.current()->fees().base;
6475
6476 env.fund(XRP(10000), alice);
6477 env.close();
6478
6479 // Bob is funded with account reserve + kIncrement reserve + 1 XRP
6480 // kIncrement reserve is for the buy offer, and 1 XRP is for offer
6481 // price
6482 env.fund(acctReserve + incReserve + XRP(1), bob);
6483 env.close();
6484
6485 // Alice mints a NFT
6486 uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
6487 env(token::mint(alice, 0u), Txflags(tfTransferable));
6488 env.close();
6489
6490 // Bob makes a buy offer for 1 XRP
6491 auto const buyOfferIndex = keylet::nftokenOffer(bob, env.seq(bob)).key;
6492 env(token::createOffer(bob, nftId, XRP(1)), token::Owner(alice));
6493 env.close();
6494
6495 // accepting the buy offer fails because bob's balance is `base fee`
6496 // drops lower than the required amount, since the previous tx burnt
6497 // drops for tx fee.
6498 env(token::acceptBuyOffer(alice, buyOfferIndex), Ter(tecINSUFFICIENT_FUNDS));
6499 env.close();
6500
6501 // send Bob `base fee` drops
6502 env(pay(env.master, bob, drops(baseFee)));
6503 env.close();
6504
6505 // Now bob can buy the offer
6506 env(token::acceptBuyOffer(alice, buyOfferIndex));
6507 env.close();
6508 }
6509
6510 // Test the reserve behavior in brokered mode.
6511 {
6512 Account const alice{"alice"};
6513 Account const bob{"bob"};
6514 Account const broker{"broker"};
6515
6516 Env env{*this, features};
6517 auto const acctReserve = env.current()->fees().reserve;
6518 auto const incReserve = env.current()->fees().increment;
6519 auto const baseFee = env.current()->fees().base;
6520
6521 env.fund(XRP(10000), alice, broker);
6522 env.close();
6523
6524 // Bob is funded with account reserve + incr reserve + 1 XRP(offer
6525 // price)
6526 env.fund(acctReserve + incReserve + XRP(1), bob);
6527 env.close();
6528
6529 // Alice mints a NFT
6530 uint256 const nftId{token::getNextID(env, alice, 0u, tfTransferable)};
6531 env(token::mint(alice, 0u), Txflags(tfTransferable));
6532 env.close();
6533
6534 // Alice creates sell offer and set broker as destination
6535 uint256 const offerAliceToBroker = keylet::nftokenOffer(alice, env.seq(alice)).key;
6536 env(token::createOffer(alice, nftId, XRP(1)),
6537 token::Destination(broker),
6538 Txflags(tfSellNFToken));
6539 env.close();
6540
6541 // Bob creates buy offer
6542 uint256 const offerBobToBroker = keylet::nftokenOffer(bob, env.seq(bob)).key;
6543 env(token::createOffer(bob, nftId, XRP(1)), token::Owner(alice));
6544 env.close();
6545
6546 // broker offers.
6547 // Returns insufficient funds, because bob burnt tx fee when he
6548 // created his buy offer, which makes his spendable balance to be
6549 // less than the required amount.
6550 env(token::brokerOffers(broker, offerBobToBroker, offerAliceToBroker),
6552 env.close();
6553
6554 // send Bob `base fee` drops
6555 env(pay(env.master, bob, drops(baseFee)));
6556 env.close();
6557
6558 // broker offers.
6559 env(token::brokerOffers(broker, offerBobToBroker, offerAliceToBroker));
6560 env.close();
6561 }
6562 }
6563
6564 void
6566 {
6567 testcase("Test fix unasked for auto-trustline.");
6568
6569 using namespace test::jtx;
6570
6571 Account const issuer{"issuer"};
6572 Account const becky{"becky"};
6573 Account const cheri{"cheri"};
6574 Account const gw("gw");
6575 IOU const gwAUD(gw["AUD"]);
6576
6577 // This test case covers issue...
6578 // https://github.com/XRPLF/rippled/issues/4925
6579 //
6580 // For an NFToken with a transfer fee, the issuer must be able to
6581 // accept the transfer fee or else a transfer should fail. If the
6582 // NFToken is transferred for a non-XRP asset, then the issuer must
6583 // have a trustline to that asset to receive the fee.
6584 //
6585 // This test looks at a situation where issuer would get a trustline
6586 // for the fee without the issuer's consent. Here are the steps:
6587 // 1. Issuer has a trustline (i.e., USD)
6588 // 2. Issuer mints NFToken with transfer fee.
6589 // 3. Becky acquires the NFToken, paying with XRP.
6590 // 4. Becky creates offer to sell NFToken for USD(100).
6591 // 5. Issuer deletes trustline for USD.
6592 // 6. Carol buys NFToken from Becky for USD(100).
6593 // 7. The transfer fee from Carol's purchase re-establishes issuer's
6594 // USD trustline.
6595 //
6596 // The fixEnforceNFTokenTrustline amendment addresses this oversight.
6597 //
6598 // We run this test case both with and without
6599 // fixEnforceNFTokenTrustline enabled so we can see the change
6600 // in behavior.
6601 //
6602 // In both cases we remove the fixRemoveNFTokenAutoTrustLine amendment.
6603 // Otherwise we can't create NFTokens with tfTrustLine enabled.
6604 FeatureBitset const localFeatures = features - fixRemoveNFTokenAutoTrustLine;
6605 for (FeatureBitset feats :
6606 {localFeatures - fixEnforceNFTokenTrustline,
6607 localFeatures | fixEnforceNFTokenTrustline})
6608 {
6609 Env env{*this, feats};
6610 env.fund(XRP(1000), issuer, becky, cheri, gw);
6611 env.close();
6612
6613 // Set trust lines so becky and cheri can use gw's currency.
6614 env(trust(becky, gwAUD(1000)));
6615 env(trust(cheri, gwAUD(1000)));
6616 env.close();
6617 env(pay(gw, cheri, gwAUD(500)));
6618 env.close();
6619
6620 // issuer creates two NFTs: one with and one without AutoTrustLine.
6621 std::uint16_t const xferFee = 5000; // 5%
6622 uint256 const nftAutoTrustID{
6623 token::getNextID(env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
6624 env(token::mint(issuer, 0u),
6625 token::XferFee(xferFee),
6626 Txflags(tfTransferable | tfTrustLine));
6627 env.close();
6628
6629 uint256 const nftNoAutoTrustID{
6630 token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
6631 env(token::mint(issuer, 0u), token::XferFee(xferFee), Txflags(tfTransferable));
6632 env.close();
6633
6634 // becky buys the nfts for 1 drop each.
6635 {
6636 uint256 const beckyBuyOfferIndex1 = keylet::nftokenOffer(becky, env.seq(becky)).key;
6637 env(token::createOffer(becky, nftAutoTrustID, drops(1)), token::Owner(issuer));
6638
6639 uint256 const beckyBuyOfferIndex2 = keylet::nftokenOffer(becky, env.seq(becky)).key;
6640 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)), token::Owner(issuer));
6641
6642 env.close();
6643 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
6644 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
6645 env.close();
6646 }
6647
6648 // becky creates offers to sell the nfts for AUD.
6649 uint256 const beckyAutoTrustOfferIndex =
6650 keylet::nftokenOffer(becky, env.seq(becky)).key;
6651 env(token::createOffer(becky, nftAutoTrustID, gwAUD(100)), Txflags(tfSellNFToken));
6652 env.close();
6653
6654 // Creating an offer for the NFToken without tfTrustLine fails
6655 // because issuer does not have a trust line for AUD.
6656 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)),
6657 Txflags(tfSellNFToken),
6658 Ter(tecNO_LINE));
6659 env.close();
6660
6661 // issuer creates a trust line. Now the offer create for the
6662 // NFToken without tfTrustLine succeeds.
6663 BEAST_EXPECT(ownerCount(env, issuer) == 0);
6664 env(trust(issuer, gwAUD(1000)));
6665 env.close();
6666 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6667
6668 uint256 const beckyNoAutoTrustOfferIndex =
6669 keylet::nftokenOffer(becky, env.seq(becky)).key;
6670 env(token::createOffer(becky, nftNoAutoTrustID, gwAUD(100)), Txflags(tfSellNFToken));
6671 env.close();
6672
6673 // Now that the offers are in place, issuer removes the trustline.
6674 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6675 env(trust(issuer, gwAUD(0)));
6676 env.close();
6677 BEAST_EXPECT(ownerCount(env, issuer) == 0);
6678
6679 // cheri attempts to accept becky's offers. Behavior with the
6680 // AutoTrustline NFT is uniform: issuer gets a new trust line.
6681 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
6682 env.close();
6683
6684 // Here's evidence that issuer got the new AUD trust line.
6685 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6686 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
6687
6688 // issuer once again removes the trust line for AUD.
6689 env(pay(issuer, gw, gwAUD(5)));
6690 env.close();
6691 BEAST_EXPECT(ownerCount(env, issuer) == 0);
6692
6693 // cheri attempts to accept the NoAutoTrustLine NFT. Behavior
6694 // depends on whether fixEnforceNFTokenTrustline is enabled.
6695 if (feats[fixEnforceNFTokenTrustline])
6696 {
6697 // With fixEnforceNFTokenTrustline cheri can't accept the
6698 // offer because issuer could not get their transfer fee
6699 // without the appropriate trustline.
6700 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex), Ter(tecNO_LINE));
6701 env.close();
6702
6703 // But if issuer re-establishes the trustline then the offer
6704 // can be accepted.
6705 env(trust(issuer, gwAUD(1000)));
6706 env.close();
6707 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6708
6709 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
6710 env.close();
6711 }
6712 else
6713 {
6714 // Without fixEnforceNFTokenTrustline the offer just works
6715 // and issuer gets a trustline that they did not request.
6716 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
6717 env.close();
6718 }
6719 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6720 BEAST_EXPECT(env.balance(issuer, gwAUD) == gwAUD(5));
6721 } // for feats
6722 }
6723
6724 void
6726 {
6727 testcase("Test fix NFT issuer is IOU issuer");
6728
6729 using namespace test::jtx;
6730
6731 Account const issuer{"issuer"};
6732 Account const becky{"becky"};
6733 Account const cheri{"cheri"};
6734 IOU const isISU(issuer["ISU"]);
6735
6736 // This test case covers issue...
6737 // https://github.com/XRPLF/rippled/issues/4941
6738 //
6739 // If an NFToken has a transfer fee then, when an offer is accepted,
6740 // a portion of the sale price goes to the issuer.
6741 //
6742 // It is possible for an issuer to issue both an IOU (for remittances)
6743 // and NFTokens. If the issuer's IOU is used to pay for the transfer
6744 // of one of the issuer's NFTokens, then paying the fee for that
6745 // transfer will fail with a tecNO_LINE.
6746 //
6747 // The problem occurs because the NFT code looks for a trust line to
6748 // pay the transfer fee. However the issuer of an IOU does not need
6749 // a trust line to accept their own issuance and, in fact, is not
6750 // allowed to have a trust line to themselves.
6751 //
6752 // This test looks at a situation where transfer of an NFToken is
6753 // prevented by this bug:
6754 // 1. Issuer issues an IOU (e.g, isISU).
6755 // 2. Becky and Cheri get trust lines for, and acquire, some isISU.
6756 // 3. Issuer mints NFToken with transfer fee.
6757 // 4. Becky acquires the NFToken, paying with XRP.
6758 // 5. Becky attempts to create an offer to sell the NFToken for
6759 // isISU(100). The attempt fails with `tecNO_LINE`.
6760 //
6761 // The featureNFTokenMintOffer amendment addresses this oversight.
6762 //
6763 // We remove the fixRemoveNFTokenAutoTrustLine amendment. Otherwise
6764 // we can't create NFTokens with tfTrustLine enabled.
6765 FeatureBitset const localFeatures = features - fixRemoveNFTokenAutoTrustLine;
6766
6767 Env env{*this, localFeatures};
6768 env.fund(XRP(1000), issuer, becky, cheri);
6769 env.close();
6770
6771 // Set trust lines so becky and cheri can use isISU.
6772 env(trust(becky, isISU(1000)));
6773 env(trust(cheri, isISU(1000)));
6774 env.close();
6775 env(pay(issuer, cheri, isISU(500)));
6776 env.close();
6777
6778 // issuer creates two NFTs: one with and one without AutoTrustLine.
6779 std::uint16_t const xferFee = 5000; // 5%
6780 uint256 const nftAutoTrustID{
6781 token::getNextID(env, issuer, 0u, tfTransferable | tfTrustLine, xferFee)};
6782 env(token::mint(issuer, 0u),
6783 token::XferFee(xferFee),
6784 Txflags(tfTransferable | tfTrustLine));
6785 env.close();
6786
6787 uint256 const nftNoAutoTrustID{token::getNextID(env, issuer, 0u, tfTransferable, xferFee)};
6788 env(token::mint(issuer, 0u), token::XferFee(xferFee), Txflags(tfTransferable));
6789 env.close();
6790
6791 // becky buys the nfts for 1 drop each.
6792 {
6793 uint256 const beckyBuyOfferIndex1 = keylet::nftokenOffer(becky, env.seq(becky)).key;
6794 env(token::createOffer(becky, nftAutoTrustID, drops(1)), token::Owner(issuer));
6795
6796 uint256 const beckyBuyOfferIndex2 = keylet::nftokenOffer(becky, env.seq(becky)).key;
6797 env(token::createOffer(becky, nftNoAutoTrustID, drops(1)), token::Owner(issuer));
6798
6799 env.close();
6800 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex1));
6801 env(token::acceptBuyOffer(issuer, beckyBuyOfferIndex2));
6802 env.close();
6803 }
6804
6805 // Behavior from here down diverges significantly based on
6806 // featureNFTokenMintOffer.
6807 if (!localFeatures[featureNFTokenMintOffer])
6808 {
6809 // Without featureNFTokenMintOffer becky simply can't
6810 // create an offer for a non-tfTrustLine NFToken that would
6811 // pay the transfer fee in issuer's own IOU.
6812 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)),
6813 Txflags(tfSellNFToken),
6814 Ter(tecNO_LINE));
6815 env.close();
6816
6817 // And issuer can't create a trust line to themselves.
6818 env(trust(issuer, isISU(1000)), Ter(temDST_IS_SRC));
6819 env.close();
6820
6821 // However if the NFToken has the tfTrustLine flag set,
6822 // then becky can create the offer.
6823 uint256 const beckyAutoTrustOfferIndex =
6824 keylet::nftokenOffer(becky, env.seq(becky)).key;
6825 env(token::createOffer(becky, nftAutoTrustID, isISU(100)), Txflags(tfSellNFToken));
6826 env.close();
6827
6828 // And cheri can accept the offer.
6829 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
6830 env.close();
6831
6832 // We verify that issuer got their transfer fee by seeing that
6833 // ISU(5) has disappeared out of cheri's and becky's balances.
6834 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
6835 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
6836 }
6837 else
6838 {
6839 // With featureNFTokenMintOffer things go better.
6840 // becky creates offers to sell the nfts for ISU.
6841 uint256 const beckyNoAutoTrustOfferIndex =
6842 keylet::nftokenOffer(becky, env.seq(becky)).key;
6843 env(token::createOffer(becky, nftNoAutoTrustID, isISU(100)), Txflags(tfSellNFToken));
6844 env.close();
6845 uint256 const beckyAutoTrustOfferIndex =
6846 keylet::nftokenOffer(becky, env.seq(becky)).key;
6847 env(token::createOffer(becky, nftAutoTrustID, isISU(100)), Txflags(tfSellNFToken));
6848 env.close();
6849
6850 // cheri accepts becky's offers. Behavior is uniform:
6851 // issuer gets paid.
6852 env(token::acceptSellOffer(cheri, beckyAutoTrustOfferIndex));
6853 env.close();
6854
6855 // We verify that issuer got their transfer fee by seeing that
6856 // ISU(5) has disappeared out of cheri's and becky's balances.
6857 BEAST_EXPECT(env.balance(becky, isISU) == isISU(95));
6858 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(400));
6859
6860 env(token::acceptSellOffer(cheri, beckyNoAutoTrustOfferIndex));
6861 env.close();
6862
6863 // We verify that issuer got their transfer fee by seeing that
6864 // an additional ISU(5) has disappeared out of cheri's and
6865 // becky's balances.
6866 BEAST_EXPECT(env.balance(becky, isISU) == isISU(190));
6867 BEAST_EXPECT(env.balance(cheri, isISU) == isISU(300));
6868 }
6869 }
6870
6871 void
6873 {
6874 testcase("Test NFTokenModify");
6875
6876 using namespace test::jtx;
6877
6878 Account const issuer{"issuer"};
6879 Account const alice("alice");
6880 Account const bob("bob");
6881
6882 bool const modifyEnabled = features[featureDynamicNFT];
6883
6884 {
6885 // Mint with tfMutable
6886 Env env{*this, features};
6887 env.fund(XRP(10000), issuer);
6888 env.close();
6889
6890 auto const expectedTer = modifyEnabled ? TER{tesSUCCESS} : TER{temINVALID_FLAG};
6891 env(token::mint(issuer, 0u), Txflags(tfMutable), Ter(expectedTer));
6892 env.close();
6893 }
6894 {
6895 Env env{*this, features};
6896 env.fund(XRP(10000), issuer);
6897 env.close();
6898
6899 // Modify a nftoken
6900 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
6901 if (modifyEnabled)
6902 {
6903 env(token::mint(issuer, 0u), Txflags(tfMutable));
6904 env.close();
6905 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6906 env(token::modify(issuer, nftId));
6907 BEAST_EXPECT(ownerCount(env, issuer) == 1);
6908 }
6909 else
6910 {
6911 env(token::mint(issuer, 0u));
6912 env.close();
6913 env(token::modify(issuer, nftId), Ter(temDISABLED));
6914 env.close();
6915 }
6916 }
6917 if (!modifyEnabled)
6918 return;
6919
6920 {
6921 Env env{*this, features};
6922 env.fund(XRP(10000), issuer);
6923 env.close();
6924
6925 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
6926 env(token::mint(issuer, 0u), Txflags(tfMutable));
6927 env.close();
6928
6929 // Set a negative fee. Exercises invalid preflight1.
6930 env(token::modify(issuer, nftId), Fee(STAmount(10ull, true)), Ter(temBAD_FEE));
6931 env.close();
6932
6933 // Invalid Flags
6934 env(token::modify(issuer, nftId), Txflags(0x00000001), Ter(temINVALID_FLAG));
6935
6936 // Invalid Owner
6937 env(token::modify(issuer, nftId), token::Owner(issuer), Ter(temMALFORMED));
6938 env.close();
6939
6940 // Invalid URI length = 0
6941 env(token::modify(issuer, nftId), token::Uri(""), Ter(temMALFORMED));
6942 env.close();
6943
6944 // Invalid URI length > 256
6945 env(token::modify(issuer, nftId),
6946 token::Uri(std::string(kMaxTokenUriLength + 1, 'q')),
6947 Ter(temMALFORMED));
6948 env.close();
6949 }
6950 {
6951 Env env{*this, features};
6952 env.fund(XRP(10000), issuer, alice, bob);
6953 env.close();
6954
6955 {
6956 // NFToken not exists
6957 uint256 const nftIDNotExists{token::getNextID(env, issuer, 0u, tfMutable)};
6958 env.close();
6959
6960 env(token::modify(issuer, nftIDNotExists), Ter(tecNO_ENTRY));
6961 env.close();
6962 }
6963 {
6964 // Invalid NFToken flag
6965 uint256 const nftIDNotModifiable{token::getNextID(env, issuer, 0u)};
6966 env(token::mint(issuer, 0u));
6967 env.close();
6968
6969 env(token::modify(issuer, nftIDNotModifiable), Ter(tecNO_PERMISSION));
6970 env.close();
6971 }
6972 {
6973 // Unauthorized account
6974 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
6975 env(token::mint(issuer, 0u), Txflags(tfMutable));
6976 env.close();
6977
6978 env(token::modify(bob, nftId), token::Owner(issuer), Ter(tecNO_PERMISSION));
6979 env.close();
6980
6981 env(token::setMinter(issuer, alice));
6982 env.close();
6983
6984 env(token::modify(bob, nftId), token::Owner(issuer), Ter(tecNO_PERMISSION));
6985 env.close();
6986 }
6987 }
6988 {
6989 Env env{*this, features};
6990 env.fund(XRP(10000), issuer, alice, bob);
6991 env.close();
6992
6993 // modify with tfFullyCanonicalSig should success
6994 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
6995 env(token::mint(issuer, 0u), Txflags(tfMutable), token::Uri("uri"));
6996 env.close();
6997
6998 env(token::modify(issuer, nftId), Txflags(tfFullyCanonicalSig));
6999 env.close();
7000 }
7001 {
7002 Env env{*this, features};
7003 env.fund(XRP(10000), issuer, alice, bob);
7004 env.close();
7005
7006 // lambda that returns the JSON form of NFTokens held by acct
7007 auto accountNFTs = [&env](Account const& acct) {
7008 json::Value params;
7009 params[jss::account] = acct.human();
7010 params[jss::type] = "state";
7011 auto response = env.rpc("json", "account_nfts", to_string(params));
7012 return response[jss::result][jss::account_nfts];
7013 };
7014
7015 // lambda that checks for the expected URI value of an NFToken
7016 auto checkURI = [&accountNFTs, this](Account const& acct, char const* uri, int line) {
7017 auto const nfts = accountNFTs(acct);
7018 if (nfts.size() == 1)
7019 {
7020 pass();
7021 }
7022 else
7023 {
7024 std::ostringstream text;
7025 text << "checkURI: unexpected NFT count on line " << line;
7026 fail(text.str(), __FILE__, line);
7027 return;
7028 }
7029
7030 if (uri == nullptr)
7031 {
7032 if (!nfts[0u].isMember(sfURI.jsonName))
7033 {
7034 pass();
7035 }
7036 else
7037 {
7038 std::ostringstream text;
7039 text << "checkURI: unexpected URI present on line " << line;
7040 fail(text.str(), __FILE__, line);
7041 }
7042 return;
7043 }
7044
7045 if (nfts[0u][sfURI.jsonName] == strHex(std::string(uri)))
7046 {
7047 pass();
7048 }
7049 else
7050 {
7051 std::ostringstream text;
7052 text << "checkURI: unexpected URI contents on line " << line;
7053 fail(text.str(), __FILE__, line);
7054 }
7055 };
7056
7057 uint256 const nftId{token::getNextID(env, issuer, 0u, tfMutable)};
7058 env.close();
7059
7060 env(token::mint(issuer, 0u), Txflags(tfMutable), token::Uri("uri"));
7061 env.close();
7062 checkURI(issuer, "uri", __LINE__);
7063
7064 // set URI Field
7065 env(token::modify(issuer, nftId), token::Uri("new_uri"));
7066 env.close();
7067 checkURI(issuer, "new_uri", __LINE__);
7068
7069 // unset URI Field
7070 env(token::modify(issuer, nftId));
7071 env.close();
7072 checkURI(issuer, nullptr, __LINE__);
7073
7074 // set URI Field
7075 env(token::modify(issuer, nftId), token::Uri("uri"));
7076 env.close();
7077 checkURI(issuer, "uri", __LINE__);
7078
7079 // Account != Owner
7080 uint256 const offerID = keylet::nftokenOffer(issuer, env.seq(issuer)).key;
7081 env(token::createOffer(issuer, nftId, XRP(0)), Txflags(tfSellNFToken));
7082 env.close();
7083 env(token::acceptSellOffer(alice, offerID));
7084 env.close();
7085 BEAST_EXPECT(ownerCount(env, issuer) == 0);
7086 BEAST_EXPECT(ownerCount(env, alice) == 1);
7087 checkURI(alice, "uri", __LINE__);
7088
7089 // Modify by owner fails.
7090 env(token::modify(alice, nftId), token::Uri("new_uri"), Ter(tecNO_PERMISSION));
7091 env.close();
7092 BEAST_EXPECT(ownerCount(env, issuer) == 0);
7093 BEAST_EXPECT(ownerCount(env, alice) == 1);
7094 checkURI(alice, "uri", __LINE__);
7095
7096 env(token::modify(issuer, nftId), token::Owner(alice), token::Uri("new_uri"));
7097 env.close();
7098 BEAST_EXPECT(ownerCount(env, issuer) == 0);
7099 BEAST_EXPECT(ownerCount(env, alice) == 1);
7100 checkURI(alice, "new_uri", __LINE__);
7101
7102 env(token::modify(issuer, nftId), token::Owner(alice));
7103 env.close();
7104 checkURI(alice, nullptr, __LINE__);
7105
7106 env(token::modify(issuer, nftId), token::Owner(alice), token::Uri("uri"));
7107 env.close();
7108 checkURI(alice, "uri", __LINE__);
7109
7110 // Modify by authorized minter
7111 env(token::setMinter(issuer, bob));
7112 env.close();
7113 env(token::modify(bob, nftId), token::Owner(alice), token::Uri("new_uri"));
7114 env.close();
7115 checkURI(alice, "new_uri", __LINE__);
7116
7117 env(token::modify(bob, nftId), token::Owner(alice));
7118 env.close();
7119 checkURI(alice, nullptr, __LINE__);
7120
7121 env(token::modify(bob, nftId), token::Owner(alice), token::Uri("uri"));
7122 env.close();
7123 checkURI(alice, "uri", __LINE__);
7124 }
7125 }
7126
7127protected:
7129
7130 void
7132 {
7133 testEnabled(features);
7134 testMintReserve(features);
7135 testMintMaxTokens(features);
7136 testMintInvalid(features);
7137 testBurnInvalid(features);
7138 testCreateOfferInvalid(features);
7139 testCancelOfferInvalid(features);
7140 testAcceptOfferInvalid(features);
7141 testMintFlagBurnable(features);
7142 testMintFlagOnlyXRP(features);
7144 testMintFlagTransferable(features);
7145 testMintTransferFee(features);
7146 testMintTaxon(features);
7147 testMintURI(features);
7150 testCreateOfferExpiration(features);
7151 testCancelOffers(features);
7152 testCancelTooManyOffers(features);
7153 testBrokeredAccept(features);
7154 testNFTokenOfferOwner(features);
7155 testNFTokenWithTickets(features);
7156 testNFTokenDeleteAccount(features);
7157 testNftXxxOffers(features);
7158 testNFTokenNegOffer(features);
7159 testIOUWithTransferFee(features);
7160 testBrokeredSaleToSelf(features);
7161 testNFTokenRemint(features);
7162 testFeatMintWithOffer(features);
7163 testTxJsonMetaFields(features);
7166 testNFTIssuerIsIOUIssuer(features);
7167 testNFTokenModify(features);
7168 }
7169
7170public:
7171 void
7172 run() override
7173 {
7174 testWithFeats(allFeatures_ - featureNFTokenMintOffer - featureDynamicNFT - fixCleanup3_1_3);
7175 }
7176};
7177
7179{
7180 void
7181 run() override
7182 {
7183 testWithFeats(allFeatures_ - featureNFTokenMintOffer - featureDynamicNFT);
7184 }
7185};
7186
7188{
7189 void
7190 run() override
7191 {
7192 testWithFeats(allFeatures_ - featureNFTokenMintOffer - featureDynamicNFT);
7193 }
7194};
7195
7197{
7198 void
7199 run() override
7200 {
7201 testWithFeats(allFeatures_ - featureDynamicNFT);
7202 }
7203};
7204
7206{
7207 void
7208 run() override
7209 {
7210 testWithFeats(allFeatures_ - fixCleanup3_1_3);
7211 }
7212};
7213
7215{
7216 void
7217 run() override
7218 {
7220 }
7221};
7222
7223BEAST_DEFINE_TESTSUITE_PRIO(NFTokenBaseUtil, app, xrpl, 2);
7224BEAST_DEFINE_TESTSUITE_PRIO(NFTokenDisallowIncoming, app, xrpl, 2);
7225BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOMintOffer, app, xrpl, 2);
7226BEAST_DEFINE_TESTSUITE_PRIO(NFTokenWOModify, app, xrpl, 2);
7227BEAST_DEFINE_TESTSUITE_PRIO(NFTokenAllFeatures, app, xrpl, 2);
7228
7229} // namespace xrpl
T back(T... args)
T back_inserter(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
A testsuite class.
Definition suite.h:50
void pass()
Record a successful test condition.
Definition suite.h:500
bool expect(Condition const &shouldBeTrue)
Evaluate a test condition.
Definition suite.h:223
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
Value removeMember(char const *key)
Remove and return the named member.
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:507
void run() override
Runs the suite.
static std::uint32_t nftCount(test::jtx::Env &env, test::jtx::Account const &acct)
void testCreateOfferExpiration(FeatureBitset features)
void testCancelOfferInvalid(FeatureBitset features)
void testBrokeredSaleToSelf(FeatureBitset features)
static std::uint32_t mintedCount(test::jtx::Env const &env, test::jtx::Account const &issuer)
void testFeatMintWithOffer(FeatureBitset features)
void testCreateOfferDestination(FeatureBitset features)
static std::uint32_t ticketCount(test::jtx::Env const &env, test::jtx::Account const &acct)
void testAcceptOfferInvalid(FeatureBitset features)
void testWithFeats(FeatureBitset features)
void testMintFlagBurnable(FeatureBitset features)
void testIOUWithTransferFee(FeatureBitset features)
void testNFTokenNegOffer(FeatureBitset features)
void testFixNFTokenBuyerReserve(FeatureBitset features)
void run() override
Runs the suite.
void testNFTokenModify(FeatureBitset features)
void testMintReserve(FeatureBitset features)
void testNFTokenOfferOwner(FeatureBitset features)
void testMintFlagOnlyXRP(FeatureBitset features)
void testMintTaxon(FeatureBitset features)
void testCancelOffers(FeatureBitset features)
void testMintInvalid(FeatureBitset features)
void testMintTransferFee(FeatureBitset features)
static std::uint32_t burnedCount(test::jtx::Env const &env, test::jtx::Account const &issuer)
void testNFTokenRemint(FeatureBitset features)
void testMintFlagTransferable(FeatureBitset features)
static std::uint32_t lastClose(test::jtx::Env &env)
FeatureBitset const allFeatures_
void testBrokeredAccept(FeatureBitset features)
void testBurnInvalid(FeatureBitset features)
void testMintFlagCreateTrustLine(FeatureBitset features)
void testUnaskedForAutoTrustline(FeatureBitset features)
void testEnabled(FeatureBitset features)
void testTxJsonMetaFields(FeatureBitset features)
void testNFTokenWithTickets(FeatureBitset features)
void testCreateOfferInvalid(FeatureBitset features)
void testNFTokenDeleteAccount(FeatureBitset features)
void testCreateOfferDestinationDisallowIncoming(FeatureBitset features)
void testCancelTooManyOffers(FeatureBitset features)
void testNFTIssuerIsIOUIssuer(FeatureBitset features)
void testMintMaxTokens(FeatureBitset features)
void testMintURI(FeatureBitset features)
void testNftXxxOffers(FeatureBitset features)
void run() override
Runs the suite.
void run() override
Runs the suite.
void run() override
Runs the suite.
void run() override
Runs the suite.
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:45
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:167
void rawReplace(SLE::ref sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:243
static constexpr int kMinOffset
Definition STAmount.h:47
json::Value getJson(JsonOptions=JsonOptions::Values::None) const override
Definition STAmount.cpp:734
static constexpr std::uint64_t kMinValue
Definition STAmount.h:51
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
std::string const & human() const
Returns the human readable public key.
Definition jtx/Account.h:92
A transaction testing environment.
Definition Env.h:143
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:284
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:275
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:864
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T find(T... args)
T insert(T... args)
T make_shared(T... args)
@ Array
array value (ordered list)
Definition json_value.h:25
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:322
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet nftokenOffer(AccountID const &owner, std::uint32_t seq)
An offer from an account to buy or sell an NFT.
Definition Indexes.cpp:407
Taxon getTaxon(uint256 const &id)
Definition nft.h:87
Taxon toTaxon(std::uint32_t i)
Definition nft.h:21
TaggedInteger< std::uint32_t, TaxonTag > Taxon
Definition nft.h:18
std::uint32_t ownerCount(Env const &env, Account const &account)
FeatureBitset testableAmendments()
Definition Env.h:76
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
constexpr FlagValue tfTrustLine
Definition TxFlags.h:387
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::enable_if_t< std::is_integral_v< Integral > &&detail::is_engine< Engine >::value, Integral > randInt(Engine &engine, Integral min, Integral max)
Return a uniformly distributed random integer.
constexpr std::size_t kMaxTokenUriLength
The maximum length of a URI inside an NFT.
Definition Protocol.h:207
@ tefNFTOKEN_IS_NOT_TRANSFERABLE
Definition TER.h:176
std::enable_if_t< std::is_integral_v< Integral >, Integral > randInt()
BEAST_DEFINE_TESTSUITE_PRIO(AccountSet, app, xrpl, 1)
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
constexpr std::size_t kMaxTokenOfferCancelCount
The maximum number of token offers that can be canceled at once.
Definition Protocol.h:56
constexpr std::uint16_t kMaxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:70
@ temBAD_EXPIRATION
Definition TER.h:77
@ temBAD_FEE
Definition TER.h:78
@ temINVALID_FLAG
Definition TER.h:97
@ temDST_IS_SRC
Definition TER.h:94
@ temMALFORMED
Definition TER.h:73
@ temDISABLED
Definition TER.h:100
@ temBAD_AMOUNT
Definition TER.h:75
@ temBAD_NFTOKEN_TRANSFER_FEE
Definition TER.h:113
TERSubset< CanCvtToTER > TER
Definition TER.h:634
@ tecCANT_ACCEPT_OWN_NFTOKEN_OFFER
Definition TER.h:322
@ tecNO_ENTRY
Definition TER.h:304
@ tecOBJECT_NOT_FOUND
Definition TER.h:324
@ tecNFTOKEN_BUY_SELL_MISMATCH
Definition TER.h:320
@ tecTOO_SOON
Definition TER.h:316
@ tecFROZEN
Definition TER.h:301
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecUNFUNDED_OFFER
Definition TER.h:282
@ tecEXPIRED
Definition TER.h:312
@ tecNO_LINE
Definition TER.h:299
@ tecNFTOKEN_OFFER_TYPE_MISMATCH
Definition TER.h:321
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:318
@ tecNO_PERMISSION
Definition TER.h:303
@ tecNO_ISSUER
Definition TER.h:297
@ tecHAS_OBLIGATIONS
Definition TER.h:315
@ tecNO_DST
Definition TER.h:288
@ tecINSUFFICIENT_PAYMENT
Definition TER.h:325
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
constexpr FlagValue tfFullyCanonicalSig
Definition TxFlags.h:42
std::enable_if_t<(std::is_same_v< Byte, unsigned char >||std::is_same_v< Byte, std::uint8_t >) &&detail::is_engine< Engine >::value, Byte > randByte(Engine &engine)
Return a random byte.
T pop_back(T... args)
T push_back(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
T str(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
T to_string(T... args)
T transform(T... args)