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