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