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