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