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