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 // bob deletes his mptoken
416 mptAlice.authorize(
417 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
418 }
419
420 // With allowlisting
421 {
422 Env env{*this, features};
423
424 // alice creates a mptokenissuance that requires authorization
425 MPTTester mptAlice(env, alice, {.holders = {bob}});
426
427 mptAlice.create({.ownerCount = 1, .flags = tfMPTRequireAuth});
428
429 // bob creates a mptoken
430 mptAlice.authorize({.account = bob, .holderCount = 1});
431
432 // alice authorizes bob
433 mptAlice.authorize({.account = alice, .holder = bob});
434
435 // Unauthorize bob's mptoken
436 mptAlice.authorize(
437 {.account = alice,
438 .holder = bob,
439 .holderCount = 1,
440 .flags = tfMPTUnauthorize});
441
442 mptAlice.authorize(
443 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
444 }
445
446 // Holder can have dangling MPToken even if issuance has been destroyed.
447 // Make sure they can still delete/unauthorize the MPToken
448 {
449 Env env{*this, features};
450 MPTTester mptAlice(env, alice, {.holders = {bob}});
451
452 mptAlice.create({.ownerCount = 1});
453
454 // bob creates a mptoken
455 mptAlice.authorize({.account = bob, .holderCount = 1});
456
457 // alice deletes her issuance
458 mptAlice.destroy({.ownerCount = 0});
459
460 // bob can delete his mptoken even though issuance is no longer
461 // existent
462 mptAlice.authorize(
463 {.account = bob, .holderCount = 0, .flags = tfMPTUnauthorize});
464 }
465 }
466
467 void
469 {
470 testcase("Validate set transaction");
471
472 using namespace test::jtx;
473 Account const alice("alice"); // issuer
474 Account const bob("bob"); // holder
475 Account const cindy("cindy");
476 // Validate fields in MPTokenIssuanceSet (preflight)
477 {
478 Env env{*this, features - featureMPTokensV1};
479 MPTTester mptAlice(env, alice, {.holders = {bob}});
480
481 mptAlice.set(
482 {.account = bob,
483 .id = makeMptID(env.seq(alice), alice),
484 .err = temDISABLED});
485
486 env.enableFeature(featureMPTokensV1);
487
488 mptAlice.create({.ownerCount = 1, .holderCount = 0});
489
490 mptAlice.authorize({.account = bob, .holderCount = 1});
491
492 // test invalid flag - only valid flags are tfMPTLock (1) and Unlock
493 // (2)
494 mptAlice.set(
495 {.account = alice,
496 .flags = 0x00000008,
497 .err = temINVALID_FLAG});
498
499 // set both lock and unlock flags at the same time will fail
500 mptAlice.set(
501 {.account = alice,
502 .flags = tfMPTLock | tfMPTUnlock,
503 .err = temINVALID_FLAG});
504
505 // if the holder is the same as the acct that submitted the tx,
506 // tx fails
507 mptAlice.set(
508 {.account = alice,
509 .holder = alice,
510 .flags = tfMPTLock,
511 .err = temMALFORMED});
512 }
513
514 // Validate fields in MPTokenIssuanceSet (preclaim)
515 // test when a mptokenissuance has disabled locking
516 {
517 Env env{*this, features};
518
519 MPTTester mptAlice(env, alice, {.holders = {bob}});
520
521 mptAlice.create({.ownerCount = 1});
522
523 // alice tries to lock a mptissuance that has disabled locking
524 mptAlice.set(
525 {.account = alice,
526 .flags = tfMPTLock,
527 .err = tecNO_PERMISSION});
528
529 // alice tries to unlock mptissuance that has disabled locking
530 mptAlice.set(
531 {.account = alice,
532 .flags = tfMPTUnlock,
533 .err = tecNO_PERMISSION});
534
535 // issuer tries to lock a bob's mptoken that has disabled
536 // locking
537 mptAlice.set(
538 {.account = alice,
539 .holder = bob,
540 .flags = tfMPTLock,
541 .err = tecNO_PERMISSION});
542
543 // issuer tries to unlock a bob's mptoken that has disabled
544 // locking
545 mptAlice.set(
546 {.account = alice,
547 .holder = bob,
548 .flags = tfMPTUnlock,
549 .err = tecNO_PERMISSION});
550 }
551
552 // Validate fields in MPTokenIssuanceSet (preclaim)
553 // test when mptokenissuance has enabled locking
554 {
555 Env env{*this, features};
556
557 MPTTester mptAlice(env, alice, {.holders = {bob}});
558
559 // alice trying to set when the mptissuance doesn't exist yet
560 mptAlice.set(
561 {.id = makeMptID(env.seq(alice), alice),
562 .flags = tfMPTLock,
563 .err = tecOBJECT_NOT_FOUND});
564
565 // create a mptokenissuance with locking
566 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanLock});
567
568 // a non-issuer acct tries to set the mptissuance
569 mptAlice.set(
570 {.account = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION});
571
572 // trying to set a holder who doesn't have a mptoken
573 mptAlice.set(
574 {.holder = bob,
575 .flags = tfMPTLock,
576 .err = tecOBJECT_NOT_FOUND});
577
578 // trying to set a holder who doesn't exist
579 mptAlice.set(
580 {.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
581 }
582 }
583
584 void
586 {
587 testcase("Enabled set transaction");
588
589 using namespace test::jtx;
590
591 // Test locking and unlocking
592 Env env{*this, features};
593 Account const alice("alice"); // issuer
594 Account const bob("bob"); // holder
595
596 MPTTester mptAlice(env, alice, {.holders = {bob}});
597
598 // create a mptokenissuance with locking
599 mptAlice.create(
600 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
601
602 mptAlice.authorize({.account = bob, .holderCount = 1});
603
604 // locks bob's mptoken
605 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
606
607 // trying to lock bob's mptoken again will still succeed
608 // but no changes to the objects
609 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
610
611 // alice locks the mptissuance
612 mptAlice.set({.account = alice, .flags = tfMPTLock});
613
614 // alice tries to lock up both mptissuance and mptoken again
615 // it will not change the flags and both will remain locked.
616 mptAlice.set({.account = alice, .flags = tfMPTLock});
617 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
618
619 // alice unlocks bob's mptoken
620 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
621
622 // locks up bob's mptoken again
623 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
624
625 // alice unlocks mptissuance
626 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
627
628 // alice unlocks bob's mptoken
629 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
630
631 // alice unlocks mptissuance and bob's mptoken again despite that
632 // they are already unlocked. Make sure this will not change the
633 // flags
634 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
635 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
636 }
637
638 void
640 {
641 testcase("Payment");
642
643 using namespace test::jtx;
644 Account const alice("alice"); // issuer
645 Account const bob("bob"); // holder
646 Account const carol("carol"); // holder
647
648 // preflight validation
649
650 // MPT is disabled
651 {
652 Env env{*this, features - featureMPTokensV1};
653 Account const alice("alice");
654 Account const bob("bob");
655
656 env.fund(XRP(1'000), alice);
657 env.fund(XRP(1'000), bob);
658 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
659
660 env(pay(alice, bob, mpt), ter(temDISABLED));
661 }
662
663 // MPT is disabled, unsigned request
664 {
665 Env env{*this, features - featureMPTokensV1};
666 Account const alice("alice"); // issuer
667 Account const carol("carol");
668 auto const USD = alice["USD"];
669
670 env.fund(XRP(1'000), alice);
671 env.fund(XRP(1'000), carol);
672 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
673
674 Json::Value jv;
675 jv[jss::secret] = alice.name();
676 jv[jss::tx_json] = pay(alice, carol, mpt);
677 jv[jss::tx_json][jss::Fee] = to_string(env.current()->fees().base);
678 auto const jrr = env.rpc("json", "submit", to_string(jv));
679 BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "temDISABLED");
680 }
681
682 // Invalid flag
683 {
684 Env env{*this, features};
685
686 MPTTester mptAlice(env, alice, {.holders = {bob}});
687
688 mptAlice.create({.ownerCount = 1, .holderCount = 0});
689 auto const MPT = mptAlice["MPT"];
690
691 mptAlice.authorize({.account = bob});
692
694 env(pay(alice, bob, MPT(10)),
695 txflags(flags),
697 }
698
699 // Invalid combination of send, sendMax, deliverMin, paths
700 {
701 Env env{*this, features};
702 Account const alice("alice");
703 Account const carol("carol");
704
705 MPTTester mptAlice(env, alice, {.holders = {carol}});
706
707 mptAlice.create({.ownerCount = 1, .holderCount = 0});
708
709 mptAlice.authorize({.account = carol});
710
711 // sendMax and DeliverMin are valid XRP amount,
712 // but is invalid combination with MPT amount
713 auto const MPT = mptAlice["MPT"];
714 env(pay(alice, carol, MPT(100)),
715 sendmax(XRP(100)),
717 env(pay(alice, carol, MPT(100)),
718 delivermin(XRP(100)),
720 // sendMax MPT is invalid with IOU or XRP
721 auto const USD = alice["USD"];
722 env(pay(alice, carol, USD(100)),
723 sendmax(MPT(100)),
725 env(pay(alice, carol, XRP(100)),
726 sendmax(MPT(100)),
728 env(pay(alice, carol, USD(100)),
729 delivermin(MPT(100)),
731 env(pay(alice, carol, XRP(100)),
732 delivermin(MPT(100)),
734 // sendmax and amount are different MPT issue
735 test::jtx::MPT const MPT1(
736 "MPT", makeMptID(env.seq(alice) + 10, alice));
737 env(pay(alice, carol, MPT1(100)),
738 sendmax(MPT(100)),
740 // paths is invalid
741 env(pay(alice, carol, MPT(100)), path(~USD), ter(temMALFORMED));
742 }
743
744 // build_path is invalid if MPT
745 {
746 Env env{*this, features};
747 Account const alice("alice");
748 Account const carol("carol");
749
750 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
751
752 mptAlice.create({.ownerCount = 1, .holderCount = 0});
753 auto const MPT = mptAlice["MPT"];
754
755 mptAlice.authorize({.account = carol});
756
757 Json::Value payment;
758 payment[jss::secret] = alice.name();
759 payment[jss::tx_json] = pay(alice, carol, MPT(100));
760
761 payment[jss::build_path] = true;
762 auto jrr = env.rpc("json", "submit", to_string(payment));
763 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
764 BEAST_EXPECT(
765 jrr[jss::result][jss::error_message] ==
766 "Field 'build_path' not allowed in this context.");
767 }
768
769 // Can't pay negative amount
770 {
771 Env env{*this, features};
772
773 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
774
775 mptAlice.create({.ownerCount = 1, .holderCount = 0});
776 auto const MPT = mptAlice["MPT"];
777
778 mptAlice.authorize({.account = bob});
779 mptAlice.authorize({.account = carol});
780
781 mptAlice.pay(alice, bob, -1, temBAD_AMOUNT);
782
783 mptAlice.pay(bob, carol, -1, temBAD_AMOUNT);
784
785 mptAlice.pay(bob, alice, -1, temBAD_AMOUNT);
786
787 env(pay(alice, bob, MPT(10)), sendmax(MPT(-1)), ter(temBAD_AMOUNT));
788 }
789
790 // Pay to self
791 {
792 Env env{*this, features};
793
794 MPTTester mptAlice(env, alice, {.holders = {bob}});
795
796 mptAlice.create({.ownerCount = 1, .holderCount = 0});
797
798 mptAlice.authorize({.account = bob});
799
800 mptAlice.pay(bob, bob, 10, temREDUNDANT);
801 }
802
803 // preclaim validation
804
805 // Destination doesn't exist
806 {
807 Env env{*this, features};
808
809 MPTTester mptAlice(env, alice, {.holders = {bob}});
810
811 mptAlice.create({.ownerCount = 1, .holderCount = 0});
812
813 mptAlice.authorize({.account = bob});
814
815 Account const bad{"bad"};
816 env.memoize(bad);
817
818 mptAlice.pay(bob, bad, 10, tecNO_DST);
819 }
820
821 // apply validation
822
823 // If RequireAuth is enabled, Payment fails if the receiver is not
824 // authorized
825 {
826 Env env{*this, features};
827
828 MPTTester mptAlice(env, alice, {.holders = {bob}});
829
830 mptAlice.create(
831 {.ownerCount = 1,
832 .holderCount = 0,
834
835 mptAlice.authorize({.account = bob});
836
837 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
838 }
839
840 // If RequireAuth is enabled, Payment fails if the sender is not
841 // authorized
842 {
843 Env env{*this, features};
844
845 MPTTester mptAlice(env, alice, {.holders = {bob}});
846
847 mptAlice.create(
848 {.ownerCount = 1,
849 .holderCount = 0,
851
852 // bob creates an empty MPToken
853 mptAlice.authorize({.account = bob});
854
855 // alice authorizes bob to hold funds
856 mptAlice.authorize({.account = alice, .holder = bob});
857
858 // alice sends 100 MPT to bob
859 mptAlice.pay(alice, bob, 100);
860
861 // alice UNAUTHORIZES bob
862 mptAlice.authorize(
863 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
864
865 // bob fails to send back to alice because he is no longer
866 // authorize to move his funds!
867 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
868 }
869
870 // Non-issuer cannot send to each other if MPTCanTransfer isn't set
871 {
872 Env env(*this, features);
873 Account const alice{"alice"};
874 Account const bob{"bob"};
875 Account const cindy{"cindy"};
876
877 MPTTester mptAlice(env, alice, {.holders = {bob, cindy}});
878
879 // alice creates issuance without MPTCanTransfer
880 mptAlice.create({.ownerCount = 1, .holderCount = 0});
881
882 // bob creates a MPToken
883 mptAlice.authorize({.account = bob});
884
885 // cindy creates a MPToken
886 mptAlice.authorize({.account = cindy});
887
888 // alice pays bob 100 tokens
889 mptAlice.pay(alice, bob, 100);
890
891 // bob tries to send cindy 10 tokens, but fails because canTransfer
892 // is off
893 mptAlice.pay(bob, cindy, 10, tecNO_AUTH);
894
895 // bob can send back to alice(issuer) just fine
896 mptAlice.pay(bob, alice, 10);
897 }
898
899 // Holder is not authorized
900 {
901 Env env{*this, features};
902
903 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
904
905 mptAlice.create(
906 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
907
908 // issuer to holder
909 mptAlice.pay(alice, bob, 100, tecNO_AUTH);
910
911 // holder to issuer
912 mptAlice.pay(bob, alice, 100, tecNO_AUTH);
913
914 // holder to holder
915 mptAlice.pay(bob, carol, 50, tecNO_AUTH);
916 }
917
918 // Payer doesn't have enough funds
919 {
920 Env env{*this, features};
921
922 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
923
924 mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
925
926 mptAlice.authorize({.account = bob});
927 mptAlice.authorize({.account = carol});
928
929 mptAlice.pay(alice, bob, 100);
930
931 // Pay to another holder
932 mptAlice.pay(bob, carol, 101, tecPATH_PARTIAL);
933
934 // Pay to the issuer
935 mptAlice.pay(bob, alice, 101, tecPATH_PARTIAL);
936 }
937
938 // MPT is locked
939 {
940 Env env{*this, features};
941
942 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
943
944 mptAlice.create(
945 {.ownerCount = 1, .flags = tfMPTCanLock | tfMPTCanTransfer});
946
947 mptAlice.authorize({.account = bob});
948 mptAlice.authorize({.account = carol});
949
950 mptAlice.pay(alice, bob, 100);
951 mptAlice.pay(alice, carol, 100);
952
953 // Global lock
954 mptAlice.set({.account = alice, .flags = tfMPTLock});
955 // Can't send between holders
956 mptAlice.pay(bob, carol, 1, tecLOCKED);
957 mptAlice.pay(carol, bob, 2, tecLOCKED);
958 // Issuer can send
959 mptAlice.pay(alice, bob, 3);
960 // Holder can send back to issuer
961 mptAlice.pay(bob, alice, 4);
962
963 // Global unlock
964 mptAlice.set({.account = alice, .flags = tfMPTUnlock});
965 // Individual lock
966 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
967 // Can't send between holders
968 mptAlice.pay(bob, carol, 5, tecLOCKED);
969 mptAlice.pay(carol, bob, 6, tecLOCKED);
970 // Issuer can send
971 mptAlice.pay(alice, bob, 7);
972 // Holder can send back to issuer
973 mptAlice.pay(bob, alice, 8);
974 }
975
976 // Transfer fee
977 {
978 Env env{*this, features};
979
980 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
981
982 // Transfer fee is 10%
983 mptAlice.create(
984 {.transferFee = 10'000,
985 .ownerCount = 1,
986 .holderCount = 0,
987 .flags = tfMPTCanTransfer});
988
989 // Holders create MPToken
990 mptAlice.authorize({.account = bob});
991 mptAlice.authorize({.account = carol});
992
993 // Payment between the issuer and the holder, no transfer fee.
994 mptAlice.pay(alice, bob, 2'000);
995
996 // Payment between the holder and the issuer, no transfer fee.
997 mptAlice.pay(bob, alice, 1'000);
998 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 1'000));
999
1000 // Payment between the holders. The sender doesn't have
1001 // enough funds to cover the transfer fee.
1002 mptAlice.pay(bob, carol, 1'000, tecPATH_PARTIAL);
1003
1004 // Payment between the holders. The sender has enough funds
1005 // but SendMax is not included.
1006 mptAlice.pay(bob, carol, 100, tecPATH_PARTIAL);
1007
1008 auto const MPT = mptAlice["MPT"];
1009 // SendMax doesn't cover the fee
1010 env(pay(bob, carol, MPT(100)),
1011 sendmax(MPT(109)),
1013
1014 // Payment succeeds if sufficient SendMax is included.
1015 // 100 to carol, 10 to issuer
1016 env(pay(bob, carol, MPT(100)), sendmax(MPT(110)));
1017 // 100 to carol, 10 to issuer
1018 env(pay(bob, carol, MPT(100)), sendmax(MPT(115)));
1019 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 780));
1020 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 200));
1021 // Payment succeeds if partial payment even if
1022 // SendMax is less than deliver amount
1023 env(pay(bob, carol, MPT(100)),
1024 sendmax(MPT(90)),
1026 // 82 to carol, 8 to issuer (90 / 1.1 ~ 81.81 (rounded to nearest) =
1027 // 82)
1028 BEAST_EXPECT(mptAlice.checkMPTokenAmount(bob, 690));
1029 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 282));
1030 }
1031
1032 // Insufficient SendMax with no transfer fee
1033 {
1034 Env env{*this, features};
1035
1036 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1037
1038 mptAlice.create(
1039 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1040
1041 // Holders create MPToken
1042 mptAlice.authorize({.account = bob});
1043 mptAlice.authorize({.account = carol});
1044 mptAlice.pay(alice, bob, 1'000);
1045
1046 auto const MPT = mptAlice["MPT"];
1047 // SendMax is less than the amount
1048 env(pay(bob, carol, MPT(100)),
1049 sendmax(MPT(99)),
1051 env(pay(bob, alice, MPT(100)),
1052 sendmax(MPT(99)),
1054
1055 // Payment succeeds if sufficient SendMax is included.
1056 env(pay(bob, carol, MPT(100)), sendmax(MPT(100)));
1057 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 100));
1058 // Payment succeeds if partial payment
1059 env(pay(bob, carol, MPT(100)),
1060 sendmax(MPT(99)),
1062 BEAST_EXPECT(mptAlice.checkMPTokenAmount(carol, 199));
1063 }
1064
1065 // DeliverMin
1066 {
1067 Env env{*this, features};
1068
1069 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1070
1071 mptAlice.create(
1072 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1073
1074 // Holders create MPToken
1075 mptAlice.authorize({.account = bob});
1076 mptAlice.authorize({.account = carol});
1077 mptAlice.pay(alice, bob, 1'000);
1078
1079 auto const MPT = mptAlice["MPT"];
1080 // Fails even with the partial payment because
1081 // deliver amount < deliverMin
1082 env(pay(bob, alice, MPT(100)),
1083 sendmax(MPT(99)),
1084 delivermin(MPT(100)),
1087 // Payment succeeds if deliver amount >= deliverMin
1088 env(pay(bob, alice, MPT(100)),
1089 sendmax(MPT(99)),
1090 delivermin(MPT(99)),
1092 }
1093
1094 // Issuer fails trying to send more than the maximum amount allowed
1095 {
1096 Env env{*this, features};
1097
1098 MPTTester mptAlice(env, alice, {.holders = {bob}});
1099
1100 mptAlice.create(
1101 {.maxAmt = 100,
1102 .ownerCount = 1,
1103 .holderCount = 0,
1104 .flags = tfMPTCanTransfer});
1105
1106 mptAlice.authorize({.account = bob});
1107
1108 // issuer sends holder the max amount allowed
1109 mptAlice.pay(alice, bob, 100);
1110
1111 // issuer tries to exceed max amount
1112 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1113 }
1114
1115 // Issuer fails trying to send more than the default maximum
1116 // amount allowed
1117 {
1118 Env env{*this, features};
1119
1120 MPTTester mptAlice(env, alice, {.holders = {bob}});
1121
1122 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1123
1124 mptAlice.authorize({.account = bob});
1125
1126 // issuer sends holder the default max amount allowed
1127 mptAlice.pay(alice, bob, maxMPTokenAmount);
1128
1129 // issuer tries to exceed max amount
1130 mptAlice.pay(alice, bob, 1, tecPATH_PARTIAL);
1131 }
1132
1133 // Pay more than max amount fails in the json parser before
1134 // transactor is called
1135 {
1136 Env env{*this, features};
1137 env.fund(XRP(1'000), alice, bob);
1138 STAmount mpt{MPTIssue{makeMptID(1, alice)}, UINT64_C(100)};
1139 Json::Value jv;
1140 jv[jss::secret] = alice.name();
1141 jv[jss::tx_json] = pay(alice, bob, mpt);
1142 jv[jss::tx_json][jss::Amount][jss::value] =
1144 auto const jrr = env.rpc("json", "submit", to_string(jv));
1145 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1146 }
1147
1148 // Pay maximum amount with the transfer fee, SendMax, and
1149 // partial payment
1150 {
1151 Env env{*this, features};
1152
1153 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1154
1155 mptAlice.create(
1156 {.maxAmt = 10'000,
1157 .transferFee = 100,
1158 .ownerCount = 1,
1159 .holderCount = 0,
1160 .flags = tfMPTCanTransfer});
1161 auto const MPT = mptAlice["MPT"];
1162
1163 mptAlice.authorize({.account = bob});
1164 mptAlice.authorize({.account = carol});
1165
1166 // issuer sends holder the max amount allowed
1167 mptAlice.pay(alice, bob, 10'000);
1168
1169 // payment between the holders
1170 env(pay(bob, carol, MPT(10'000)),
1171 sendmax(MPT(10'000)),
1173 // Verify the metadata
1174 auto const meta = env.meta()->getJson(
1175 JsonOptions::none)[sfAffectedNodes.fieldName];
1176 // Issuer got 10 in the transfer fees
1177 BEAST_EXPECT(
1178 meta[0u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1179 [sfOutstandingAmount.fieldName] == "9990");
1180 // Destination account got 9'990
1181 BEAST_EXPECT(
1182 meta[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1183 [sfMPTAmount.fieldName] == "9990");
1184 // Source account spent 10'000
1185 BEAST_EXPECT(
1186 meta[2u][sfModifiedNode.fieldName][sfPreviousFields.fieldName]
1187 [sfMPTAmount.fieldName] == "10000");
1188 BEAST_EXPECT(
1189 !meta[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName]
1190 .isMember(sfMPTAmount.fieldName));
1191
1192 // payment between the holders fails without
1193 // partial payment
1194 env(pay(bob, carol, MPT(10'000)),
1195 sendmax(MPT(10'000)),
1197 }
1198
1199 // Pay maximum allowed amount
1200 {
1201 Env env{*this, features};
1202
1203 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1204
1205 mptAlice.create(
1206 {.maxAmt = maxMPTokenAmount,
1207 .ownerCount = 1,
1208 .holderCount = 0,
1209 .flags = tfMPTCanTransfer});
1210 auto const MPT = mptAlice["MPT"];
1211
1212 mptAlice.authorize({.account = bob});
1213 mptAlice.authorize({.account = carol});
1214
1215 // issuer sends holder the max amount allowed
1216 mptAlice.pay(alice, bob, maxMPTokenAmount);
1217 BEAST_EXPECT(
1218 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1219
1220 // payment between the holders
1221 mptAlice.pay(bob, carol, maxMPTokenAmount);
1222 BEAST_EXPECT(
1223 mptAlice.checkMPTokenOutstandingAmount(maxMPTokenAmount));
1224 // holder pays back to the issuer
1225 mptAlice.pay(carol, alice, maxMPTokenAmount);
1226 BEAST_EXPECT(mptAlice.checkMPTokenOutstandingAmount(0));
1227 }
1228
1229 // Issuer fails trying to send fund after issuance was destroyed
1230 {
1231 Env env{*this, features};
1232
1233 MPTTester mptAlice(env, alice, {.holders = {bob}});
1234
1235 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1236
1237 mptAlice.authorize({.account = bob});
1238
1239 // alice destroys issuance
1240 mptAlice.destroy({.ownerCount = 0});
1241
1242 // alice tries to send bob fund after issuance is destroyed, should
1243 // fail.
1244 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1245 }
1246
1247 // Non-existent issuance
1248 {
1249 Env env{*this, features};
1250
1251 env.fund(XRP(1'000), alice, bob);
1252
1253 STAmount const mpt{MPTID{0}, 100};
1254 env(pay(alice, bob, mpt), ter(tecOBJECT_NOT_FOUND));
1255 }
1256
1257 // Issuer fails trying to send to an account, which doesn't own MPT for
1258 // an issuance that was destroyed
1259 {
1260 Env env{*this, features};
1261
1262 MPTTester mptAlice(env, alice, {.holders = {bob}});
1263
1264 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1265
1266 // alice destroys issuance
1267 mptAlice.destroy({.ownerCount = 0});
1268
1269 // alice tries to send bob who doesn't own the MPT after issuance is
1270 // destroyed, it should fail
1271 mptAlice.pay(alice, bob, 100, tecOBJECT_NOT_FOUND);
1272 }
1273
1274 // Issuers issues maximum amount of MPT to a holder, the holder should
1275 // be able to transfer the max amount to someone else
1276 {
1277 Env env{*this, features};
1278 Account const alice("alice");
1279 Account const carol("bob");
1280 Account const bob("carol");
1281
1282 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1283
1284 mptAlice.create(
1285 {.maxAmt = 100, .ownerCount = 1, .flags = tfMPTCanTransfer});
1286
1287 mptAlice.authorize({.account = bob});
1288 mptAlice.authorize({.account = carol});
1289
1290 mptAlice.pay(alice, bob, 100);
1291
1292 // transfer max amount to another holder
1293 mptAlice.pay(bob, carol, 100);
1294 }
1295
1296 // Simple payment
1297 {
1298 Env env{*this, features};
1299
1300 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
1301
1302 mptAlice.create(
1303 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanTransfer});
1304
1305 mptAlice.authorize({.account = bob});
1306 mptAlice.authorize({.account = carol});
1307
1308 // issuer to holder
1309 mptAlice.pay(alice, bob, 100);
1310
1311 // holder to issuer
1312 mptAlice.pay(bob, alice, 100);
1313
1314 // holder to holder
1315 mptAlice.pay(alice, bob, 100);
1316 mptAlice.pay(bob, carol, 50);
1317 }
1318 }
1319
1320 void
1322 {
1323 testcase("DepositPreauth");
1324
1325 using namespace test::jtx;
1326 Account const alice("alice"); // issuer
1327 Account const bob("bob"); // holder
1328 Account const diana("diana");
1329 Account const dpIssuer("dpIssuer"); // holder
1330
1331 char const credType[] = "abcde";
1332
1333 {
1334 Env env(*this);
1335
1336 env.fund(XRP(50000), diana, dpIssuer);
1337 env.close();
1338
1339 MPTTester mptAlice(env, alice, {.holders = {bob}});
1340 mptAlice.create(
1341 {.ownerCount = 1,
1342 .holderCount = 0,
1344
1345 env(pay(diana, bob, XRP(500)));
1346 env.close();
1347
1348 // bob creates an empty MPToken
1349 mptAlice.authorize({.account = bob});
1350 // alice authorizes bob to hold funds
1351 mptAlice.authorize({.account = alice, .holder = bob});
1352
1353 // Bob require preauthorization
1354 env(fset(bob, asfDepositAuth));
1355 env.close();
1356
1357 // alice try to send 100 MPT to bob, not authorized
1358 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1359 env.close();
1360
1361 // Bob authorize alice
1362 env(deposit::auth(bob, alice));
1363 env.close();
1364
1365 // alice sends 100 MPT to bob
1366 mptAlice.pay(alice, bob, 100);
1367 env.close();
1368
1369 // Create credentials
1370 env(credentials::create(alice, dpIssuer, credType));
1371 env.close();
1372 env(credentials::accept(alice, dpIssuer, credType));
1373 env.close();
1374 auto const jv =
1375 credentials::ledgerEntry(env, alice, dpIssuer, credType);
1376 std::string const credIdx = jv[jss::result][jss::index].asString();
1377
1378 // alice sends 100 MPT to bob with credentials which aren't required
1379 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1380 env.close();
1381
1382 // Bob revoke authorization
1383 env(deposit::unauth(bob, alice));
1384 env.close();
1385
1386 // alice try to send 100 MPT to bob, not authorized
1387 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1388 env.close();
1389
1390 // alice sends 100 MPT to bob with credentials, not authorized
1391 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION, {{credIdx}});
1392 env.close();
1393
1394 // Bob authorize credentials
1395 env(deposit::authCredentials(bob, {{dpIssuer, credType}}));
1396 env.close();
1397
1398 // alice try to send 100 MPT to bob, not authorized
1399 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1400 env.close();
1401
1402 // alice sends 100 MPT to bob with credentials
1403 mptAlice.pay(alice, bob, 100, tesSUCCESS, {{credIdx}});
1404 env.close();
1405 }
1406
1407 testcase("DepositPreauth disabled featureCredentials");
1408 {
1409 Env env(*this, supported_amendments() - featureCredentials);
1410
1411 std::string const credIdx =
1412 "D007AE4B6E1274B4AF872588267B810C2F82716726351D1C7D38D3E5499FC6"
1413 "E2";
1414
1415 env.fund(XRP(50000), diana, dpIssuer);
1416 env.close();
1417
1418 MPTTester mptAlice(env, alice, {.holders = {bob}});
1419 mptAlice.create(
1420 {.ownerCount = 1,
1421 .holderCount = 0,
1423
1424 env(pay(diana, bob, XRP(500)));
1425 env.close();
1426
1427 // bob creates an empty MPToken
1428 mptAlice.authorize({.account = bob});
1429 // alice authorizes bob to hold funds
1430 mptAlice.authorize({.account = alice, .holder = bob});
1431
1432 // Bob require preauthorization
1433 env(fset(bob, asfDepositAuth));
1434 env.close();
1435
1436 // alice try to send 100 MPT to bob, not authorized
1437 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1438 env.close();
1439
1440 // alice try to send 100 MPT to bob with credentials, amendment
1441 // disabled
1442 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1443 env.close();
1444
1445 // Bob authorize alice
1446 env(deposit::auth(bob, alice));
1447 env.close();
1448
1449 // alice sends 100 MPT to bob
1450 mptAlice.pay(alice, bob, 100);
1451 env.close();
1452
1453 // alice sends 100 MPT to bob with credentials, amendment disabled
1454 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1455 env.close();
1456
1457 // Bob revoke authorization
1458 env(deposit::unauth(bob, alice));
1459 env.close();
1460
1461 // alice try to send 100 MPT to bob
1462 mptAlice.pay(alice, bob, 100, tecNO_PERMISSION);
1463 env.close();
1464
1465 // alice sends 100 MPT to bob with credentials, amendment disabled
1466 mptAlice.pay(alice, bob, 100, temDISABLED, {{credIdx}});
1467 env.close();
1468 }
1469 }
1470
1471 void
1473 {
1474 testcase("MPT Issue Invalid in Transaction");
1475 using namespace test::jtx;
1476
1477 // Validate that every transaction with an amount/issue field,
1478 // which doesn't support MPT, fails.
1479
1480 // keyed by transaction + amount/issue field
1481 std::set<std::string> txWithAmounts;
1482 for (auto const& format : TxFormats::getInstance())
1483 {
1484 for (auto const& e : format.getSOTemplate())
1485 {
1486 // Transaction has amount/issue fields.
1487 // Exclude pseudo-transaction SetFee. Don't consider
1488 // the Fee field since it's included in every transaction.
1489 if (e.supportMPT() == soeMPTNotSupported &&
1490 e.sField().getName() != jss::Fee &&
1491 format.getName() != jss::SetFee)
1492 {
1493 txWithAmounts.insert(
1494 format.getName() + e.sField().fieldName);
1495 break;
1496 }
1497 }
1498 }
1499
1500 Account const alice("alice");
1501 auto const USD = alice["USD"];
1502 Account const carol("carol");
1503 MPTIssue issue(makeMptID(1, alice));
1504 STAmount mpt{issue, UINT64_C(100)};
1505 auto const jvb = bridge(alice, USD, alice, USD);
1506 for (auto const& feature : {features, features - featureMPTokensV1})
1507 {
1508 Env env{*this, feature};
1509 env.fund(XRP(1'000), alice);
1510 env.fund(XRP(1'000), carol);
1511 auto test = [&](Json::Value const& jv,
1512 std::string const& mptField) {
1513 txWithAmounts.erase(
1514 jv[jss::TransactionType].asString() + mptField);
1515
1516 // tx is signed
1517 auto jtx = env.jt(jv);
1518 Serializer s;
1519 jtx.stx->add(s);
1520 auto jrr = env.rpc("submit", strHex(s.slice()));
1521 BEAST_EXPECT(
1522 jrr[jss::result][jss::error] == "invalidTransaction");
1523
1524 // tx is unsigned
1525 Json::Value jv1;
1526 jv1[jss::secret] = alice.name();
1527 jv1[jss::tx_json] = jv;
1528 jrr = env.rpc("json", "submit", to_string(jv1));
1529 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1530
1531 jrr = env.rpc("json", "sign", to_string(jv1));
1532 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
1533 };
1534 auto toSFieldRef = [](SField const& field) {
1535 return std::ref(field);
1536 };
1537 auto setMPTFields = [&](SField const& field,
1538 Json::Value& jv,
1539 bool withAmount = true) {
1540 jv[jss::Asset] = to_json(xrpIssue());
1541 jv[jss::Asset2] = to_json(USD.issue());
1542 if (withAmount)
1543 jv[field.fieldName] =
1544 USD(10).value().getJson(JsonOptions::none);
1545 if (field == sfAsset)
1546 jv[jss::Asset] = to_json(mpt.get<MPTIssue>());
1547 else if (field == sfAsset2)
1548 jv[jss::Asset2] = to_json(mpt.get<MPTIssue>());
1549 else
1550 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
1551 };
1552 // All transactions with sfAmount, which don't support MPT.
1553 // Transactions with amount fields, which can't be MPT.
1554 // Transactions with issue fields, which can't be MPT.
1555
1556 // AMMCreate
1557 auto ammCreate = [&](SField const& field) {
1558 Json::Value jv;
1559 jv[jss::TransactionType] = jss::AMMCreate;
1560 jv[jss::Account] = alice.human();
1561 jv[jss::Amount] = (field.fieldName == sfAmount.fieldName)
1562 ? mpt.getJson(JsonOptions::none)
1563 : "100000000";
1564 jv[jss::Amount2] = (field.fieldName == sfAmount2.fieldName)
1565 ? mpt.getJson(JsonOptions::none)
1566 : "100000000";
1567 jv[jss::TradingFee] = 0;
1568 test(jv, field.fieldName);
1569 };
1570 ammCreate(sfAmount);
1571 ammCreate(sfAmount2);
1572 // AMMDeposit
1573 auto ammDeposit = [&](SField const& field) {
1574 Json::Value jv;
1575 jv[jss::TransactionType] = jss::AMMDeposit;
1576 jv[jss::Account] = alice.human();
1577 jv[jss::Flags] = tfSingleAsset;
1578 setMPTFields(field, jv);
1579 test(jv, field.fieldName);
1580 };
1581 for (SField const& field :
1582 {toSFieldRef(sfAmount),
1583 toSFieldRef(sfAmount2),
1584 toSFieldRef(sfEPrice),
1585 toSFieldRef(sfLPTokenOut),
1586 toSFieldRef(sfAsset),
1587 toSFieldRef(sfAsset2)})
1588 ammDeposit(field);
1589 // AMMWithdraw
1590 auto ammWithdraw = [&](SField const& field) {
1591 Json::Value jv;
1592 jv[jss::TransactionType] = jss::AMMWithdraw;
1593 jv[jss::Account] = alice.human();
1594 jv[jss::Flags] = tfSingleAsset;
1595 setMPTFields(field, jv);
1596 test(jv, field.fieldName);
1597 };
1598 ammWithdraw(sfAmount);
1599 for (SField const& field :
1600 {toSFieldRef(sfAmount2),
1601 toSFieldRef(sfEPrice),
1602 toSFieldRef(sfLPTokenIn),
1603 toSFieldRef(sfAsset),
1604 toSFieldRef(sfAsset2)})
1605 ammWithdraw(field);
1606 // AMMBid
1607 auto ammBid = [&](SField const& field) {
1608 Json::Value jv;
1609 jv[jss::TransactionType] = jss::AMMBid;
1610 jv[jss::Account] = alice.human();
1611 setMPTFields(field, jv);
1612 test(jv, field.fieldName);
1613 };
1614 for (SField const& field :
1615 {toSFieldRef(sfBidMin),
1616 toSFieldRef(sfBidMax),
1617 toSFieldRef(sfAsset),
1618 toSFieldRef(sfAsset2)})
1619 ammBid(field);
1620 // AMMClawback
1621 auto ammClawback = [&](SField const& field) {
1622 Json::Value jv;
1623 jv[jss::TransactionType] = jss::AMMClawback;
1624 jv[jss::Account] = alice.human();
1625 jv[jss::Holder] = carol.human();
1626 setMPTFields(field, jv);
1627 test(jv, field.fieldName);
1628 };
1629 for (SField const& field :
1630 {toSFieldRef(sfAmount),
1631 toSFieldRef(sfAsset),
1632 toSFieldRef(sfAsset2)})
1633 ammClawback(field);
1634 // AMMDelete
1635 auto ammDelete = [&](SField const& field) {
1636 Json::Value jv;
1637 jv[jss::TransactionType] = jss::AMMDelete;
1638 jv[jss::Account] = alice.human();
1639 setMPTFields(field, jv, false);
1640 test(jv, field.fieldName);
1641 };
1642 ammDelete(sfAsset);
1643 ammDelete(sfAsset2);
1644 // AMMVote
1645 auto ammVote = [&](SField const& field) {
1646 Json::Value jv;
1647 jv[jss::TransactionType] = jss::AMMVote;
1648 jv[jss::Account] = alice.human();
1649 jv[jss::TradingFee] = 100;
1650 setMPTFields(field, jv, false);
1651 test(jv, field.fieldName);
1652 };
1653 ammVote(sfAsset);
1654 ammVote(sfAsset2);
1655 // CheckCash
1656 auto checkCash = [&](SField const& field) {
1657 Json::Value jv;
1658 jv[jss::TransactionType] = jss::CheckCash;
1659 jv[jss::Account] = alice.human();
1660 jv[sfCheckID.fieldName] = to_string(uint256{1});
1661 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
1662 test(jv, field.fieldName);
1663 };
1664 checkCash(sfAmount);
1665 checkCash(sfDeliverMin);
1666 // CheckCreate
1667 {
1668 Json::Value jv;
1669 jv[jss::TransactionType] = jss::CheckCreate;
1670 jv[jss::Account] = alice.human();
1671 jv[jss::Destination] = carol.human();
1672 jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
1673 test(jv, jss::SendMax.c_str());
1674 }
1675 // EscrowCreate
1676 {
1677 Json::Value jv;
1678 jv[jss::TransactionType] = jss::EscrowCreate;
1679 jv[jss::Account] = alice.human();
1680 jv[jss::Destination] = carol.human();
1681 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1682 test(jv, jss::Amount.c_str());
1683 }
1684 // OfferCreate
1685 {
1686 Json::Value jv = offer(alice, USD(100), mpt);
1687 test(jv, jss::TakerPays.c_str());
1688 jv = offer(alice, mpt, USD(100));
1689 test(jv, jss::TakerGets.c_str());
1690 }
1691 // PaymentChannelCreate
1692 {
1693 Json::Value jv;
1694 jv[jss::TransactionType] = jss::PaymentChannelCreate;
1695 jv[jss::Account] = alice.human();
1696 jv[jss::Destination] = carol.human();
1697 jv[jss::SettleDelay] = 1;
1698 jv[sfPublicKey.fieldName] = strHex(alice.pk().slice());
1699 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1700 test(jv, jss::Amount.c_str());
1701 }
1702 // PaymentChannelFund
1703 {
1704 Json::Value jv;
1705 jv[jss::TransactionType] = jss::PaymentChannelFund;
1706 jv[jss::Account] = alice.human();
1707 jv[sfChannel.fieldName] = to_string(uint256{1});
1708 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1709 test(jv, jss::Amount.c_str());
1710 }
1711 // PaymentChannelClaim
1712 {
1713 Json::Value jv;
1714 jv[jss::TransactionType] = jss::PaymentChannelClaim;
1715 jv[jss::Account] = alice.human();
1716 jv[sfChannel.fieldName] = to_string(uint256{1});
1717 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1718 test(jv, jss::Amount.c_str());
1719 }
1720 // NFTokenCreateOffer
1721 {
1722 Json::Value jv;
1723 jv[jss::TransactionType] = jss::NFTokenCreateOffer;
1724 jv[jss::Account] = alice.human();
1725 jv[sfNFTokenID.fieldName] = to_string(uint256{1});
1726 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1727 test(jv, jss::Amount.c_str());
1728 }
1729 // NFTokenAcceptOffer
1730 {
1731 Json::Value jv;
1732 jv[jss::TransactionType] = jss::NFTokenAcceptOffer;
1733 jv[jss::Account] = alice.human();
1734 jv[sfNFTokenBrokerFee.fieldName] =
1735 mpt.getJson(JsonOptions::none);
1736 test(jv, sfNFTokenBrokerFee.fieldName);
1737 }
1738 // NFTokenMint
1739 {
1740 Json::Value jv;
1741 jv[jss::TransactionType] = jss::NFTokenMint;
1742 jv[jss::Account] = alice.human();
1743 jv[sfNFTokenTaxon.fieldName] = 1;
1744 jv[jss::Amount] = mpt.getJson(JsonOptions::none);
1745 test(jv, jss::Amount.c_str());
1746 }
1747 // TrustSet
1748 auto trustSet = [&](SField const& field) {
1749 Json::Value jv;
1750 jv[jss::TransactionType] = jss::TrustSet;
1751 jv[jss::Account] = alice.human();
1752 jv[jss::Flags] = 0;
1753 jv[field.fieldName] = mpt.getJson(JsonOptions::none);
1754 test(jv, field.fieldName);
1755 };
1756 trustSet(sfLimitAmount);
1757 trustSet(sfFee);
1758 // XChainCommit
1759 {
1760 Json::Value const jv = xchain_commit(alice, jvb, 1, mpt);
1761 test(jv, jss::Amount.c_str());
1762 }
1763 // XChainClaim
1764 {
1765 Json::Value const jv = xchain_claim(alice, jvb, 1, mpt, alice);
1766 test(jv, jss::Amount.c_str());
1767 }
1768 // XChainCreateClaimID
1769 {
1770 Json::Value const jv =
1771 xchain_create_claim_id(alice, jvb, mpt, alice);
1772 test(jv, sfSignatureReward.fieldName);
1773 }
1774 // XChainAddClaimAttestation
1775 {
1776 Json::Value const jv = claim_attestation(
1777 alice,
1778 jvb,
1779 alice,
1780 mpt,
1781 alice,
1782 true,
1783 1,
1784 alice,
1785 signer(alice));
1786 test(jv, jss::Amount.c_str());
1787 }
1788 // XChainAddAccountCreateAttestation
1789 {
1791 alice,
1792 jvb,
1793 alice,
1794 mpt,
1795 XRP(10),
1796 alice,
1797 false,
1798 1,
1799 alice,
1800 signer(alice));
1801 for (auto const& field :
1802 {sfAmount.fieldName, sfSignatureReward.fieldName})
1803 {
1804 jv[field] = mpt.getJson(JsonOptions::none);
1805 test(jv, field);
1806 }
1807 }
1808 // XChainAccountCreateCommit
1809 {
1811 alice, jvb, alice, mpt, XRP(10));
1812 for (auto const& field :
1813 {sfAmount.fieldName, sfSignatureReward.fieldName})
1814 {
1815 jv[field] = mpt.getJson(JsonOptions::none);
1816 test(jv, field);
1817 }
1818 }
1819 // XChain[Create|Modify]Bridge
1820 auto bridgeTx = [&](Json::StaticString const& tt,
1821 STAmount const& rewardAmount,
1822 STAmount const& minAccountAmount,
1823 std::string const& field) {
1824 Json::Value jv;
1825 jv[jss::TransactionType] = tt;
1826 jv[jss::Account] = alice.human();
1827 jv[sfXChainBridge.fieldName] = jvb;
1828 jv[sfSignatureReward.fieldName] =
1829 rewardAmount.getJson(JsonOptions::none);
1830 jv[sfMinAccountCreateAmount.fieldName] =
1831 minAccountAmount.getJson(JsonOptions::none);
1832 test(jv, field);
1833 };
1834 auto reward = STAmount{sfSignatureReward, mpt};
1835 auto minAmount = STAmount{sfMinAccountCreateAmount, USD(10)};
1836 for (SField const& field :
1837 {std::ref(sfSignatureReward),
1838 std::ref(sfMinAccountCreateAmount)})
1839 {
1840 bridgeTx(
1841 jss::XChainCreateBridge,
1842 reward,
1843 minAmount,
1844 field.fieldName);
1845 bridgeTx(
1846 jss::XChainModifyBridge,
1847 reward,
1848 minAmount,
1849 field.fieldName);
1850 reward = STAmount{sfSignatureReward, USD(10)};
1851 minAmount = STAmount{sfMinAccountCreateAmount, mpt};
1852 }
1853 }
1854 BEAST_EXPECT(txWithAmounts.empty());
1855 }
1856
1857 void
1859 {
1860 // checks synthetically injected mptissuanceid from `tx` response
1861 testcase("Test synthetic fields from tx response");
1862
1863 using namespace test::jtx;
1864
1865 Account const alice{"alice"};
1866
1867 auto cfg = envconfig();
1868 cfg->FEES.reference_fee = 10;
1869 Env env{*this, std::move(cfg), features};
1870 MPTTester mptAlice(env, alice);
1871
1872 mptAlice.create();
1873
1874 std::string const txHash{
1875 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
1876 BEAST_EXPECTS(
1877 txHash ==
1878 "E11F0E0CA14219922B7881F060B9CEE67CFBC87E4049A441ED2AE348FF8FAC"
1879 "0E",
1880 txHash);
1881 Json::Value const meta = env.rpc("tx", txHash)[jss::result][jss::meta];
1882 auto const id = meta[jss::mpt_issuance_id].asString();
1883 // Expect mpt_issuance_id field
1884 BEAST_EXPECT(meta.isMember(jss::mpt_issuance_id));
1885 BEAST_EXPECT(id == to_string(mptAlice.issuanceID()));
1886 BEAST_EXPECTS(
1887 id == "00000004AE123A8556F3CF91154711376AFB0F894F832B3D", id);
1888 }
1889
1890 void
1892 {
1893 testcase("MPT clawback validations");
1894 using namespace test::jtx;
1895
1896 // Make sure clawback cannot work when featureMPTokensV1 is disabled
1897 {
1898 Env env(*this, features - featureMPTokensV1);
1899 Account const alice{"alice"};
1900 Account const bob{"bob"};
1901
1902 env.fund(XRP(1000), alice, bob);
1903 env.close();
1904
1905 auto const USD = alice["USD"];
1906 auto const mpt = ripple::test::jtx::MPT(
1907 alice.name(), makeMptID(env.seq(alice), alice));
1908
1909 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
1910 env.close();
1911
1912 env(claw(alice, mpt(5)), ter(temDISABLED));
1913 env.close();
1914
1915 env(claw(alice, mpt(5), bob), ter(temDISABLED));
1916 env.close();
1917 }
1918
1919 // Test preflight
1920 {
1921 Env env(*this, features);
1922 Account const alice{"alice"};
1923 Account const bob{"bob"};
1924
1925 env.fund(XRP(1000), alice, bob);
1926 env.close();
1927
1928 auto const USD = alice["USD"];
1929 auto const mpt = ripple::test::jtx::MPT(
1930 alice.name(), makeMptID(env.seq(alice), alice));
1931
1932 // clawing back IOU from a MPT holder fails
1933 env(claw(alice, bob["USD"](5), bob), ter(temMALFORMED));
1934 env.close();
1935
1936 // clawing back MPT without specifying a holder fails
1937 env(claw(alice, mpt(5)), ter(temMALFORMED));
1938 env.close();
1939
1940 // clawing back zero amount fails
1941 env(claw(alice, mpt(0), bob), ter(temBAD_AMOUNT));
1942 env.close();
1943
1944 // alice can't claw back from herself
1945 env(claw(alice, mpt(5), alice), ter(temMALFORMED));
1946 env.close();
1947
1948 // can't clawback negative amount
1949 env(claw(alice, mpt(-1), bob), ter(temBAD_AMOUNT));
1950 env.close();
1951 }
1952
1953 // Preclaim - clawback fails when MPTCanClawback is disabled on issuance
1954 {
1955 Env env(*this, features);
1956 Account const alice{"alice"};
1957 Account const bob{"bob"};
1958
1959 MPTTester mptAlice(env, alice, {.holders = {bob}});
1960
1961 // enable asfAllowTrustLineClawback for alice
1962 env(fset(alice, asfAllowTrustLineClawback));
1963 env.close();
1965
1966 // Create issuance without enabling clawback
1967 mptAlice.create({.ownerCount = 1, .holderCount = 0});
1968
1969 mptAlice.authorize({.account = bob});
1970
1971 mptAlice.pay(alice, bob, 100);
1972
1973 // alice cannot clawback before she didn't enable MPTCanClawback
1974 // asfAllowTrustLineClawback has no effect
1975 mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
1976 }
1977
1978 // Preclaim - test various scenarios
1979 {
1980 Env env(*this, features);
1981 Account const alice{"alice"};
1982 Account const bob{"bob"};
1983 Account const carol{"carol"};
1984 env.fund(XRP(1000), carol);
1985 env.close();
1986 MPTTester mptAlice(env, alice, {.holders = {bob}});
1987
1988 auto const fakeMpt = ripple::test::jtx::MPT(
1989 alice.name(), makeMptID(env.seq(alice), alice));
1990
1991 // issuer tries to clawback MPT where issuance doesn't exist
1992 env(claw(alice, fakeMpt(5), bob), ter(tecOBJECT_NOT_FOUND));
1993 env.close();
1994
1995 // alice creates issuance
1996 mptAlice.create(
1997 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
1998
1999 // alice tries to clawback from someone who doesn't have MPToken
2000 mptAlice.claw(alice, bob, 1, tecOBJECT_NOT_FOUND);
2001
2002 // bob creates a MPToken
2003 mptAlice.authorize({.account = bob});
2004
2005 // clawback fails because bob currently has a balance of zero
2006 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2007
2008 // alice pays bob 100 tokens
2009 mptAlice.pay(alice, bob, 100);
2010
2011 // carol fails tries to clawback from bob because he is not the
2012 // issuer
2013 mptAlice.claw(carol, bob, 1, tecNO_PERMISSION);
2014 }
2015
2016 // clawback more than max amount
2017 // fails in the json parser before
2018 // transactor is called
2019 {
2020 Env env(*this, features);
2021 Account const alice{"alice"};
2022 Account const bob{"bob"};
2023
2024 env.fund(XRP(1000), alice, bob);
2025 env.close();
2026
2027 auto const mpt = ripple::test::jtx::MPT(
2028 alice.name(), makeMptID(env.seq(alice), alice));
2029
2030 Json::Value jv = claw(alice, mpt(1), bob);
2031 jv[jss::Amount][jss::value] = to_string(maxMPTokenAmount + 1);
2032 Json::Value jv1;
2033 jv1[jss::secret] = alice.name();
2034 jv1[jss::tx_json] = jv;
2035 auto const jrr = env.rpc("json", "submit", to_string(jv1));
2036 BEAST_EXPECT(jrr[jss::result][jss::error] == "invalidParams");
2037 }
2038 }
2039
2040 void
2042 {
2043 testcase("MPT Clawback");
2044 using namespace test::jtx;
2045
2046 {
2047 Env env(*this, features);
2048 Account const alice{"alice"};
2049 Account const bob{"bob"};
2050
2051 MPTTester mptAlice(env, alice, {.holders = {bob}});
2052
2053 // alice creates issuance
2054 mptAlice.create(
2055 {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanClawback});
2056
2057 // bob creates a MPToken
2058 mptAlice.authorize({.account = bob});
2059
2060 // alice pays bob 100 tokens
2061 mptAlice.pay(alice, bob, 100);
2062
2063 mptAlice.claw(alice, bob, 1);
2064
2065 mptAlice.claw(alice, bob, 1000);
2066
2067 // clawback fails because bob currently has a balance of zero
2068 mptAlice.claw(alice, bob, 1, tecINSUFFICIENT_FUNDS);
2069 }
2070
2071 // Test that globally locked funds can be clawed
2072 {
2073 Env env(*this, features);
2074 Account const alice{"alice"};
2075 Account const bob{"bob"};
2076
2077 MPTTester mptAlice(env, alice, {.holders = {bob}});
2078
2079 // alice creates issuance
2080 mptAlice.create(
2081 {.ownerCount = 1,
2082 .holderCount = 0,
2083 .flags = tfMPTCanLock | tfMPTCanClawback});
2084
2085 // bob creates a MPToken
2086 mptAlice.authorize({.account = bob});
2087
2088 // alice pays bob 100 tokens
2089 mptAlice.pay(alice, bob, 100);
2090
2091 mptAlice.set({.account = alice, .flags = tfMPTLock});
2092
2093 mptAlice.claw(alice, bob, 100);
2094 }
2095
2096 // Test that individually locked funds can be clawed
2097 {
2098 Env env(*this, features);
2099 Account const alice{"alice"};
2100 Account const bob{"bob"};
2101
2102 MPTTester mptAlice(env, alice, {.holders = {bob}});
2103
2104 // alice creates issuance
2105 mptAlice.create(
2106 {.ownerCount = 1,
2107 .holderCount = 0,
2108 .flags = tfMPTCanLock | tfMPTCanClawback});
2109
2110 // bob creates a MPToken
2111 mptAlice.authorize({.account = bob});
2112
2113 // alice pays bob 100 tokens
2114 mptAlice.pay(alice, bob, 100);
2115
2116 mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
2117
2118 mptAlice.claw(alice, bob, 100);
2119 }
2120
2121 // Test that unauthorized funds can be clawed back
2122 {
2123 Env env(*this, features);
2124 Account const alice{"alice"};
2125 Account const bob{"bob"};
2126
2127 MPTTester mptAlice(env, alice, {.holders = {bob}});
2128
2129 // alice creates issuance
2130 mptAlice.create(
2131 {.ownerCount = 1,
2132 .holderCount = 0,
2134
2135 // bob creates a MPToken
2136 mptAlice.authorize({.account = bob});
2137
2138 // alice authorizes bob
2139 mptAlice.authorize({.account = alice, .holder = bob});
2140
2141 // alice pays bob 100 tokens
2142 mptAlice.pay(alice, bob, 100);
2143
2144 // alice unauthorizes bob
2145 mptAlice.authorize(
2146 {.account = alice, .holder = bob, .flags = tfMPTUnauthorize});
2147
2148 mptAlice.claw(alice, bob, 100);
2149 }
2150 }
2151
2152 void
2154 {
2155 using namespace test::jtx;
2156 testcase("Tokens Equality");
2157 Currency const cur1{to_currency("CU1")};
2158 Currency const cur2{to_currency("CU2")};
2159 Account const gw1{"gw1"};
2160 Account const gw2{"gw2"};
2161 MPTID const mpt1 = makeMptID(1, gw1);
2162 MPTID const mpt1a = makeMptID(1, gw1);
2163 MPTID const mpt2 = makeMptID(1, gw2);
2164 MPTID const mpt3 = makeMptID(2, gw2);
2165 Asset const assetCur1Gw1{Issue{cur1, gw1}};
2166 Asset const assetCur1Gw1a{Issue{cur1, gw1}};
2167 Asset const assetCur2Gw1{Issue{cur2, gw1}};
2168 Asset const assetCur2Gw2{Issue{cur2, gw2}};
2169 Asset const assetMpt1Gw1{mpt1};
2170 Asset const assetMpt1Gw1a{mpt1a};
2171 Asset const assetMpt1Gw2{mpt2};
2172 Asset const assetMpt2Gw2{mpt3};
2173
2174 // Assets holding Issue
2175 // Currencies are equal regardless of the issuer
2176 BEAST_EXPECT(equalTokens(assetCur1Gw1, assetCur1Gw1a));
2177 BEAST_EXPECT(equalTokens(assetCur2Gw1, assetCur2Gw2));
2178 // Currencies are different regardless of whether the issuers
2179 // are the same or not
2180 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw1));
2181 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetCur2Gw2));
2182
2183 // Assets holding MPTIssue
2184 // MPTIDs are the same if the sequence and the issuer are the same
2185 BEAST_EXPECT(equalTokens(assetMpt1Gw1, assetMpt1Gw1a));
2186 // MPTIDs are different if sequence and the issuer don't match
2187 BEAST_EXPECT(!equalTokens(assetMpt1Gw1, assetMpt1Gw2));
2188 BEAST_EXPECT(!equalTokens(assetMpt1Gw2, assetMpt2Gw2));
2189
2190 // Assets holding Issue and MPTIssue
2191 BEAST_EXPECT(!equalTokens(assetCur1Gw1, assetMpt1Gw1));
2192 BEAST_EXPECT(!equalTokens(assetMpt2Gw2, assetCur2Gw2));
2193 }
2194
2195 void
2197 {
2198 using namespace test::jtx;
2199 Account const gw{"gw"};
2200 Asset const asset1{makeMptID(1, gw)};
2201 Asset const asset2{makeMptID(2, gw)};
2202 Asset const asset3{makeMptID(3, gw)};
2203 STAmount const amt1{asset1, 100};
2204 STAmount const amt2{asset2, 100};
2205 STAmount const amt3{asset3, 10'000};
2206
2207 {
2208 testcase("Test STAmount MPT arithmetics");
2209 using namespace std::string_literals;
2210 STAmount res = multiply(amt1, amt2, asset3);
2211 BEAST_EXPECT(res == amt3);
2212
2213 res = mulRound(amt1, amt2, asset3, true);
2214 BEAST_EXPECT(res == amt3);
2215
2216 res = mulRoundStrict(amt1, amt2, asset3, true);
2217 BEAST_EXPECT(res == amt3);
2218
2219 // overflow, any value > 3037000499ull
2220 STAmount mptOverflow{asset2, UINT64_C(3037000500)};
2221 try
2222 {
2223 res = multiply(mptOverflow, mptOverflow, asset3);
2224 fail("should throw runtime exception 1");
2225 }
2226 catch (std::runtime_error const& e)
2227 {
2228 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2229 }
2230 // overflow, (v1 >> 32) * v2 > 2147483648ull
2231 mptOverflow = STAmount{asset2, UINT64_C(2147483648)};
2232 uint64_t const mantissa = (2ull << 32) + 2;
2233 try
2234 {
2235 res = multiply(STAmount{asset1, mantissa}, mptOverflow, asset3);
2236 fail("should throw runtime exception 2");
2237 }
2238 catch (std::runtime_error const& e)
2239 {
2240 BEAST_EXPECTS(e.what() == "MPT value overflow"s, e.what());
2241 }
2242 }
2243
2244 {
2245 testcase("Test MPTAmount arithmetics");
2246 MPTAmount mptAmt1{100};
2247 MPTAmount const mptAmt2{100};
2248 BEAST_EXPECT((mptAmt1 += mptAmt2) == MPTAmount{200});
2249 BEAST_EXPECT(mptAmt1 == 200);
2250 BEAST_EXPECT((mptAmt1 -= mptAmt2) == mptAmt1);
2251 BEAST_EXPECT(mptAmt1 == mptAmt2);
2252 BEAST_EXPECT(mptAmt1 == 100);
2253 BEAST_EXPECT(MPTAmount::minPositiveAmount() == MPTAmount{1});
2254 }
2255
2256 {
2257 testcase("Test MPTIssue from/to Json");
2258 MPTIssue const issue1{asset1.get<MPTIssue>()};
2259 Json::Value const jv = to_json(issue1);
2260 BEAST_EXPECT(
2261 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2262 BEAST_EXPECT(issue1 == mptIssueFromJson(jv));
2263 }
2264
2265 {
2266 testcase("Test Asset from/to Json");
2267 Json::Value const jv = to_json(asset1);
2268 BEAST_EXPECT(
2269 jv[jss::mpt_issuance_id] == to_string(asset1.get<MPTIssue>()));
2270 BEAST_EXPECT(
2271 to_string(jv) ==
2272 "{\"mpt_issuance_id\":"
2273 "\"00000001A407AF5856CCF3C42619DAA925813FC955C72983\"}");
2274 BEAST_EXPECT(asset1 == assetFromJson(jv));
2275 }
2276 }
2277
2278public:
2279 void
2280 run() override
2281 {
2282 using namespace test::jtx;
2284
2285 // MPTokenIssuanceCreate
2288
2289 // MPTokenIssuanceDestroy
2292
2293 // MPTokenAuthorize
2296
2297 // MPTokenIssuanceSet
2300
2301 // MPT clawback
2304
2305 // Test Direct Payment
2308
2309 // Test MPT Amount is invalid in Tx, which don't support MPT
2311
2312 // Test parsed MPTokenIssuanceID in API response metadata
2314
2315 // Test tokens equality
2317
2318 // Test helpers
2320 }
2321};
2322
2323BEAST_DEFINE_TESTSUITE_PRIO(MPToken, tx, ripple, 2);
2324
2325} // namespace test
2326} // namespace ripple
Lightweight wrapper to tag static string.
Definition: json_value.h:62
Represents a JSON value.
Definition: json_value.h:148
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:475
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:949
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:144
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:120
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:534
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:769
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:204
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:50
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:83
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:73
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:215
constexpr bool equalTokens(Asset const &lhs, Asset const &rhs)
Definition: Asset.h:188
constexpr std::uint32_t asfDepositAuth
Definition: TxFlags.h:84
constexpr std::uint32_t const tfMPTCanTransfer
Definition: TxFlags.h:152
Asset assetFromJson(Json::Value const &jv)
Definition: Asset.cpp:70
constexpr std::uint32_t const tfMPTCanTrade
Definition: TxFlags.h:151
constexpr std::uint32_t const tfMPTUnlock
Definition: TxFlags.h:163
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:84
Json::Value to_json(Asset const &asset)
Definition: Asset.cpp:82
constexpr std::uint32_t tfPartialPayment
Definition: TxFlags.h:105
std::string strHex(FwdIt begin, FwdIt end)
Definition: strHex.h:30
constexpr std::uint32_t const tfMPTUnauthorize
Definition: TxFlags.h:158
@ tecNO_DST
Definition: TER.h:277
@ tecOBJECT_NOT_FOUND
Definition: TER.h:313
@ tecDUPLICATE
Definition: TER.h:302
@ tecINSUFFICIENT_FUNDS
Definition: TER.h:312
@ tecNO_PERMISSION
Definition: TER.h:292
@ tecHAS_OBLIGATIONS
Definition: TER.h:304
@ tecPATH_PARTIAL
Definition: TER.h:269
@ tecINSUFFICIENT_RESERVE
Definition: TER.h:294
@ tecNO_AUTH
Definition: TER.h:287
@ tecLOCKED
Definition: TER.h:345
constexpr std::uint32_t const tfMPTLock
Definition: TxFlags.h:162
constexpr std::uint32_t tfNoRippleDirect
Definition: TxFlags.h:104
@ tesSUCCESS
Definition: TER.h:242
constexpr std::uint32_t tfLimitQuality
Definition: TxFlags.h:106
@ 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:1542
constexpr std::uint32_t asfAllowTrustLineClawback
Definition: TxFlags.h:93
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition: Indexes.cpp:162
STAmount mulRoundStrict(STAmount const &v1, STAmount const &v2, Asset const &asset, bool roundUp)
Definition: STAmount.cpp:1553
constexpr std::uint32_t const tfMPTCanEscrow
Definition: TxFlags.h:150
constexpr std::uint32_t const tfMPTRequireAuth
Definition: TxFlags.h:149
constexpr std::uint32_t const tfMPTCanLock
Definition: TxFlags.h:148
constexpr std::uint32_t const tfMPTCanClawback
Definition: TxFlags.h:153
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:143
@ 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:38
T what(T... args)