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