rippled
Loading...
Searching...
No Matches
MPToken_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/credentials.h>
22#include <test/jtx/permissioned_domains.h>
23#include <test/jtx/trust.h>
24#include <test/jtx/xchain_bridge.h>
25
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/beast/utility/Zero.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/jss.h>
32
33namespace ripple {
34namespace test {
35
37{
38 void
40 {
41 testcase("Create Validate");
42 using namespace test::jtx;
43 Account const alice("alice");
44
45 // test preflight of MPTokenIssuanceCreate
46 {
47 // If the MPT amendment is not enabled, you should not be able to
48 // create MPTokenIssuances
49 Env env{*this, features - featureMPTokensV1};
50 MPTTester mptAlice(env, alice);
51
52 mptAlice.create({.ownerCount = 0, .err = temDISABLED});
53 }
54
55 // test preflight of MPTokenIssuanceCreate
56 {
57 Env env{*this, features};
58 MPTTester mptAlice(env, alice);
59
60 mptAlice.create({.flags = 0x00000001, .err = temINVALID_FLAG});
61
62 // tries to set a txfee while not enabling in the flag
63 mptAlice.create(
64 {.maxAmt = 100,
65 .assetScale = 0,
66 .transferFee = 1,
67 .metadata = "test",
68 .err = temMALFORMED});
69
70 if (!features[featureSingleAssetVault])
71 {
72 // tries to set DomainID when SAV is disabled
73 mptAlice.create(
74 {.maxAmt = 100,
75 .assetScale = 0,
76 .metadata = "test",
77 .flags = tfMPTRequireAuth,
78 .domainID = uint256(42),
79 .err = temDISABLED});
80 }
81 else if (!features[featurePermissionedDomains])
82 {
83 // tries to set DomainID when PD is disabled
84 mptAlice.create(
85 {.maxAmt = 100,
86 .assetScale = 0,
87 .metadata = "test",
88 .flags = tfMPTRequireAuth,
89 .domainID = uint256(42),
90 .err = temDISABLED});
91 }
92 else
93 {
94 // tries to set DomainID when RequireAuth is not set
95 mptAlice.create(
96 {.maxAmt = 100,
97 .assetScale = 0,
98 .metadata = "test",
99 .domainID = uint256(42),
100 .err = temMALFORMED});
101
102 // tries to set zero DomainID
103 mptAlice.create(
104 {.maxAmt = 100,
105 .assetScale = 0,
106 .metadata = "test",
107 .flags = tfMPTRequireAuth,
108 .domainID = beast::zero,
109 .err = temMALFORMED});
110 }
111
112 // tries to set a txfee greater than max
113 mptAlice.create(
114 {.maxAmt = 100,
115 .assetScale = 0,
116 .transferFee = maxTransferFee + 1,
117 .metadata = "test",
118 .flags = tfMPTCanTransfer,
119 .err = temBAD_TRANSFER_FEE});
120
121 // tries to set a txfee while not enabling transfer
122 mptAlice.create(
123 {.maxAmt = 100,
124 .assetScale = 0,
125 .transferFee = maxTransferFee,
126 .metadata = "test",
127 .err = temMALFORMED});
128
129 // empty metadata returns error
130 mptAlice.create(
131 {.maxAmt = 100,
132 .assetScale = 0,
133 .transferFee = 0,
134 .metadata = "",
135 .err = temMALFORMED});
136
137 // MaximumAmout of 0 returns error
138 mptAlice.create(
139 {.maxAmt = 0,
140 .assetScale = 1,
141 .transferFee = 1,
142 .metadata = "test",
143 .err = temMALFORMED});
144
145 // MaximumAmount larger than 63 bit returns error
146 mptAlice.create(
147 {.maxAmt = 0xFFFF'FFFF'FFFF'FFF0, // 18'446'744'073'709'551'600
148 .assetScale = 0,
149 .transferFee = 0,
150 .metadata = "test",
151 .err = temMALFORMED});
152 mptAlice.create(
153 {.maxAmt = maxMPTokenAmount + 1, // 9'223'372'036'854'775'808
154 .assetScale = 0,
155 .transferFee = 0,
156 .metadata = "test",
157 .err = temMALFORMED});
158 }
159 }
160
161 void
163 {
164 testcase("Create Enabled");
165
166 using namespace test::jtx;
167 Account const alice("alice");
168
169 {
170 // If the MPT amendment IS enabled, you should be able to create
171 // MPTokenIssuances
172 Env env{*this, features};
173 MPTTester mptAlice(env, alice);
174 mptAlice.create(
175 {.maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
176 .assetScale = 1,
177 .transferFee = 10,
178 .metadata = "123",
179 .ownerCount = 1,
182
183 // Get the hash for the most recent transaction.
184 std::string const txHash{
185 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
186
187 Json::Value const result = env.rpc("tx", txHash)[jss::result];
188 BEAST_EXPECT(
189 result[sfMaximumAmount.getJsonName()] == "9223372036854775807");
190 }
191
192 if (features[featureSingleAssetVault])
193 {
194 // Add permissioned domain
195 Account const credIssuer1{"credIssuer1"};
196 std::string const credType = "credential";
197
198 pdomain::Credentials const credentials1{
199 {.issuer = credIssuer1, .credType = credType}};
200
201 {
202 Env env{*this, features};
203 env.fund(XRP(1000), credIssuer1);
204
205 env(pdomain::setTx(credIssuer1, credentials1));
206 auto const domainId1 = [&]() {
207 auto tx = env.tx()->getJson(JsonOptions::none);
208 return pdomain::getNewDomain(env.meta());
209 }();
210
211 MPTTester mptAlice(env, alice);
212 mptAlice.create({
213 .maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
214 .assetScale = 1,
215 .transferFee = 10,
216 .metadata = "123",
217 .ownerCount = 1,
220 .domainID = domainId1,
221 });
222
223 // Get the hash for the most recent transaction.
224 std::string const txHash{
225 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
226
227 Json::Value const result = env.rpc("tx", txHash)[jss::result];
228 BEAST_EXPECT(
229 result[sfMaximumAmount.getJsonName()] ==
230 "9223372036854775807");
231 }
232 }
233 }
234
235 void
237 {
238 testcase("Destroy Validate");
239
240 using namespace test::jtx;
241 Account const alice("alice");
242 Account const bob("bob");
243 // MPTokenIssuanceDestroy (preflight)
244 {
245 Env env{*this, features - featureMPTokensV1};
246 MPTTester mptAlice(env, alice);
247 auto const id = makeMptID(env.seq(alice), alice);
248 mptAlice.destroy({.id = id, .ownerCount = 0, .err = temDISABLED});
249
250 env.enableFeature(featureMPTokensV1);
251
252 mptAlice.destroy(
253 {.id = id, .flags = 0x00000001, .err = temINVALID_FLAG});
254 }
255
256 // MPTokenIssuanceDestroy (preclaim)
257 {
258 Env env{*this, features};
259 MPTTester mptAlice(env, alice, {.holders = {bob}});
260
261 mptAlice.destroy(
262 {.id = makeMptID(env.seq(alice), alice),
263 .ownerCount = 0,
264 .err = tecOBJECT_NOT_FOUND});
265
266 mptAlice.create({.ownerCount = 1});
267
268 // a non-issuer tries to destroy a mptissuance they didn't issue
269 mptAlice.destroy({.issuer = bob, .err = tecNO_PERMISSION});
270
271 // Make sure that issuer can't delete issuance when it still has
272 // outstanding balance
273 {
274 // bob now holds a mptoken object
275 mptAlice.authorize({.account = bob, .holderCount = 1});
276
277 // alice pays bob 100 tokens
278 mptAlice.pay(alice, bob, 100);
279
280 mptAlice.destroy({.err = tecHAS_OBLIGATIONS});
281 }
282 }
283 }
284
285 void
287 {
288 testcase("Destroy Enabled");
289
290 using namespace test::jtx;
291 Account const alice("alice");
292
293 // If the MPT amendment IS enabled, you should be able to destroy
294 // MPTokenIssuances
295 Env env{*this, features};
296 MPTTester mptAlice(env, alice);
297
298 mptAlice.create({.ownerCount = 1});
299
300 mptAlice.destroy({.ownerCount = 0});
301 }
302
303 void
305 {
306 testcase("Validate authorize transaction");
307
308 using namespace test::jtx;
309 Account const alice("alice");
310 Account const bob("bob");
311 Account const cindy("cindy");
312 // Validate amendment enable in MPTokenAuthorize (preflight)
313 {
314 Env env{*this, features - featureMPTokensV1};
315 MPTTester mptAlice(env, alice, {.holders = {bob}});
316
317 mptAlice.authorize(
318 {.account = bob,
319 .id = makeMptID(env.seq(alice), alice),
320 .err = temDISABLED});
321 }
322
323 // Validate fields in MPTokenAuthorize (preflight)
324 {
325 Env env{*this, features};
326 MPTTester mptAlice(env, alice, {.holders = {bob}});
327
328 mptAlice.create({.ownerCount = 1});
329
330 // The only valid MPTokenAuthorize flag is tfMPTUnauthorize, which
331 // has a value of 1
332 mptAlice.authorize(
333 {.account = bob, .flags = 0x00000002, .err = temINVALID_FLAG});
334
335 mptAlice.authorize(
336 {.account = bob, .holder = bob, .err = temMALFORMED});
337
338 mptAlice.authorize({.holder = alice, .err = temMALFORMED});
339 }
340
341 // Try authorizing when MPTokenIssuance doesn't exist in
342 // MPTokenAuthorize (preclaim)
343 {
344 Env env{*this, features};
345 MPTTester mptAlice(env, alice, {.holders = {bob}});
346 auto const id = makeMptID(env.seq(alice), alice);
347
348 mptAlice.authorize(
349 {.holder = bob, .id = id, .err = tecOBJECT_NOT_FOUND});
350
351 mptAlice.authorize(
352 {.account = bob, .id = id, .err = tecOBJECT_NOT_FOUND});
353 }
354
355 // Test bad scenarios without allowlisting in MPTokenAuthorize
356 // (preclaim)
357 {
358 Env env{*this, features};
359 MPTTester mptAlice(env, alice, {.holders = {bob}});
360
361 mptAlice.create({.ownerCount = 1});
362
363 // bob submits a tx with a holder field
364 mptAlice.authorize(
365 {.account = bob, .holder = alice, .err = tecNO_PERMISSION});
366
367 // alice tries to hold onto her own token
368 mptAlice.authorize({.account = alice, .err = tecNO_PERMISSION});
369
370 // the mpt does not enable allowlisting
371 mptAlice.authorize({.holder = bob, .err = tecNO_AUTH});
372
373 // bob now holds a mptoken object
374 mptAlice.authorize({.account = bob, .holderCount = 1});
375
376 // bob cannot create the mptoken the second time
377 mptAlice.authorize({.account = bob, .err = tecDUPLICATE});
378
379 // Check that bob cannot delete MPToken when his balance is
380 // non-zero
381 {
382 // alice pays bob 100 tokens
383 mptAlice.pay(alice, bob, 100);
384
385 // bob tries to delete his MPToken, but fails since he still
386 // holds tokens
387 mptAlice.authorize(
388 {.account = bob,
389 .flags = tfMPTUnauthorize,
390 .err = tecHAS_OBLIGATIONS});
391
392 // bob pays back alice 100 tokens
393 mptAlice.pay(bob, alice, 100);
394 }
395
396 // bob deletes/unauthorizes his MPToken
397 mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
398
399 // bob receives error when he tries to delete his MPToken that has
400 // already been deleted
401 mptAlice.authorize(
402 {.account = bob,
403 .holderCount = 0,
404 .flags = tfMPTUnauthorize,
405 .err = tecOBJECT_NOT_FOUND});
406 }
407
408 // Test bad scenarios with allow-listing in MPTokenAuthorize (preclaim)
409 {
410 Env env{*this, features};
411 MPTTester mptAlice(env, alice, {.holders = {bob}});
412
413 mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth});
414
415 // alice submits a tx without specifying a holder's account
416 mptAlice.authorize({.err = tecNO_PERMISSION});
417
418 // alice submits a tx to authorize a holder that hasn't created
419 // a mptoken yet
420 mptAlice.authorize({.holder = bob, .err = tecOBJECT_NOT_FOUND});
421
422 // alice specifys a holder acct that doesn't exist
423 mptAlice.authorize({.holder = cindy, .err = tecNO_DST});
424
425 // bob now holds a mptoken object
426 mptAlice.authorize({.account = bob, .holderCount = 1});
427
428 // alice tries to unauthorize bob.
429 // although tx is successful,
430 // but nothing happens because bob hasn't been authorized yet
431 mptAlice.authorize({.holder = bob, .flags = tfMPTUnauthorize});
432
433 // alice authorizes bob
434 // make sure bob's mptoken has set lsfMPTAuthorized
435 mptAlice.authorize({.holder = bob});
436
437 // alice tries authorizes bob again.
438 // tx is successful, but bob is already authorized,
439 // so no changes
440 mptAlice.authorize({.holder = bob});
441
442 // bob deletes his mptoken
443 mptAlice.authorize(
444 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
445 }
446
447 // Test mptoken reserve requirement - first two mpts free (doApply)
448 {
449 Env env{*this, features};
450 auto const acctReserve = env.current()->fees().accountReserve(0);
451 auto const incReserve = env.current()->fees().increment;
452
453 // 1 drop
454 BEAST_EXPECT(incReserve > XRPAmount(1));
455 MPTTester mptAlice1(
456 env,
457 alice,
458 {.holders = {bob},
459 .xrpHolders = acctReserve + (incReserve - 1)});
460 mptAlice1.create();
461
462 MPTTester mptAlice2(env, alice, {.fund = false});
463 mptAlice2.create();
464
465 MPTTester mptAlice3(env, alice, {.fund = false});
466 mptAlice3.create({.ownerCount = 3});
467
468 // first mpt for free
469 mptAlice1.authorize({.account = bob, .holderCount = 1});
470
471 // second mpt free
472 mptAlice2.authorize({.account = bob, .holderCount = 2});
473
474 mptAlice3.authorize(
475 {.account = bob, .err = tecINSUFFICIENT_RESERVE});
476
477 env(pay(
478 env.master, bob, drops(incReserve + incReserve + incReserve)));
479 env.close();
480
481 mptAlice3.authorize({.account = bob, .holderCount = 3});
482 }
483 }
484
485 void
487 {
488 testcase("Authorize Enabled");
489
490 using namespace test::jtx;
491 Account const alice("alice");
492 Account const bob("bob");
493 // Basic authorization without allowlisting
494 {
495 Env env{*this, features};
496
497 // alice create mptissuance without allowisting
498 MPTTester mptAlice(env, alice, {.holders = {bob}});
499
500 mptAlice.create({.ownerCount = 1});
501
502 // bob creates a mptoken
503 mptAlice.authorize({.account = bob, .holderCount = 1});
504
505 mptAlice.authorize(
506 {.account = bob, .holderCount = 1, .err = tecDUPLICATE});
507
508 // bob deletes his mptoken
509 mptAlice.authorize(
510 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
511 }
512
513 // With allowlisting
514 {
515 Env env{*this, features};
516
517 // alice creates a mptokenissuance that requires authorization
518 MPTTester mptAlice(env, alice, {.holders = {bob}});
519
520 mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth});
521
522 // bob creates a mptoken
523 mptAlice.authorize({.account = bob, .holderCount = 1});
524
525 // alice authorizes bob
526 mptAlice.authorize({.account = alice, .holder = bob});
527
528 // Unauthorize bob's mptoken
529 mptAlice.authorize(
530 {.account = alice,
531 .holder = bob,
532 .holderCount = 1,
533 .flags = tfMPTUnauthorize});
534
535 mptAlice.authorize(
536 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
537 }
538
539 // Holder can have dangling MPToken even if issuance has been destroyed.
540 // Make sure they can still delete/unauthorize the MPToken
541 {
542 Env env{*this, features};
543 MPTTester mptAlice(env, alice, {.holders = {bob}});
544
545 mptAlice.create({.ownerCount = 1});
546
547 // bob creates a mptoken
548 mptAlice.authorize({.account = bob, .holderCount = 1});
549
550 // alice deletes her issuance
551 mptAlice.destroy({.ownerCount = 0});
552
553 // bob can delete his mptoken even though issuance is no longer
554 // existent
555 mptAlice.authorize(
556 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
557 }
558 }
559
560 void
562 {
563 testcase("Validate set transaction");
564
565 using namespace test::jtx;
566 Account const alice("alice"); // issuer
567 Account const bob("bob"); // holder
568 Account const cindy("cindy");
569 // Validate fields in MPTokenIssuanceSet (preflight)
570 {
571 Env env{*this, features - featureMPTokensV1};
572 MPTTester mptAlice(env, alice, {.holders = {bob}});
573
574 mptAlice.set(
575 {.account = bob,
576 .id = makeMptID(env.seq(alice), alice),
577 .err = temDISABLED});
578
579 env.enableFeature(featureMPTokensV1);
580
581 mptAlice.create({.ownerCount = 1, .holderCount = 0});
582
583 mptAlice.authorize({.account = bob, .holderCount = 1});
584
585 // test invalid flag - only valid flags are tfMPTLock (1) and Unlock
586 // (2)
587 mptAlice.set(
588 {.account = alice,
589 .flags = 0x00000008,
590 .err = temINVALID_FLAG});
591
592 if (!features[featureSingleAssetVault] &&
593 !features[featureDynamicMPT])
594 {
595 // test invalid flags - nothing is being changed
596 mptAlice.set(
597 {.account = alice,
598 .flags = 0x00000000,
599 .err = tecNO_PERMISSION});
600
601 mptAlice.set(
602 {.account = alice,
603 .holder = bob,
604 .flags = 0x00000000,
605 .err = tecNO_PERMISSION});
606
607 // cannot set DomainID since SAV is not enabled
608 mptAlice.set(
609 {.account = alice,
610 .domainID = uint256(42),
611 .err = temDISABLED});
612 }
613 else
614 {
615 // test invalid flags - nothing is being changed
616 mptAlice.set(
617 {.account = alice,
618 .flags = 0x00000000,
619 .err = temMALFORMED});
620
621 mptAlice.set(
622 {.account = alice,
623 .holder = bob,
624 .flags = 0x00000000,
625 .err = temMALFORMED});
626
627 if (!features[featurePermissionedDomains] ||
628 !features[featureSingleAssetVault])
629 {
630 // cannot set DomainID since PD is not enabled
631 mptAlice.set(
632 {.account = alice,
633 .domainID = uint256(42),
634 .err = temDISABLED});
635 }
636 else if (features[featureSingleAssetVault])
637 {
638 // cannot set DomainID since Holder is set
639 mptAlice.set(
640 {.account = alice,
641 .holder = bob,
642 .domainID = uint256(42),
643 .err = temMALFORMED});
644 }
645 }
646
647 // set both lock and unlock flags at the same time will fail
648 mptAlice.set(
649 {.account = alice,
650 .flags = tfMPTLock | tfMPTUnlock,
651 .err = temINVALID_FLAG});
652
653 // if the holder is the same as the acct that submitted the tx,
654 // tx fails
655 mptAlice.set(
656 {.account = alice,
657 .holder = alice,
658 .flags = tfMPTLock,
659 .err = temMALFORMED});
660 }
661
662 // Validate fields in MPTokenIssuanceSet (preclaim)
663 // test when a mptokenissuance has disabled locking
664 {
665 Env env{*this, features};
666
667 MPTTester mptAlice(env, alice, {.holders = {bob}});
668
669 mptAlice.create({.ownerCount = 1});
670
671 // alice tries to lock a mptissuance that has disabled locking
672 mptAlice.set(
673 {.account = alice,
674 .flags = tfMPTLock,
675 .err = tecNO_PERMISSION});
676
677 // alice tries to unlock mptissuance that has disabled locking
678 mptAlice.set(
679 {.account = alice,
680 .flags = tfMPTUnlock,
681 .err = tecNO_PERMISSION});
682
683 // issuer tries to lock a bob's mptoken that has disabled
684 // locking
685 mptAlice.set(
686 {.account = alice,
687 .holder = bob,
688 .flags = tfMPTLock,
689 .err = tecNO_PERMISSION});
690
691 // issuer tries to unlock a bob's mptoken that has disabled
692 // locking
693 mptAlice.set(
694 {.account = alice,
695 .holder = bob,
696 .flags = tfMPTUnlock,
697 .err = tecNO_PERMISSION});
698 }
699
700 // Validate fields in MPTokenIssuanceSet (preclaim)
701 // test when mptokenissuance has enabled locking
702 {
703 Env env{*this, features};
704
705 MPTTester mptAlice(env, alice, {.holders = {bob}});
706
707 // alice trying to set when the mptissuance doesn't exist yet
708 mptAlice.set(
709 {.id = makeMptID(env.seq(alice), alice),
710 .flags = tfMPTLock,
711 .err = tecOBJECT_NOT_FOUND});
712
713 // create a mptokenissuance with locking
714 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanLock});
715
716 // a non-issuer acct tries to set the mptissuance
717 mptAlice.set(
718 {.account = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION});
719
720 // trying to set a holder who doesn't have a mptoken
721 mptAlice.set(
722 {.holder = bob,
723 .flags = tfMPTLock,
724 .err = tecOBJECT_NOT_FOUND});
725
726 // trying to set a holder who doesn't exist
727 mptAlice.set(
728 {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
729 }
730
731 if (features[featureSingleAssetVault] &&
732 features[featurePermissionedDomains])
733 {
734 // Add permissioned domain
735 Account const credIssuer1{"credIssuer1"};
736 std::string const credType = "credential";
737
738 pdomain::Credentials const credentials1{
739 {.issuer = credIssuer1, .credType = credType}};
740
741 {
742 Env env{*this, features};
743
744 MPTTester mptAlice(env, alice);
745 mptAlice.create({});
746
747 // Trying to set DomainID on a public MPTokenIssuance
748 mptAlice.set(
749 {.domainID = uint256(42), .err = tecNO_PERMISSION});
750
751 mptAlice.set(
752 {.domainID = beast::zero, .err = tecNO_PERMISSION});
753 }
754
755 {
756 Env env{*this, features};
757
758 MPTTester mptAlice(env, alice);
759 mptAlice.create({.flags = tfMPTRequireAuth});
760
761 // Trying to set non-existing DomainID
762 mptAlice.set(
763 {.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND});
764
765 // Trying to lock but locking is disabled
766 mptAlice.set(
767 {.flags = tfMPTUnlock,
768 .domainID = uint256(42),
769 .err = tecNO_PERMISSION});
770
771 mptAlice.set(
772 {.flags = tfMPTUnlock,
773 .domainID = beast::zero,
774 .err = tecNO_PERMISSION});
775 }
776 }
777 }
778
779 void
781 {
782 testcase("Enabled set transaction");
783
784 using namespace test::jtx;
785 Account const alice("alice"); // issuer
786 Account const bob("bob"); // holder
787
788 {
789 // Test locking and unlocking
790 Env env{*this, features};
791
792 MPTTester mptAlice(env, alice, {.holders = {bob}});
793
794 // create a mptokenissuance with locking
795 mptAlice.create(
796 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
797
798 mptAlice.authorize({.account = bob, .holderCount = 1});
799
800 // locks bob's mptoken
801 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
802
803 // trying to lock bob's mptoken again will still succeed
804 // but no changes to the objects
805 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
806
807 // alice locks the mptissuance
808 mptAlice.set({.account = alice, .flags = tfMPTLock});
809
810 // alice tries to lock up both mptissuance and mptoken again
811 // it will not change the flags and both will remain locked.
812 mptAlice.set({.account = alice, .flags = tfMPTLock});
813 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
814
815 // alice unlocks bob's mptoken
816 mptAlice.set(
817 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
818
819 // locks up bob's mptoken again
820 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
821 if (!features[featureSingleAssetVault])
822 {
823 // Delete bobs' mptoken even though it is locked
824 mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
825
826 mptAlice.set(
827 {.account = alice,
828 .holder = bob,
829 .flags = tfMPTUnlock,
830 .err = tecOBJECT_NOT_FOUND});
831
832 return;
833 }
834
835 // Cannot delete locked MPToken
836 mptAlice.authorize(
837 {.account = bob,
838 .flags = tfMPTUnauthorize,
839 .err = tecNO_PERMISSION});
840
841 // alice unlocks mptissuance
842 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
843
844 // alice unlocks bob's mptoken
845 mptAlice.set(
846 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
847
848 // alice unlocks mptissuance and bob's mptoken again despite that
849 // they are already unlocked. Make sure this will not change the
850 // flags
851 mptAlice.set(
852 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
853 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
854 }
855
856 if (features[featureSingleAssetVault])
857 {
858 // Add permissioned domain
859 std::string const credType = "credential";
860
861 // Test setting and resetting domain ID
862 Env env{*this, features};
863
864 auto const domainId1 = [&]() {
865 Account const credIssuer1{"credIssuer1"};
866 env.fund(XRP(1000), credIssuer1);
867
868 pdomain::Credentials const credentials1{
869 {.issuer = credIssuer1, .credType = credType}};
870
871 env(pdomain::setTx(credIssuer1, credentials1));
872 return [&]() {
873 auto tx = env.tx()->getJson(JsonOptions::none);
874 return pdomain::getNewDomain(env.meta());
875 }();
876 }();
877
878 auto const domainId2 = [&]() {
879 Account const credIssuer2{"credIssuer2"};
880 env.fund(XRP(1000), credIssuer2);
881
882 pdomain::Credentials const credentials2{
883 {.issuer = credIssuer2, .credType = credType}};
884
885 env(pdomain::setTx(credIssuer2, credentials2));
886 return [&]() {
887 auto tx = env.tx()->getJson(JsonOptions::none);
888 return pdomain::getNewDomain(env.meta());
889 }();
890 }();
891
892 MPTTester mptAlice(env, alice, {.holders = {bob}});
893
894 // create a mptokenissuance with auth.
895 mptAlice.create(
896 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth});
897 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
898
899 // reset "domain not set" to "domain not set", i.e. no change
900 mptAlice.set({.domainID = beast::zero});
901 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
902
903 // reset "domain not set" to domain1
904 mptAlice.set({.domainID = domainId1});
905 BEAST_EXPECT(mptAlice.checkDomainID(domainId1));
906
907 // reset domain1 to domain2
908 mptAlice.set({.domainID = domainId2});
909 BEAST_EXPECT(mptAlice.checkDomainID(domainId2));
910
911 // reset domain to "domain not set"
912 mptAlice.set({.domainID = beast::zero});
913 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
914 }
915 }
916
917 void
919 {
920 testcase("Payment");
921
922 using namespace test::jtx;
923 Account const alice("alice"); // issuer
924 Account const bob("bob"); // holder
925 Account const carol("carol"); // holder
926
927 // preflight validation
928
929 // MPT is disabled
930 {
931 Env env{*this, features - featureMPTokensV1};
932 Account const alice("alice");
933 Account const bob("bob");
934
935 env.fund(XRP(1'000), alice);
936 env.fund(XRP(1'000), bob);
937 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
938
939 env(pay(alice, bob, mpt), ter(temDISABLED));
940 }
941
942 // MPT is disabled, unsigned request
943 {
944 Env env{*this, features - featureMPTokensV1};
945 Account const alice("alice"); // issuer
946 Account const carol("carol");
947 auto const USD = alice["USD"];
948
949 env.fund(XRP(1'000), alice);
950 env.fund(XRP(1'000), carol);
951 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
952
953 Json::Value jv;
954 jv[jss::secret] = alice.name();
955 jv[jss::tx_json] = pay(alice, carol, mpt);
956 jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
957 auto const jrr = env.rpc("json", "submit", to_string(jv));
958 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "temDISABLED");
959 }
960
961 // Invalid flag
962 {
963 Env env{*this, features};
964
965 MPTTester mptAlice(env, alice, {.holders = {bob}});
966
967 mptAlice.create({.ownerCount = 1, .holderCount = 0});
968 auto const MPT = mptAlice["MPT"];
969
970 mptAlice.authorize({.account = bob});
971
973 env(pay(alice, bob, MPT(10)),
974 txflags(flags),
976 }
977
978 // Invalid combination of send, sendMax, deliverMin, paths
979 {
980 Env env{*this, features};
981 Account const alice("alice");
982 Account const carol("carol");
983
984 MPTTester mptAlice(env, alice, {.holders = {carol}});
985
986 mptAlice.create({.ownerCount = 1, .holderCount = 0});
987
988 mptAlice.authorize({.account = carol});
989
990 // sendMax and DeliverMin are valid XRP amount,
991 // but is invalid combination with MPT amount
992 auto const MPT = mptAlice["MPT"];
993 env(pay(alice, carol, MPT(100)),
994 sendmax(XRP(100)),
996 env(pay(alice, carol, MPT(100)),
997 delivermin(XRP(100)),
999 // sendMax MPT is invalid with IOU or XRP
1000 auto const USD = alice["USD"];
1001 env(pay(alice, carol, USD(100)),
1002 sendmax(MPT(100)),
1003 ter(temMALFORMED));
1004 env(pay(alice, carol, XRP(100)),
1005 sendmax(MPT(100)),
1006 ter(temMALFORMED));
1007 env(pay(alice, carol, USD(100)),
1008 delivermin(MPT(100)),
1010 env(pay(alice, carol, XRP(100)),
1011 delivermin(MPT(100)),
1013 // sendmax and amount are different MPT issue
1014 test::jtx::MPT const MPT1(
1015 "MPT", makeMptID(env.seq(alice) + 10, alice));
1016 env(pay(alice, carol, MPT1(100)),
1017 sendmax(MPT(100)),
1018 ter(temMALFORMED));
1019 // paths is invalid
1020 env(pay(alice, carol, MPT(100)), path(~USD), ter(temMALFORMED));
1021 }
1022
1023 // build_path is invalid if MPT
1024 {
1025 Env env{*this, features};
1026 Account const alice("alice");
1027 Account const carol("carol");
1028
1029 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1030
1031 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1032 auto const MPT = mptAlice["MPT"];
1033
1034 mptAlice.authorize({.account = carol});
1035
1036 Json::Value payment;
1037 payment[jss::secret] = alice.name();
1038 payment[jss::tx_json] = pay(alice, carol, MPT(100));
1039
1040 payment[jss::build_path] = true;
1041 auto jrr = env.rpc("json", "submit", to_string(payment));
1042 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1043 BEAST_EXPECT(
1044 jrr[jss::result][jss::error_message] ==
1045 "Field 'build_path' not allowed in this context.");
1046 }
1047
1048 // Can't pay negative amount
1049 {
1050 Env env{*this, features};
1051
1052 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1053
1054 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1055 auto const MPT = mptAlice["MPT"];
1056
1057 mptAlice.authorize({.account = bob});
1058 mptAlice.authorize({.account = carol});
1059
1060 mptAlice.pay(alice, bob, -1, temBAD_AMOUNT);
1061
1062 mptAlice.pay(bob, carol, -1, temBAD_AMOUNT);
1063
1064 mptAlice.pay(bob, alice, -1, temBAD_AMOUNT);
1065
1066 env(pay(alice, bob, MPT(10)), sendmax(MPT(-1)), ter(temBAD_AMOUNT));
1067 }
1068
1069 // Pay to self
1070 {
1071 Env env{*this, features};
1072
1073 MPTTester mptAlice(env, alice, {.holders = {bob}});
1074
1075 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1076
1077 mptAlice.authorize({.account = bob});
1078
1079 mptAlice.pay(bob, bob, 10, temREDUNDANT);
1080 }
1081
1082 // preclaim validation
1083
1084 // Destination doesn't exist
1085 {
1086 Env env{*this, features};
1087
1088 MPTTester mptAlice(env, alice, {.holders = {bob}});
1089
1090 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1091
1092 mptAlice.authorize({.account = bob});
1093
1094 Account const bad{"bad"};
1095 env.memoize(bad);
1096
1097 mptAlice.pay(bob, bad, 10, tecNO_DST);
1098 }
1099
1100 // apply validation
1101
1102 // If RequireAuth is enabled, Payment fails if the receiver is not
1103 // authorized
1104 {
1105 Env env{*this, features};
1106
1107 MPTTester mptAlice(env, alice, {.holders = {bob}});
1108
1109 mptAlice.create(
1110 {.ownerCount = 1,
1111 .holderCount = 0,
1113
1114 mptAlice.authorize({.account = bob});
1115
1116 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1117 }
1118
1119 // If RequireAuth is enabled, Payment fails if the sender is not
1120 // authorized
1121 {
1122 Env env{*this, features};
1123
1124 MPTTester mptAlice(env, alice, {.holders = {bob}});
1125
1126 mptAlice.create(
1127 {.ownerCount = 1,
1128 .holderCount = 0,
1130
1131 // bob creates an empty MPToken
1132 mptAlice.authorize({.account = bob});
1133
1134 // alice authorizes bob to hold funds
1135 mptAlice.authorize({.account = alice, .holder = bob});
1136
1137 // alice sends 100 MPT to bob
1138 mptAlice.pay(alice, bob, 100);
1139
1140 // alice UNAUTHORIZES bob
1141 mptAlice.authorize(
1142 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
1143
1144 // bob fails to send back to alice because he is no longer
1145 // authorize to move his funds!
1146 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
1147 }
1148
1149 if (features[featureSingleAssetVault] &&
1150 features[featurePermissionedDomains])
1151 {
1152 // If RequireAuth is enabled and domain is a match, payment succeeds
1153 {
1154 Env env{*this, features};
1155 std::string const credType = "credential";
1156 Account const credIssuer1{"credIssuer1"};
1157 env.fund(XRP(1000), credIssuer1, bob);
1158
1159 auto const domainId1 = [&]() {
1160 pdomain::Credentials const credentials1{
1161 {.issuer = credIssuer1, .credType = credType}};
1162
1163 env(pdomain::setTx(credIssuer1, credentials1));
1164 return [&]() {
1165 auto tx = env.tx()->getJson(JsonOptions::none);
1166 return pdomain::getNewDomain(env.meta());
1167 }();
1168 }();
1169 // bob is authorized via domain
1170 env(credentials::create(bob, credIssuer1, credType));
1171 env(credentials::accept(bob, credIssuer1, credType));
1172 env.close();
1173
1174 MPTTester mptAlice(env, alice, {});
1175 env.close();
1176
1177 mptAlice.create({
1178 .ownerCount = 1,
1179 .holderCount = 0,
1181 .domainID = domainId1,
1182 });
1183
1184 mptAlice.authorize({.account = bob});
1185 env.close();
1186
1187 // bob is authorized via domain
1188 mptAlice.pay(alice, bob, 100);
1189 mptAlice.set({.domainID = beast::zero});
1190
1191 // bob is no longer authorized
1192 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1193 }
1194
1195 {
1196 Env env{*this, features};
1197 std::string const credType = "credential";
1198 Account const credIssuer1{"credIssuer1"};
1199 env.fund(XRP(1000), credIssuer1, bob);
1200
1201 auto const domainId1 = [&]() {
1202 pdomain::Credentials const credentials1{
1203 {.issuer = credIssuer1, .credType = credType}};
1204
1205 env(pdomain::setTx(credIssuer1, credentials1));
1206 return [&]() {
1207 auto tx = env.tx()->getJson(JsonOptions::none);
1208 return pdomain::getNewDomain(env.meta());
1209 }();
1210 }();
1211 // bob is authorized via domain
1212 env(credentials::create(bob, credIssuer1, credType));
1213 env(credentials::accept(bob, credIssuer1, credType));
1214 env.close();
1215
1216 MPTTester mptAlice(env, alice, {});
1217 env.close();
1218
1219 mptAlice.create({
1220 .ownerCount = 1,
1221 .holderCount = 0,
1223 .domainID = domainId1,
1224 });
1225
1226 // bob creates an empty MPToken
1227 mptAlice.authorize({.account = bob});
1228
1229 // alice authorizes bob to hold funds
1230 mptAlice.authorize({.account = alice, .holder = bob});
1231
1232 // alice sends 100 MPT to bob
1233 mptAlice.pay(alice, bob, 100);
1234
1235 // alice UNAUTHORIZES bob
1236 mptAlice.authorize(
1237 {.account = alice,
1238 .holder = bob,
1239 .flags = tfMPTUnauthorize});
1240
1241 // bob is still authorized, via domain
1242 mptAlice.pay(bob, alice, 10);
1243
1244 mptAlice.set({.domainID = beast::zero});
1245
1246 // bob fails to send back to alice because he is no longer
1247 // authorize to move his funds!
1248 mptAlice.pay(bob, alice, 10, tecNO_AUTH);
1249 }
1250
1251 {
1252 Env env{*this, features};
1253 std::string const credType = "credential";
1254 // credIssuer1 is the owner of domainId1 and a credential issuer
1255 Account const credIssuer1{"credIssuer1"};
1256 // credIssuer2 is the owner of domainId2 and a credential issuer
1257 // Note, domainId2 also lists credentials issued by credIssuer1
1258 Account const credIssuer2{"credIssuer2"};
1259 env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol);
1260
1261 auto const domainId1 = [&]() {
1262 pdomain::Credentials const credentials{
1263 {.issuer = credIssuer1, .credType = credType}};
1264
1265 env(pdomain::setTx(credIssuer1, credentials));
1266 return [&]() {
1267 auto tx = env.tx()->getJson(JsonOptions::none);
1268 return pdomain::getNewDomain(env.meta());
1269 }();
1270 }();
1271
1272 auto const domainId2 = [&]() {
1273 pdomain::Credentials const credentials{
1274 {.issuer = credIssuer1, .credType = credType},
1275 {.issuer = credIssuer2, .credType = credType}};
1276
1277 env(pdomain::setTx(credIssuer2, credentials));
1278 return [&]() {
1279 auto tx = env.tx()->getJson(JsonOptions::none);
1280 return pdomain::getNewDomain(env.meta());
1281 }();
1282 }();
1283
1284 // bob is authorized via credIssuer1 which is recognized by both
1285 // domainId1 and domainId2
1286 env(credentials::create(bob, credIssuer1, credType));
1287 env(credentials::accept(bob, credIssuer1, credType));
1288 env.close();
1289
1290 // carol is authorized via credIssuer2, only recognized by
1291 // domainId2
1292 env(credentials::create(carol, credIssuer2, credType));
1293 env(credentials::accept(carol, credIssuer2, credType));
1294 env.close();
1295
1296 MPTTester mptAlice(env, alice, {});
1297 env.close();
1298
1299 mptAlice.create({
1300 .ownerCount = 1,
1301 .holderCount = 0,
1303 .domainID = domainId1,
1304 });
1305
1306 // bob and carol create an empty MPToken
1307 mptAlice.authorize({.account = bob});
1308 mptAlice.authorize({.account = carol});
1309 env.close();
1310
1311 // alice sends 50 MPT to bob but cannot send to carol
1312 mptAlice.pay(alice, bob, 50);
1313 mptAlice.pay(alice, carol, 50, tecNO_AUTH);
1314 env.close();
1315
1316 // bob cannot send to carol because they are not on the same
1317 // domain (since credIssuer2 is not recognized by domainId1)
1318 mptAlice.pay(bob, carol, 10, tecNO_AUTH);
1319 env.close();
1320
1321 // alice updates domainID to domainId2 which recognizes both
1322 // credIssuer1 and credIssuer2
1323 mptAlice.set({.domainID = domainId2});
1324 // alice can now send to carol
1325 mptAlice.pay(alice, carol, 10);
1326 env.close();
1327
1328 // bob can now send to carol because both are in the same
1329 // domain
1330 mptAlice.pay(bob, carol, 10);
1331 env.close();
1332
1333 // bob loses his authorization and can no longer send MPT
1335 credIssuer1, bob, credIssuer1, credType));
1336 env.close();
1337
1338 mptAlice.pay(bob, carol, 10, tecNO_AUTH);
1339 mptAlice.pay(bob, alice, 10, tecNO_AUTH);
1340 }
1341 }
1342
1343 // Non-issuer cannot send to each other if MPTCanTransfer isn't set
1344 {
1345 Env env(*this, features);
1346 Account const alice{"alice"};
1347 Account const bob{"bob"};
1348 Account const cindy{"cindy"};
1349
1350 MPTTester mptAlice(env, alice, {.holders = {bob, cindy}});
1351
1352 // alice creates issuance without MPTCanTransfer
1353 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1354
1355 // bob creates a MPToken
1356 mptAlice.authorize({.account = bob});
1357
1358 // cindy creates a MPToken
1359 mptAlice.authorize({.account = cindy});
1360
1361 // alice pays bob 100 tokens
1362 mptAlice.pay(alice, bob, 100);
1363
1364 // bob tries to send cindy 10 tokens, but fails because canTransfer
1365 // is off
1366 mptAlice.pay(bob, cindy, 10, tecNO_AUTH);
1367
1368 // bob can send back to alice(issuer) just fine
1369 mptAlice.pay(bob, alice, 10);
1370 }
1371
1372 // Holder is not authorized
1373 {
1374 Env env{*this, features};
1375
1376 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1377
1378 mptAlice.create(
1379 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1380
1381 // issuer to holder
1382 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1383
1384 // holder to issuer
1385 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
1386
1387 // holder to holder
1388 mptAlice.pay(bob, carol, 50, tecNO_AUTH);
1389 }
1390
1391 // Payer doesn't have enough funds
1392 {
1393 Env env{*this, features};
1394
1395 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1396
1397 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
1398
1399 mptAlice.authorize({.account = bob});
1400 mptAlice.authorize({.account = carol});
1401
1402 mptAlice.pay(alice, bob, 100);
1403
1404 // Pay to another holder
1405 mptAlice.pay(bob, carol, 101, tecPATH_PARTIAL);
1406
1407 // Pay to the issuer
1408 mptAlice.pay(bob, alice, 101, tecPATH_PARTIAL);
1409 }
1410
1411 // MPT is locked
1412 {
1413 Env env{*this, features};
1414
1415 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1416
1417 mptAlice.create(
1418 {.ownerCount = 1, .flags = tfMPTCanLock | tfMPTCanTransfer});
1419
1420 mptAlice.authorize({.account = bob});
1421 mptAlice.authorize({.account = carol});
1422
1423 mptAlice.pay(alice, bob, 100);
1424 mptAlice.pay(alice, carol, 100);
1425
1426 // Global lock
1427 mptAlice.set({.account = alice, .flags = tfMPTLock});
1428 // Can't send between holders
1429 mptAlice.pay(bob, carol, 1, tecLOCKED);
1430 mptAlice.pay(carol, bob, 2, tecLOCKED);
1431 // Issuer can send
1432 mptAlice.pay(alice, bob, 3);
1433 // Holder can send back to issuer
1434 mptAlice.pay(bob, alice, 4);
1435
1436 // Global unlock
1437 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
1438 // Individual lock
1439 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
1440 // Can't send between holders
1441 mptAlice.pay(bob, carol, 5, tecLOCKED);
1442 mptAlice.pay(carol, bob, 6, tecLOCKED);
1443 // Issuer can send
1444 mptAlice.pay(alice, bob, 7);
1445 // Holder can send back to issuer
1446 mptAlice.pay(bob, alice, 8);
1447 }
1448
1449 // Transfer fee
1450 {
1451 Env env{*this, features};
1452
1453 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1454
1455 // Transfer fee is 10%
1456 mptAlice.create(
1457 {.transferFee = 10'000,
1458 .ownerCount = 1,
1459 .holderCount = 0,
1460 .flags = tfMPTCanTransfer});
1461
1462 // Holders create MPToken
1463 mptAlice.authorize({.account = bob});
1464 mptAlice.authorize({.account = carol});
1465
1466 // Payment between the issuer and the holder, no transfer fee.
1467 mptAlice.pay(alice, bob, 2'000);
1468
1469 // Payment between the holder and the issuer, no transfer fee.
1470 mptAlice.pay(bob, alice, 1'000);
1471 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1'000));
1472
1473 // Payment between the holders. The sender doesn't have
1474 // enough funds to cover the transfer fee.
1475 mptAlice.pay(bob, carol, 1'000, tecPATH_PARTIAL);
1476
1477 // Payment between the holders. The sender has enough funds
1478 // but SendMax is not included.
1479 mptAlice.pay(bob, carol, 100, tecPATH_PARTIAL);
1480
1481 auto const MPT = mptAlice["MPT"];
1482 // SendMax doesn't cover the fee
1483 env(pay(bob, carol, MPT(100)),
1484 sendmax(MPT(109)),
1486
1487 // Payment succeeds if sufficient SendMax is included.
1488 // 100 to carol, 10 to issuer
1489 env(pay(bob, carol, MPT(100)), sendmax(MPT(110)));
1490 // 100 to carol, 10 to issuer
1491 env(pay(bob, carol, MPT(100)), sendmax(MPT(115)));
1492 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 780));
1493 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 200));
1494 // Payment succeeds if partial payment even if
1495 // SendMax is less than deliver amount
1496 env(pay(bob, carol, MPT(100)),
1497 sendmax(MPT(90)),
1499 // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) =
1500 // 82)
1501 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690));
1502 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 282));
1503 }
1504
1505 // Insufficient SendMax with no transfer fee
1506 {
1507 Env env{*this, features};
1508
1509 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1510
1511 mptAlice.create(
1512 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1513
1514 // Holders create MPToken
1515 mptAlice.authorize({.account = bob});
1516 mptAlice.authorize({.account = carol});
1517 mptAlice.pay(alice, bob, 1'000);
1518
1519 auto const MPT = mptAlice["MPT"];
1520 // SendMax is less than the amount
1521 env(pay(bob, carol, MPT(100)),
1522 sendmax(MPT(99)),
1524 env(pay(bob, alice, MPT(100)),
1525 sendmax(MPT(99)),
1527
1528 // Payment succeeds if sufficient SendMax is included.
1529 env(pay(bob, carol, MPT(100)), sendmax(MPT(100)));
1530 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 100));
1531 // Payment succeeds if partial payment
1532 env(pay(bob, carol, MPT(100)),
1533 sendmax(MPT(99)),
1535 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 199));
1536 }
1537
1538 // DeliverMin
1539 {
1540 Env env{*this, features};
1541
1542 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1543
1544 mptAlice.create(
1545 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1546
1547 // Holders create MPToken
1548 mptAlice.authorize({.account = bob});
1549 mptAlice.authorize({.account = carol});
1550 mptAlice.pay(alice, bob, 1'000);
1551
1552 auto const MPT = mptAlice["MPT"];
1553 // Fails even with the partial payment because
1554 // deliver amount < deliverMin
1555 env(pay(bob, alice, MPT(100)),
1556 sendmax(MPT(99)),
1557 delivermin(MPT(100)),
1560 // Payment succeeds if deliver amount >= deliverMin
1561 env(pay(bob, alice, MPT(100)),
1562 sendmax(MPT(99)),
1563 delivermin(MPT(99)),
1565 }
1566
1567 // Issuer fails trying to send more than the maximum amount allowed
1568 {
1569 Env env{*this, features};
1570
1571 MPTTester mptAlice(env, alice, {.holders = {bob}});
1572
1573 mptAlice.create(
1574 {.maxAmt = 100,
1575 .ownerCount = 1,
1576 .holderCount = 0,
1577 .flags = tfMPTCanTransfer});
1578
1579 mptAlice.authorize({.account = bob});
1580
1581 // issuer sends holder the max amount allowed
1582 mptAlice.pay(alice, bob, 100);
1583
1584 // issuer tries to exceed max amount
1585 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1586 }
1587
1588 // Issuer fails trying to send more than the default maximum
1589 // amount allowed
1590 {
1591 Env env{*this, features};
1592
1593 MPTTester mptAlice(env, alice, {.holders = {bob}});
1594
1595 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1596
1597 mptAlice.authorize({.account = bob});
1598
1599 // issuer sends holder the default max amount allowed
1600 mptAlice.pay(alice, bob, maxMPTokenAmount);
1601
1602 // issuer tries to exceed max amount
1603 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1604 }
1605
1606 // Pay more than max amount fails in the json parser before
1607 // transactor is called
1608 {
1609 Env env{*this, features};
1610 env.fund(XRP(1'000), alice, bob);
1611 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
1612 Json::Value jv;
1613 jv[jss::secret] = alice.name();
1614 jv[jss::tx_json] = pay(alice, bob, mpt);
1615 jv[jss::tx_json][jss::Amount][jss::value] =
1617 auto const jrr = env.rpc("json", "submit", to_string(jv));
1618 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1619 }
1620
1621 // Pay maximum amount with the transfer fee, SendMax, and
1622 // partial payment
1623 {
1624 Env env{*this, features};
1625
1626 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1627
1628 mptAlice.create(
1629 {.maxAmt = 10'000,
1630 .transferFee = 100,
1631 .ownerCount = 1,
1632 .holderCount = 0,
1633 .flags = tfMPTCanTransfer});
1634 auto const MPT = mptAlice["MPT"];
1635
1636 mptAlice.authorize({.account = bob});
1637 mptAlice.authorize({.account = carol});
1638
1639 // issuer sends holder the max amount allowed
1640 mptAlice.pay(alice, bob, 10'000);
1641
1642 // payment between the holders
1643 env(pay(bob, carol, MPT(10'000)),
1644 sendmax(MPT(10'000)),
1646 // Verify the metadata
1647 auto const meta = env.meta()->getJson(
1648 JsonOptions::none)[sfAffectedNodes.fieldName];
1649 // Issuer got 10 in the transfer fees
1650 BEAST_EXPECT(
1651 meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1652 [sfOutstandingAmount.fieldName] == "9990");
1653 // Destination account got 9'990
1654 BEAST_EXPECT(
1655 meta[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1656 [sfMPTAmount.fieldName] == "9990");
1657 // Source account spent 10'000
1658 BEAST_EXPECT(
1659 meta[2u][sfModifiedNode.fieldName][sfPreviousFields.fieldName]
1660 [sfMPTAmount.fieldName] == "10000");
1661 BEAST_EXPECT(
1662 !meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1663 .isMember(sfMPTAmount.fieldName));
1664
1665 // payment between the holders fails without
1666 // partial payment
1667 env(pay(bob, carol, MPT(10'000)),
1668 sendmax(MPT(10'000)),
1670 }
1671
1672 // Pay maximum allowed amount
1673 {
1674 Env env{*this, features};
1675
1676 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1677
1678 mptAlice.create(
1679 {.maxAmt = maxMPTokenAmount,
1680 .ownerCount = 1,
1681 .holderCount = 0,
1682 .flags = tfMPTCanTransfer});
1683 auto const MPT = mptAlice["MPT"];
1684
1685 mptAlice.authorize({.account = bob});
1686 mptAlice.authorize({.account = carol});
1687
1688 // issuer sends holder the max amount allowed
1689 mptAlice.pay(alice, bob, maxMPTokenAmount);
1690 BEAST_EXPECT(
1691 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1692
1693 // payment between the holders
1694 mptAlice.pay(bob, carol, maxMPTokenAmount);
1695 BEAST_EXPECT(
1696 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1697 // holder pays back to the issuer
1698 mptAlice.pay(carol, alice, maxMPTokenAmount);
1699 BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0));
1700 }
1701
1702 // Issuer fails trying to send fund after issuance was destroyed
1703 {
1704 Env env{*this, features};
1705
1706 MPTTester mptAlice(env, alice, {.holders = {bob}});
1707
1708 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1709
1710 mptAlice.authorize({.account = bob});
1711
1712 // alice destroys issuance
1713 mptAlice.destroy({.ownerCount = 0});
1714
1715 // alice tries to send bob fund after issuance is destroyed, should
1716 // fail.
1717 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1718 }
1719
1720 // Non-existent issuance
1721 {
1722 Env env{*this, features};
1723
1724 env.fund(XRP(1'000), alice, bob);
1725
1726 STAmount const mpt{MPTID{0}, 100};
1727 env(pay(alice, bob, mpt), ter(tecOBJECT_NOT_FOUND));
1728 }
1729
1730 // Issuer fails trying to send to an account, which doesn't own MPT for
1731 // an issuance that was destroyed
1732 {
1733 Env env{*this, features};
1734
1735 MPTTester mptAlice(env, alice, {.holders = {bob}});
1736
1737 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1738
1739 // alice destroys issuance
1740 mptAlice.destroy({.ownerCount = 0});
1741
1742 // alice tries to send bob who doesn't own the MPT after issuance is
1743 // destroyed, it should fail
1744 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1745 }
1746
1747 // Issuers issues maximum amount of MPT to a holder, the holder should
1748 // be able to transfer the max amount to someone else
1749 {
1750 Env env{*this, features};
1751 Account const alice("alice");
1752 Account const carol("bob");
1753 Account const bob("carol");
1754
1755 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1756
1757 mptAlice.create(
1758 {.maxAmt = 100, .ownerCount = 1, .flags = tfMPTCanTransfer});
1759
1760 mptAlice.authorize({.account = bob});
1761 mptAlice.authorize({.account = carol});
1762
1763 mptAlice.pay(alice, bob, 100);
1764
1765 // transfer max amount to another holder
1766 mptAlice.pay(bob, carol, 100);
1767 }
1768
1769 // Simple payment
1770 {
1771 Env env{*this, features};
1772
1773 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1774
1775 mptAlice.create(
1776 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1777
1778 mptAlice.authorize({.account = bob});
1779 mptAlice.authorize({.account = carol});
1780
1781 // issuer to holder
1782 mptAlice.pay(alice, bob, 100);
1783
1784 // holder to issuer
1785 mptAlice.pay(bob, alice, 100);
1786
1787 // holder to holder
1788 mptAlice.pay(alice, bob, 100);
1789 mptAlice.pay(bob, carol, 50);
1790 }
1791 }
1792
1793 void
1795 {
1796 using namespace test::jtx;
1797 Account const alice("alice"); // issuer
1798 Account const bob("bob"); // holder
1799 Account const diana("diana");
1800 Account const dpIssuer("dpIssuer"); // holder
1801
1802 char const credType[] = "abcde";
1803
1804 if (features[featureCredentials])
1805 {
1806 testcase("DepositPreauth");
1807
1808 Env env(*this, features);
1809
1810 env.fund(XRP(50000), diana, dpIssuer);
1811 env.close();
1812
1813 MPTTester mptAlice(env, alice, {.holders = {bob}});
1814 mptAlice.create(
1815 {.ownerCount = 1,
1816 .holderCount = 0,
1818
1819 env(pay(diana, bob, XRP(500)));
1820 env.close();
1821
1822 // bob creates an empty MPToken
1823 mptAlice.authorize({.account = bob});
1824 // alice authorizes bob to hold funds
1825 mptAlice.authorize({.account = alice, .holder = bob});
1826
1827 // Bob require preauthorization
1828 env(fset(bob, asfDepositAuth));
1829 env.close();
1830
1831 // alice try to send 100 MPT to bob, not authorized
1832 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1833 env.close();
1834
1835 // Bob authorize alice
1836 env(deposit::auth(bob, alice));
1837 env.close();
1838
1839 // alice sends 100 MPT to bob
1840 mptAlice.pay(alice, bob, 100);
1841 env.close();
1842
1843 // Create credentials
1844 env(credentials::create(alice, dpIssuer, credType));
1845 env.close();
1846 env(credentials::accept(alice, dpIssuer, credType));
1847 env.close();
1848 auto const jv =
1849 credentials::ledgerEntry(env, alice, dpIssuer, credType);
1850 std::string const credIdx = jv[jss::result][jss::index].asString();
1851
1852 // alice sends 100 MPT to bob with credentials which aren't required
1853 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1854 env.close();
1855
1856 // Bob revoke authorization
1857 env(deposit::unauth(bob, alice));
1858 env.close();
1859
1860 // alice try to send 100 MPT to bob, not authorized
1861 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1862 env.close();
1863
1864 // alice sends 100 MPT to bob with credentials, not authorized
1865 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION, {{credIdx}});
1866 env.close();
1867
1868 // Bob authorize credentials
1869 env(deposit::authCredentials(bob, {{dpIssuer, credType}}));
1870 env.close();
1871
1872 // alice try to send 100 MPT to bob, not authorized
1873 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1874 env.close();
1875
1876 // alice sends 100 MPT to bob with credentials
1877 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1878 env.close();
1879 }
1880
1881 testcase("DepositPreauth disabled featureCredentials");
1882 {
1883 Env env(*this, testable_amendments() - featureCredentials);
1884
1885 std::string const credIdx =
1886 "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6"
1887 "E2";
1888
1889 env.fund(XRP(50000), diana, dpIssuer);
1890 env.close();
1891
1892 MPTTester mptAlice(env, alice, {.holders = {bob}});
1893 mptAlice.create(
1894 {.ownerCount = 1,
1895 .holderCount = 0,
1897
1898 env(pay(diana, bob, XRP(500)));
1899 env.close();
1900
1901 // bob creates an empty MPToken
1902 mptAlice.authorize({.account = bob});
1903 // alice authorizes bob to hold funds
1904 mptAlice.authorize({.account = alice, .holder = bob});
1905
1906 // Bob require preauthorization
1907 env(fset(bob, asfDepositAuth));
1908 env.close();
1909
1910 // alice try to send 100 MPT to bob, not authorized
1911 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1912 env.close();
1913
1914 // alice try to send 100 MPT to bob with credentials, amendment
1915 // disabled
1916 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1917 env.close();
1918
1919 // Bob authorize alice
1920 env(deposit::auth(bob, alice));
1921 env.close();
1922
1923 // alice sends 100 MPT to bob
1924 mptAlice.pay(alice, bob, 100);
1925 env.close();
1926
1927 // alice sends 100 MPT to bob with credentials, amendment disabled
1928 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1929 env.close();
1930
1931 // Bob revoke authorization
1932 env(deposit::unauth(bob, alice));
1933 env.close();
1934
1935 // alice try to send 100 MPT to bob
1936 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1937 env.close();
1938
1939 // alice sends 100 MPT to bob with credentials, amendment disabled
1940 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1941 env.close();
1942 }
1943 }
1944
1945 void
1947 {
1948 testcase("MPT Issue Invalid in Transaction");
1949 using namespace test::jtx;
1950
1951 // Validate that every transaction with an amount/issue field,
1952 // which doesn't support MPT, fails.
1953
1954 // keyed by transaction + amount/issue field
1955 std::set<std::string> txWithAmounts;
1956 for (auto const& format : TxFormats::getInstance())
1957 {
1958 for (auto const& e : format.getSOTemplate())
1959 {
1960 // Transaction has amount/issue fields.
1961 // Exclude pseudo-transaction SetFee. Don't consider
1962 // the Fee field since it's included in every transaction.
1963 if (e.supportMPT() == soeMPTNotSupported &&
1964 e.sField().getName() != jss::Fee &&
1965 format.getName() != jss::SetFee)
1966 {
1967 txWithAmounts.insert(
1968 format.getName() + e.sField().fieldName);
1969 break;
1970 }
1971 }
1972 }
1973
1974 Account const alice("alice");
1975 auto const USD = alice["USD"];
1976 Account const carol("carol");
1977 MPTIssue issue(makeMptID(1, alice));
1978 STAmount mpt{issue, UINT64_C(100)};
1979 auto const jvb = bridge(alice, USD, alice, USD);
1980 for (auto const& feature : {features, features - featureMPTokensV1})
1981 {
1982 Env env{*this, feature};
1983 env.fund(XRP(1'000), alice);
1984 env.fund(XRP(1'000), carol);
1985 auto test = [&](Json::Value const& jv,
1986 std::string const& mptField) {
1987 txWithAmounts.erase(
1988 jv[jss::TransactionType].asString() + mptField);
1989
1990 // tx is signed
1991 auto jtx = env.jt(jv);
1992 Serializer s;
1993 jtx.stx->add(s);
1994 auto jrr = env.rpc("submit", strHex(s.slice()));
1995 BEAST_EXPECT(
1996 jrr[jss::result][jss::error] == "invalidTransaction");
1997
1998 // tx is unsigned
1999 Json::Value jv1;
2000 jv1[jss::secret] = alice.name();
2001 jv1[jss::tx_json] = jv;
2002 jrr = env.rpc("json", "submit", to_string(jv1));
2003 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2004
2005 jrr = env.rpc("json", "sign", to_string(jv1));
2006 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2007 };
2008 auto toSFieldRef = [](SField const& field) {
2009 return std::ref(field);
2010 };
2011 auto setMPTFields = [&](SField const& field,
2012 Json::Value& jv,
2013 bool withAmount = true) {
2014 jv[jss::Asset] = to_json(xrpIssue());
2015 jv[jss::Asset2] = to_json(USD.issue());
2016 if (withAmount)
2017 jv[field.fieldName] =
2018 USD(10).value().getJson(JsonOptions::none);
2019 if (field == sfAsset)
2020 jv[jss::Asset] = to_json(mpt.get<MPTIssue>());
2021 else if (field == sfAsset2)
2022 jv[jss::Asset2] = to_json(mpt.get<MPTIssue>());
2023 else
2024 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2025 };
2026 // All transactions with sfAmount, which don't support MPT.
2027 // Transactions with amount fields, which can't be MPT.
2028 // Transactions with issue fields, which can't be MPT.
2029
2030 // AMMCreate
2031 auto ammCreate = [&](SField const& field) {
2032 Json::Value jv;
2033 jv[jss::TransactionType] = jss::AMMCreate;
2034 jv[jss::Account] = alice.human();
2035 jv[jss::Amount] = (field.fieldName == sfAmount.fieldName)
2036 ? mpt.getJson(JsonOptions::none)
2037 : "100000000";
2038 jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName)
2039 ? mpt.getJson(JsonOptions::none)
2040 : "100000000";
2041 jv[jss::TradingFee] = 0;
2042 test(jv, field.fieldName);
2043 };
2044 ammCreate(sfAmount);
2045 ammCreate(sfAmount2);
2046 // AMMDeposit
2047 auto ammDeposit = [&](SField const& field) {
2048 Json::Value jv;
2049 jv[jss::TransactionType] = jss::AMMDeposit;
2050 jv[jss::Account] = alice.human();
2051 jv[jss::Flags] = tfSingleAsset;
2052 setMPTFields(field, jv);
2053 test(jv, field.fieldName);
2054 };
2055 for (SField const& field :
2056 {toSFieldRef(sfAmount),
2057 toSFieldRef(sfAmount2),
2058 toSFieldRef(sfEPrice),
2059 toSFieldRef(sfLPTokenOut),
2060 toSFieldRef(sfAsset),
2061 toSFieldRef(sfAsset2)})
2062 ammDeposit(field);
2063 // AMMWithdraw
2064 auto ammWithdraw = [&](SField const& field) {
2065 Json::Value jv;
2066 jv[jss::TransactionType] = jss::AMMWithdraw;
2067 jv[jss::Account] = alice.human();
2068 jv[jss::Flags] = tfSingleAsset;
2069 setMPTFields(field, jv);
2070 test(jv, field.fieldName);
2071 };
2072 ammWithdraw(sfAmount);
2073 for (SField const& field :
2074 {toSFieldRef(sfAmount2),
2075 toSFieldRef(sfEPrice),
2076 toSFieldRef(sfLPTokenIn),
2077 toSFieldRef(sfAsset),
2078 toSFieldRef(sfAsset2)})
2079 ammWithdraw(field);
2080 // AMMBid
2081 auto ammBid = [&](SField const& field) {
2082 Json::Value jv;
2083 jv[jss::TransactionType] = jss::AMMBid;
2084 jv[jss::Account] = alice.human();
2085 setMPTFields(field, jv);
2086 test(jv, field.fieldName);
2087 };
2088 for (SField const& field :
2089 {toSFieldRef(sfBidMin),
2090 toSFieldRef(sfBidMax),
2091 toSFieldRef(sfAsset),
2092 toSFieldRef(sfAsset2)})
2093 ammBid(field);
2094 // AMMClawback
2095 auto ammClawback = [&](SField const& field) {
2096 Json::Value jv;
2097 jv[jss::TransactionType] = jss::AMMClawback;
2098 jv[jss::Account] = alice.human();
2099 jv[jss::Holder] = carol.human();
2100 setMPTFields(field, jv);
2101 test(jv, field.fieldName);
2102 };
2103 for (SField const& field :
2104 {toSFieldRef(sfAmount),
2105 toSFieldRef(sfAsset),
2106 toSFieldRef(sfAsset2)})
2107 ammClawback(field);
2108 // AMMDelete
2109 auto ammDelete = [&](SField const& field) {
2110 Json::Value jv;
2111 jv[jss::TransactionType] = jss::AMMDelete;
2112 jv[jss::Account] = alice.human();
2113 setMPTFields(field, jv, false);
2114 test(jv, field.fieldName);
2115 };
2116 ammDelete(sfAsset);
2117 ammDelete(sfAsset2);
2118 // AMMVote
2119 auto ammVote = [&](SField const& field) {
2120 Json::Value jv;
2121 jv[jss::TransactionType] = jss::AMMVote;
2122 jv[jss::Account] = alice.human();
2123 jv[jss::TradingFee] = 100;
2124 setMPTFields(field, jv, false);
2125 test(jv, field.fieldName);
2126 };
2127 ammVote(sfAsset);
2128 ammVote(sfAsset2);
2129 // CheckCash
2130 auto checkCash = [&](SField const& field) {
2131 Json::Value jv;
2132 jv[jss::TransactionType] = jss::CheckCash;
2133 jv[jss::Account] = alice.human();
2134 jv[sfCheckID.fieldName] = to_string(uint256{1});
2135 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2136 test(jv, field.fieldName);
2137 };
2138 checkCash(sfAmount);
2139 checkCash(sfDeliverMin);
2140 // CheckCreate
2141 {
2142 Json::Value jv;
2143 jv[jss::TransactionType] = jss::CheckCreate;
2144 jv[jss::Account] = alice.human();
2145 jv[jss::Destination] = carol.human();
2146 jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
2147 test(jv, jss::SendMax.c_str());
2148 }
2149 // OfferCreate
2150 {
2151 Json::Value jv = offer(alice, USD(100), mpt);
2152 test(jv, jss::TakerPays.c_str());
2153 jv = offer(alice, mpt, USD(100));
2154 test(jv, jss::TakerGets.c_str());
2155 }
2156 // PaymentChannelCreate
2157 {
2158 Json::Value jv;
2159 jv[jss::TransactionType] = jss::PaymentChannelCreate;
2160 jv[jss::Account] = alice.human();
2161 jv[jss::Destination] = carol.human();
2162 jv[jss::SettleDelay] = 1;
2163 jv[sfPublicKey.fieldName] = strHex(alice.pk().slice());
2164 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2165 test(jv, jss::Amount.c_str());
2166 }
2167 // PaymentChannelFund
2168 {
2169 Json::Value jv;
2170 jv[jss::TransactionType] = jss::PaymentChannelFund;
2171 jv[jss::Account] = alice.human();
2172 jv[sfChannel.fieldName] = to_string(uint256{1});
2173 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2174 test(jv, jss::Amount.c_str());
2175 }
2176 // PaymentChannelClaim
2177 {
2178 Json::Value jv;
2179 jv[jss::TransactionType] = jss::PaymentChannelClaim;
2180 jv[jss::Account] = alice.human();
2181 jv[sfChannel.fieldName] = to_string(uint256{1});
2182 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2183 test(jv, jss::Amount.c_str());
2184 }
2185 // NFTokenCreateOffer
2186 {
2187 Json::Value jv;
2188 jv[jss::TransactionType] = jss::NFTokenCreateOffer;
2189 jv[jss::Account] = alice.human();
2190 jv[sfNFTokenID.fieldName] = to_string(uint256{1});
2191 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2192 test(jv, jss::Amount.c_str());
2193 }
2194 // NFTokenAcceptOffer
2195 {
2196 Json::Value jv;
2197 jv[jss::TransactionType] = jss::NFTokenAcceptOffer;
2198 jv[jss::Account] = alice.human();
2199 jv[sfNFTokenBrokerFee.fieldName] =
2200 mpt.getJson(JsonOptions::none);
2201 test(jv, sfNFTokenBrokerFee.fieldName);
2202 }
2203 // NFTokenMint
2204 {
2205 Json::Value jv;
2206 jv[jss::TransactionType] = jss::NFTokenMint;
2207 jv[jss::Account] = alice.human();
2208 jv[sfNFTokenTaxon.fieldName] = 1;
2209 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2210 test(jv, jss::Amount.c_str());
2211 }
2212 // TrustSet
2213 auto trustSet = [&](SField const& field) {
2214 Json::Value jv;
2215 jv[jss::TransactionType] = jss::TrustSet;
2216 jv[jss::Account] = alice.human();
2217 jv[jss::Flags] = 0;
2218 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2219 test(jv, field.fieldName);
2220 };
2221 trustSet(sfLimitAmount);
2222 trustSet(sfFee);
2223 // XChainCommit
2224 {
2225 Json::Value const jv = xchain_commit(alice, jvb, 1, mpt);
2226 test(jv, jss::Amount.c_str());
2227 }
2228 // XChainClaim
2229 {
2230 Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice);
2231 test(jv, jss::Amount.c_str());
2232 }
2233 // XChainCreateClaimID
2234 {
2235 Json::Value const jv =
2236 xchain_create_claim_id(alice, jvb, mpt, alice);
2237 test(jv, sfSignatureReward.fieldName);
2238 }
2239 // XChainAddClaimAttestation
2240 {
2241 Json::Value const jv = claim_attestation(
2242 alice,
2243 jvb,
2244 alice,
2245 mpt,
2246 alice,
2247 true,
2248 1,
2249 alice,
2250 signer(alice));
2251 test(jv, jss::Amount.c_str());
2252 }
2253 // XChainAddAccountCreateAttestation
2254 {
2256 alice,
2257 jvb,
2258 alice,
2259 mpt,
2260 XRP(10),
2261 alice,
2262 false,
2263 1,
2264 alice,
2265 signer(alice));
2266 for (auto const& field :
2267 {sfAmount.fieldName, sfSignatureReward.fieldName})
2268 {
2269 jv[field] = mpt.getJson(JsonOptions::none);
2270 test(jv, field);
2271 }
2272 }
2273 // XChainAccountCreateCommit
2274 {
2276 alice, jvb, alice, mpt, XRP(10));
2277 for (auto const& field :
2278 {sfAmount.fieldName, sfSignatureReward.fieldName})
2279 {
2280 jv[field] = mpt.getJson(JsonOptions::none);
2281 test(jv, field);
2282 }
2283 }
2284 // XChain[Create|Modify]Bridge
2285 auto bridgeTx = [&](Json::StaticString const& tt,
2286 STAmount const& rewardAmount,
2287 STAmount const& minAccountAmount,
2288 std::string const& field) {
2289 Json::Value jv;
2290 jv[jss::TransactionType] = tt;
2291 jv[jss::Account] = alice.human();
2292 jv[sfXChainBridge.fieldName] = jvb;
2293 jv[sfSignatureReward.fieldName] =
2294 rewardAmount.getJson(JsonOptions::none);
2295 jv[sfMinAccountCreateAmount.fieldName] =
2296 minAccountAmount.getJson(JsonOptions::none);
2297 test(jv, field);
2298 };
2299 auto reward = STAmount{sfSignatureReward, mpt};
2300 auto minAmount = STAmount{sfMinAccountCreateAmount, USD(10)};
2301 for (SField const& field :
2302 {std::ref(sfSignatureReward),
2303 std::ref(sfMinAccountCreateAmount)})
2304 {
2305 bridgeTx(
2306 jss::XChainCreateBridge,
2307 reward,
2308 minAmount,
2309 field.fieldName);
2310 bridgeTx(
2311 jss::XChainModifyBridge,
2312 reward,
2313 minAmount,
2314 field.fieldName);
2315 reward = STAmount{sfSignatureReward, USD(10)};
2316 minAmount = STAmount{sfMinAccountCreateAmount, mpt};
2317 }
2318 }
2319 BEAST_EXPECT(txWithAmounts.empty());
2320 }
2321
2322 void
2324 {
2325 // checks synthetically injected mptissuanceid from `tx` response
2326 testcase("Test synthetic fields from tx response");
2327
2328 using namespace test::jtx;
2329
2330 Account const alice{"alice"};
2331
2332 auto cfg = envconfig();
2333 cfg->FEES.reference_fee = 10;
2334 Env env{*this, std::move(cfg), features};
2335 MPTTester mptAlice(env, alice);
2336
2337 mptAlice.create();
2338
2339 std::string const txHash{
2340 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
2341 BEAST_EXPECTS(
2342 txHash ==
2343 "E11F0E0CA14219922B7881F060B9CEE67CFBC87E4049A441ED2AE348FF8FAC"
2344 "0E",
2345 txHash);
2346 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
2347 auto const id = meta[jss::mpt_issuance_id].asString();
2348 // Expect mpt_issuance_id field
2349 BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
2350 BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
2351 BEAST_EXPECTS(
2352 id == "00000004AE123A8556F3CF91154711376AFB0F894F832B3D", id);
2353 }
2354
2355 void
2357 {
2358 testcase("MPT clawback validations");
2359 using namespace test::jtx;
2360
2361 // Make sure clawback cannot work when featureMPTokensV1 is disabled
2362 {
2363 Env env(*this, features - featureMPTokensV1);
2364 Account const alice{"alice"};
2365 Account const bob{"bob"};
2366
2367 env.fund(XRP(1000), alice, bob);
2368 env.close();
2369
2370 auto const USD = alice["USD"];
2371 auto const mpt = ripple::test::jtx::MPT(
2372 alice.name(), makeMptID(env.seq(alice), alice));
2373
2374 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
2375 env.close();
2376
2377 env(claw(alice, mpt(5)), ter(temDISABLED));
2378 env.close();
2379
2380 env(claw(alice, mpt(5), bob), ter(temDISABLED));
2381 env.close();
2382 }
2383
2384 // Test preflight
2385 {
2386 Env env(*this, features);
2387 Account const alice{"alice"};
2388 Account const bob{"bob"};
2389
2390 env.fund(XRP(1000), alice, bob);
2391 env.close();
2392
2393 auto const USD = alice["USD"];
2394 auto const mpt = ripple::test::jtx::MPT(
2395 alice.name(), makeMptID(env.seq(alice), alice));
2396
2397 // clawing back IOU from a MPT holder fails
2398 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
2399 env.close();
2400
2401 // clawing back MPT without specifying a holder fails
2402 env(claw(alice, mpt(5)), ter(temMALFORMED));
2403 env.close();
2404
2405 // clawing back zero amount fails
2406 env(claw(alice, mpt(0), bob), ter(temBAD_AMOUNT));
2407 env.close();
2408
2409 // alice can't claw back from herself
2410 env(claw(alice, mpt(5), alice), ter(temMALFORMED));
2411 env.close();
2412
2413 // can't clawback negative amount
2414 env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT));
2415 env.close();
2416 }
2417
2418 // Preclaim - clawback fails when MPTCanClawback is disabled on issuance
2419 {
2420 Env env(*this, features);
2421 Account const alice{"alice"};
2422 Account const bob{"bob"};
2423
2424 MPTTester mptAlice(env, alice, {.holders = {bob}});
2425
2426 // enable asfAllowTrustLineClawback for alice
2427 env(fset(alice, asfAllowTrustLineClawback));
2428 env.close();
2430
2431 // Create issuance without enabling clawback
2432 mptAlice.create({.ownerCount = 1, .holderCount = 0});
2433
2434 mptAlice.authorize({.account = bob});
2435
2436 mptAlice.pay(alice, bob, 100);
2437
2438 // alice cannot clawback before she didn't enable MPTCanClawback
2439 // asfAllowTrustLineClawback has no effect
2440 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
2441 }
2442
2443 // Preclaim - test various scenarios
2444 {
2445 Env env(*this, features);
2446 Account const alice{"alice"};
2447 Account const bob{"bob"};
2448 Account const carol{"carol"};
2449 env.fund(XRP(1000), carol);
2450 env.close();
2451 MPTTester mptAlice(env, alice, {.holders = {bob}});
2452
2453 auto const fakeMpt = ripple::test::jtx::MPT(
2454 alice.name(), makeMptID(env.seq(alice), alice));
2455
2456 // issuer tries to clawback MPT where issuance doesn't exist
2457 env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND));
2458 env.close();
2459
2460 // alice creates issuance
2461 mptAlice.create(
2462 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2463
2464 // alice tries to clawback from someone who doesn't have MPToken
2465 mptAlice.claw(alice, bob, 1, tecOBJECT_NOT_FOUND);
2466
2467 // bob creates a MPToken
2468 mptAlice.authorize({.account = bob});
2469
2470 // clawback fails because bob currently has a balance of zero
2471 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2472
2473 // alice pays bob 100 tokens
2474 mptAlice.pay(alice, bob, 100);
2475
2476 // carol fails tries to clawback from bob because he is not the
2477 // issuer
2478 mptAlice.claw(carol, bob, 1, tecNO_PERMISSION);
2479 }
2480
2481 // clawback more than max amount
2482 // fails in the json parser before
2483 // transactor is called
2484 {
2485 Env env(*this, features);
2486 Account const alice{"alice"};
2487 Account const bob{"bob"};
2488
2489 env.fund(XRP(1000), alice, bob);
2490 env.close();
2491
2492 auto const mpt = ripple::test::jtx::MPT(
2493 alice.name(), makeMptID(env.seq(alice), alice));
2494
2495 Json::Value jv = claw(alice, mpt(1), bob);
2496 jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
2497 Json::Value jv1;
2498 jv1[jss::secret] = alice.name();
2499 jv1[jss::tx_json] = jv;
2500 auto const jrr = env.rpc("json", "submit", to_string(jv1));
2501 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2502 }
2503 }
2504
2505 void
2507 {
2508 testcase("MPT Clawback");
2509 using namespace test::jtx;
2510
2511 {
2512 Env env(*this, features);
2513 Account const alice{"alice"};
2514 Account const bob{"bob"};
2515
2516 MPTTester mptAlice(env, alice, {.holders = {bob}});
2517
2518 // alice creates issuance
2519 mptAlice.create(
2520 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2521
2522 // bob creates a MPToken
2523 mptAlice.authorize({.account = bob});
2524
2525 // alice pays bob 100 tokens
2526 mptAlice.pay(alice, bob, 100);
2527
2528 mptAlice.claw(alice, bob, 1);
2529
2530 mptAlice.claw(alice, bob, 1000);
2531
2532 // clawback fails because bob currently has a balance of zero
2533 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2534 }
2535
2536 // Test that globally locked funds can be clawed
2537 {
2538 Env env(*this, features);
2539 Account const alice{"alice"};
2540 Account const bob{"bob"};
2541
2542 MPTTester mptAlice(env, alice, {.holders = {bob}});
2543
2544 // alice creates issuance
2545 mptAlice.create(
2546 {.ownerCount = 1,
2547 .holderCount = 0,
2548 .flags = tfMPTCanLock | tfMPTCanClawback});
2549
2550 // bob creates a MPToken
2551 mptAlice.authorize({.account = bob});
2552
2553 // alice pays bob 100 tokens
2554 mptAlice.pay(alice, bob, 100);
2555
2556 mptAlice.set({.account = alice, .flags = tfMPTLock});
2557
2558 mptAlice.claw(alice, bob, 100);
2559 }
2560
2561 // Test that individually locked funds can be clawed
2562 {
2563 Env env(*this, features);
2564 Account const alice{"alice"};
2565 Account const bob{"bob"};
2566
2567 MPTTester mptAlice(env, alice, {.holders = {bob}});
2568
2569 // alice creates issuance
2570 mptAlice.create(
2571 {.ownerCount = 1,
2572 .holderCount = 0,
2573 .flags = tfMPTCanLock | tfMPTCanClawback});
2574
2575 // bob creates a MPToken
2576 mptAlice.authorize({.account = bob});
2577
2578 // alice pays bob 100 tokens
2579 mptAlice.pay(alice, bob, 100);
2580
2581 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
2582
2583 mptAlice.claw(alice, bob, 100);
2584 }
2585
2586 // Test that unauthorized funds can be clawed back
2587 {
2588 Env env(*this, features);
2589 Account const alice{"alice"};
2590 Account const bob{"bob"};
2591
2592 MPTTester mptAlice(env, alice, {.holders = {bob}});
2593
2594 // alice creates issuance
2595 mptAlice.create(
2596 {.ownerCount = 1,
2597 .holderCount = 0,
2599
2600 // bob creates a MPToken
2601 mptAlice.authorize({.account = bob});
2602
2603 // alice authorizes bob
2604 mptAlice.authorize({.account = alice, .holder = bob});
2605
2606 // alice pays bob 100 tokens
2607 mptAlice.pay(alice, bob, 100);
2608
2609 // alice unauthorizes bob
2610 mptAlice.authorize(
2611 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
2612
2613 mptAlice.claw(alice, bob, 100);
2614 }
2615 }
2616
2617 void
2619 {
2620 using namespace test::jtx;
2621 testcase("Tokens Equality");
2622 Currency const cur1{to_currency("CU1")};
2623 Currency const cur2{to_currency("CU2")};
2624 Account const gw1{"gw1"};
2625 Account const gw2{"gw2"};
2626 MPTID const mpt1 = makeMptID(1, gw1);
2627 MPTID const mpt1a = makeMptID(1, gw1);
2628 MPTID const mpt2 = makeMptID(1, gw2);
2629 MPTID const mpt3 = makeMptID(2, gw2);
2630 Asset const assetCur1Gw1{Issue{cur1, gw1}};
2631 Asset const assetCur1Gw1a{Issue{cur1, gw1}};
2632 Asset const assetCur2Gw1{Issue{cur2, gw1}};
2633 Asset const assetCur2Gw2{Issue{cur2, gw2}};
2634 Asset const assetMpt1Gw1{mpt1};
2635 Asset const assetMpt1Gw1a{mpt1a};
2636 Asset const assetMpt1Gw2{mpt2};
2637 Asset const assetMpt2Gw2{mpt3};
2638
2639 // Assets holding Issue
2640 // Currencies are equal regardless of the issuer
2641 BEAST_EXPECT(equalTokens(assetCur1Gw1, assetCur1Gw1a));
2642 BEAST_EXPECT(equalTokens(assetCur2Gw1, assetCur2Gw2));
2643 // Currencies are different regardless of whether the issuers
2644 // are the same or not
2645 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw1));
2646 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw2));
2647
2648 // Assets holding MPTIssue
2649 // MPTIDs are the same if the sequence and the issuer are the same
2650 BEAST_EXPECT(equalTokens(assetMpt1Gw1, assetMpt1Gw1a));
2651 // MPTIDs are different if sequence and the issuer don't match
2652 BEAST_EXPECT(!equalTokens(assetMpt1Gw1, assetMpt1Gw2));
2653 BEAST_EXPECT(!equalTokens(assetMpt1Gw2, assetMpt2Gw2));
2654
2655 // Assets holding Issue and MPTIssue
2656 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetMpt1Gw1));
2657 BEAST_EXPECT(!equalTokens(assetMpt2Gw2, assetCur2Gw2));
2658 }
2659
2660 void
2662 {
2663 using namespace test::jtx;
2664 Account const gw{"gw"};
2665 Asset const asset1{makeMptID(1, gw)};
2666 Asset const asset2{makeMptID(2, gw)};
2667 Asset const asset3{makeMptID(3, gw)};
2668 STAmount const amt1{asset1, 100};
2669 STAmount const amt2{asset2, 100};
2670 STAmount const amt3{asset3, 10'000};
2671
2672 {
2673 testcase("Test STAmount MPT arithmetics");
2674 using namespace std::string_literals;
2675 STAmount res = multiply(amt1, amt2, asset3);
2676 BEAST_EXPECT(res == amt3);
2677
2678 res = mulRound(amt1, amt2, asset3, true);
2679 BEAST_EXPECT(res == amt3);
2680
2681 res = mulRoundStrict(amt1, amt2, asset3, true);
2682 BEAST_EXPECT(res == amt3);
2683
2684 // overflow, any value > 3037000499ull
2685 STAmount mptOverflow{asset2, UINT64_C(3037000500)};
2686 try
2687 {
2688 res = multiply(mptOverflow, mptOverflow, asset3);
2689 fail("should throw runtime exception 1");
2690 }
2691 catch (std::runtime_error const& e)
2692 {
2693 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2694 }
2695 // overflow, (v1 >> 32) * v2 > 2147483648ull
2696 mptOverflow = STAmount{asset2, UINT64_C(2147483648)};
2697 uint64_t const mantissa = (2ull << 32) + 2;
2698 try
2699 {
2700 res = multiply(STAmount{asset1, mantissa}, mptOverflow, asset3);
2701 fail("should throw runtime exception 2");
2702 }
2703 catch (std::runtime_error const& e)
2704 {
2705 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2706 }
2707 }
2708
2709 {
2710 testcase("Test MPTAmount arithmetics");
2711 MPTAmount mptAmt1{100};
2712 MPTAmount const mptAmt2{100};
2713 BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200});
2714 BEAST_EXPECT(mptAmt1 == 200);
2715 BEAST_EXPECT((mptAmt1 -= mptAmt2) == mptAmt1);
2716 BEAST_EXPECT(mptAmt1 == mptAmt2);
2717 BEAST_EXPECT(mptAmt1 == 100);
2718 BEAST_EXPECT(MPTAmount::minPositiveAmount() == MPTAmount{1});
2719 }
2720
2721 {
2722 testcase("Test MPTIssue from/to Json");
2723 MPTIssue const issue1{asset1.get<MPTIssue>()};
2724 Json::Value const jv = to_json(issue1);
2725 BEAST_EXPECT(
2726 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2727 BEAST_EXPECT(issue1 == mptIssueFromJson(jv));
2728 }
2729
2730 {
2731 testcase("Test Asset from/to Json");
2732 Json::Value const jv = to_json(asset1);
2733 BEAST_EXPECT(
2734 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2735 BEAST_EXPECT(
2736 to_string(jv) ==
2737 "{\"mpt_issuance_id\":"
2738 "\"00000001A407AF5856CCF3C42619DAA925813FC955C72983\"}");
2739 BEAST_EXPECT(asset1 == assetFromJson(jv));
2740 }
2741 }
2742
2743 void
2745 {
2746 testcase("invalid MPTokenIssuanceCreate for DynamicMPT");
2747
2748 using namespace test::jtx;
2749 Account const alice("alice");
2750
2751 // Can not provide MutableFlags when DynamicMPT amendment is not enabled
2752 {
2753 Env env{*this, features - featureDynamicMPT};
2754 MPTTester mptAlice(env, alice);
2755 mptAlice.create(
2756 {.ownerCount = 0, .mutableFlags = 2, .err = temDISABLED});
2757 mptAlice.create(
2758 {.ownerCount = 0, .mutableFlags = 0, .err = temDISABLED});
2759 }
2760
2761 // MutableFlags contains invalid values
2762 {
2763 Env env{*this, features};
2764 MPTTester mptAlice(env, alice);
2765
2766 // Value 1 is reserved for MPT lock.
2767 mptAlice.create(
2768 {.ownerCount = 0, .mutableFlags = 1, .err = temINVALID_FLAG});
2769 mptAlice.create(
2770 {.ownerCount = 0, .mutableFlags = 17, .err = temINVALID_FLAG});
2771 mptAlice.create(
2772 {.ownerCount = 0,
2773 .mutableFlags = 65535,
2774 .err = temINVALID_FLAG});
2775
2776 // MutableFlags can not be 0
2777 mptAlice.create(
2778 {.ownerCount = 0, .mutableFlags = 0, .err = temINVALID_FLAG});
2779 }
2780 }
2781
2782 void
2784 {
2785 testcase("invalid MPTokenIssuanceSet for DynamicMPT");
2786
2787 using namespace test::jtx;
2788 Account const alice("alice");
2789 Account const bob("bob");
2790
2791 // Can not provide MutableFlags, MPTokenMetadata or TransferFee when
2792 // DynamicMPT amendment is not enabled
2793 {
2794 Env env{*this, features - featureDynamicMPT};
2795 MPTTester mptAlice(env, alice, {.holders = {bob}});
2796 auto const mptID = makeMptID(env.seq(alice), alice);
2797
2798 // MutableFlags is not allowed when DynamicMPT is not enabled
2799 mptAlice.set(
2800 {.account = alice,
2801 .id = mptID,
2802 .mutableFlags = 2,
2803 .err = temDISABLED});
2804 mptAlice.set(
2805 {.account = alice,
2806 .id = mptID,
2807 .mutableFlags = 0,
2808 .err = temDISABLED});
2809
2810 // MPTokenMetadata is not allowed when DynamicMPT is not enabled
2811 mptAlice.set(
2812 {.account = alice,
2813 .id = mptID,
2814 .metadata = "test",
2815 .err = temDISABLED});
2816 mptAlice.set(
2817 {.account = alice,
2818 .id = mptID,
2819 .metadata = "",
2820 .err = temDISABLED});
2821
2822 // TransferFee is not allowed when DynamicMPT is not enabled
2823 mptAlice.set(
2824 {.account = alice,
2825 .id = mptID,
2826 .transferFee = 100,
2827 .err = temDISABLED});
2828 mptAlice.set(
2829 {.account = alice,
2830 .id = mptID,
2831 .transferFee = 0,
2832 .err = temDISABLED});
2833 }
2834
2835 // Can not provide holder when MutableFlags, MPTokenMetadata or
2836 // TransferFee is present
2837 {
2838 Env env{*this, features};
2839 MPTTester mptAlice(env, alice, {.holders = {bob}});
2840 auto const mptID = makeMptID(env.seq(alice), alice);
2841
2842 // Holder is not allowed when MutableFlags is present
2843 mptAlice.set(
2844 {.account = alice,
2845 .holder = bob,
2846 .id = mptID,
2847 .mutableFlags = 2,
2848 .err = temMALFORMED});
2849
2850 // Holder is not allowed when MPTokenMetadata is present
2851 mptAlice.set(
2852 {.account = alice,
2853 .holder = bob,
2854 .id = mptID,
2855 .metadata = "test",
2856 .err = temMALFORMED});
2857
2858 // Holder is not allowed when TransferFee is present
2859 mptAlice.set(
2860 {.account = alice,
2861 .holder = bob,
2862 .id = mptID,
2863 .transferFee = 100,
2864 .err = temMALFORMED});
2865 }
2866
2867 // Can not set Flags when MutableFlags, MPTokenMetadata or
2868 // TransferFee is present
2869 {
2870 Env env{*this, features};
2871 MPTTester mptAlice(env, alice, {.holders = {bob}});
2872 mptAlice.create(
2873 {.ownerCount = 1,
2874 .mutableFlags = tmfMPTCanMutateMetadata |
2876
2877 // Setting flags is not allowed when MutableFlags is present
2878 mptAlice.set(
2879 {.account = alice,
2880 .flags = tfMPTCanLock,
2881 .mutableFlags = 2,
2882 .err = temMALFORMED});
2883
2884 // Setting flags is not allowed when MPTokenMetadata is present
2885 mptAlice.set(
2886 {.account = alice,
2887 .flags = tfMPTCanLock,
2888 .metadata = "test",
2889 .err = temMALFORMED});
2890
2891 // setting flags is not allowed when TransferFee is present
2892 mptAlice.set(
2893 {.account = alice,
2894 .flags = tfMPTCanLock,
2895 .transferFee = 100,
2896 .err = temMALFORMED});
2897 }
2898
2899 // Flags being 0 or tfFullyCanonicalSig is fine
2900 {
2901 Env env{*this, features};
2902 MPTTester mptAlice(env, alice, {.holders = {bob}});
2903
2904 mptAlice.create(
2905 {.transferFee = 10,
2906 .ownerCount = 1,
2907 .flags = tfMPTCanTransfer,
2908 .mutableFlags =
2910
2911 mptAlice.set(
2912 {.account = alice,
2913 .flags = 0,
2914 .transferFee = 100,
2915 .metadata = "test"});
2916 mptAlice.set(
2917 {.account = alice,
2918 .flags = tfFullyCanonicalSig,
2919 .transferFee = 200,
2920 .metadata = "test2"});
2921 }
2922
2923 // Invalid MutableFlags
2924 {
2925 Env env{*this, features};
2926 MPTTester mptAlice(env, alice, {.holders = {bob}});
2927 auto const mptID = makeMptID(env.seq(alice), alice);
2928
2929 for (auto const flags : {10000, 0, 5000})
2930 {
2931 mptAlice.set(
2932 {.account = alice,
2933 .id = mptID,
2934 .mutableFlags = flags,
2935 .err = temINVALID_FLAG});
2936 }
2937 }
2938
2939 // Can not set and clear the same mutable flag
2940 {
2941 Env env{*this, features};
2942 MPTTester mptAlice(env, alice, {.holders = {bob}});
2943 auto const mptID = makeMptID(env.seq(alice), alice);
2944
2945 auto const flagCombinations = {
2955
2956 for (auto const& mutableFlags : flagCombinations)
2957 {
2958 mptAlice.set(
2959 {.account = alice,
2960 .id = mptID,
2961 .mutableFlags = mutableFlags,
2962 .err = temINVALID_FLAG});
2963 }
2964 }
2965
2966 // Can not mutate flag which is not mutable
2967 {
2968 Env env{*this, features};
2969 MPTTester mptAlice(env, alice, {.holders = {bob}});
2970
2971 mptAlice.create({.ownerCount = 1});
2972
2973 auto const mutableFlags = {
2986
2987 for (auto const& mutableFlag : mutableFlags)
2988 {
2989 mptAlice.set(
2990 {.account = alice,
2991 .mutableFlags = mutableFlag,
2992 .err = tecNO_PERMISSION});
2993 }
2994 }
2995
2996 // Metadata exceeding max length
2997 {
2998 Env env{*this, features};
2999 MPTTester mptAlice(env, alice, {.holders = {bob}});
3000
3001 mptAlice.create(
3002 {.ownerCount = 1, .mutableFlags = tmfMPTCanMutateMetadata});
3003
3004 std::string metadata(maxMPTokenMetadataLength + 1, 'a');
3005 mptAlice.set(
3006 {.account = alice, .metadata = metadata, .err = temMALFORMED});
3007 }
3008
3009 // Can not mutate metadata when it is not mutable
3010 {
3011 Env env{*this, features};
3012 MPTTester mptAlice(env, alice, {.holders = {bob}});
3013
3014 mptAlice.create({.ownerCount = 1});
3015 mptAlice.set(
3016 {.account = alice,
3017 .metadata = "test",
3018 .err = tecNO_PERMISSION});
3019 }
3020
3021 // Transfer fee exceeding the max value
3022 {
3023 Env env{*this, features};
3024 MPTTester mptAlice(env, alice, {.holders = {bob}});
3025 auto const mptID = makeMptID(env.seq(alice), alice);
3026
3027 mptAlice.create(
3028 {.ownerCount = 1, .mutableFlags = tmfMPTCanMutateTransferFee});
3029
3030 mptAlice.set(
3031 {.account = alice,
3032 .id = mptID,
3033 .transferFee = maxTransferFee + 1,
3034 .err = temBAD_TRANSFER_FEE});
3035 }
3036
3037 // Test setting non-zero transfer fee and clearing MPTCanTransfer at the
3038 // same time
3039 {
3040 Env env{*this, features};
3041 MPTTester mptAlice(env, alice, {.holders = {bob}});
3042
3043 mptAlice.create(
3044 {.transferFee = 100,
3045 .ownerCount = 1,
3046 .flags = tfMPTCanTransfer,
3047 .mutableFlags =
3049
3050 // Can not set non-zero transfer fee and clear MPTCanTransfer at the
3051 // same time
3052 mptAlice.set(
3053 {.account = alice,
3054 .mutableFlags = tmfMPTClearCanTransfer,
3055 .transferFee = 1,
3056 .err = temMALFORMED});
3057
3058 // Can set transfer fee to zero and clear MPTCanTransfer at the same
3059 // time. tfMPTCanTransfer will be cleared and TransferFee field will
3060 // be removed.
3061 mptAlice.set(
3062 {.account = alice,
3063 .mutableFlags = tmfMPTClearCanTransfer,
3064 .transferFee = 0});
3065 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3066 }
3067
3068 // Can not set non-zero transfer fee when MPTCanTransfer is not set
3069 {
3070 Env env{*this, features};
3071 MPTTester mptAlice(env, alice, {.holders = {bob}});
3072
3073 mptAlice.create(
3074 {.ownerCount = 1,
3075 .mutableFlags =
3077
3078 mptAlice.set(
3079 {.account = alice,
3080 .transferFee = 100,
3081 .err = tecNO_PERMISSION});
3082
3083 // Can not set transfer fee even when trying to set MPTCanTransfer
3084 // at the same time. MPTCanTransfer must be set first, then transfer
3085 // fee can be set in a separate transaction.
3086 mptAlice.set(
3087 {.account = alice,
3088 .mutableFlags = tmfMPTSetCanTransfer,
3089 .transferFee = 100,
3090 .err = tecNO_PERMISSION});
3091 }
3092
3093 // Can not mutate transfer fee when it is not mutable
3094 {
3095 Env env{*this, features};
3096 MPTTester mptAlice(env, alice, {.holders = {bob}});
3097
3098 mptAlice.create(
3099 {.transferFee = 10,
3100 .ownerCount = 1,
3101 .flags = tfMPTCanTransfer});
3102
3103 mptAlice.set(
3104 {.account = alice,
3105 .transferFee = 100,
3106 .err = tecNO_PERMISSION});
3107
3108 mptAlice.set(
3109 {.account = alice, .transferFee = 0, .err = tecNO_PERMISSION});
3110 }
3111
3112 // Set some flags mutable. Can not mutate the others
3113 {
3114 Env env{*this, features};
3115 MPTTester mptAlice(env, alice, {.holders = {bob}});
3116
3117 mptAlice.create(
3118 {.ownerCount = 1,
3119 .mutableFlags = tmfMPTCanMutateCanTrade |
3121
3122 // Can not mutate transfer fee
3123 mptAlice.set(
3124 {.account = alice,
3125 .transferFee = 100,
3126 .err = tecNO_PERMISSION});
3127
3128 auto const invalidFlags = {
3137
3138 // Can not mutate flags which are not mutable
3139 for (auto const& mutableFlag : invalidFlags)
3140 {
3141 mptAlice.set(
3142 {.account = alice,
3143 .mutableFlags = mutableFlag,
3144 .err = tecNO_PERMISSION});
3145 }
3146
3147 // Can mutate MPTCanTrade
3148 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade});
3149 mptAlice.set(
3150 {.account = alice, .mutableFlags = tmfMPTClearCanTrade});
3151
3152 // Can mutate MPTCanTransfer
3153 mptAlice.set(
3154 {.account = alice, .mutableFlags = tmfMPTSetCanTransfer});
3155 mptAlice.set(
3156 {.account = alice, .mutableFlags = tmfMPTClearCanTransfer});
3157
3158 // Can mutate metadata
3159 mptAlice.set({.account = alice, .metadata = "test"});
3160 mptAlice.set({.account = alice, .metadata = ""});
3161 }
3162 }
3163
3164 void
3166 {
3167 testcase("Mutate MPT");
3168 using namespace test::jtx;
3169
3170 Account const alice("alice");
3171
3172 // Mutate metadata
3173 {
3174 Env env{*this, features};
3175 MPTTester mptAlice(env, alice);
3176 mptAlice.create(
3177 {.metadata = "test",
3178 .ownerCount = 1,
3179 .mutableFlags = tmfMPTCanMutateMetadata});
3180
3181 std::vector<std::string> metadatas = {
3182 "mutate metadata",
3183 "mutate metadata 2",
3184 "mutate metadata 3",
3185 "mutate metadata 3",
3186 "test",
3187 "mutate metadata"};
3188
3189 for (auto const& metadata : metadatas)
3190 {
3191 mptAlice.set({.account = alice, .metadata = metadata});
3192 BEAST_EXPECT(mptAlice.checkMetadata(metadata));
3193 }
3194
3195 // Metadata being empty will remove the field
3196 mptAlice.set({.account = alice, .metadata = ""});
3197 BEAST_EXPECT(!mptAlice.isMetadataPresent());
3198 }
3199
3200 // Mutate transfer fee
3201 {
3202 Env env{*this, features};
3203 MPTTester mptAlice(env, alice);
3204 mptAlice.create(
3205 {.transferFee = 100,
3206 .metadata = "test",
3207 .ownerCount = 1,
3208 .flags = tfMPTCanTransfer,
3209 .mutableFlags = tmfMPTCanMutateTransferFee});
3210
3212 1, 10, 100, 200, 500, 1000, maxTransferFee})
3213 {
3214 mptAlice.set({.account = alice, .transferFee = fee});
3215 BEAST_EXPECT(mptAlice.checkTransferFee(fee));
3216 }
3217
3218 // Setting TransferFee to zero will remove the field
3219 mptAlice.set({.account = alice, .transferFee = 0});
3220 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3221
3222 // Set transfer fee again
3223 mptAlice.set({.account = alice, .transferFee = 10});
3224 BEAST_EXPECT(mptAlice.checkTransferFee(10));
3225 }
3226
3227 // Test flag toggling
3228 {
3229 auto testFlagToggle = [&](std::uint32_t createFlags,
3230 std::uint32_t setFlags,
3231 std::uint32_t clearFlags) {
3232 Env env{*this, features};
3233 MPTTester mptAlice(env, alice);
3234
3235 // Create the MPT object with the specified initial flags
3236 mptAlice.create(
3237 {.metadata = "test",
3238 .ownerCount = 1,
3239 .mutableFlags = createFlags});
3240
3241 // Set and clear the flag multiple times
3242 mptAlice.set({.account = alice, .mutableFlags = setFlags});
3243 mptAlice.set({.account = alice, .mutableFlags = clearFlags});
3244 mptAlice.set({.account = alice, .mutableFlags = clearFlags});
3245 mptAlice.set({.account = alice, .mutableFlags = setFlags});
3246 mptAlice.set({.account = alice, .mutableFlags = setFlags});
3247 mptAlice.set({.account = alice, .mutableFlags = clearFlags});
3248 mptAlice.set({.account = alice, .mutableFlags = setFlags});
3249 mptAlice.set({.account = alice, .mutableFlags = clearFlags});
3250 };
3251
3252 testFlagToggle(
3254 testFlagToggle(
3258 testFlagToggle(
3262 testFlagToggle(
3266 testFlagToggle(
3270 testFlagToggle(
3274 }
3275 }
3276
3277 void
3279 {
3280 testcase("Mutate MPTCanLock");
3281 using namespace test::jtx;
3282
3283 Account const alice("alice");
3284 Account const bob("bob");
3285
3286 // Individual lock
3287 {
3288 Env env{*this, features};
3289 MPTTester mptAlice(env, alice, {.holders = {bob}});
3290 mptAlice.create(
3291 {.ownerCount = 1,
3292 .holderCount = 0,
3293 .flags = tfMPTCanLock | tfMPTCanTransfer,
3294 .mutableFlags = tmfMPTCanMutateCanLock |
3296 mptAlice.authorize({.account = bob, .holderCount = 1});
3297
3298 // Lock bob's mptoken
3299 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
3300
3301 // Can mutate the mutable flags and fields
3302 mptAlice.set(
3303 {.account = alice, .mutableFlags = tmfMPTClearCanLock});
3304 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
3305 mptAlice.set(
3306 {.account = alice, .mutableFlags = tmfMPTClearCanLock});
3307 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade});
3308 mptAlice.set(
3309 {.account = alice, .mutableFlags = tmfMPTClearCanTrade});
3310 mptAlice.set({.account = alice, .transferFee = 200});
3311 }
3312
3313 // Global lock
3314 {
3315 Env env{*this, features};
3316 MPTTester mptAlice(env, alice, {.holders = {bob}});
3317 mptAlice.create(
3318 {.ownerCount = 1,
3319 .holderCount = 0,
3320 .flags = tfMPTCanLock,
3321 .mutableFlags = tmfMPTCanMutateCanLock |
3323 mptAlice.authorize({.account = bob, .holderCount = 1});
3324
3325 // Lock issuance
3326 mptAlice.set({.account = alice, .flags = tfMPTLock});
3327
3328 // Can mutate the mutable flags and fields
3329 mptAlice.set(
3330 {.account = alice, .mutableFlags = tmfMPTClearCanLock});
3331 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
3332 mptAlice.set(
3333 {.account = alice, .mutableFlags = tmfMPTClearCanLock});
3334 mptAlice.set(
3335 {.account = alice, .mutableFlags = tmfMPTSetCanClawback});
3336 mptAlice.set(
3337 {.account = alice, .mutableFlags = tmfMPTClearCanClawback});
3338 mptAlice.set({.account = alice, .metadata = "mutate"});
3339 }
3340
3341 // Test lock and unlock after mutating MPTCanLock
3342 {
3343 Env env{*this, features};
3344 MPTTester mptAlice(env, alice, {.holders = {bob}});
3345 mptAlice.create(
3346 {.ownerCount = 1,
3347 .holderCount = 0,
3348 .flags = tfMPTCanLock,
3349 .mutableFlags = tmfMPTCanMutateCanLock |
3351 mptAlice.authorize({.account = bob, .holderCount = 1});
3352
3353 // Can lock and unlock
3354 mptAlice.set({.account = alice, .flags = tfMPTLock});
3355 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
3356 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
3357 mptAlice.set(
3358 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
3359
3360 // Clear lsfMPTCanLock
3361 mptAlice.set(
3362 {.account = alice, .mutableFlags = tmfMPTClearCanLock});
3363
3364 // Can not lock or unlock
3365 mptAlice.set(
3366 {.account = alice,
3367 .flags = tfMPTLock,
3368 .err = tecNO_PERMISSION});
3369 mptAlice.set(
3370 {.account = alice,
3371 .flags = tfMPTUnlock,
3372 .err = tecNO_PERMISSION});
3373 mptAlice.set(
3374 {.account = alice,
3375 .holder = bob,
3376 .flags = tfMPTLock,
3377 .err = tecNO_PERMISSION});
3378 mptAlice.set(
3379 {.account = alice,
3380 .holder = bob,
3381 .flags = tfMPTUnlock,
3382 .err = tecNO_PERMISSION});
3383
3384 // Set MPTCanLock again
3385 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
3386
3387 // Can lock and unlock again
3388 mptAlice.set({.account = alice, .flags = tfMPTLock});
3389 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
3390 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
3391 mptAlice.set(
3392 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
3393 }
3394 }
3395
3396 void
3398 {
3399 testcase("Mutate MPTRequireAuth");
3400 using namespace test::jtx;
3401
3402 Env env{*this, features};
3403 Account const alice("alice");
3404 Account const bob("bob");
3405
3406 MPTTester mptAlice(env, alice, {.holders = {bob}});
3407 mptAlice.create(
3408 {.ownerCount = 1,
3409 .flags = tfMPTRequireAuth,
3410 .mutableFlags = tmfMPTCanMutateRequireAuth});
3411
3412 mptAlice.authorize({.account = bob});
3413 mptAlice.authorize({.account = alice, .holder = bob});
3414
3415 // Pay to bob
3416 mptAlice.pay(alice, bob, 1000);
3417
3418 // Unauthorize bob
3419 mptAlice.authorize(
3420 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
3421
3422 // Can not pay to bob
3423 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
3424
3425 // Clear RequireAuth
3426 mptAlice.set(
3427 {.account = alice, .mutableFlags = tmfMPTClearRequireAuth});
3428
3429 // Can pay to bob
3430 mptAlice.pay(alice, bob, 1000);
3431
3432 // Set RequireAuth again
3433 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth});
3434
3435 // Can not pay to bob since he is not authorized
3436 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
3437
3438 // Authorize bob again
3439 mptAlice.authorize({.account = alice, .holder = bob});
3440
3441 // Can pay to bob again
3442 mptAlice.pay(alice, bob, 100);
3443 }
3444
3445 void
3447 {
3448 testcase("Mutate MPTCanEscrow");
3449 using namespace test::jtx;
3450 using namespace std::literals;
3451
3452 Env env{*this, features};
3453 auto const baseFee = env.current()->fees().base;
3454 auto const alice = Account("alice");
3455 auto const bob = Account("bob");
3456 auto const carol = Account("carol");
3457
3458 MPTTester mptAlice(env, alice, {.holders = {carol, bob}});
3459 mptAlice.create(
3460 {.ownerCount = 1,
3461 .holderCount = 0,
3462 .flags = tfMPTCanTransfer,
3463 .mutableFlags = tmfMPTCanMutateCanEscrow});
3464 mptAlice.authorize({.account = carol});
3465 mptAlice.authorize({.account = bob});
3466
3467 auto const MPT = mptAlice["MPT"];
3468 env(pay(alice, carol, MPT(10'000)));
3469 env(pay(alice, bob, MPT(10'000)));
3470 env.close();
3471
3472 // MPTCanEscrow is not enabled
3473 env(escrow::create(carol, bob, MPT(3)),
3475 escrow::finish_time(env.now() + 1s),
3476 fee(baseFee * 150),
3478
3479 // MPTCanEscrow is enabled now
3480 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanEscrow});
3481 env(escrow::create(carol, bob, MPT(3)),
3483 escrow::finish_time(env.now() + 1s),
3484 fee(baseFee * 150));
3485
3486 // Clear MPTCanEscrow
3487 mptAlice.set({.account = alice, .mutableFlags = tmfMPTClearCanEscrow});
3488 env(escrow::create(carol, bob, MPT(3)),
3490 escrow::finish_time(env.now() + 1s),
3491 fee(baseFee * 150),
3493 }
3494
3495 void
3497 {
3498 testcase("Mutate MPTCanTransfer");
3499
3500 using namespace test::jtx;
3501 Account const alice("alice");
3502 Account const bob("bob");
3503 Account const carol("carol");
3504
3505 {
3506 Env env{*this, features};
3507
3508 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
3509 mptAlice.create(
3510 {.ownerCount = 1,
3511 .mutableFlags =
3513
3514 mptAlice.authorize({.account = bob});
3515 mptAlice.authorize({.account = carol});
3516
3517 // Pay to bob
3518 mptAlice.pay(alice, bob, 1000);
3519
3520 // Bob can not pay carol since MPTCanTransfer is not set
3521 mptAlice.pay(bob, carol, 50, tecNO_AUTH);
3522
3523 // Can not set non-zero transfer fee when MPTCanTransfer is not set
3524 mptAlice.set(
3525 {.account = alice,
3526 .transferFee = 100,
3527 .err = tecNO_PERMISSION});
3528
3529 // Can not set non-zero transfer fee even when trying to set
3530 // MPTCanTransfer at the same time
3531 mptAlice.set(
3532 {.account = alice,
3533 .mutableFlags = tmfMPTSetCanTransfer,
3534 .transferFee = 100,
3535 .err = tecNO_PERMISSION});
3536
3537 // Alice sets MPTCanTransfer
3538 mptAlice.set(
3539 {.account = alice, .mutableFlags = tmfMPTSetCanTransfer});
3540
3541 // Can set transfer fee now
3542 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3543 mptAlice.set({.account = alice, .transferFee = 100});
3544 BEAST_EXPECT(mptAlice.isTransferFeePresent());
3545
3546 // Bob can pay carol
3547 mptAlice.pay(bob, carol, 50);
3548
3549 // Alice clears MPTCanTransfer
3550 mptAlice.set(
3551 {.account = alice, .mutableFlags = tmfMPTClearCanTransfer});
3552
3553 // TransferFee field is removed when MPTCanTransfer is cleared
3554 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3555
3556 // Bob can not pay
3557 mptAlice.pay(bob, carol, 50, tecNO_AUTH);
3558 }
3559
3560 // Can set transfer fee to zero when MPTCanTransfer is not set, but
3561 // tmfMPTCanMutateTransferFee is set.
3562 {
3563 Env env{*this, features};
3564
3565 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
3566 mptAlice.create(
3567 {.transferFee = 100,
3568 .ownerCount = 1,
3569 .flags = tfMPTCanTransfer,
3570 .mutableFlags =
3572
3573 BEAST_EXPECT(mptAlice.checkTransferFee(100));
3574
3575 // Clear MPTCanTransfer and transfer fee is removed
3576 mptAlice.set(
3577 {.account = alice, .mutableFlags = tmfMPTClearCanTransfer});
3578 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3579
3580 // Can still set transfer fee to zero, although it is already zero
3581 mptAlice.set({.account = alice, .transferFee = 0});
3582
3583 // TransferFee field is still not present
3584 BEAST_EXPECT(!mptAlice.isTransferFeePresent());
3585 }
3586 }
3587
3588 void
3590 {
3591 testcase("Mutate MPTCanClawback");
3592
3593 using namespace test::jtx;
3594 Env env(*this, features);
3595 Account const alice{"alice"};
3596 Account const bob{"bob"};
3597
3598 MPTTester mptAlice(env, alice, {.holders = {bob}});
3599
3600 mptAlice.create(
3601 {.ownerCount = 1,
3602 .holderCount = 0,
3603 .mutableFlags = tmfMPTCanMutateCanClawback});
3604
3605 // Bob creates an MPToken
3606 mptAlice.authorize({.account = bob});
3607
3608 // Alice pays bob 100 tokens
3609 mptAlice.pay(alice, bob, 100);
3610
3611 // MPTCanClawback is not enabled
3612 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
3613
3614 // Enable MPTCanClawback
3615 mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback});
3616
3617 // Can clawback now
3618 mptAlice.claw(alice, bob, 1);
3619
3620 // Clear MPTCanClawback
3621 mptAlice.set(
3622 {.account = alice, .mutableFlags = tmfMPTClearCanClawback});
3623
3624 // Can not clawback
3625 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
3626 }
3627
3628public:
3629 void
3630 run() override
3631 {
3632 using namespace test::jtx;
3634
3635 // MPTokenIssuanceCreate
3636 testCreateValidation(all - featureSingleAssetVault);
3637 testCreateValidation(all - featurePermissionedDomains);
3639 testCreateEnabled(all - featureSingleAssetVault);
3641
3642 // MPTokenIssuanceDestroy
3643 testDestroyValidation(all - featureSingleAssetVault);
3645 testDestroyEnabled(all - featureSingleAssetVault);
3647
3648 // MPTokenAuthorize
3649 testAuthorizeValidation(all - featureSingleAssetVault);
3651 testAuthorizeEnabled(all - featureSingleAssetVault);
3653
3654 // MPTokenIssuanceSet
3655 testSetValidation(all - featureSingleAssetVault - featureDynamicMPT);
3656 testSetValidation(all - featureSingleAssetVault);
3657 testSetValidation(all - featureDynamicMPT);
3658 testSetValidation(all - featurePermissionedDomains);
3660
3661 testSetEnabled(all - featureSingleAssetVault);
3663
3664 // MPT clawback
3667
3668 // Test Direct Payment
3671 testDepositPreauth(all - featureCredentials);
3672
3673 // Test MPT Amount is invalid in Tx, which don't support MPT
3675
3676 // Test parsed MPTokenIssuanceID in API response metadata
3678
3679 // Test tokens equality
3681
3682 // Test helpers
3684
3685 // Dynamic MPT
3694 }
3695};
3696
3697BEAST_DEFINE_TESTSUITE_PRIO(MPToken, app, ripple, 2);
3698
3699} // namespace test
3700} // namespace ripple
Lightweight wrapper to tag static string.
Definition json_value.h:63
Represents a JSON value.
Definition json_value.h:149
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
A currency issued by an account.
Definition Issue.h:33
static MPTAmount minPositiveAmount()
Definition MPTAmount.cpp:63
Slice slice() const noexcept
Definition PublicKey.h:122
Identifies fields.
Definition SField.h:144
Slice slice() const noexcept
Definition Serializer.h:66
static TxFormats const & getInstance()
Definition TxFormats.cpp:71
void run() override
Runs the suite.
void testCreateValidation(FeatureBitset features)
void testMutateCanLock(FeatureBitset features)
void testMutateCanClawback(FeatureBitset features)
void testInvalidSetDynamic(FeatureBitset features)
void testMutateMPT(FeatureBitset features)
void testMutateRequireAuth(FeatureBitset features)
void testClawback(FeatureBitset features)
void testAuthorizeValidation(FeatureBitset features)
void testSetValidation(FeatureBitset features)
void testClawbackValidation(FeatureBitset features)
void testCreateEnabled(FeatureBitset features)
void testDestroyEnabled(FeatureBitset features)
void testMutateCanEscrow(FeatureBitset features)
void testPayment(FeatureBitset features)
void testMPTInvalidInTx(FeatureBitset features)
void testMutateCanTransfer(FeatureBitset features)
void testAuthorizeEnabled(FeatureBitset features)
void testDepositPreauth(FeatureBitset features)
void testDestroyValidation(FeatureBitset features)
void testInvalidCreateDynamic(FeatureBitset features)
void testSetEnabled(FeatureBitset features)
void testTxJsonMetaFields(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition Account.h:39
PublicKey const & pk() const
Return the public key.
Definition Account.h:94
AccountID id() const
Returns the Account ID.
Definition Account.h:111
std::string const & name() const
Return the name.
Definition Account.h:87
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
A transaction testing environment.
Definition Env.h:121
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:268
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:547
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:791
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:150
void set(MPTSet const &set={})
Definition mpt.cpp:223
bool isMetadataPresent() const
Definition mpt.cpp:370
bool checkTransferFee(std::uint16_t transferFee) const
Definition mpt.cpp:378
MPTID const & issuanceID() const
Definition mpt.h:227
bool checkMetadata(std::string const &metadata) const
Definition mpt.cpp:359
bool isTransferFeePresent() const
Definition mpt.cpp:388
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:87
void destroy(MPTDestroy const &arg=MPTDestroy{})
Definition mpt.cpp:121
Converts to MPT Issue or STAmount.
Sets the DeliverMin on a JTx.
Definition delivermin.h:33
Set the fee on a JTx.
Definition fee.h:37
Match set account flags.
Definition flags.h:128
Add a path.
Definition paths.h:58
Sets the SendMax on a JTx.
Definition sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
T empty(T... args)
T erase(T... args)
T insert(T... args)
T is_same_v
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:48
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:62
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
Json::Value unauth(Account const &account, Account const &unauth)
Remove preauthorization for deposit.
Definition deposit.cpp:43
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:54
std::array< std::uint8_t, 39 > const cb1
Definition escrow.h:71
Json::Value create(AccountID const &account, AccountID const &to, STAmount const &amount)
Definition escrow.cpp:33
auto const finish_time
Set the "FinishAfter" time tag on a JTx.
Definition escrow.h:98
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
Json::Value create_account_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, jtx::signer const &signer)
Json::Value claim_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, std::optional< jtx::Account > const &dst, jtx::signer const &signer)
Json::Value bridge(Account const &lockingChainDoor, Issue const &lockingChainIssue, Account const &issuingChainDoor, Issue const &issuingChainIssue)
Json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
Definition trust.cpp:69
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
Json::Value xchain_claim(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, Account const &dst)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:54
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
Json::Value xchain_commit(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, std::optional< Account > const &dst)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:247
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:201
constexpr std::uint32_t const tmfMPTClearCanClawback
Definition TxFlags.h:194
constexpr std::uint32_t const tmfMPTCanMutateMetadata
Definition TxFlags.h:165
constexpr std::uint32_t const tmfMPTClearCanEscrow
Definition TxFlags.h:188
constexpr std::uint32_t const tmfMPTCanMutateCanEscrow
Definition TxFlags.h:161
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:85
constexpr std::uint32_t const tmfMPTCanMutateCanTrade
Definition TxFlags.h:162
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:152
constexpr std::uint32_t const tmfMPTSetCanClawback
Definition TxFlags.h:193
constexpr std::uint32_t const tmfMPTSetRequireAuth
Definition TxFlags.h:185
constexpr std::uint32_t const tmfMPTClearCanTrade
Definition TxFlags.h:190
base_uint< 256 > uint256
Definition base_uint.h:558
Asset assetFromJson(Json::Value const &jv)
Definition Asset.cpp:77
constexpr std::uint32_t const tmfMPTCanMutateTransferFee
Definition TxFlags.h:166
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:151
constexpr std::uint32_t const tfMPTUnlock
Definition TxFlags.h:177
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:116
constexpr std::uint32_t const tmfMPTClearRequireAuth
Definition TxFlags.h:186
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:82
constexpr std::uint32_t const tmfMPTSetCanLock
Definition TxFlags.h:183
constexpr std::uint32_t const tmfMPTCanMutateCanLock
Definition TxFlags.h:159
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:53
constexpr std::uint32_t const tmfMPTSetCanTrade
Definition TxFlags.h:189
MPTIssue mptIssueFromJson(Json::Value const &jv)
Definition MPTIssue.cpp:78
constexpr std::uint32_t const tmfMPTCanMutateCanClawback
Definition TxFlags.h:164
std::size_t constexpr maxMPTokenMetadataLength
The maximum length of MPTokenMetadata.
Definition Protocol.h:113
Json::Value to_json(Asset const &asset)
Definition Asset.h:123
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:172
constexpr std::uint32_t const tmfMPTSetCanEscrow
Definition TxFlags.h:187
constexpr std::uint32_t const tmfMPTClearCanLock
Definition TxFlags.h:184
constexpr std::uint32_t const tmfMPTSetCanTransfer
Definition TxFlags.h:191
@ tecNO_DST
Definition TER.h:290
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecDUPLICATE
Definition TER.h:315
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecNO_PERMISSION
Definition TER.h:305
@ tecHAS_OBLIGATIONS
Definition TER.h:317
@ tecPATH_PARTIAL
Definition TER.h:282
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:300
@ tecLOCKED
Definition TER.h:358
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:176
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:107
@ tesSUCCESS
Definition TER.h:244
constexpr std::uint32_t const tmfMPTCanMutateRequireAuth
Definition TxFlags.h:160
constexpr std::uint32_t const tmfMPTCanMutateCanTransfer
Definition TxFlags.h:163
constexpr std::uint32_t tfLimitQuality
Definition TxFlags.h:109
@ soeMPTNotSupported
Definition SOTemplate.h:43
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
Definition TxFlags.h:60
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:94
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:170
STAmount mulRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:150
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:149
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:148
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:153
constexpr std::uint32_t const tmfMPTClearCanTransfer
Definition TxFlags.h:192
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:84
@ temBAD_AMOUNT
Definition TER.h:89
@ temREDUNDANT
Definition TER.h:112
@ temBAD_TRANSFER_FEE
Definition TER.h:142
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
T ref(T... args)
A signer in a SignerList.
Definition multisign.h:39
T what(T... args)