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