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