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