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