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, testable_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 // OfferCreate
1698 {
1699 Json::Value jv = offer(alice, USD(100), mpt);
1700 test(jv, jss::TakerPays.c_str());
1701 jv = offer(alice, mpt, USD(100));
1702 test(jv, jss::TakerGets.c_str());
1703 }
1704 // PaymentChannelCreate
1705 {
1706 Json::Value jv;
1707 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1708 jv[jss::Account] = alice.human();
1709 jv[jss::Destination] = carol.human();
1710 jv[jss::SettleDelay] = 1;
1711 jv[sfPublicKey.fieldName] = strHex(alice.pk().slice());
1712 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1713 test(jv, jss::Amount.c_str());
1714 }
1715 // PaymentChannelFund
1716 {
1717 Json::Value jv;
1718 jv[jss::TransactionType] = jss::PaymentChannelFund;
1719 jv[jss::Account] = alice.human();
1720 jv[sfChannel.fieldName] = to_string(uint256{1});
1721 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1722 test(jv, jss::Amount.c_str());
1723 }
1724 // PaymentChannelClaim
1725 {
1726 Json::Value jv;
1727 jv[jss::TransactionType] = jss::PaymentChannelClaim;
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 // NFTokenCreateOffer
1734 {
1735 Json::Value jv;
1736 jv[jss::TransactionType] = jss::NFTokenCreateOffer;
1737 jv[jss::Account] = alice.human();
1738 jv[sfNFTokenID.fieldName] = to_string(uint256{1});
1739 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1740 test(jv, jss::Amount.c_str());
1741 }
1742 // NFTokenAcceptOffer
1743 {
1744 Json::Value jv;
1745 jv[jss::TransactionType] = jss::NFTokenAcceptOffer;
1746 jv[jss::Account] = alice.human();
1747 jv[sfNFTokenBrokerFee.fieldName] =
1748 mpt.getJson(JsonOptions::none);
1749 test(jv, sfNFTokenBrokerFee.fieldName);
1750 }
1751 // NFTokenMint
1752 {
1753 Json::Value jv;
1754 jv[jss::TransactionType] = jss::NFTokenMint;
1755 jv[jss::Account] = alice.human();
1756 jv[sfNFTokenTaxon.fieldName] = 1;
1757 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1758 test(jv, jss::Amount.c_str());
1759 }
1760 // TrustSet
1761 auto trustSet = [&](SField const& field) {
1762 Json::Value jv;
1763 jv[jss::TransactionType] = jss::TrustSet;
1764 jv[jss::Account] = alice.human();
1765 jv[jss::Flags] = 0;
1766 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
1767 test(jv, field.fieldName);
1768 };
1769 trustSet(sfLimitAmount);
1770 trustSet(sfFee);
1771 // XChainCommit
1772 {
1773 Json::Value const jv = xchain_commit(alice, jvb, 1, mpt);
1774 test(jv, jss::Amount.c_str());
1775 }
1776 // XChainClaim
1777 {
1778 Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice);
1779 test(jv, jss::Amount.c_str());
1780 }
1781 // XChainCreateClaimID
1782 {
1783 Json::Value const jv =
1784 xchain_create_claim_id(alice, jvb, mpt, alice);
1785 test(jv, sfSignatureReward.fieldName);
1786 }
1787 // XChainAddClaimAttestation
1788 {
1789 Json::Value const jv = claim_attestation(
1790 alice,
1791 jvb,
1792 alice,
1793 mpt,
1794 alice,
1795 true,
1796 1,
1797 alice,
1798 signer(alice));
1799 test(jv, jss::Amount.c_str());
1800 }
1801 // XChainAddAccountCreateAttestation
1802 {
1804 alice,
1805 jvb,
1806 alice,
1807 mpt,
1808 XRP(10),
1809 alice,
1810 false,
1811 1,
1812 alice,
1813 signer(alice));
1814 for (auto const& field :
1815 {sfAmount.fieldName, sfSignatureReward.fieldName})
1816 {
1817 jv[field] = mpt.getJson(JsonOptions::none);
1818 test(jv, field);
1819 }
1820 }
1821 // XChainAccountCreateCommit
1822 {
1824 alice, jvb, alice, mpt, XRP(10));
1825 for (auto const& field :
1826 {sfAmount.fieldName, sfSignatureReward.fieldName})
1827 {
1828 jv[field] = mpt.getJson(JsonOptions::none);
1829 test(jv, field);
1830 }
1831 }
1832 // XChain[Create|Modify]Bridge
1833 auto bridgeTx = [&](Json::StaticString const& tt,
1834 STAmount const& rewardAmount,
1835 STAmount const& minAccountAmount,
1836 std::string const& field) {
1837 Json::Value jv;
1838 jv[jss::TransactionType] = tt;
1839 jv[jss::Account] = alice.human();
1840 jv[sfXChainBridge.fieldName] = jvb;
1841 jv[sfSignatureReward.fieldName] =
1842 rewardAmount.getJson(JsonOptions::none);
1843 jv[sfMinAccountCreateAmount.fieldName] =
1844 minAccountAmount.getJson(JsonOptions::none);
1845 test(jv, field);
1846 };
1847 auto reward = STAmount{sfSignatureReward, mpt};
1848 auto minAmount = STAmount{sfMinAccountCreateAmount, USD(10)};
1849 for (SField const& field :
1850 {std::ref(sfSignatureReward),
1851 std::ref(sfMinAccountCreateAmount)})
1852 {
1853 bridgeTx(
1854 jss::XChainCreateBridge,
1855 reward,
1856 minAmount,
1857 field.fieldName);
1858 bridgeTx(
1859 jss::XChainModifyBridge,
1860 reward,
1861 minAmount,
1862 field.fieldName);
1863 reward = STAmount{sfSignatureReward, USD(10)};
1864 minAmount = STAmount{sfMinAccountCreateAmount, mpt};
1865 }
1866 }
1867 BEAST_EXPECT(txWithAmounts.empty());
1868 }
1869
1870 void
1872 {
1873 // checks synthetically injected mptissuanceid from `tx` response
1874 testcase("Test synthetic fields from tx response");
1875
1876 using namespace test::jtx;
1877
1878 Account const alice{"alice"};
1879
1880 auto cfg = envconfig();
1881 cfg->FEES.reference_fee = 10;
1882 Env env{*this, std::move(cfg), features};
1883 MPTTester mptAlice(env, alice);
1884
1885 mptAlice.create();
1886
1887 std::string const txHash{
1888 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
1889 BEAST_EXPECTS(
1890 txHash ==
1891 "E11F0E0CA14219922B7881F060B9CEE67CFBC87E4049A441ED2AE348FF8FAC"
1892 "0E",
1893 txHash);
1894 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
1895 auto const id = meta[jss::mpt_issuance_id].asString();
1896 // Expect mpt_issuance_id field
1897 BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
1898 BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
1899 BEAST_EXPECTS(
1900 id == "00000004AE123A8556F3CF91154711376AFB0F894F832B3D", id);
1901 }
1902
1903 void
1905 {
1906 testcase("MPT clawback validations");
1907 using namespace test::jtx;
1908
1909 // Make sure clawback cannot work when featureMPTokensV1 is disabled
1910 {
1911 Env env(*this, features - featureMPTokensV1);
1912 Account const alice{"alice"};
1913 Account const bob{"bob"};
1914
1915 env.fund(XRP(1000), alice, bob);
1916 env.close();
1917
1918 auto const USD = alice["USD"];
1919 auto const mpt = ripple::test::jtx::MPT(
1920 alice.name(), makeMptID(env.seq(alice), alice));
1921
1922 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
1923 env.close();
1924
1925 env(claw(alice, mpt(5)), ter(temDISABLED));
1926 env.close();
1927
1928 env(claw(alice, mpt(5), bob), ter(temDISABLED));
1929 env.close();
1930 }
1931
1932 // Test preflight
1933 {
1934 Env env(*this, features);
1935 Account const alice{"alice"};
1936 Account const bob{"bob"};
1937
1938 env.fund(XRP(1000), alice, bob);
1939 env.close();
1940
1941 auto const USD = alice["USD"];
1942 auto const mpt = ripple::test::jtx::MPT(
1943 alice.name(), makeMptID(env.seq(alice), alice));
1944
1945 // clawing back IOU from a MPT holder fails
1946 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
1947 env.close();
1948
1949 // clawing back MPT without specifying a holder fails
1950 env(claw(alice, mpt(5)), ter(temMALFORMED));
1951 env.close();
1952
1953 // clawing back zero amount fails
1954 env(claw(alice, mpt(0), bob), ter(temBAD_AMOUNT));
1955 env.close();
1956
1957 // alice can't claw back from herself
1958 env(claw(alice, mpt(5), alice), ter(temMALFORMED));
1959 env.close();
1960
1961 // can't clawback negative amount
1962 env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT));
1963 env.close();
1964 }
1965
1966 // Preclaim - clawback fails when MPTCanClawback is disabled on issuance
1967 {
1968 Env env(*this, features);
1969 Account const alice{"alice"};
1970 Account const bob{"bob"};
1971
1972 MPTTester mptAlice(env, alice, {.holders = {bob}});
1973
1974 // enable asfAllowTrustLineClawback for alice
1975 env(fset(alice, asfAllowTrustLineClawback));
1976 env.close();
1978
1979 // Create issuance without enabling clawback
1980 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1981
1982 mptAlice.authorize({.account = bob});
1983
1984 mptAlice.pay(alice, bob, 100);
1985
1986 // alice cannot clawback before she didn't enable MPTCanClawback
1987 // asfAllowTrustLineClawback has no effect
1988 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
1989 }
1990
1991 // Preclaim - test various scenarios
1992 {
1993 Env env(*this, features);
1994 Account const alice{"alice"};
1995 Account const bob{"bob"};
1996 Account const carol{"carol"};
1997 env.fund(XRP(1000), carol);
1998 env.close();
1999 MPTTester mptAlice(env, alice, {.holders = {bob}});
2000
2001 auto const fakeMpt = ripple::test::jtx::MPT(
2002 alice.name(), makeMptID(env.seq(alice), alice));
2003
2004 // issuer tries to clawback MPT where issuance doesn't exist
2005 env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND));
2006 env.close();
2007
2008 // alice creates issuance
2009 mptAlice.create(
2010 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2011
2012 // alice tries to clawback from someone who doesn't have MPToken
2013 mptAlice.claw(alice, bob, 1, tecOBJECT_NOT_FOUND);
2014
2015 // bob creates a MPToken
2016 mptAlice.authorize({.account = bob});
2017
2018 // clawback fails because bob currently has a balance of zero
2019 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2020
2021 // alice pays bob 100 tokens
2022 mptAlice.pay(alice, bob, 100);
2023
2024 // carol fails tries to clawback from bob because he is not the
2025 // issuer
2026 mptAlice.claw(carol, bob, 1, tecNO_PERMISSION);
2027 }
2028
2029 // clawback more than max amount
2030 // fails in the json parser before
2031 // transactor is called
2032 {
2033 Env env(*this, features);
2034 Account const alice{"alice"};
2035 Account const bob{"bob"};
2036
2037 env.fund(XRP(1000), alice, bob);
2038 env.close();
2039
2040 auto const mpt = ripple::test::jtx::MPT(
2041 alice.name(), makeMptID(env.seq(alice), alice));
2042
2043 Json::Value jv = claw(alice, mpt(1), bob);
2044 jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
2045 Json::Value jv1;
2046 jv1[jss::secret] = alice.name();
2047 jv1[jss::tx_json] = jv;
2048 auto const jrr = env.rpc("json", "submit", to_string(jv1));
2049 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2050 }
2051 }
2052
2053 void
2055 {
2056 testcase("MPT Clawback");
2057 using namespace test::jtx;
2058
2059 {
2060 Env env(*this, features);
2061 Account const alice{"alice"};
2062 Account const bob{"bob"};
2063
2064 MPTTester mptAlice(env, alice, {.holders = {bob}});
2065
2066 // alice creates issuance
2067 mptAlice.create(
2068 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2069
2070 // bob creates a MPToken
2071 mptAlice.authorize({.account = bob});
2072
2073 // alice pays bob 100 tokens
2074 mptAlice.pay(alice, bob, 100);
2075
2076 mptAlice.claw(alice, bob, 1);
2077
2078 mptAlice.claw(alice, bob, 1000);
2079
2080 // clawback fails because bob currently has a balance of zero
2081 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2082 }
2083
2084 // Test that globally locked funds can be clawed
2085 {
2086 Env env(*this, features);
2087 Account const alice{"alice"};
2088 Account const bob{"bob"};
2089
2090 MPTTester mptAlice(env, alice, {.holders = {bob}});
2091
2092 // alice creates issuance
2093 mptAlice.create(
2094 {.ownerCount = 1,
2095 .holderCount = 0,
2096 .flags = tfMPTCanLock | tfMPTCanClawback});
2097
2098 // bob creates a MPToken
2099 mptAlice.authorize({.account = bob});
2100
2101 // alice pays bob 100 tokens
2102 mptAlice.pay(alice, bob, 100);
2103
2104 mptAlice.set({.account = alice, .flags = tfMPTLock});
2105
2106 mptAlice.claw(alice, bob, 100);
2107 }
2108
2109 // Test that individually locked funds can be clawed
2110 {
2111 Env env(*this, features);
2112 Account const alice{"alice"};
2113 Account const bob{"bob"};
2114
2115 MPTTester mptAlice(env, alice, {.holders = {bob}});
2116
2117 // alice creates issuance
2118 mptAlice.create(
2119 {.ownerCount = 1,
2120 .holderCount = 0,
2121 .flags = tfMPTCanLock | tfMPTCanClawback});
2122
2123 // bob creates a MPToken
2124 mptAlice.authorize({.account = bob});
2125
2126 // alice pays bob 100 tokens
2127 mptAlice.pay(alice, bob, 100);
2128
2129 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
2130
2131 mptAlice.claw(alice, bob, 100);
2132 }
2133
2134 // Test that unauthorized funds can be clawed back
2135 {
2136 Env env(*this, features);
2137 Account const alice{"alice"};
2138 Account const bob{"bob"};
2139
2140 MPTTester mptAlice(env, alice, {.holders = {bob}});
2141
2142 // alice creates issuance
2143 mptAlice.create(
2144 {.ownerCount = 1,
2145 .holderCount = 0,
2147
2148 // bob creates a MPToken
2149 mptAlice.authorize({.account = bob});
2150
2151 // alice authorizes bob
2152 mptAlice.authorize({.account = alice, .holder = bob});
2153
2154 // alice pays bob 100 tokens
2155 mptAlice.pay(alice, bob, 100);
2156
2157 // alice unauthorizes bob
2158 mptAlice.authorize(
2159 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
2160
2161 mptAlice.claw(alice, bob, 100);
2162 }
2163 }
2164
2165 void
2167 {
2168 using namespace test::jtx;
2169 testcase("Tokens Equality");
2170 Currency const cur1{to_currency("CU1")};
2171 Currency const cur2{to_currency("CU2")};
2172 Account const gw1{"gw1"};
2173 Account const gw2{"gw2"};
2174 MPTID const mpt1 = makeMptID(1, gw1);
2175 MPTID const mpt1a = makeMptID(1, gw1);
2176 MPTID const mpt2 = makeMptID(1, gw2);
2177 MPTID const mpt3 = makeMptID(2, gw2);
2178 Asset const assetCur1Gw1{Issue{cur1, gw1}};
2179 Asset const assetCur1Gw1a{Issue{cur1, gw1}};
2180 Asset const assetCur2Gw1{Issue{cur2, gw1}};
2181 Asset const assetCur2Gw2{Issue{cur2, gw2}};
2182 Asset const assetMpt1Gw1{mpt1};
2183 Asset const assetMpt1Gw1a{mpt1a};
2184 Asset const assetMpt1Gw2{mpt2};
2185 Asset const assetMpt2Gw2{mpt3};
2186
2187 // Assets holding Issue
2188 // Currencies are equal regardless of the issuer
2189 BEAST_EXPECT(equalTokens(assetCur1Gw1, assetCur1Gw1a));
2190 BEAST_EXPECT(equalTokens(assetCur2Gw1, assetCur2Gw2));
2191 // Currencies are different regardless of whether the issuers
2192 // are the same or not
2193 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw1));
2194 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw2));
2195
2196 // Assets holding MPTIssue
2197 // MPTIDs are the same if the sequence and the issuer are the same
2198 BEAST_EXPECT(equalTokens(assetMpt1Gw1, assetMpt1Gw1a));
2199 // MPTIDs are different if sequence and the issuer don't match
2200 BEAST_EXPECT(!equalTokens(assetMpt1Gw1, assetMpt1Gw2));
2201 BEAST_EXPECT(!equalTokens(assetMpt1Gw2, assetMpt2Gw2));
2202
2203 // Assets holding Issue and MPTIssue
2204 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetMpt1Gw1));
2205 BEAST_EXPECT(!equalTokens(assetMpt2Gw2, assetCur2Gw2));
2206 }
2207
2208 void
2210 {
2211 using namespace test::jtx;
2212 Account const gw{"gw"};
2213 Asset const asset1{makeMptID(1, gw)};
2214 Asset const asset2{makeMptID(2, gw)};
2215 Asset const asset3{makeMptID(3, gw)};
2216 STAmount const amt1{asset1, 100};
2217 STAmount const amt2{asset2, 100};
2218 STAmount const amt3{asset3, 10'000};
2219
2220 {
2221 testcase("Test STAmount MPT arithmetics");
2222 using namespace std::string_literals;
2223 STAmount res = multiply(amt1, amt2, asset3);
2224 BEAST_EXPECT(res == amt3);
2225
2226 res = mulRound(amt1, amt2, asset3, true);
2227 BEAST_EXPECT(res == amt3);
2228
2229 res = mulRoundStrict(amt1, amt2, asset3, true);
2230 BEAST_EXPECT(res == amt3);
2231
2232 // overflow, any value > 3037000499ull
2233 STAmount mptOverflow{asset2, UINT64_C(3037000500)};
2234 try
2235 {
2236 res = multiply(mptOverflow, mptOverflow, asset3);
2237 fail("should throw runtime exception 1");
2238 }
2239 catch (std::runtime_error const& e)
2240 {
2241 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2242 }
2243 // overflow, (v1 >> 32) * v2 > 2147483648ull
2244 mptOverflow = STAmount{asset2, UINT64_C(2147483648)};
2245 uint64_t const mantissa = (2ull << 32) + 2;
2246 try
2247 {
2248 res = multiply(STAmount{asset1, mantissa}, mptOverflow, asset3);
2249 fail("should throw runtime exception 2");
2250 }
2251 catch (std::runtime_error const& e)
2252 {
2253 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2254 }
2255 }
2256
2257 {
2258 testcase("Test MPTAmount arithmetics");
2259 MPTAmount mptAmt1{100};
2260 MPTAmount const mptAmt2{100};
2261 BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200});
2262 BEAST_EXPECT(mptAmt1 == 200);
2263 BEAST_EXPECT((mptAmt1 -= mptAmt2) == mptAmt1);
2264 BEAST_EXPECT(mptAmt1 == mptAmt2);
2265 BEAST_EXPECT(mptAmt1 == 100);
2266 BEAST_EXPECT(MPTAmount::minPositiveAmount() == MPTAmount{1});
2267 }
2268
2269 {
2270 testcase("Test MPTIssue from/to Json");
2271 MPTIssue const issue1{asset1.get<MPTIssue>()};
2272 Json::Value const jv = to_json(issue1);
2273 BEAST_EXPECT(
2274 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2275 BEAST_EXPECT(issue1 == mptIssueFromJson(jv));
2276 }
2277
2278 {
2279 testcase("Test Asset from/to Json");
2280 Json::Value const jv = to_json(asset1);
2281 BEAST_EXPECT(
2282 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2283 BEAST_EXPECT(
2284 to_string(jv) ==
2285 "{\"mpt_issuance_id\":"
2286 "\"00000001A407AF5856CCF3C42619DAA925813FC955C72983\"}");
2287 BEAST_EXPECT(asset1 == assetFromJson(jv));
2288 }
2289 }
2290
2291public:
2292 void
2293 run() override
2294 {
2295 using namespace test::jtx;
2297
2298 // MPTokenIssuanceCreate
2299 testCreateValidation(all - featureSingleAssetVault);
2300 testCreateValidation(all | featureSingleAssetVault);
2301 testCreateEnabled(all - featureSingleAssetVault);
2302 testCreateEnabled(all | featureSingleAssetVault);
2303
2304 // MPTokenIssuanceDestroy
2305 testDestroyValidation(all - featureSingleAssetVault);
2306 testDestroyValidation(all | featureSingleAssetVault);
2307 testDestroyEnabled(all - featureSingleAssetVault);
2308 testDestroyEnabled(all | featureSingleAssetVault);
2309
2310 // MPTokenAuthorize
2311 testAuthorizeValidation(all - featureSingleAssetVault);
2312 testAuthorizeValidation(all | featureSingleAssetVault);
2313 testAuthorizeEnabled(all - featureSingleAssetVault);
2314 testAuthorizeEnabled(all | featureSingleAssetVault);
2315
2316 // MPTokenIssuanceSet
2318 testSetEnabled(all - featureSingleAssetVault);
2319 testSetEnabled(all | featureSingleAssetVault);
2320
2321 // MPT clawback
2324
2325 // Test Direct Payment
2328
2329 // Test MPT Amount is invalid in Tx, which don't support MPT
2331
2332 // Test parsed MPTokenIssuanceID in API response metadata
2334
2335 // Test tokens equality
2337
2338 // Test helpers
2340 }
2341};
2342
2343BEAST_DEFINE_TESTSUITE_PRIO(MPToken, tx, ripple, 2);
2344
2345} // namespace test
2346} // namespace ripple
Lightweight wrapper to tag static string.
Definition: json_value.h:63
Represents a JSON value.
Definition: json_value.h:149
std::string asString() const
Returns the unquoted string value.
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:33
static MPTAmount minPositiveAmount()
Definition: MPTAmount.cpp:63
Slice slice() const noexcept
Definition: PublicKey.h:122
Identifies fields.
Definition: SField.h:143
Slice slice() const noexcept
Definition: Serializer.h:66
static TxFormats const & getInstance()
Definition: TxFormats.cpp:70
void run() override
Runs the suite.
void testCreateValidation(FeatureBitset features)
void testClawback(FeatureBitset features)
void testAuthorizeValidation(FeatureBitset features)
void testSetValidation(FeatureBitset features)
void testClawbackValidation(FeatureBitset features)
void testCreateEnabled(FeatureBitset features)
void testDestroyEnabled(FeatureBitset features)
void testPayment(FeatureBitset features)
void testMPTInvalidInTx(FeatureBitset features)
void testAuthorizeEnabled(FeatureBitset features)
void 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:254
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:544
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp: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:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
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:206
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:128
Add a path.
Definition: paths.h:58
Sets the SendMax on a JTx.
Definition: sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:35
Set the flags on a JTx.
Definition: txflags.h:31
T empty(T... args)
T erase(T... args)
T insert(T... args)
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
FeatureBitset testable_amendments()
Definition: Env.h:74
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:29
Json::Value xchain_commit(Account const &acc, Json::Value const &bridge, std::uint32_t claimID, AnyAmount const &amt, std::optional< Account > const &dst)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:115
constexpr std::uint32_t tfSingleAsset
Definition: TxFlags.h:211
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition: Asset.h:201
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:85
constexpr std::uint32_t const tfMPTCanTransfer
Definition: TxFlags.h:149
Asset assetFromJson(Json::Value const &jv)
Definition: Asset.cpp:77
constexpr std::uint32_t const tfMPTCanTrade
Definition: TxFlags.h:148
constexpr std::uint32_t const tfMPTUnlock
Definition: TxFlags.h:160
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition: Protocol.h:117
std::uint16_t constexpr maxTransferFee
The maximum token transfer fee allowed.
Definition: Protocol.h:83
STAmount multiply(STAmount const &amount, Rate const &rate)
Definition: Rate2.cpp:53
MPTIssue mptIssueFromJson(Json::Value const &jv)
Definition: MPTIssue.cpp:78
Json::Value to_json(Asset const &asset)
Definition: Asset.h:123
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:108
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
constexpr std::uint32_t const tfMPTUnauthorize
Definition: TxFlags.h:155
@ tecNO_DST
Definition: TER.h:290
@ tecOBJECT_NOT_FOUND
Definition: TER.h:326
@ tecDUPLICATE
Definition: TER.h:315
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:325
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecHAS_OBLIGATIONS
Definition: TER.h:317
@ tecPATH_PARTIAL
Definition: TER.h:282
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:307
@ tecNO_AUTH
Definition: TER.h:300
@ tecLOCKED
Definition: TER.h:358
constexpr std::uint32_t const tfMPTLock
Definition: TxFlags.h:159
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:107
@ tesSUCCESS
Definition: TER.h:244
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:109
@ soeMPTNotSupported
Definition: SOTemplate.h:43
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
STAmount mulRound(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1644
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:1655
constexpr std::uint32_t const tfMPTCanEscrow
Definition: TxFlags.h:147
constexpr std::uint32_t const tfMPTRequireAuth
Definition: TxFlags.h:146
constexpr std::uint32_t const tfMPTCanLock
Definition: TxFlags.h:145
constexpr std::uint32_t const tfMPTCanClawback
Definition: TxFlags.h:150
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition: UintTypes.cpp:84
@ temBAD_AMOUNT
Definition: TER.h:89
@ temREDUNDANT
Definition: TER.h:112
@ temBAD_TRANSFER_FEE
Definition: TER.h:142
@ temMALFORMED
Definition: TER.h:87
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
T ref(T... args)
A signer in a SignerList.
Definition: multisign.h:39
T what(T... args)