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