rippled
Loading...
Searching...
No Matches
MPToken_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/credentials.h>
22#include <test/jtx/permissioned_domains.h>
23#include <test/jtx/trust.h>
24#include <test/jtx/xchain_bridge.h>
25
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/beast/utility/Zero.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/TER.h>
30#include <xrpl/protocol/TxFlags.h>
31#include <xrpl/protocol/jss.h>
32
33namespace ripple {
34namespace test {
35
37{
38 void
40 {
41 testcase("Create Validate");
42 using namespace test::jtx;
43 Account const alice("alice");
44
45 // test preflight of MPTokenIssuanceCreate
46 {
47 // If the MPT amendment is not enabled, you should not be able to
48 // create MPTokenIssuances
49 Env env{*this, features - featureMPTokensV1};
50 MPTTester mptAlice(env, alice);
51
52 mptAlice.create({.ownerCount = 0, .err = temDISABLED});
53 }
54
55 // test preflight of MPTokenIssuanceCreate
56 {
57 Env env{*this, features};
58 MPTTester mptAlice(env, alice);
59
60 mptAlice.create({.flags = 0x00000001, .err = temINVALID_FLAG});
61
62 // tries to set a txfee while not enabling in the flag
63 mptAlice.create(
64 {.maxAmt = 100,
65 .assetScale = 0,
66 .transferFee = 1,
67 .metadata = "test",
68 .err = temMALFORMED});
69
70 if (!features[featureSingleAssetVault])
71 {
72 // tries to set DomainID when SAV is disabled
73 mptAlice.create(
74 {.maxAmt = 100,
75 .assetScale = 0,
76 .metadata = "test",
77 .flags = tfMPTRequireAuth,
78 .domainID = uint256(42),
79 .err = temDISABLED});
80 }
81 else if (!features[featurePermissionedDomains])
82 {
83 // tries to set DomainID when PD is disabled
84 mptAlice.create(
85 {.maxAmt = 100,
86 .assetScale = 0,
87 .metadata = "test",
88 .flags = tfMPTRequireAuth,
89 .domainID = uint256(42),
90 .err = temDISABLED});
91 }
92 else
93 {
94 // tries to set DomainID when RequireAuth is not set
95 mptAlice.create(
96 {.maxAmt = 100,
97 .assetScale = 0,
98 .metadata = "test",
99 .domainID = uint256(42),
100 .err = temMALFORMED});
101
102 // tries to set zero DomainID
103 mptAlice.create(
104 {.maxAmt = 100,
105 .assetScale = 0,
106 .metadata = "test",
107 .flags = tfMPTRequireAuth,
108 .domainID = beast::zero,
109 .err = temMALFORMED});
110 }
111
112 // tries to set a txfee greater than max
113 mptAlice.create(
114 {.maxAmt = 100,
115 .assetScale = 0,
116 .transferFee = maxTransferFee + 1,
117 .metadata = "test",
118 .flags = tfMPTCanTransfer,
119 .err = temBAD_TRANSFER_FEE});
120
121 // tries to set a txfee while not enabling transfer
122 mptAlice.create(
123 {.maxAmt = 100,
124 .assetScale = 0,
125 .transferFee = maxTransferFee,
126 .metadata = "test",
127 .err = temMALFORMED});
128
129 // empty metadata returns error
130 mptAlice.create(
131 {.maxAmt = 100,
132 .assetScale = 0,
133 .transferFee = 0,
134 .metadata = "",
135 .err = temMALFORMED});
136
137 // MaximumAmout of 0 returns error
138 mptAlice.create(
139 {.maxAmt = 0,
140 .assetScale = 1,
141 .transferFee = 1,
142 .metadata = "test",
143 .err = temMALFORMED});
144
145 // MaximumAmount larger than 63 bit returns error
146 mptAlice.create(
147 {.maxAmt = 0xFFFF'FFFF'FFFF'FFF0, // 18'446'744'073'709'551'600
148 .assetScale = 0,
149 .transferFee = 0,
150 .metadata = "test",
151 .err = temMALFORMED});
152 mptAlice.create(
153 {.maxAmt = maxMPTokenAmount + 1, // 9'223'372'036'854'775'808
154 .assetScale = 0,
155 .transferFee = 0,
156 .metadata = "test",
157 .err = temMALFORMED});
158 }
159 }
160
161 void
163 {
164 testcase("Create Enabled");
165
166 using namespace test::jtx;
167 Account const alice("alice");
168
169 {
170 // If the MPT amendment IS enabled, you should be able to create
171 // MPTokenIssuances
172 Env env{*this, features};
173 MPTTester mptAlice(env, alice);
174 mptAlice.create(
175 {.maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
176 .assetScale = 1,
177 .transferFee = 10,
178 .metadata = "123",
179 .ownerCount = 1,
182
183 // Get the hash for the most recent transaction.
184 std::string const txHash{
185 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
186
187 Json::Value const result = env.rpc("tx", txHash)[jss::result];
188 BEAST_EXPECT(
189 result[sfMaximumAmount.getJsonName()] == "9223372036854775807");
190 }
191
192 if (features[featureSingleAssetVault])
193 {
194 // Add permissioned domain
195 Account const credIssuer1{"credIssuer1"};
196 std::string const credType = "credential";
197
198 pdomain::Credentials const credentials1{
199 {.issuer = credIssuer1, .credType = credType}};
200
201 {
202 Env env{*this, features};
203 env.fund(XRP(1000), credIssuer1);
204
205 env(pdomain::setTx(credIssuer1, credentials1));
206 auto const domainId1 = [&]() {
207 auto tx = env.tx()->getJson(JsonOptions::none);
208 return pdomain::getNewDomain(env.meta());
209 }();
210
211 MPTTester mptAlice(env, alice);
212 mptAlice.create({
213 .maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
214 .assetScale = 1,
215 .transferFee = 10,
216 .metadata = "123",
217 .ownerCount = 1,
220 .domainID = domainId1,
221 });
222
223 // Get the hash for the most recent transaction.
224 std::string const txHash{
225 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
226
227 Json::Value const result = env.rpc("tx", txHash)[jss::result];
228 BEAST_EXPECT(
229 result[sfMaximumAmount.getJsonName()] ==
230 "9223372036854775807");
231 }
232 }
233 }
234
235 void
237 {
238 testcase("Destroy Validate");
239
240 using namespace test::jtx;
241 Account const alice("alice");
242 Account const bob("bob");
243 // MPTokenIssuanceDestroy (preflight)
244 {
245 Env env{*this, features - featureMPTokensV1};
246 MPTTester mptAlice(env, alice);
247 auto const id = makeMptID(env.seq(alice), alice);
248 mptAlice.destroy({.id = id, .ownerCount = 0, .err = temDISABLED});
249
250 env.enableFeature(featureMPTokensV1);
251
252 mptAlice.destroy(
253 {.id = id, .flags = 0x00000001, .err = temINVALID_FLAG});
254 }
255
256 // MPTokenIssuanceDestroy (preclaim)
257 {
258 Env env{*this, features};
259 MPTTester mptAlice(env, alice, {.holders = {bob}});
260
261 mptAlice.destroy(
262 {.id = makeMptID(env.seq(alice), alice),
263 .ownerCount = 0,
264 .err = tecOBJECT_NOT_FOUND});
265
266 mptAlice.create({.ownerCount = 1});
267
268 // a non-issuer tries to destroy a mptissuance they didn't issue
269 mptAlice.destroy({.issuer = bob, .err = tecNO_PERMISSION});
270
271 // Make sure that issuer can't delete issuance when it still has
272 // outstanding balance
273 {
274 // bob now holds a mptoken object
275 mptAlice.authorize({.account = bob, .holderCount = 1});
276
277 // alice pays bob 100 tokens
278 mptAlice.pay(alice, bob, 100);
279
280 mptAlice.destroy({.err = tecHAS_OBLIGATIONS});
281 }
282 }
283 }
284
285 void
287 {
288 testcase("Destroy Enabled");
289
290 using namespace test::jtx;
291 Account const alice("alice");
292
293 // If the MPT amendment IS enabled, you should be able to destroy
294 // MPTokenIssuances
295 Env env{*this, features};
296 MPTTester mptAlice(env, alice);
297
298 mptAlice.create({.ownerCount = 1});
299
300 mptAlice.destroy({.ownerCount = 0});
301 }
302
303 void
305 {
306 testcase("Validate authorize transaction");
307
308 using namespace test::jtx;
309 Account const alice("alice");
310 Account const bob("bob");
311 Account const cindy("cindy");
312 // Validate amendment enable in MPTokenAuthorize (preflight)
313 {
314 Env env{*this, features - featureMPTokensV1};
315 MPTTester mptAlice(env, alice, {.holders = {bob}});
316
317 mptAlice.authorize(
318 {.account = bob,
319 .id = makeMptID(env.seq(alice), alice),
320 .err = temDISABLED});
321 }
322
323 // Validate fields in MPTokenAuthorize (preflight)
324 {
325 Env env{*this, features};
326 MPTTester mptAlice(env, alice, {.holders = {bob}});
327
328 mptAlice.create({.ownerCount = 1});
329
330 // The only valid MPTokenAuthorize flag is tfMPTUnauthorize, which
331 // has a value of 1
332 mptAlice.authorize(
333 {.account = bob, .flags = 0x00000002, .err = temINVALID_FLAG});
334
335 mptAlice.authorize(
336 {.account = bob, .holder = bob, .err = temMALFORMED});
337
338 mptAlice.authorize({.holder = alice, .err = temMALFORMED});
339 }
340
341 // Try authorizing when MPTokenIssuance doesn't exist in
342 // MPTokenAuthorize (preclaim)
343 {
344 Env env{*this, features};
345 MPTTester mptAlice(env, alice, {.holders = {bob}});
346 auto const id = makeMptID(env.seq(alice), alice);
347
348 mptAlice.authorize(
349 {.holder = bob, .id = id, .err = tecOBJECT_NOT_FOUND});
350
351 mptAlice.authorize(
352 {.account = bob, .id = id, .err = tecOBJECT_NOT_FOUND});
353 }
354
355 // Test bad scenarios without allowlisting in MPTokenAuthorize
356 // (preclaim)
357 {
358 Env env{*this, features};
359 MPTTester mptAlice(env, alice, {.holders = {bob}});
360
361 mptAlice.create({.ownerCount = 1});
362
363 // bob submits a tx with a holder field
364 mptAlice.authorize(
365 {.account = bob, .holder = alice, .err = tecNO_PERMISSION});
366
367 // alice tries to hold onto her own token
368 mptAlice.authorize({.account = alice, .err = tecNO_PERMISSION});
369
370 // the mpt does not enable allowlisting
371 mptAlice.authorize({.holder = bob, .err = tecNO_AUTH});
372
373 // bob now holds a mptoken object
374 mptAlice.authorize({.account = bob, .holderCount = 1});
375
376 // bob cannot create the mptoken the second time
377 mptAlice.authorize({.account = bob, .err = tecDUPLICATE});
378
379 // Check that bob cannot delete MPToken when his balance is
380 // non-zero
381 {
382 // alice pays bob 100 tokens
383 mptAlice.pay(alice, bob, 100);
384
385 // bob tries to delete his MPToken, but fails since he still
386 // holds tokens
387 mptAlice.authorize(
388 {.account = bob,
389 .flags = tfMPTUnauthorize,
390 .err = tecHAS_OBLIGATIONS});
391
392 // bob pays back alice 100 tokens
393 mptAlice.pay(bob, alice, 100);
394 }
395
396 // bob deletes/unauthorizes his MPToken
397 mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
398
399 // bob receives error when he tries to delete his MPToken that has
400 // already been deleted
401 mptAlice.authorize(
402 {.account = bob,
403 .holderCount = 0,
404 .flags = tfMPTUnauthorize,
405 .err = tecOBJECT_NOT_FOUND});
406 }
407
408 // Test bad scenarios with allow-listing in MPTokenAuthorize (preclaim)
409 {
410 Env env{*this, features};
411 MPTTester mptAlice(env, alice, {.holders = {bob}});
412
413 mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth});
414
415 // alice submits a tx without specifying a holder's account
416 mptAlice.authorize({.err = tecNO_PERMISSION});
417
418 // alice submits a tx to authorize a holder that hasn't created
419 // a mptoken yet
420 mptAlice.authorize({.holder = bob, .err = tecOBJECT_NOT_FOUND});
421
422 // alice specifys a holder acct that doesn't exist
423 mptAlice.authorize({.holder = cindy, .err = tecNO_DST});
424
425 // bob now holds a mptoken object
426 mptAlice.authorize({.account = bob, .holderCount = 1});
427
428 // alice tries to unauthorize bob.
429 // although tx is successful,
430 // but nothing happens because bob hasn't been authorized yet
431 mptAlice.authorize({.holder = bob, .flags = tfMPTUnauthorize});
432
433 // alice authorizes bob
434 // make sure bob's mptoken has set lsfMPTAuthorized
435 mptAlice.authorize({.holder = bob});
436
437 // alice tries authorizes bob again.
438 // tx is successful, but bob is already authorized,
439 // so no changes
440 mptAlice.authorize({.holder = bob});
441
442 // bob deletes his mptoken
443 mptAlice.authorize(
444 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
445 }
446
447 // Test mptoken reserve requirement - first two mpts free (doApply)
448 {
449 Env env{*this, features};
450 auto const acctReserve = env.current()->fees().accountReserve(0);
451 auto const incReserve = env.current()->fees().increment;
452
453 // 1 drop
454 BEAST_EXPECT(incReserve > XRPAmount(1));
455 MPTTester mptAlice1(
456 env,
457 alice,
458 {.holders = {bob},
459 .xrpHolders = acctReserve + (incReserve - 1)});
460 mptAlice1.create();
461
462 MPTTester mptAlice2(env, alice, {.fund = false});
463 mptAlice2.create();
464
465 MPTTester mptAlice3(env, alice, {.fund = false});
466 mptAlice3.create({.ownerCount = 3});
467
468 // first mpt for free
469 mptAlice1.authorize({.account = bob, .holderCount = 1});
470
471 // second mpt free
472 mptAlice2.authorize({.account = bob, .holderCount = 2});
473
474 mptAlice3.authorize(
475 {.account = bob, .err = tecINSUFFICIENT_RESERVE});
476
477 env(pay(
478 env.master, bob, drops(incReserve + incReserve + incReserve)));
479 env.close();
480
481 mptAlice3.authorize({.account = bob, .holderCount = 3});
482 }
483 }
484
485 void
487 {
488 testcase("Authorize Enabled");
489
490 using namespace test::jtx;
491 Account const alice("alice");
492 Account const bob("bob");
493 // Basic authorization without allowlisting
494 {
495 Env env{*this, features};
496
497 // alice create mptissuance without allowisting
498 MPTTester mptAlice(env, alice, {.holders = {bob}});
499
500 mptAlice.create({.ownerCount = 1});
501
502 // bob creates a mptoken
503 mptAlice.authorize({.account = bob, .holderCount = 1});
504
505 mptAlice.authorize(
506 {.account = bob, .holderCount = 1, .err = tecDUPLICATE});
507
508 // bob deletes his mptoken
509 mptAlice.authorize(
510 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
511 }
512
513 // With allowlisting
514 {
515 Env env{*this, features};
516
517 // alice creates a mptokenissuance that requires authorization
518 MPTTester mptAlice(env, alice, {.holders = {bob}});
519
520 mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth});
521
522 // bob creates a mptoken
523 mptAlice.authorize({.account = bob, .holderCount = 1});
524
525 // alice authorizes bob
526 mptAlice.authorize({.account = alice, .holder = bob});
527
528 // Unauthorize bob's mptoken
529 mptAlice.authorize(
530 {.account = alice,
531 .holder = bob,
532 .holderCount = 1,
533 .flags = tfMPTUnauthorize});
534
535 mptAlice.authorize(
536 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
537 }
538
539 // Holder can have dangling MPToken even if issuance has been destroyed.
540 // Make sure they can still delete/unauthorize the MPToken
541 {
542 Env env{*this, features};
543 MPTTester mptAlice(env, alice, {.holders = {bob}});
544
545 mptAlice.create({.ownerCount = 1});
546
547 // bob creates a mptoken
548 mptAlice.authorize({.account = bob, .holderCount = 1});
549
550 // alice deletes her issuance
551 mptAlice.destroy({.ownerCount = 0});
552
553 // bob can delete his mptoken even though issuance is no longer
554 // existent
555 mptAlice.authorize(
556 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
557 }
558 }
559
560 void
562 {
563 testcase("Validate set transaction");
564
565 using namespace test::jtx;
566 Account const alice("alice"); // issuer
567 Account const bob("bob"); // holder
568 Account const cindy("cindy");
569 // Validate fields in MPTokenIssuanceSet (preflight)
570 {
571 Env env{*this, features - featureMPTokensV1};
572 MPTTester mptAlice(env, alice, {.holders = {bob}});
573
574 mptAlice.set(
575 {.account = bob,
576 .id = makeMptID(env.seq(alice), alice),
577 .err = temDISABLED});
578
579 env.enableFeature(featureMPTokensV1);
580
581 mptAlice.create({.ownerCount = 1, .holderCount = 0});
582
583 mptAlice.authorize({.account = bob, .holderCount = 1});
584
585 // test invalid flag - only valid flags are tfMPTLock (1) and Unlock
586 // (2)
587 mptAlice.set(
588 {.account = alice,
589 .flags = 0x00000008,
590 .err = temINVALID_FLAG});
591
592 if (!features[featureSingleAssetVault])
593 {
594 // test invalid flags - nothing is being changed
595 mptAlice.set(
596 {.account = alice,
597 .flags = 0x00000000,
598 .err = tecNO_PERMISSION});
599
600 mptAlice.set(
601 {.account = alice,
602 .holder = bob,
603 .flags = 0x00000000,
604 .err = tecNO_PERMISSION});
605
606 // cannot set DomainID since SAV is not enabled
607 mptAlice.set(
608 {.account = alice,
609 .domainID = uint256(42),
610 .err = temDISABLED});
611 }
612 else
613 {
614 // test invalid flags - nothing is being changed
615 mptAlice.set(
616 {.account = alice,
617 .flags = 0x00000000,
618 .err = temMALFORMED});
619
620 mptAlice.set(
621 {.account = alice,
622 .holder = bob,
623 .flags = 0x00000000,
624 .err = temMALFORMED});
625
626 if (!features[featurePermissionedDomains])
627 {
628 // cannot set DomainID since PD is not enabled
629 mptAlice.set(
630 {.account = alice,
631 .domainID = uint256(42),
632 .err = temDISABLED});
633 }
634 else
635 {
636 // cannot set DomainID since Holder is set
637 mptAlice.set(
638 {.account = alice,
639 .holder = bob,
640 .domainID = uint256(42),
641 .err = temMALFORMED});
642 }
643 }
644
645 // set both lock and unlock flags at the same time will fail
646 mptAlice.set(
647 {.account = alice,
648 .flags = tfMPTLock | tfMPTUnlock,
649 .err = temINVALID_FLAG});
650
651 // if the holder is the same as the acct that submitted the tx,
652 // tx fails
653 mptAlice.set(
654 {.account = alice,
655 .holder = alice,
656 .flags = tfMPTLock,
657 .err = temMALFORMED});
658 }
659
660 // Validate fields in MPTokenIssuanceSet (preclaim)
661 // test when a mptokenissuance has disabled locking
662 {
663 Env env{*this, features};
664
665 MPTTester mptAlice(env, alice, {.holders = {bob}});
666
667 mptAlice.create({.ownerCount = 1});
668
669 // alice tries to lock a mptissuance that has disabled locking
670 mptAlice.set(
671 {.account = alice,
672 .flags = tfMPTLock,
673 .err = tecNO_PERMISSION});
674
675 // alice tries to unlock mptissuance that has disabled locking
676 mptAlice.set(
677 {.account = alice,
678 .flags = tfMPTUnlock,
679 .err = tecNO_PERMISSION});
680
681 // issuer tries to lock a bob's mptoken that has disabled
682 // locking
683 mptAlice.set(
684 {.account = alice,
685 .holder = bob,
686 .flags = tfMPTLock,
687 .err = tecNO_PERMISSION});
688
689 // issuer tries to unlock a bob's mptoken that has disabled
690 // locking
691 mptAlice.set(
692 {.account = alice,
693 .holder = bob,
694 .flags = tfMPTUnlock,
695 .err = tecNO_PERMISSION});
696 }
697
698 // Validate fields in MPTokenIssuanceSet (preclaim)
699 // test when mptokenissuance has enabled locking
700 {
701 Env env{*this, features};
702
703 MPTTester mptAlice(env, alice, {.holders = {bob}});
704
705 // alice trying to set when the mptissuance doesn't exist yet
706 mptAlice.set(
707 {.id = makeMptID(env.seq(alice), alice),
708 .flags = tfMPTLock,
709 .err = tecOBJECT_NOT_FOUND});
710
711 // create a mptokenissuance with locking
712 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanLock});
713
714 // a non-issuer acct tries to set the mptissuance
715 mptAlice.set(
716 {.account = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION});
717
718 // trying to set a holder who doesn't have a mptoken
719 mptAlice.set(
720 {.holder = bob,
721 .flags = tfMPTLock,
722 .err = tecOBJECT_NOT_FOUND});
723
724 // trying to set a holder who doesn't exist
725 mptAlice.set(
726 {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
727 }
728
729 if (features[featureSingleAssetVault] &&
730 features[featurePermissionedDomains])
731 {
732 // Add permissioned domain
733 Account const credIssuer1{"credIssuer1"};
734 std::string const credType = "credential";
735
736 pdomain::Credentials const credentials1{
737 {.issuer = credIssuer1, .credType = credType}};
738
739 {
740 Env env{*this, features};
741
742 MPTTester mptAlice(env, alice);
743 mptAlice.create({});
744
745 // Trying to set DomainID on a public MPTokenIssuance
746 mptAlice.set(
747 {.domainID = uint256(42), .err = tecNO_PERMISSION});
748
749 mptAlice.set(
750 {.domainID = beast::zero, .err = tecNO_PERMISSION});
751 }
752
753 {
754 Env env{*this, features};
755
756 MPTTester mptAlice(env, alice);
757 mptAlice.create({.flags = tfMPTRequireAuth});
758
759 // Trying to set non-existing DomainID
760 mptAlice.set(
761 {.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND});
762
763 // Trying to lock but locking is disabled
764 mptAlice.set(
765 {.flags = tfMPTUnlock,
766 .domainID = uint256(42),
767 .err = tecNO_PERMISSION});
768
769 mptAlice.set(
770 {.flags = tfMPTUnlock,
771 .domainID = beast::zero,
772 .err = tecNO_PERMISSION});
773 }
774 }
775 }
776
777 void
779 {
780 testcase("Enabled set transaction");
781
782 using namespace test::jtx;
783 Account const alice("alice"); // issuer
784 Account const bob("bob"); // holder
785
786 {
787 // Test locking and unlocking
788 Env env{*this, features};
789
790 MPTTester mptAlice(env, alice, {.holders = {bob}});
791
792 // create a mptokenissuance with locking
793 mptAlice.create(
794 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
795
796 mptAlice.authorize({.account = bob, .holderCount = 1});
797
798 // locks bob's mptoken
799 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
800
801 // trying to lock bob's mptoken again will still succeed
802 // but no changes to the objects
803 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
804
805 // alice locks the mptissuance
806 mptAlice.set({.account = alice, .flags = tfMPTLock});
807
808 // alice tries to lock up both mptissuance and mptoken again
809 // it will not change the flags and both will remain locked.
810 mptAlice.set({.account = alice, .flags = tfMPTLock});
811 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
812
813 // alice unlocks bob's mptoken
814 mptAlice.set(
815 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
816
817 // locks up bob's mptoken again
818 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
819 if (!features[featureSingleAssetVault])
820 {
821 // Delete bobs' mptoken even though it is locked
822 mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
823
824 mptAlice.set(
825 {.account = alice,
826 .holder = bob,
827 .flags = tfMPTUnlock,
828 .err = tecOBJECT_NOT_FOUND});
829
830 return;
831 }
832
833 // Cannot delete locked MPToken
834 mptAlice.authorize(
835 {.account = bob,
836 .flags = tfMPTUnauthorize,
837 .err = tecNO_PERMISSION});
838
839 // alice unlocks mptissuance
840 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
841
842 // alice unlocks bob's mptoken
843 mptAlice.set(
844 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
845
846 // alice unlocks mptissuance and bob's mptoken again despite that
847 // they are already unlocked. Make sure this will not change the
848 // flags
849 mptAlice.set(
850 {.account = alice, .holder = bob, .flags = tfMPTUnlock});
851 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
852 }
853
854 if (features[featureSingleAssetVault])
855 {
856 // Add permissioned domain
857 std::string const credType = "credential";
858
859 // Test setting and resetting domain ID
860 Env env{*this, features};
861
862 auto const domainId1 = [&]() {
863 Account const credIssuer1{"credIssuer1"};
864 env.fund(XRP(1000), credIssuer1);
865
866 pdomain::Credentials const credentials1{
867 {.issuer = credIssuer1, .credType = credType}};
868
869 env(pdomain::setTx(credIssuer1, credentials1));
870 return [&]() {
871 auto tx = env.tx()->getJson(JsonOptions::none);
872 return pdomain::getNewDomain(env.meta());
873 }();
874 }();
875
876 auto const domainId2 = [&]() {
877 Account const credIssuer2{"credIssuer2"};
878 env.fund(XRP(1000), credIssuer2);
879
880 pdomain::Credentials const credentials2{
881 {.issuer = credIssuer2, .credType = credType}};
882
883 env(pdomain::setTx(credIssuer2, credentials2));
884 return [&]() {
885 auto tx = env.tx()->getJson(JsonOptions::none);
886 return pdomain::getNewDomain(env.meta());
887 }();
888 }();
889
890 MPTTester mptAlice(env, alice, {.holders = {bob}});
891
892 // create a mptokenissuance with auth.
893 mptAlice.create(
894 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth});
895 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
896
897 // reset "domain not set" to "domain not set", i.e. no change
898 mptAlice.set({.domainID = beast::zero});
899 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
900
901 // reset "domain not set" to domain1
902 mptAlice.set({.domainID = domainId1});
903 BEAST_EXPECT(mptAlice.checkDomainID(domainId1));
904
905 // reset domain1 to domain2
906 mptAlice.set({.domainID = domainId2});
907 BEAST_EXPECT(mptAlice.checkDomainID(domainId2));
908
909 // reset domain to "domain not set"
910 mptAlice.set({.domainID = beast::zero});
911 BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
912 }
913 }
914
915 void
917 {
918 testcase("Payment");
919
920 using namespace test::jtx;
921 Account const alice("alice"); // issuer
922 Account const bob("bob"); // holder
923 Account const carol("carol"); // holder
924
925 // preflight validation
926
927 // MPT is disabled
928 {
929 Env env{*this, features - featureMPTokensV1};
930 Account const alice("alice");
931 Account const bob("bob");
932
933 env.fund(XRP(1'000), alice);
934 env.fund(XRP(1'000), bob);
935 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
936
937 env(pay(alice, bob, mpt), ter(temDISABLED));
938 }
939
940 // MPT is disabled, unsigned request
941 {
942 Env env{*this, features - featureMPTokensV1};
943 Account const alice("alice"); // issuer
944 Account const carol("carol");
945 auto const USD = alice["USD"];
946
947 env.fund(XRP(1'000), alice);
948 env.fund(XRP(1'000), carol);
949 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
950
951 Json::Value jv;
952 jv[jss::secret] = alice.name();
953 jv[jss::tx_json] = pay(alice, carol, mpt);
954 jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
955 auto const jrr = env.rpc("json", "submit", to_string(jv));
956 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "temDISABLED");
957 }
958
959 // Invalid flag
960 {
961 Env env{*this, features};
962
963 MPTTester mptAlice(env, alice, {.holders = {bob}});
964
965 mptAlice.create({.ownerCount = 1, .holderCount = 0});
966 auto const MPT = mptAlice["MPT"];
967
968 mptAlice.authorize({.account = bob});
969
971 env(pay(alice, bob, MPT(10)),
972 txflags(flags),
974 }
975
976 // Invalid combination of send, sendMax, deliverMin, paths
977 {
978 Env env{*this, features};
979 Account const alice("alice");
980 Account const carol("carol");
981
982 MPTTester mptAlice(env, alice, {.holders = {carol}});
983
984 mptAlice.create({.ownerCount = 1, .holderCount = 0});
985
986 mptAlice.authorize({.account = carol});
987
988 // sendMax and DeliverMin are valid XRP amount,
989 // but is invalid combination with MPT amount
990 auto const MPT = mptAlice["MPT"];
991 env(pay(alice, carol, MPT(100)),
992 sendmax(XRP(100)),
994 env(pay(alice, carol, MPT(100)),
995 delivermin(XRP(100)),
997 // sendMax MPT is invalid with IOU or XRP
998 auto const USD = alice["USD"];
999 env(pay(alice, carol, USD(100)),
1000 sendmax(MPT(100)),
1001 ter(temMALFORMED));
1002 env(pay(alice, carol, XRP(100)),
1003 sendmax(MPT(100)),
1004 ter(temMALFORMED));
1005 env(pay(alice, carol, USD(100)),
1006 delivermin(MPT(100)),
1008 env(pay(alice, carol, XRP(100)),
1009 delivermin(MPT(100)),
1011 // sendmax and amount are different MPT issue
1012 test::jtx::MPT const MPT1(
1013 "MPT", makeMptID(env.seq(alice) + 10, alice));
1014 env(pay(alice, carol, MPT1(100)),
1015 sendmax(MPT(100)),
1016 ter(temMALFORMED));
1017 // paths is invalid
1018 env(pay(alice, carol, MPT(100)), path(~USD), ter(temMALFORMED));
1019 }
1020
1021 // build_path is invalid if MPT
1022 {
1023 Env env{*this, features};
1024 Account const alice("alice");
1025 Account const carol("carol");
1026
1027 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1028
1029 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1030 auto const MPT = mptAlice["MPT"];
1031
1032 mptAlice.authorize({.account = carol});
1033
1034 Json::Value payment;
1035 payment[jss::secret] = alice.name();
1036 payment[jss::tx_json] = pay(alice, carol, MPT(100));
1037
1038 payment[jss::build_path] = true;
1039 auto jrr = env.rpc("json", "submit", to_string(payment));
1040 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1041 BEAST_EXPECT(
1042 jrr[jss::result][jss::error_message] ==
1043 "Field 'build_path' not allowed in this context.");
1044 }
1045
1046 // Can't pay negative amount
1047 {
1048 Env env{*this, features};
1049
1050 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1051
1052 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1053 auto const MPT = mptAlice["MPT"];
1054
1055 mptAlice.authorize({.account = bob});
1056 mptAlice.authorize({.account = carol});
1057
1058 mptAlice.pay(alice, bob, -1, temBAD_AMOUNT);
1059
1060 mptAlice.pay(bob, carol, -1, temBAD_AMOUNT);
1061
1062 mptAlice.pay(bob, alice, -1, temBAD_AMOUNT);
1063
1064 env(pay(alice, bob, MPT(10)), sendmax(MPT(-1)), ter(temBAD_AMOUNT));
1065 }
1066
1067 // Pay to self
1068 {
1069 Env env{*this, features};
1070
1071 MPTTester mptAlice(env, alice, {.holders = {bob}});
1072
1073 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1074
1075 mptAlice.authorize({.account = bob});
1076
1077 mptAlice.pay(bob, bob, 10, temREDUNDANT);
1078 }
1079
1080 // preclaim validation
1081
1082 // Destination doesn't exist
1083 {
1084 Env env{*this, features};
1085
1086 MPTTester mptAlice(env, alice, {.holders = {bob}});
1087
1088 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1089
1090 mptAlice.authorize({.account = bob});
1091
1092 Account const bad{"bad"};
1093 env.memoize(bad);
1094
1095 mptAlice.pay(bob, bad, 10, tecNO_DST);
1096 }
1097
1098 // apply validation
1099
1100 // If RequireAuth is enabled, Payment fails if the receiver 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 mptAlice.authorize({.account = bob});
1113
1114 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1115 }
1116
1117 // If RequireAuth is enabled, Payment fails if the sender is not
1118 // authorized
1119 {
1120 Env env{*this, features};
1121
1122 MPTTester mptAlice(env, alice, {.holders = {bob}});
1123
1124 mptAlice.create(
1125 {.ownerCount = 1,
1126 .holderCount = 0,
1128
1129 // bob creates an empty MPToken
1130 mptAlice.authorize({.account = bob});
1131
1132 // alice authorizes bob to hold funds
1133 mptAlice.authorize({.account = alice, .holder = bob});
1134
1135 // alice sends 100 MPT to bob
1136 mptAlice.pay(alice, bob, 100);
1137
1138 // alice UNAUTHORIZES bob
1139 mptAlice.authorize(
1140 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
1141
1142 // bob fails to send back to alice because he is no longer
1143 // authorize to move his funds!
1144 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
1145 }
1146
1147 if (features[featureSingleAssetVault] &&
1148 features[featurePermissionedDomains])
1149 {
1150 // If RequireAuth is enabled and domain is a match, payment succeeds
1151 {
1152 Env env{*this, features};
1153 std::string const credType = "credential";
1154 Account const credIssuer1{"credIssuer1"};
1155 env.fund(XRP(1000), credIssuer1, bob);
1156
1157 auto const domainId1 = [&]() {
1158 pdomain::Credentials const credentials1{
1159 {.issuer = credIssuer1, .credType = credType}};
1160
1161 env(pdomain::setTx(credIssuer1, credentials1));
1162 return [&]() {
1163 auto tx = env.tx()->getJson(JsonOptions::none);
1164 return pdomain::getNewDomain(env.meta());
1165 }();
1166 }();
1167 // bob is authorized via domain
1168 env(credentials::create(bob, credIssuer1, credType));
1169 env(credentials::accept(bob, credIssuer1, credType));
1170 env.close();
1171
1172 MPTTester mptAlice(env, alice, {});
1173 env.close();
1174
1175 mptAlice.create({
1176 .ownerCount = 1,
1177 .holderCount = 0,
1179 .domainID = domainId1,
1180 });
1181
1182 mptAlice.authorize({.account = bob});
1183 env.close();
1184
1185 // bob is authorized via domain
1186 mptAlice.pay(alice, bob, 100);
1187 mptAlice.set({.domainID = beast::zero});
1188
1189 // bob is no longer authorized
1190 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1191 }
1192
1193 {
1194 Env env{*this, features};
1195 std::string const credType = "credential";
1196 Account const credIssuer1{"credIssuer1"};
1197 env.fund(XRP(1000), credIssuer1, bob);
1198
1199 auto const domainId1 = [&]() {
1200 pdomain::Credentials const credentials1{
1201 {.issuer = credIssuer1, .credType = credType}};
1202
1203 env(pdomain::setTx(credIssuer1, credentials1));
1204 return [&]() {
1205 auto tx = env.tx()->getJson(JsonOptions::none);
1206 return pdomain::getNewDomain(env.meta());
1207 }();
1208 }();
1209 // bob is authorized via domain
1210 env(credentials::create(bob, credIssuer1, credType));
1211 env(credentials::accept(bob, credIssuer1, credType));
1212 env.close();
1213
1214 MPTTester mptAlice(env, alice, {});
1215 env.close();
1216
1217 mptAlice.create({
1218 .ownerCount = 1,
1219 .holderCount = 0,
1221 .domainID = domainId1,
1222 });
1223
1224 // bob creates an empty MPToken
1225 mptAlice.authorize({.account = bob});
1226
1227 // alice authorizes bob to hold funds
1228 mptAlice.authorize({.account = alice, .holder = bob});
1229
1230 // alice sends 100 MPT to bob
1231 mptAlice.pay(alice, bob, 100);
1232
1233 // alice UNAUTHORIZES bob
1234 mptAlice.authorize(
1235 {.account = alice,
1236 .holder = bob,
1237 .flags = tfMPTUnauthorize});
1238
1239 // bob is still authorized, via domain
1240 mptAlice.pay(bob, alice, 10);
1241
1242 mptAlice.set({.domainID = beast::zero});
1243
1244 // bob fails to send back to alice because he is no longer
1245 // authorize to move his funds!
1246 mptAlice.pay(bob, alice, 10, tecNO_AUTH);
1247 }
1248
1249 {
1250 Env env{*this, features};
1251 std::string const credType = "credential";
1252 // credIssuer1 is the owner of domainId1 and a credential issuer
1253 Account const credIssuer1{"credIssuer1"};
1254 // credIssuer2 is the owner of domainId2 and a credential issuer
1255 // Note, domainId2 also lists credentials issued by credIssuer1
1256 Account const credIssuer2{"credIssuer2"};
1257 env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol);
1258
1259 auto const domainId1 = [&]() {
1260 pdomain::Credentials const credentials{
1261 {.issuer = credIssuer1, .credType = credType}};
1262
1263 env(pdomain::setTx(credIssuer1, credentials));
1264 return [&]() {
1265 auto tx = env.tx()->getJson(JsonOptions::none);
1266 return pdomain::getNewDomain(env.meta());
1267 }();
1268 }();
1269
1270 auto const domainId2 = [&]() {
1271 pdomain::Credentials const credentials{
1272 {.issuer = credIssuer1, .credType = credType},
1273 {.issuer = credIssuer2, .credType = credType}};
1274
1275 env(pdomain::setTx(credIssuer2, credentials));
1276 return [&]() {
1277 auto tx = env.tx()->getJson(JsonOptions::none);
1278 return pdomain::getNewDomain(env.meta());
1279 }();
1280 }();
1281
1282 // bob is authorized via credIssuer1 which is recognized by both
1283 // domainId1 and domainId2
1284 env(credentials::create(bob, credIssuer1, credType));
1285 env(credentials::accept(bob, credIssuer1, credType));
1286 env.close();
1287
1288 // carol is authorized via credIssuer2, only recognized by
1289 // domainId2
1290 env(credentials::create(carol, credIssuer2, credType));
1291 env(credentials::accept(carol, credIssuer2, credType));
1292 env.close();
1293
1294 MPTTester mptAlice(env, alice, {});
1295 env.close();
1296
1297 mptAlice.create({
1298 .ownerCount = 1,
1299 .holderCount = 0,
1301 .domainID = domainId1,
1302 });
1303
1304 // bob and carol create an empty MPToken
1305 mptAlice.authorize({.account = bob});
1306 mptAlice.authorize({.account = carol});
1307 env.close();
1308
1309 // alice sends 50 MPT to bob but cannot send to carol
1310 mptAlice.pay(alice, bob, 50);
1311 mptAlice.pay(alice, carol, 50, tecNO_AUTH);
1312 env.close();
1313
1314 // bob cannot send to carol because they are not on the same
1315 // domain (since credIssuer2 is not recognized by domainId1)
1316 mptAlice.pay(bob, carol, 10, tecNO_AUTH);
1317 env.close();
1318
1319 // alice updates domainID to domainId2 which recognizes both
1320 // credIssuer1 and credIssuer2
1321 mptAlice.set({.domainID = domainId2});
1322 // alice can now send to carol
1323 mptAlice.pay(alice, carol, 10);
1324 env.close();
1325
1326 // bob can now send to carol because both are in the same
1327 // domain
1328 mptAlice.pay(bob, carol, 10);
1329 env.close();
1330
1331 // bob loses his authorization and can no longer send MPT
1333 credIssuer1, bob, credIssuer1, credType));
1334 env.close();
1335
1336 mptAlice.pay(bob, carol, 10, tecNO_AUTH);
1337 mptAlice.pay(bob, alice, 10, tecNO_AUTH);
1338 }
1339 }
1340
1341 // Non-issuer cannot send to each other if MPTCanTransfer isn't set
1342 {
1343 Env env(*this, features);
1344 Account const alice{"alice"};
1345 Account const bob{"bob"};
1346 Account const cindy{"cindy"};
1347
1348 MPTTester mptAlice(env, alice, {.holders = {bob, cindy}});
1349
1350 // alice creates issuance without MPTCanTransfer
1351 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1352
1353 // bob creates a MPToken
1354 mptAlice.authorize({.account = bob});
1355
1356 // cindy creates a MPToken
1357 mptAlice.authorize({.account = cindy});
1358
1359 // alice pays bob 100 tokens
1360 mptAlice.pay(alice, bob, 100);
1361
1362 // bob tries to send cindy 10 tokens, but fails because canTransfer
1363 // is off
1364 mptAlice.pay(bob, cindy, 10, tecNO_AUTH);
1365
1366 // bob can send back to alice(issuer) just fine
1367 mptAlice.pay(bob, alice, 10);
1368 }
1369
1370 // Holder is not authorized
1371 {
1372 Env env{*this, features};
1373
1374 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1375
1376 mptAlice.create(
1377 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1378
1379 // issuer to holder
1380 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
1381
1382 // holder to issuer
1383 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
1384
1385 // holder to holder
1386 mptAlice.pay(bob, carol, 50, tecNO_AUTH);
1387 }
1388
1389 // Payer doesn't have enough funds
1390 {
1391 Env env{*this, features};
1392
1393 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1394
1395 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
1396
1397 mptAlice.authorize({.account = bob});
1398 mptAlice.authorize({.account = carol});
1399
1400 mptAlice.pay(alice, bob, 100);
1401
1402 // Pay to another holder
1403 mptAlice.pay(bob, carol, 101, tecPATH_PARTIAL);
1404
1405 // Pay to the issuer
1406 mptAlice.pay(bob, alice, 101, tecPATH_PARTIAL);
1407 }
1408
1409 // MPT is locked
1410 {
1411 Env env{*this, features};
1412
1413 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1414
1415 mptAlice.create(
1416 {.ownerCount = 1, .flags = tfMPTCanLock | tfMPTCanTransfer});
1417
1418 mptAlice.authorize({.account = bob});
1419 mptAlice.authorize({.account = carol});
1420
1421 mptAlice.pay(alice, bob, 100);
1422 mptAlice.pay(alice, carol, 100);
1423
1424 // Global lock
1425 mptAlice.set({.account = alice, .flags = tfMPTLock});
1426 // Can't send between holders
1427 mptAlice.pay(bob, carol, 1, tecLOCKED);
1428 mptAlice.pay(carol, bob, 2, tecLOCKED);
1429 // Issuer can send
1430 mptAlice.pay(alice, bob, 3);
1431 // Holder can send back to issuer
1432 mptAlice.pay(bob, alice, 4);
1433
1434 // Global unlock
1435 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
1436 // Individual lock
1437 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
1438 // Can't send between holders
1439 mptAlice.pay(bob, carol, 5, tecLOCKED);
1440 mptAlice.pay(carol, bob, 6, tecLOCKED);
1441 // Issuer can send
1442 mptAlice.pay(alice, bob, 7);
1443 // Holder can send back to issuer
1444 mptAlice.pay(bob, alice, 8);
1445 }
1446
1447 // Transfer fee
1448 {
1449 Env env{*this, features};
1450
1451 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1452
1453 // Transfer fee is 10%
1454 mptAlice.create(
1455 {.transferFee = 10'000,
1456 .ownerCount = 1,
1457 .holderCount = 0,
1458 .flags = tfMPTCanTransfer});
1459
1460 // Holders create MPToken
1461 mptAlice.authorize({.account = bob});
1462 mptAlice.authorize({.account = carol});
1463
1464 // Payment between the issuer and the holder, no transfer fee.
1465 mptAlice.pay(alice, bob, 2'000);
1466
1467 // Payment between the holder and the issuer, no transfer fee.
1468 mptAlice.pay(bob, alice, 1'000);
1469 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1'000));
1470
1471 // Payment between the holders. The sender doesn't have
1472 // enough funds to cover the transfer fee.
1473 mptAlice.pay(bob, carol, 1'000, tecPATH_PARTIAL);
1474
1475 // Payment between the holders. The sender has enough funds
1476 // but SendMax is not included.
1477 mptAlice.pay(bob, carol, 100, tecPATH_PARTIAL);
1478
1479 auto const MPT = mptAlice["MPT"];
1480 // SendMax doesn't cover the fee
1481 env(pay(bob, carol, MPT(100)),
1482 sendmax(MPT(109)),
1484
1485 // Payment succeeds if sufficient SendMax is included.
1486 // 100 to carol, 10 to issuer
1487 env(pay(bob, carol, MPT(100)), sendmax(MPT(110)));
1488 // 100 to carol, 10 to issuer
1489 env(pay(bob, carol, MPT(100)), sendmax(MPT(115)));
1490 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 780));
1491 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 200));
1492 // Payment succeeds if partial payment even if
1493 // SendMax is less than deliver amount
1494 env(pay(bob, carol, MPT(100)),
1495 sendmax(MPT(90)),
1497 // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) =
1498 // 82)
1499 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690));
1500 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 282));
1501 }
1502
1503 // Insufficient SendMax with no transfer fee
1504 {
1505 Env env{*this, features};
1506
1507 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1508
1509 mptAlice.create(
1510 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1511
1512 // Holders create MPToken
1513 mptAlice.authorize({.account = bob});
1514 mptAlice.authorize({.account = carol});
1515 mptAlice.pay(alice, bob, 1'000);
1516
1517 auto const MPT = mptAlice["MPT"];
1518 // SendMax is less than the amount
1519 env(pay(bob, carol, MPT(100)),
1520 sendmax(MPT(99)),
1522 env(pay(bob, alice, MPT(100)),
1523 sendmax(MPT(99)),
1525
1526 // Payment succeeds if sufficient SendMax is included.
1527 env(pay(bob, carol, MPT(100)), sendmax(MPT(100)));
1528 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 100));
1529 // Payment succeeds if partial payment
1530 env(pay(bob, carol, MPT(100)),
1531 sendmax(MPT(99)),
1533 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 199));
1534 }
1535
1536 // DeliverMin
1537 {
1538 Env env{*this, features};
1539
1540 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1541
1542 mptAlice.create(
1543 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1544
1545 // Holders create MPToken
1546 mptAlice.authorize({.account = bob});
1547 mptAlice.authorize({.account = carol});
1548 mptAlice.pay(alice, bob, 1'000);
1549
1550 auto const MPT = mptAlice["MPT"];
1551 // Fails even with the partial payment because
1552 // deliver amount < deliverMin
1553 env(pay(bob, alice, MPT(100)),
1554 sendmax(MPT(99)),
1555 delivermin(MPT(100)),
1558 // Payment succeeds if deliver amount >= deliverMin
1559 env(pay(bob, alice, MPT(100)),
1560 sendmax(MPT(99)),
1561 delivermin(MPT(99)),
1563 }
1564
1565 // Issuer fails trying to send more than the maximum amount allowed
1566 {
1567 Env env{*this, features};
1568
1569 MPTTester mptAlice(env, alice, {.holders = {bob}});
1570
1571 mptAlice.create(
1572 {.maxAmt = 100,
1573 .ownerCount = 1,
1574 .holderCount = 0,
1575 .flags = tfMPTCanTransfer});
1576
1577 mptAlice.authorize({.account = bob});
1578
1579 // issuer sends holder the max amount allowed
1580 mptAlice.pay(alice, bob, 100);
1581
1582 // issuer tries to exceed max amount
1583 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1584 }
1585
1586 // Issuer fails trying to send more than the default maximum
1587 // amount allowed
1588 {
1589 Env env{*this, features};
1590
1591 MPTTester mptAlice(env, alice, {.holders = {bob}});
1592
1593 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1594
1595 mptAlice.authorize({.account = bob});
1596
1597 // issuer sends holder the default max amount allowed
1598 mptAlice.pay(alice, bob, maxMPTokenAmount);
1599
1600 // issuer tries to exceed max amount
1601 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1602 }
1603
1604 // Pay more than max amount fails in the json parser before
1605 // transactor is called
1606 {
1607 Env env{*this, features};
1608 env.fund(XRP(1'000), alice, bob);
1609 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
1610 Json::Value jv;
1611 jv[jss::secret] = alice.name();
1612 jv[jss::tx_json] = pay(alice, bob, mpt);
1613 jv[jss::tx_json][jss::Amount][jss::value] =
1615 auto const jrr = env.rpc("json", "submit", to_string(jv));
1616 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1617 }
1618
1619 // Pay maximum amount with the transfer fee, SendMax, and
1620 // partial payment
1621 {
1622 Env env{*this, features};
1623
1624 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1625
1626 mptAlice.create(
1627 {.maxAmt = 10'000,
1628 .transferFee = 100,
1629 .ownerCount = 1,
1630 .holderCount = 0,
1631 .flags = tfMPTCanTransfer});
1632 auto const MPT = mptAlice["MPT"];
1633
1634 mptAlice.authorize({.account = bob});
1635 mptAlice.authorize({.account = carol});
1636
1637 // issuer sends holder the max amount allowed
1638 mptAlice.pay(alice, bob, 10'000);
1639
1640 // payment between the holders
1641 env(pay(bob, carol, MPT(10'000)),
1642 sendmax(MPT(10'000)),
1644 // Verify the metadata
1645 auto const meta = env.meta()->getJson(
1646 JsonOptions::none)[sfAffectedNodes.fieldName];
1647 // Issuer got 10 in the transfer fees
1648 BEAST_EXPECT(
1649 meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1650 [sfOutstandingAmount.fieldName] == "9990");
1651 // Destination account got 9'990
1652 BEAST_EXPECT(
1653 meta[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1654 [sfMPTAmount.fieldName] == "9990");
1655 // Source account spent 10'000
1656 BEAST_EXPECT(
1657 meta[2u][sfModifiedNode.fieldName][sfPreviousFields.fieldName]
1658 [sfMPTAmount.fieldName] == "10000");
1659 BEAST_EXPECT(
1660 !meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1661 .isMember(sfMPTAmount.fieldName));
1662
1663 // payment between the holders fails without
1664 // partial payment
1665 env(pay(bob, carol, MPT(10'000)),
1666 sendmax(MPT(10'000)),
1668 }
1669
1670 // Pay maximum allowed amount
1671 {
1672 Env env{*this, features};
1673
1674 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1675
1676 mptAlice.create(
1677 {.maxAmt = maxMPTokenAmount,
1678 .ownerCount = 1,
1679 .holderCount = 0,
1680 .flags = tfMPTCanTransfer});
1681 auto const MPT = mptAlice["MPT"];
1682
1683 mptAlice.authorize({.account = bob});
1684 mptAlice.authorize({.account = carol});
1685
1686 // issuer sends holder the max amount allowed
1687 mptAlice.pay(alice, bob, maxMPTokenAmount);
1688 BEAST_EXPECT(
1689 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1690
1691 // payment between the holders
1692 mptAlice.pay(bob, carol, maxMPTokenAmount);
1693 BEAST_EXPECT(
1694 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1695 // holder pays back to the issuer
1696 mptAlice.pay(carol, alice, maxMPTokenAmount);
1697 BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0));
1698 }
1699
1700 // Issuer fails trying to send fund after issuance was destroyed
1701 {
1702 Env env{*this, features};
1703
1704 MPTTester mptAlice(env, alice, {.holders = {bob}});
1705
1706 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1707
1708 mptAlice.authorize({.account = bob});
1709
1710 // alice destroys issuance
1711 mptAlice.destroy({.ownerCount = 0});
1712
1713 // alice tries to send bob fund after issuance is destroyed, should
1714 // fail.
1715 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1716 }
1717
1718 // Non-existent issuance
1719 {
1720 Env env{*this, features};
1721
1722 env.fund(XRP(1'000), alice, bob);
1723
1724 STAmount const mpt{MPTID{0}, 100};
1725 env(pay(alice, bob, mpt), ter(tecOBJECT_NOT_FOUND));
1726 }
1727
1728 // Issuer fails trying to send to an account, which doesn't own MPT for
1729 // an issuance that was destroyed
1730 {
1731 Env env{*this, features};
1732
1733 MPTTester mptAlice(env, alice, {.holders = {bob}});
1734
1735 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1736
1737 // alice destroys issuance
1738 mptAlice.destroy({.ownerCount = 0});
1739
1740 // alice tries to send bob who doesn't own the MPT after issuance is
1741 // destroyed, it should fail
1742 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1743 }
1744
1745 // Issuers issues maximum amount of MPT to a holder, the holder should
1746 // be able to transfer the max amount to someone else
1747 {
1748 Env env{*this, features};
1749 Account const alice("alice");
1750 Account const carol("bob");
1751 Account const bob("carol");
1752
1753 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1754
1755 mptAlice.create(
1756 {.maxAmt = 100, .ownerCount = 1, .flags = tfMPTCanTransfer});
1757
1758 mptAlice.authorize({.account = bob});
1759 mptAlice.authorize({.account = carol});
1760
1761 mptAlice.pay(alice, bob, 100);
1762
1763 // transfer max amount to another holder
1764 mptAlice.pay(bob, carol, 100);
1765 }
1766
1767 // Simple payment
1768 {
1769 Env env{*this, features};
1770
1771 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1772
1773 mptAlice.create(
1774 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1775
1776 mptAlice.authorize({.account = bob});
1777 mptAlice.authorize({.account = carol});
1778
1779 // issuer to holder
1780 mptAlice.pay(alice, bob, 100);
1781
1782 // holder to issuer
1783 mptAlice.pay(bob, alice, 100);
1784
1785 // holder to holder
1786 mptAlice.pay(alice, bob, 100);
1787 mptAlice.pay(bob, carol, 50);
1788 }
1789 }
1790
1791 void
1793 {
1794 using namespace test::jtx;
1795 Account const alice("alice"); // issuer
1796 Account const bob("bob"); // holder
1797 Account const diana("diana");
1798 Account const dpIssuer("dpIssuer"); // holder
1799
1800 char const credType[] = "abcde";
1801
1802 if (features[featureCredentials])
1803 {
1804 testcase("DepositPreauth");
1805
1806 Env env(*this, features);
1807
1808 env.fund(XRP(50000), diana, dpIssuer);
1809 env.close();
1810
1811 MPTTester mptAlice(env, alice, {.holders = {bob}});
1812 mptAlice.create(
1813 {.ownerCount = 1,
1814 .holderCount = 0,
1816
1817 env(pay(diana, bob, XRP(500)));
1818 env.close();
1819
1820 // bob creates an empty MPToken
1821 mptAlice.authorize({.account = bob});
1822 // alice authorizes bob to hold funds
1823 mptAlice.authorize({.account = alice, .holder = bob});
1824
1825 // Bob require preauthorization
1826 env(fset(bob, asfDepositAuth));
1827 env.close();
1828
1829 // alice try to send 100 MPT to bob, not authorized
1830 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1831 env.close();
1832
1833 // Bob authorize alice
1834 env(deposit::auth(bob, alice));
1835 env.close();
1836
1837 // alice sends 100 MPT to bob
1838 mptAlice.pay(alice, bob, 100);
1839 env.close();
1840
1841 // Create credentials
1842 env(credentials::create(alice, dpIssuer, credType));
1843 env.close();
1844 env(credentials::accept(alice, dpIssuer, credType));
1845 env.close();
1846 auto const jv =
1847 credentials::ledgerEntry(env, alice, dpIssuer, credType);
1848 std::string const credIdx = jv[jss::result][jss::index].asString();
1849
1850 // alice sends 100 MPT to bob with credentials which aren't required
1851 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1852 env.close();
1853
1854 // Bob revoke authorization
1855 env(deposit::unauth(bob, alice));
1856 env.close();
1857
1858 // alice try to send 100 MPT to bob, not authorized
1859 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1860 env.close();
1861
1862 // alice sends 100 MPT to bob with credentials, not authorized
1863 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION, {{credIdx}});
1864 env.close();
1865
1866 // Bob authorize credentials
1867 env(deposit::authCredentials(bob, {{dpIssuer, credType}}));
1868 env.close();
1869
1870 // alice try to send 100 MPT to bob, not authorized
1871 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1872 env.close();
1873
1874 // alice sends 100 MPT to bob with credentials
1875 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1876 env.close();
1877 }
1878
1879 testcase("DepositPreauth disabled featureCredentials");
1880 {
1881 Env env(*this, testable_amendments() - featureCredentials);
1882
1883 std::string const credIdx =
1884 "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6"
1885 "E2";
1886
1887 env.fund(XRP(50000), diana, dpIssuer);
1888 env.close();
1889
1890 MPTTester mptAlice(env, alice, {.holders = {bob}});
1891 mptAlice.create(
1892 {.ownerCount = 1,
1893 .holderCount = 0,
1895
1896 env(pay(diana, bob, XRP(500)));
1897 env.close();
1898
1899 // bob creates an empty MPToken
1900 mptAlice.authorize({.account = bob});
1901 // alice authorizes bob to hold funds
1902 mptAlice.authorize({.account = alice, .holder = bob});
1903
1904 // Bob require preauthorization
1905 env(fset(bob, asfDepositAuth));
1906 env.close();
1907
1908 // alice try to send 100 MPT to bob, not authorized
1909 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1910 env.close();
1911
1912 // alice try to send 100 MPT to bob with credentials, amendment
1913 // disabled
1914 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1915 env.close();
1916
1917 // Bob authorize alice
1918 env(deposit::auth(bob, alice));
1919 env.close();
1920
1921 // alice sends 100 MPT to bob
1922 mptAlice.pay(alice, bob, 100);
1923 env.close();
1924
1925 // alice sends 100 MPT to bob with credentials, amendment disabled
1926 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1927 env.close();
1928
1929 // Bob revoke authorization
1930 env(deposit::unauth(bob, alice));
1931 env.close();
1932
1933 // alice try to send 100 MPT to bob
1934 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1935 env.close();
1936
1937 // alice sends 100 MPT to bob with credentials, amendment disabled
1938 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1939 env.close();
1940 }
1941 }
1942
1943 void
1945 {
1946 testcase("MPT Issue Invalid in Transaction");
1947 using namespace test::jtx;
1948
1949 // Validate that every transaction with an amount/issue field,
1950 // which doesn't support MPT, fails.
1951
1952 // keyed by transaction + amount/issue field
1953 std::set<std::string> txWithAmounts;
1954 for (auto const& format : TxFormats::getInstance())
1955 {
1956 for (auto const& e : format.getSOTemplate())
1957 {
1958 // Transaction has amount/issue fields.
1959 // Exclude pseudo-transaction SetFee. Don't consider
1960 // the Fee field since it's included in every transaction.
1961 if (e.supportMPT() == soeMPTNotSupported &&
1962 e.sField().getName() != jss::Fee &&
1963 format.getName() != jss::SetFee)
1964 {
1965 txWithAmounts.insert(
1966 format.getName() + e.sField().fieldName);
1967 break;
1968 }
1969 }
1970 }
1971
1972 Account const alice("alice");
1973 auto const USD = alice["USD"];
1974 Account const carol("carol");
1975 MPTIssue issue(makeMptID(1, alice));
1976 STAmount mpt{issue, UINT64_C(100)};
1977 auto const jvb = bridge(alice, USD, alice, USD);
1978 for (auto const& feature : {features, features - featureMPTokensV1})
1979 {
1980 Env env{*this, feature};
1981 env.fund(XRP(1'000), alice);
1982 env.fund(XRP(1'000), carol);
1983 auto test = [&](Json::Value const& jv,
1984 std::string const& mptField) {
1985 txWithAmounts.erase(
1986 jv[jss::TransactionType].asString() + mptField);
1987
1988 // tx is signed
1989 auto jtx = env.jt(jv);
1990 Serializer s;
1991 jtx.stx->add(s);
1992 auto jrr = env.rpc("submit", strHex(s.slice()));
1993 BEAST_EXPECT(
1994 jrr[jss::result][jss::error] == "invalidTransaction");
1995
1996 // tx is unsigned
1997 Json::Value jv1;
1998 jv1[jss::secret] = alice.name();
1999 jv1[jss::tx_json] = jv;
2000 jrr = env.rpc("json", "submit", to_string(jv1));
2001 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2002
2003 jrr = env.rpc("json", "sign", to_string(jv1));
2004 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2005 };
2006 auto toSFieldRef = [](SField const& field) {
2007 return std::ref(field);
2008 };
2009 auto setMPTFields = [&](SField const& field,
2010 Json::Value& jv,
2011 bool withAmount = true) {
2012 jv[jss::Asset] = to_json(xrpIssue());
2013 jv[jss::Asset2] = to_json(USD.issue());
2014 if (withAmount)
2015 jv[field.fieldName] =
2016 USD(10).value().getJson(JsonOptions::none);
2017 if (field == sfAsset)
2018 jv[jss::Asset] = to_json(mpt.get<MPTIssue>());
2019 else if (field == sfAsset2)
2020 jv[jss::Asset2] = to_json(mpt.get<MPTIssue>());
2021 else
2022 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2023 };
2024 // All transactions with sfAmount, which don't support MPT.
2025 // Transactions with amount fields, which can't be MPT.
2026 // Transactions with issue fields, which can't be MPT.
2027
2028 // AMMCreate
2029 auto ammCreate = [&](SField const& field) {
2030 Json::Value jv;
2031 jv[jss::TransactionType] = jss::AMMCreate;
2032 jv[jss::Account] = alice.human();
2033 jv[jss::Amount] = (field.fieldName == sfAmount.fieldName)
2034 ? mpt.getJson(JsonOptions::none)
2035 : "100000000";
2036 jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName)
2037 ? mpt.getJson(JsonOptions::none)
2038 : "100000000";
2039 jv[jss::TradingFee] = 0;
2040 test(jv, field.fieldName);
2041 };
2042 ammCreate(sfAmount);
2043 ammCreate(sfAmount2);
2044 // AMMDeposit
2045 auto ammDeposit = [&](SField const& field) {
2046 Json::Value jv;
2047 jv[jss::TransactionType] = jss::AMMDeposit;
2048 jv[jss::Account] = alice.human();
2049 jv[jss::Flags] = tfSingleAsset;
2050 setMPTFields(field, jv);
2051 test(jv, field.fieldName);
2052 };
2053 for (SField const& field :
2054 {toSFieldRef(sfAmount),
2055 toSFieldRef(sfAmount2),
2056 toSFieldRef(sfEPrice),
2057 toSFieldRef(sfLPTokenOut),
2058 toSFieldRef(sfAsset),
2059 toSFieldRef(sfAsset2)})
2060 ammDeposit(field);
2061 // AMMWithdraw
2062 auto ammWithdraw = [&](SField const& field) {
2063 Json::Value jv;
2064 jv[jss::TransactionType] = jss::AMMWithdraw;
2065 jv[jss::Account] = alice.human();
2066 jv[jss::Flags] = tfSingleAsset;
2067 setMPTFields(field, jv);
2068 test(jv, field.fieldName);
2069 };
2070 ammWithdraw(sfAmount);
2071 for (SField const& field :
2072 {toSFieldRef(sfAmount2),
2073 toSFieldRef(sfEPrice),
2074 toSFieldRef(sfLPTokenIn),
2075 toSFieldRef(sfAsset),
2076 toSFieldRef(sfAsset2)})
2077 ammWithdraw(field);
2078 // AMMBid
2079 auto ammBid = [&](SField const& field) {
2080 Json::Value jv;
2081 jv[jss::TransactionType] = jss::AMMBid;
2082 jv[jss::Account] = alice.human();
2083 setMPTFields(field, jv);
2084 test(jv, field.fieldName);
2085 };
2086 for (SField const& field :
2087 {toSFieldRef(sfBidMin),
2088 toSFieldRef(sfBidMax),
2089 toSFieldRef(sfAsset),
2090 toSFieldRef(sfAsset2)})
2091 ammBid(field);
2092 // AMMClawback
2093 auto ammClawback = [&](SField const& field) {
2094 Json::Value jv;
2095 jv[jss::TransactionType] = jss::AMMClawback;
2096 jv[jss::Account] = alice.human();
2097 jv[jss::Holder] = carol.human();
2098 setMPTFields(field, jv);
2099 test(jv, field.fieldName);
2100 };
2101 for (SField const& field :
2102 {toSFieldRef(sfAmount),
2103 toSFieldRef(sfAsset),
2104 toSFieldRef(sfAsset2)})
2105 ammClawback(field);
2106 // AMMDelete
2107 auto ammDelete = [&](SField const& field) {
2108 Json::Value jv;
2109 jv[jss::TransactionType] = jss::AMMDelete;
2110 jv[jss::Account] = alice.human();
2111 setMPTFields(field, jv, false);
2112 test(jv, field.fieldName);
2113 };
2114 ammDelete(sfAsset);
2115 ammDelete(sfAsset2);
2116 // AMMVote
2117 auto ammVote = [&](SField const& field) {
2118 Json::Value jv;
2119 jv[jss::TransactionType] = jss::AMMVote;
2120 jv[jss::Account] = alice.human();
2121 jv[jss::TradingFee] = 100;
2122 setMPTFields(field, jv, false);
2123 test(jv, field.fieldName);
2124 };
2125 ammVote(sfAsset);
2126 ammVote(sfAsset2);
2127 // CheckCash
2128 auto checkCash = [&](SField const& field) {
2129 Json::Value jv;
2130 jv[jss::TransactionType] = jss::CheckCash;
2131 jv[jss::Account] = alice.human();
2132 jv[sfCheckID.fieldName] = to_string(uint256{1});
2133 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2134 test(jv, field.fieldName);
2135 };
2136 checkCash(sfAmount);
2137 checkCash(sfDeliverMin);
2138 // CheckCreate
2139 {
2140 Json::Value jv;
2141 jv[jss::TransactionType] = jss::CheckCreate;
2142 jv[jss::Account] = alice.human();
2143 jv[jss::Destination] = carol.human();
2144 jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
2145 test(jv, jss::SendMax.c_str());
2146 }
2147 // OfferCreate
2148 {
2149 Json::Value jv = offer(alice, USD(100), mpt);
2150 test(jv, jss::TakerPays.c_str());
2151 jv = offer(alice, mpt, USD(100));
2152 test(jv, jss::TakerGets.c_str());
2153 }
2154 // PaymentChannelCreate
2155 {
2156 Json::Value jv;
2157 jv[jss::TransactionType] = jss::PaymentChannelCreate;
2158 jv[jss::Account] = alice.human();
2159 jv[jss::Destination] = carol.human();
2160 jv[jss::SettleDelay] = 1;
2161 jv[sfPublicKey.fieldName] = strHex(alice.pk().slice());
2162 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2163 test(jv, jss::Amount.c_str());
2164 }
2165 // PaymentChannelFund
2166 {
2167 Json::Value jv;
2168 jv[jss::TransactionType] = jss::PaymentChannelFund;
2169 jv[jss::Account] = alice.human();
2170 jv[sfChannel.fieldName] = to_string(uint256{1});
2171 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2172 test(jv, jss::Amount.c_str());
2173 }
2174 // PaymentChannelClaim
2175 {
2176 Json::Value jv;
2177 jv[jss::TransactionType] = jss::PaymentChannelClaim;
2178 jv[jss::Account] = alice.human();
2179 jv[sfChannel.fieldName] = to_string(uint256{1});
2180 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2181 test(jv, jss::Amount.c_str());
2182 }
2183 // NFTokenCreateOffer
2184 {
2185 Json::Value jv;
2186 jv[jss::TransactionType] = jss::NFTokenCreateOffer;
2187 jv[jss::Account] = alice.human();
2188 jv[sfNFTokenID.fieldName] = to_string(uint256{1});
2189 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2190 test(jv, jss::Amount.c_str());
2191 }
2192 // NFTokenAcceptOffer
2193 {
2194 Json::Value jv;
2195 jv[jss::TransactionType] = jss::NFTokenAcceptOffer;
2196 jv[jss::Account] = alice.human();
2197 jv[sfNFTokenBrokerFee.fieldName] =
2198 mpt.getJson(JsonOptions::none);
2199 test(jv, sfNFTokenBrokerFee.fieldName);
2200 }
2201 // NFTokenMint
2202 {
2203 Json::Value jv;
2204 jv[jss::TransactionType] = jss::NFTokenMint;
2205 jv[jss::Account] = alice.human();
2206 jv[sfNFTokenTaxon.fieldName] = 1;
2207 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
2208 test(jv, jss::Amount.c_str());
2209 }
2210 // TrustSet
2211 auto trustSet = [&](SField const& field) {
2212 Json::Value jv;
2213 jv[jss::TransactionType] = jss::TrustSet;
2214 jv[jss::Account] = alice.human();
2215 jv[jss::Flags] = 0;
2216 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
2217 test(jv, field.fieldName);
2218 };
2219 trustSet(sfLimitAmount);
2220 trustSet(sfFee);
2221 // XChainCommit
2222 {
2223 Json::Value const jv = xchain_commit(alice, jvb, 1, mpt);
2224 test(jv, jss::Amount.c_str());
2225 }
2226 // XChainClaim
2227 {
2228 Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice);
2229 test(jv, jss::Amount.c_str());
2230 }
2231 // XChainCreateClaimID
2232 {
2233 Json::Value const jv =
2234 xchain_create_claim_id(alice, jvb, mpt, alice);
2235 test(jv, sfSignatureReward.fieldName);
2236 }
2237 // XChainAddClaimAttestation
2238 {
2239 Json::Value const jv = claim_attestation(
2240 alice,
2241 jvb,
2242 alice,
2243 mpt,
2244 alice,
2245 true,
2246 1,
2247 alice,
2248 signer(alice));
2249 test(jv, jss::Amount.c_str());
2250 }
2251 // XChainAddAccountCreateAttestation
2252 {
2254 alice,
2255 jvb,
2256 alice,
2257 mpt,
2258 XRP(10),
2259 alice,
2260 false,
2261 1,
2262 alice,
2263 signer(alice));
2264 for (auto const& field :
2265 {sfAmount.fieldName, sfSignatureReward.fieldName})
2266 {
2267 jv[field] = mpt.getJson(JsonOptions::none);
2268 test(jv, field);
2269 }
2270 }
2271 // XChainAccountCreateCommit
2272 {
2274 alice, jvb, alice, mpt, XRP(10));
2275 for (auto const& field :
2276 {sfAmount.fieldName, sfSignatureReward.fieldName})
2277 {
2278 jv[field] = mpt.getJson(JsonOptions::none);
2279 test(jv, field);
2280 }
2281 }
2282 // XChain[Create|Modify]Bridge
2283 auto bridgeTx = [&](Json::StaticString const& tt,
2284 STAmount const& rewardAmount,
2285 STAmount const& minAccountAmount,
2286 std::string const& field) {
2287 Json::Value jv;
2288 jv[jss::TransactionType] = tt;
2289 jv[jss::Account] = alice.human();
2290 jv[sfXChainBridge.fieldName] = jvb;
2291 jv[sfSignatureReward.fieldName] =
2292 rewardAmount.getJson(JsonOptions::none);
2293 jv[sfMinAccountCreateAmount.fieldName] =
2294 minAccountAmount.getJson(JsonOptions::none);
2295 test(jv, field);
2296 };
2297 auto reward = STAmount{sfSignatureReward, mpt};
2298 auto minAmount = STAmount{sfMinAccountCreateAmount, USD(10)};
2299 for (SField const& field :
2300 {std::ref(sfSignatureReward),
2301 std::ref(sfMinAccountCreateAmount)})
2302 {
2303 bridgeTx(
2304 jss::XChainCreateBridge,
2305 reward,
2306 minAmount,
2307 field.fieldName);
2308 bridgeTx(
2309 jss::XChainModifyBridge,
2310 reward,
2311 minAmount,
2312 field.fieldName);
2313 reward = STAmount{sfSignatureReward, USD(10)};
2314 minAmount = STAmount{sfMinAccountCreateAmount, mpt};
2315 }
2316 }
2317 BEAST_EXPECT(txWithAmounts.empty());
2318 }
2319
2320 void
2322 {
2323 // checks synthetically injected mptissuanceid from `tx` response
2324 testcase("Test synthetic fields from tx response");
2325
2326 using namespace test::jtx;
2327
2328 Account const alice{"alice"};
2329
2330 auto cfg = envconfig();
2331 cfg->FEES.reference_fee = 10;
2332 Env env{*this, std::move(cfg), features};
2333 MPTTester mptAlice(env, alice);
2334
2335 mptAlice.create();
2336
2337 std::string const txHash{
2338 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
2339 BEAST_EXPECTS(
2340 txHash ==
2341 "E11F0E0CA14219922B7881F060B9CEE67CFBC87E4049A441ED2AE348FF8FAC"
2342 "0E",
2343 txHash);
2344 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
2345 auto const id = meta[jss::mpt_issuance_id].asString();
2346 // Expect mpt_issuance_id field
2347 BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
2348 BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
2349 BEAST_EXPECTS(
2350 id == "00000004AE123A8556F3CF91154711376AFB0F894F832B3D", id);
2351 }
2352
2353 void
2355 {
2356 testcase("MPT clawback validations");
2357 using namespace test::jtx;
2358
2359 // Make sure clawback cannot work when featureMPTokensV1 is disabled
2360 {
2361 Env env(*this, features - featureMPTokensV1);
2362 Account const alice{"alice"};
2363 Account const bob{"bob"};
2364
2365 env.fund(XRP(1000), alice, bob);
2366 env.close();
2367
2368 auto const USD = alice["USD"];
2369 auto const mpt = ripple::test::jtx::MPT(
2370 alice.name(), makeMptID(env.seq(alice), alice));
2371
2372 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
2373 env.close();
2374
2375 env(claw(alice, mpt(5)), ter(temDISABLED));
2376 env.close();
2377
2378 env(claw(alice, mpt(5), bob), ter(temDISABLED));
2379 env.close();
2380 }
2381
2382 // Test preflight
2383 {
2384 Env env(*this, features);
2385 Account const alice{"alice"};
2386 Account const bob{"bob"};
2387
2388 env.fund(XRP(1000), alice, bob);
2389 env.close();
2390
2391 auto const USD = alice["USD"];
2392 auto const mpt = ripple::test::jtx::MPT(
2393 alice.name(), makeMptID(env.seq(alice), alice));
2394
2395 // clawing back IOU from a MPT holder fails
2396 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
2397 env.close();
2398
2399 // clawing back MPT without specifying a holder fails
2400 env(claw(alice, mpt(5)), ter(temMALFORMED));
2401 env.close();
2402
2403 // clawing back zero amount fails
2404 env(claw(alice, mpt(0), bob), ter(temBAD_AMOUNT));
2405 env.close();
2406
2407 // alice can't claw back from herself
2408 env(claw(alice, mpt(5), alice), ter(temMALFORMED));
2409 env.close();
2410
2411 // can't clawback negative amount
2412 env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT));
2413 env.close();
2414 }
2415
2416 // Preclaim - clawback fails when MPTCanClawback is disabled on issuance
2417 {
2418 Env env(*this, features);
2419 Account const alice{"alice"};
2420 Account const bob{"bob"};
2421
2422 MPTTester mptAlice(env, alice, {.holders = {bob}});
2423
2424 // enable asfAllowTrustLineClawback for alice
2425 env(fset(alice, asfAllowTrustLineClawback));
2426 env.close();
2428
2429 // Create issuance without enabling clawback
2430 mptAlice.create({.ownerCount = 1, .holderCount = 0});
2431
2432 mptAlice.authorize({.account = bob});
2433
2434 mptAlice.pay(alice, bob, 100);
2435
2436 // alice cannot clawback before she didn't enable MPTCanClawback
2437 // asfAllowTrustLineClawback has no effect
2438 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
2439 }
2440
2441 // Preclaim - test various scenarios
2442 {
2443 Env env(*this, features);
2444 Account const alice{"alice"};
2445 Account const bob{"bob"};
2446 Account const carol{"carol"};
2447 env.fund(XRP(1000), carol);
2448 env.close();
2449 MPTTester mptAlice(env, alice, {.holders = {bob}});
2450
2451 auto const fakeMpt = ripple::test::jtx::MPT(
2452 alice.name(), makeMptID(env.seq(alice), alice));
2453
2454 // issuer tries to clawback MPT where issuance doesn't exist
2455 env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND));
2456 env.close();
2457
2458 // alice creates issuance
2459 mptAlice.create(
2460 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2461
2462 // alice tries to clawback from someone who doesn't have MPToken
2463 mptAlice.claw(alice, bob, 1, tecOBJECT_NOT_FOUND);
2464
2465 // bob creates a MPToken
2466 mptAlice.authorize({.account = bob});
2467
2468 // clawback fails because bob currently has a balance of zero
2469 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2470
2471 // alice pays bob 100 tokens
2472 mptAlice.pay(alice, bob, 100);
2473
2474 // carol fails tries to clawback from bob because he is not the
2475 // issuer
2476 mptAlice.claw(carol, bob, 1, tecNO_PERMISSION);
2477 }
2478
2479 // clawback more than max amount
2480 // fails in the json parser before
2481 // transactor is called
2482 {
2483 Env env(*this, features);
2484 Account const alice{"alice"};
2485 Account const bob{"bob"};
2486
2487 env.fund(XRP(1000), alice, bob);
2488 env.close();
2489
2490 auto const mpt = ripple::test::jtx::MPT(
2491 alice.name(), makeMptID(env.seq(alice), alice));
2492
2493 Json::Value jv = claw(alice, mpt(1), bob);
2494 jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
2495 Json::Value jv1;
2496 jv1[jss::secret] = alice.name();
2497 jv1[jss::tx_json] = jv;
2498 auto const jrr = env.rpc("json", "submit", to_string(jv1));
2499 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2500 }
2501 }
2502
2503 void
2505 {
2506 testcase("MPT Clawback");
2507 using namespace test::jtx;
2508
2509 {
2510 Env env(*this, features);
2511 Account const alice{"alice"};
2512 Account const bob{"bob"};
2513
2514 MPTTester mptAlice(env, alice, {.holders = {bob}});
2515
2516 // alice creates issuance
2517 mptAlice.create(
2518 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2519
2520 // bob creates a MPToken
2521 mptAlice.authorize({.account = bob});
2522
2523 // alice pays bob 100 tokens
2524 mptAlice.pay(alice, bob, 100);
2525
2526 mptAlice.claw(alice, bob, 1);
2527
2528 mptAlice.claw(alice, bob, 1000);
2529
2530 // clawback fails because bob currently has a balance of zero
2531 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2532 }
2533
2534 // Test that globally locked funds can be clawed
2535 {
2536 Env env(*this, features);
2537 Account const alice{"alice"};
2538 Account const bob{"bob"};
2539
2540 MPTTester mptAlice(env, alice, {.holders = {bob}});
2541
2542 // alice creates issuance
2543 mptAlice.create(
2544 {.ownerCount = 1,
2545 .holderCount = 0,
2546 .flags = tfMPTCanLock | tfMPTCanClawback});
2547
2548 // bob creates a MPToken
2549 mptAlice.authorize({.account = bob});
2550
2551 // alice pays bob 100 tokens
2552 mptAlice.pay(alice, bob, 100);
2553
2554 mptAlice.set({.account = alice, .flags = tfMPTLock});
2555
2556 mptAlice.claw(alice, bob, 100);
2557 }
2558
2559 // Test that individually locked funds can be clawed
2560 {
2561 Env env(*this, features);
2562 Account const alice{"alice"};
2563 Account const bob{"bob"};
2564
2565 MPTTester mptAlice(env, alice, {.holders = {bob}});
2566
2567 // alice creates issuance
2568 mptAlice.create(
2569 {.ownerCount = 1,
2570 .holderCount = 0,
2571 .flags = tfMPTCanLock | tfMPTCanClawback});
2572
2573 // bob creates a MPToken
2574 mptAlice.authorize({.account = bob});
2575
2576 // alice pays bob 100 tokens
2577 mptAlice.pay(alice, bob, 100);
2578
2579 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
2580
2581 mptAlice.claw(alice, bob, 100);
2582 }
2583
2584 // Test that unauthorized funds can be clawed back
2585 {
2586 Env env(*this, features);
2587 Account const alice{"alice"};
2588 Account const bob{"bob"};
2589
2590 MPTTester mptAlice(env, alice, {.holders = {bob}});
2591
2592 // alice creates issuance
2593 mptAlice.create(
2594 {.ownerCount = 1,
2595 .holderCount = 0,
2597
2598 // bob creates a MPToken
2599 mptAlice.authorize({.account = bob});
2600
2601 // alice authorizes bob
2602 mptAlice.authorize({.account = alice, .holder = bob});
2603
2604 // alice pays bob 100 tokens
2605 mptAlice.pay(alice, bob, 100);
2606
2607 // alice unauthorizes bob
2608 mptAlice.authorize(
2609 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
2610
2611 mptAlice.claw(alice, bob, 100);
2612 }
2613 }
2614
2615 void
2617 {
2618 using namespace test::jtx;
2619 testcase("Tokens Equality");
2620 Currency const cur1{to_currency("CU1")};
2621 Currency const cur2{to_currency("CU2")};
2622 Account const gw1{"gw1"};
2623 Account const gw2{"gw2"};
2624 MPTID const mpt1 = makeMptID(1, gw1);
2625 MPTID const mpt1a = makeMptID(1, gw1);
2626 MPTID const mpt2 = makeMptID(1, gw2);
2627 MPTID const mpt3 = makeMptID(2, gw2);
2628 Asset const assetCur1Gw1{Issue{cur1, gw1}};
2629 Asset const assetCur1Gw1a{Issue{cur1, gw1}};
2630 Asset const assetCur2Gw1{Issue{cur2, gw1}};
2631 Asset const assetCur2Gw2{Issue{cur2, gw2}};
2632 Asset const assetMpt1Gw1{mpt1};
2633 Asset const assetMpt1Gw1a{mpt1a};
2634 Asset const assetMpt1Gw2{mpt2};
2635 Asset const assetMpt2Gw2{mpt3};
2636
2637 // Assets holding Issue
2638 // Currencies are equal regardless of the issuer
2639 BEAST_EXPECT(equalTokens(assetCur1Gw1, assetCur1Gw1a));
2640 BEAST_EXPECT(equalTokens(assetCur2Gw1, assetCur2Gw2));
2641 // Currencies are different regardless of whether the issuers
2642 // are the same or not
2643 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw1));
2644 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw2));
2645
2646 // Assets holding MPTIssue
2647 // MPTIDs are the same if the sequence and the issuer are the same
2648 BEAST_EXPECT(equalTokens(assetMpt1Gw1, assetMpt1Gw1a));
2649 // MPTIDs are different if sequence and the issuer don't match
2650 BEAST_EXPECT(!equalTokens(assetMpt1Gw1, assetMpt1Gw2));
2651 BEAST_EXPECT(!equalTokens(assetMpt1Gw2, assetMpt2Gw2));
2652
2653 // Assets holding Issue and MPTIssue
2654 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetMpt1Gw1));
2655 BEAST_EXPECT(!equalTokens(assetMpt2Gw2, assetCur2Gw2));
2656 }
2657
2658 void
2660 {
2661 using namespace test::jtx;
2662 Account const gw{"gw"};
2663 Asset const asset1{makeMptID(1, gw)};
2664 Asset const asset2{makeMptID(2, gw)};
2665 Asset const asset3{makeMptID(3, gw)};
2666 STAmount const amt1{asset1, 100};
2667 STAmount const amt2{asset2, 100};
2668 STAmount const amt3{asset3, 10'000};
2669
2670 {
2671 testcase("Test STAmount MPT arithmetics");
2672 using namespace std::string_literals;
2673 STAmount res = multiply(amt1, amt2, asset3);
2674 BEAST_EXPECT(res == amt3);
2675
2676 res = mulRound(amt1, amt2, asset3, true);
2677 BEAST_EXPECT(res == amt3);
2678
2679 res = mulRoundStrict(amt1, amt2, asset3, true);
2680 BEAST_EXPECT(res == amt3);
2681
2682 // overflow, any value > 3037000499ull
2683 STAmount mptOverflow{asset2, UINT64_C(3037000500)};
2684 try
2685 {
2686 res = multiply(mptOverflow, mptOverflow, asset3);
2687 fail("should throw runtime exception 1");
2688 }
2689 catch (std::runtime_error const& e)
2690 {
2691 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2692 }
2693 // overflow, (v1 >> 32) * v2 > 2147483648ull
2694 mptOverflow = STAmount{asset2, UINT64_C(2147483648)};
2695 uint64_t const mantissa = (2ull << 32) + 2;
2696 try
2697 {
2698 res = multiply(STAmount{asset1, mantissa}, mptOverflow, asset3);
2699 fail("should throw runtime exception 2");
2700 }
2701 catch (std::runtime_error const& e)
2702 {
2703 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2704 }
2705 }
2706
2707 {
2708 testcase("Test MPTAmount arithmetics");
2709 MPTAmount mptAmt1{100};
2710 MPTAmount const mptAmt2{100};
2711 BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200});
2712 BEAST_EXPECT(mptAmt1 == 200);
2713 BEAST_EXPECT((mptAmt1 -= mptAmt2) == mptAmt1);
2714 BEAST_EXPECT(mptAmt1 == mptAmt2);
2715 BEAST_EXPECT(mptAmt1 == 100);
2716 BEAST_EXPECT(MPTAmount::minPositiveAmount() == MPTAmount{1});
2717 }
2718
2719 {
2720 testcase("Test MPTIssue from/to Json");
2721 MPTIssue const issue1{asset1.get<MPTIssue>()};
2722 Json::Value const jv = to_json(issue1);
2723 BEAST_EXPECT(
2724 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2725 BEAST_EXPECT(issue1 == mptIssueFromJson(jv));
2726 }
2727
2728 {
2729 testcase("Test Asset from/to Json");
2730 Json::Value const jv = to_json(asset1);
2731 BEAST_EXPECT(
2732 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2733 BEAST_EXPECT(
2734 to_string(jv) ==
2735 "{\"mpt_issuance_id\":"
2736 "\"00000001A407AF5856CCF3C42619DAA925813FC955C72983\"}");
2737 BEAST_EXPECT(asset1 == assetFromJson(jv));
2738 }
2739 }
2740
2741public:
2742 void
2743 run() override
2744 {
2745 using namespace test::jtx;
2747
2748 // MPTokenIssuanceCreate
2749 testCreateValidation(all - featureSingleAssetVault);
2751 (all | featureSingleAssetVault) - featurePermissionedDomains);
2752 testCreateValidation(all | featureSingleAssetVault);
2753 testCreateEnabled(all - featureSingleAssetVault);
2754 testCreateEnabled(all | featureSingleAssetVault);
2755
2756 // MPTokenIssuanceDestroy
2757 testDestroyValidation(all - featureSingleAssetVault);
2758 testDestroyValidation(all | featureSingleAssetVault);
2759 testDestroyEnabled(all - featureSingleAssetVault);
2760 testDestroyEnabled(all | featureSingleAssetVault);
2761
2762 // MPTokenAuthorize
2763 testAuthorizeValidation(all - featureSingleAssetVault);
2764 testAuthorizeValidation(all | featureSingleAssetVault);
2765 testAuthorizeEnabled(all - featureSingleAssetVault);
2766 testAuthorizeEnabled(all | featureSingleAssetVault);
2767
2768 // MPTokenIssuanceSet
2769 testSetValidation(all - featureSingleAssetVault);
2771 (all | featureSingleAssetVault) - featurePermissionedDomains);
2772 testSetValidation(all | featureSingleAssetVault);
2773
2774 testSetEnabled(all - featureSingleAssetVault);
2775 testSetEnabled(all | featureSingleAssetVault);
2776
2777 // MPT clawback
2780
2781 // Test Direct Payment
2782 testPayment(all | featureSingleAssetVault);
2784 testDepositPreauth(all - featureCredentials);
2785
2786 // Test MPT Amount is invalid in Tx, which don't support MPT
2788
2789 // Test parsed MPTokenIssuanceID in API response metadata
2791
2792 // Test tokens equality
2794
2795 // Test helpers
2797 }
2798};
2799
2800BEAST_DEFINE_TESTSUITE_PRIO(MPToken, app, ripple, 2);
2801
2802} // namespace test
2803} // namespace ripple
Lightweight wrapper to tag static string.
Definition json_value.h:63
Represents a JSON value.
Definition json_value.h:149
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
A currency issued by an account.
Definition Issue.h:33
static MPTAmount minPositiveAmount()
Definition MPTAmount.cpp:63
Slice slice() const noexcept
Definition PublicKey.h:122
Identifies fields.
Definition SField.h:143
Slice slice() const noexcept
Definition Serializer.h:66
static TxFormats const & getInstance()
Definition TxFormats.cpp:70
void run() override
Runs the suite.
void testCreateValidation(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 testPayment(FeatureBitset features)
void testMPTInvalidInTx(FeatureBitset features)
void testAuthorizeEnabled(FeatureBitset features)
void testDepositPreauth(FeatureBitset features)
void testDestroyValidation(FeatureBitset features)
void testSetEnabled(FeatureBitset features)
void testTxJsonMetaFields(FeatureBitset features)
Immutable cryptographic account descriptor.
Definition Account.h:39
PublicKey const & pk() const
Return the public key.
Definition Account.h:94
AccountID id() const
Returns the Account ID.
Definition Account.h:111
std::string const & name() const
Return the name.
Definition Account.h:87
std::string const & human() const
Returns the human readable public key.
Definition Account.h:118
A transaction testing environment.
Definition Env.h:121
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:258
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:544
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:279
void authorize(MPTAuthorize const &arg=MPTAuthorize{})
Definition mpt.cpp:148
void set(MPTSet const &set={})
Definition mpt.cpp:221
MPTID const & issuanceID() const
Definition mpt.h:211
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:87
void destroy(MPTDestroy const &arg=MPTDestroy{})
Definition mpt.cpp:119
Converts to MPT Issue or STAmount.
Sets the DeliverMin on a JTx.
Definition delivermin.h:33
Match set account flags.
Definition flags.h:128
Add a path.
Definition paths.h:58
Sets the SendMax on a JTx.
Definition sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
T empty(T... args)
T erase(T... args)
T insert(T... args)
T is_same_v
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:48
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:62
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
Json::Value unauth(Account const &account, Account const &unauth)
Remove preauthorization for deposit.
Definition deposit.cpp:43
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:32
Json::Value authCredentials(jtx::Account const &account, std::vector< AuthorizeCredentials > const &auth)
Definition deposit.cpp:54
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
Json::Value create_account_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, jtx::signer const &signer)
Json::Value claim_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t claimID, std::optional< jtx::Account > const &dst, jtx::signer const &signer)
Json::Value bridge(Account const &lockingChainDoor, Issue const &lockingChainIssue, Account const &issuingChainDoor, Issue const &issuingChainIssue)
Json::Value claw(Account const &account, STAmount const &amount, std::optional< Account > const &mptHolder)
Definition trust.cpp:69
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value sidechain_xchain_account_create(Account const &acc, Json::Value const &bridge, Account const &dst, AnyAmount const &amt, AnyAmount const &reward)
Json::Value xchain_claim(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, Account const &dst)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:54
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
Json::Value xchain_commit(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, std::optional< Account > const &dst)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:105
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
constexpr std::uint32_t tfSingleAsset
Definition TxFlags.h:211
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition Asset.h:201
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:85
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:149
base_uint< 256 > uint256
Definition base_uint.h:558
Asset assetFromJson(Json::Value const &jv)
Definition Asset.cpp:77
constexpr std::uint32_t const tfMPTCanTrade
Definition TxFlags.h:148
constexpr std::uint32_t const tfMPTUnlock
Definition TxFlags.h:160
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:117
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition Protocol.h:83
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition Rate2.cpp:53
MPTIssue mptIssueFromJson(Json::Value const &jv)
Definition MPTIssue.cpp:78
Json::Value to_json(Asset const &asset)
Definition Asset.h:123
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:155
@ tecNO_DST
Definition TER.h:290
@ tecOBJECT_NOT_FOUND
Definition TER.h:326
@ tecDUPLICATE
Definition TER.h:315
@ tecINSUFFICIENT_FUNDS
Definition TER.h:325
@ tecNO_PERMISSION
Definition TER.h:305
@ tecHAS_OBLIGATIONS
Definition TER.h:317
@ tecPATH_PARTIAL
Definition TER.h:282
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecNO_AUTH
Definition TER.h:300
@ tecLOCKED
Definition TER.h:358
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:159
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:107
@ tesSUCCESS
Definition TER.h:244
constexpr std::uint32_t tfLimitQuality
Definition TxFlags.h:109
@ soeMPTNotSupported
Definition SOTemplate.h:43
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:94
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:170
STAmount mulRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
constexpr std::uint32_t const tfMPTCanEscrow
Definition TxFlags.h:147
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:146
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:145
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:150
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:84
@ temBAD_AMOUNT
Definition TER.h:89
@ temREDUNDANT
Definition TER.h:112
@ temBAD_TRANSFER_FEE
Definition TER.h:142
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
T ref(T... args)
A signer in a SignerList.
Definition multisign.h:39
T what(T... args)