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