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