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