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