rippled
Loading...
Searching...
No Matches
Vault_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/amount.h>
5
6#include <xrpl/basics/base_uint.h>
7#include <xrpl/beast/unit_test/suite.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/json/json_forwards.h>
10#include <xrpl/json/json_value.h>
11#include <xrpl/ledger/Sandbox.h>
12#include <xrpl/ledger/View.h>
13#include <xrpl/protocol/AccountID.h>
14#include <xrpl/protocol/Asset.h>
15#include <xrpl/protocol/Feature.h>
16#include <xrpl/protocol/Indexes.h>
17#include <xrpl/protocol/Issue.h>
18#include <xrpl/protocol/MPTIssue.h>
19#include <xrpl/protocol/Protocol.h>
20#include <xrpl/protocol/SField.h>
21#include <xrpl/protocol/STAmount.h>
22#include <xrpl/protocol/STNumber.h>
23#include <xrpl/protocol/TER.h>
24#include <xrpl/protocol/TxFlags.h>
25#include <xrpl/protocol/XRPAmount.h>
26#include <xrpl/protocol/jss.h>
27
28#include <optional>
29
30namespace ripple {
31
33{
36
37 static auto constexpr negativeAmount =
38 [](PrettyAsset const& asset) -> PrettyAmount {
39 return {STAmount{asset.raw(), 1ul, 0, true, STAmount::unchecked{}}, ""};
40 };
41
42 void
44 {
45 using namespace test::jtx;
46 Account issuer{"issuer"};
47 Account owner{"owner"};
48 Account depositor{"depositor"};
49 Account charlie{"charlie"}; // authorized 3rd party
50 Account dave{"dave"};
51
52 auto const testSequence = [&, this](
53 std::string const& prefix,
54 Env& env,
55 Vault& vault,
56 PrettyAsset const& asset) {
57 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
58 tx[sfData] = "AFEED00E";
59 tx[sfAssetsMaximum] = asset(100).number();
60 env(tx);
61 env.close();
62 BEAST_EXPECT(env.le(keylet));
63 std::uint64_t const scale = asset.raw().holds<MPTIssue>() ? 1 : 1e6;
64
65 auto const [share, vaultAccount] =
66 [&env,
67 keylet = keylet,
68 asset,
70 auto const vault = env.le(keylet);
71 BEAST_EXPECT(vault != nullptr);
72 if (asset.raw().holds<Issue>() && !asset.raw().native())
73 BEAST_EXPECT(vault->at(sfScale) == 6);
74 else
75 BEAST_EXPECT(vault->at(sfScale) == 0);
76 auto const shares =
77 env.le(keylet::mptIssuance(vault->at(sfShareMPTID)));
78 BEAST_EXPECT(shares != nullptr);
79 if (asset.raw().holds<Issue>() && !asset.raw().native())
80 BEAST_EXPECT(shares->at(sfAssetScale) == 6);
81 else
82 BEAST_EXPECT(shares->at(sfAssetScale) == 0);
83 return {
84 MPTIssue(vault->at(sfShareMPTID)),
85 Account("vault", vault->at(sfAccount))};
86 }();
87 auto const shares = share.raw().get<MPTIssue>();
88 env.memoize(vaultAccount);
89
90 // Several 3rd party accounts which cannot receive funds
91 Account alice{"alice"};
92 Account erin{"erin"}; // not authorized by issuer
93 env.fund(XRP(1000), alice, erin);
94 env(fset(alice, asfDepositAuth));
95 env.close();
96
97 {
98 testcase(prefix + " fail to deposit more than assets held");
99 auto tx = vault.deposit(
100 {.depositor = depositor,
101 .id = keylet.key,
102 .amount = asset(10000)});
103 env(tx, ter(tecINSUFFICIENT_FUNDS));
104 env.close();
105 }
106
107 {
108 testcase(prefix + " deposit non-zero amount");
109 auto tx = vault.deposit(
110 {.depositor = depositor,
111 .id = keylet.key,
112 .amount = asset(50)});
113 env(tx);
114 env.close();
115 BEAST_EXPECT(
116 env.balance(depositor, shares) == share(50 * scale));
117 }
118
119 {
120 testcase(prefix + " deposit non-zero amount again");
121 auto tx = vault.deposit(
122 {.depositor = depositor,
123 .id = keylet.key,
124 .amount = asset(50)});
125 env(tx);
126 env.close();
127 BEAST_EXPECT(
128 env.balance(depositor, shares) == share(100 * scale));
129 }
130
131 {
132 testcase(prefix + " fail to delete non-empty vault");
133 auto tx = vault.del({.owner = owner, .id = keylet.key});
134 env(tx, ter(tecHAS_OBLIGATIONS));
135 env.close();
136 }
137
138 {
139 testcase(prefix + " fail to update because wrong owner");
140 auto tx = vault.set({.owner = issuer, .id = keylet.key});
141 tx[sfAssetsMaximum] = asset(50).number();
142 env(tx, ter(tecNO_PERMISSION));
143 env.close();
144 }
145
146 {
147 testcase(
148 prefix + " fail to set maximum lower than current amount");
149 auto tx = vault.set({.owner = owner, .id = keylet.key});
150 tx[sfAssetsMaximum] = asset(50).number();
151 env(tx, ter(tecLIMIT_EXCEEDED));
152 env.close();
153 }
154
155 {
156 testcase(prefix + " set maximum higher than current amount");
157 auto tx = vault.set({.owner = owner, .id = keylet.key});
158 tx[sfAssetsMaximum] = asset(150).number();
159 env(tx);
160 env.close();
161 }
162
163 {
164 testcase(prefix + " set maximum is idempotent, set it again");
165 auto tx = vault.set({.owner = owner, .id = keylet.key});
166 tx[sfAssetsMaximum] = asset(150).number();
167 env(tx);
168 env.close();
169 }
170
171 {
172 testcase(prefix + " set data");
173 auto tx = vault.set({.owner = owner, .id = keylet.key});
174 tx[sfData] = "0";
175 env(tx);
176 env.close();
177 }
178
179 {
180 testcase(prefix + " fail to set domain on public vault");
181 auto tx = vault.set({.owner = owner, .id = keylet.key});
182 tx[sfDomainID] = to_string(base_uint<256>(42ul));
183 env(tx, ter{tecNO_PERMISSION});
184 env.close();
185 }
186
187 {
188 testcase(prefix + " fail to deposit more than maximum");
189 auto tx = vault.deposit(
190 {.depositor = depositor,
191 .id = keylet.key,
192 .amount = asset(100)});
193 env(tx, ter(tecLIMIT_EXCEEDED));
194 env.close();
195 }
196
197 {
198 testcase(prefix + " reset maximum to zero i.e. not enforced");
199 auto tx = vault.set({.owner = owner, .id = keylet.key});
200 tx[sfAssetsMaximum] = asset(0).number();
201 env(tx);
202 env.close();
203 }
204
205 {
206 testcase(prefix + " fail to withdraw more than assets held");
207 auto tx = vault.withdraw(
208 {.depositor = depositor,
209 .id = keylet.key,
210 .amount = asset(1000)});
211 env(tx, ter(tecINSUFFICIENT_FUNDS));
212 env.close();
213 }
214
215 {
216 testcase(prefix + " deposit some more");
217 auto tx = vault.deposit(
218 {.depositor = depositor,
219 .id = keylet.key,
220 .amount = asset(100)});
221 env(tx);
222 env.close();
223 BEAST_EXPECT(
224 env.balance(depositor, shares) == share(200 * scale));
225 }
226
227 {
228 testcase(prefix + " clawback some");
229 auto code =
230 asset.raw().native() ? ter(temMALFORMED) : ter(tesSUCCESS);
231 auto tx = vault.clawback(
232 {.issuer = issuer,
233 .id = keylet.key,
234 .holder = depositor,
235 .amount = asset(10)});
236 env(tx, code);
237 env.close();
238 if (!asset.raw().native())
239 {
240 BEAST_EXPECT(
241 env.balance(depositor, shares) == share(190 * scale));
242 }
243 }
244
245 {
246 testcase(prefix + " clawback all");
247 auto code = asset.raw().native() ? ter(tecNO_PERMISSION)
248 : ter(tesSUCCESS);
249 auto tx = vault.clawback(
250 {.issuer = issuer, .id = keylet.key, .holder = depositor});
251 env(tx, code);
252 env.close();
253 if (!asset.raw().native())
254 {
255 BEAST_EXPECT(env.balance(depositor, shares) == share(0));
256
257 {
258 auto tx = vault.clawback(
259 {.issuer = issuer,
260 .id = keylet.key,
261 .holder = depositor,
262 .amount = asset(10)});
263 env(tx, ter{tecPRECISION_LOSS});
264 env.close();
265 }
266
267 {
268 auto tx = vault.withdraw(
269 {.depositor = depositor,
270 .id = keylet.key,
271 .amount = asset(10)});
272 env(tx, ter{tecPRECISION_LOSS});
273 env.close();
274 }
275 }
276 }
277
278 if (!asset.raw().native())
279 {
280 testcase(prefix + " deposit again");
281 auto tx = vault.deposit(
282 {.depositor = depositor,
283 .id = keylet.key,
284 .amount = asset(200)});
285 env(tx);
286 env.close();
287 BEAST_EXPECT(
288 env.balance(depositor, shares) == share(200 * scale));
289 }
290 else
291 {
292 testcase(prefix + " deposit/withdrawal same or less than fee");
293 auto const amount = env.current()->fees().base;
294
295 auto tx = vault.deposit(
296 {.depositor = depositor,
297 .id = keylet.key,
298 .amount = amount});
299 env(tx);
300 env.close();
301
302 tx = vault.withdraw(
303 {.depositor = depositor,
304 .id = keylet.key,
305 .amount = amount});
306 env(tx);
307 env.close();
308
309 tx = vault.deposit(
310 {.depositor = depositor,
311 .id = keylet.key,
312 .amount = amount});
313 env(tx);
314 env.close();
315
316 // Withdraw to 3rd party
317 tx = vault.withdraw(
318 {.depositor = depositor,
319 .id = keylet.key,
320 .amount = amount});
321 tx[sfDestination] = charlie.human();
322 env(tx);
323 env.close();
324
325 tx = vault.deposit(
326 {.depositor = depositor,
327 .id = keylet.key,
328 .amount = amount - 1});
329 env(tx);
330 env.close();
331
332 tx = vault.withdraw(
333 {.depositor = depositor,
334 .id = keylet.key,
335 .amount = amount - 1});
336 env(tx);
337 env.close();
338 }
339
340 {
341 testcase(
342 prefix + " fail to withdraw to 3rd party lsfDepositAuth");
343 auto tx = vault.withdraw(
344 {.depositor = depositor,
345 .id = keylet.key,
346 .amount = asset(100)});
347 tx[sfDestination] = alice.human();
348 env(tx, ter{tecNO_PERMISSION});
349 env.close();
350 }
351
352 {
353 testcase(prefix + " fail to withdraw to zero destination");
354 auto tx = vault.withdraw(
355 {.depositor = depositor,
356 .id = keylet.key,
357 .amount = asset(1000)});
358 tx[sfDestination] = "0";
359 env(tx, ter(temMALFORMED));
360 env.close();
361 }
362
363 if (!asset.raw().native())
364 {
365 testcase(
366 prefix + " fail to withdraw to 3rd party no authorization");
367 auto tx = vault.withdraw(
368 {.depositor = depositor,
369 .id = keylet.key,
370 .amount = asset(100)});
371 tx[sfDestination] = erin.human();
372 env(tx,
373 ter{asset.raw().holds<Issue>() ? tecNO_LINE : tecNO_AUTH});
374 env.close();
375 }
376
377 {
378 testcase(
379 prefix +
380 " fail to withdraw to 3rd party lsfRequireDestTag");
381 auto tx = vault.withdraw(
382 {.depositor = depositor,
383 .id = keylet.key,
384 .amount = asset(100)});
385 tx[sfDestination] = dave.human();
386 env(tx, ter{tecDST_TAG_NEEDED});
387 env.close();
388 }
389
390 {
391 testcase(prefix + " withdraw to 3rd party lsfRequireDestTag");
392 auto tx = vault.withdraw(
393 {.depositor = depositor,
394 .id = keylet.key,
395 .amount = asset(50)});
396 tx[sfDestination] = dave.human();
397 tx[sfDestinationTag] = "0";
398 env(tx);
399 env.close();
400 }
401
402 {
403 testcase(prefix + " deposit again");
404 auto tx = vault.deposit(
405 {.depositor = dave, .id = keylet.key, .amount = asset(50)});
406 env(tx);
407 env.close();
408 }
409
410 {
411 testcase(prefix + " fail to withdraw lsfRequireDestTag");
412 auto tx = vault.withdraw(
413 {.depositor = dave, .id = keylet.key, .amount = asset(50)});
414 env(tx, ter{tecDST_TAG_NEEDED});
415 env.close();
416 }
417
418 {
419 testcase(prefix + " withdraw with tag");
420 auto tx = vault.withdraw(
421 {.depositor = dave, .id = keylet.key, .amount = asset(50)});
422 tx[sfDestinationTag] = "0";
423 env(tx);
424 env.close();
425 }
426
427 {
428 testcase(prefix + " withdraw to authorized 3rd party");
429 auto tx = vault.withdraw(
430 {.depositor = depositor,
431 .id = keylet.key,
432 .amount = asset(50)});
433 tx[sfDestination] = charlie.human();
434 env(tx);
435 env.close();
436 BEAST_EXPECT(
437 env.balance(depositor, shares) == share(100 * scale));
438 }
439
440 {
441 testcase(prefix + " withdraw to issuer");
442 auto tx = vault.withdraw(
443 {.depositor = depositor,
444 .id = keylet.key,
445 .amount = asset(50)});
446 tx[sfDestination] = issuer.human();
447 env(tx);
448 env.close();
449 BEAST_EXPECT(
450 env.balance(depositor, shares) == share(50 * scale));
451 }
452
453 if (!asset.raw().native())
454 {
455 testcase(prefix + " issuer deposits");
456 auto tx = vault.deposit(
457 {.depositor = issuer,
458 .id = keylet.key,
459 .amount = asset(10)});
460 env(tx);
461 env.close();
462 BEAST_EXPECT(env.balance(issuer, shares) == share(10 * scale));
463
464 testcase(prefix + " issuer withdraws");
465 tx = vault.withdraw(
466 {.depositor = issuer,
467 .id = keylet.key,
468 .amount = share(10 * scale)});
469 env(tx);
470 env.close();
471 BEAST_EXPECT(env.balance(issuer, shares) == share(0 * scale));
472 }
473
474 {
475 testcase(prefix + " withdraw remaining assets");
476 auto tx = vault.withdraw(
477 {.depositor = depositor,
478 .id = keylet.key,
479 .amount = asset(50)});
480 env(tx);
481 env.close();
482 BEAST_EXPECT(env.balance(depositor, shares) == share(0));
483
484 if (!asset.raw().native())
485 {
486 auto tx = vault.clawback(
487 {.issuer = issuer,
488 .id = keylet.key,
489 .holder = depositor,
490 .amount = asset(0)});
491 env(tx, ter{tecPRECISION_LOSS});
492 env.close();
493 }
494
495 {
496 auto tx = vault.withdraw(
497 {.depositor = depositor,
498 .id = keylet.key,
499 .amount = share(10)});
500 env(tx, ter{tecINSUFFICIENT_FUNDS});
501 env.close();
502 }
503 }
504
505 if (!asset.raw().native() && asset.raw().holds<Issue>())
506 {
507 testcase(prefix + " temporary authorization for 3rd party");
508 env(trust(erin, asset(1000)));
509 env(trust(issuer, asset(0), erin, tfSetfAuth));
510 env(pay(issuer, erin, asset(10)));
511
512 // Erin deposits all in vault, then sends shares to depositor
513 auto tx = vault.deposit(
514 {.depositor = erin, .id = keylet.key, .amount = asset(10)});
515 env(tx);
516 env.close();
517 {
518 auto tx = pay(erin, depositor, share(10 * scale));
519
520 // depositor no longer has MPToken for shares
521 env(tx, ter{tecNO_AUTH});
522 env.close();
523
524 // depositor will gain MPToken for shares again
525 env(vault.deposit(
526 {.depositor = depositor,
527 .id = keylet.key,
528 .amount = asset(1)}));
529 env.close();
530
531 env(tx);
532 env.close();
533 }
534
535 testcase(prefix + " withdraw to authorized 3rd party");
536 // Depositor withdraws assets, destined to Erin
537 tx = vault.withdraw(
538 {.depositor = depositor,
539 .id = keylet.key,
540 .amount = asset(10)});
541 tx[sfDestination] = erin.human();
542 env(tx);
543 env.close();
544
545 // Erin returns assets to issuer
546 env(pay(erin, issuer, asset(10)));
547 env.close();
548
549 testcase(prefix + " fail to pay to unauthorized 3rd party");
550 env(trust(erin, asset(0)));
551 env.close();
552
553 // Erin has MPToken but is no longer authorized to hold assets
554 env(pay(depositor, erin, share(1)), ter{tecNO_LINE});
555 env.close();
556
557 // Depositor withdraws remaining single asset
558 tx = vault.withdraw(
559 {.depositor = depositor,
560 .id = keylet.key,
561 .amount = asset(1)});
562 env(tx);
563 env.close();
564 }
565
566 {
567 testcase(prefix + " fail to delete because wrong owner");
568 auto tx = vault.del({.owner = issuer, .id = keylet.key});
569 env(tx, ter(tecNO_PERMISSION));
570 env.close();
571 }
572
573 {
574 testcase(prefix + " delete empty vault");
575 auto tx = vault.del({.owner = owner, .id = keylet.key});
576 env(tx);
577 env.close();
578 BEAST_EXPECT(!env.le(keylet));
579 }
580 };
581
582 auto testCases = [&, this](
583 std::string prefix,
584 std::function<PrettyAsset(Env & env)> setup) {
585 Env env{*this, testable_amendments() | featureSingleAssetVault};
586
587 Vault vault{env};
588 env.fund(XRP(1000), issuer, owner, depositor, charlie, dave);
589 env.close();
590 env(fset(issuer, asfAllowTrustLineClawback));
591 env(fset(issuer, asfRequireAuth));
592 env(fset(dave, asfRequireDest));
593 env.close();
594 env.require(flags(issuer, asfAllowTrustLineClawback));
595 env.require(flags(issuer, asfRequireAuth));
596
597 PrettyAsset asset = setup(env);
598 testSequence(prefix, env, vault, asset);
599 };
600
601 testCases("XRP", [&](Env& env) -> PrettyAsset {
602 return {xrpIssue(), 1'000'000};
603 });
604
605 testCases("IOU", [&](Env& env) -> Asset {
606 PrettyAsset asset = issuer["IOU"];
607 env(trust(owner, asset(1000)));
608 env(trust(depositor, asset(1000)));
609 env(trust(charlie, asset(1000)));
610 env(trust(dave, asset(1000)));
611 env(trust(issuer, asset(0), owner, tfSetfAuth));
612 env(trust(issuer, asset(0), depositor, tfSetfAuth));
613 env(trust(issuer, asset(0), charlie, tfSetfAuth));
614 env(trust(issuer, asset(0), dave, tfSetfAuth));
615 env(pay(issuer, depositor, asset(1000)));
616 env.close();
617 return asset;
618 });
619
620 testCases("MPT", [&](Env& env) -> Asset {
621 MPTTester mptt{env, issuer, mptInitNoFund};
622 mptt.create(
624 PrettyAsset asset = mptt.issuanceID();
625 mptt.authorize({.account = depositor});
626 mptt.authorize({.account = charlie});
627 mptt.authorize({.account = dave});
628 env(pay(issuer, depositor, asset(1000)));
629 env.close();
630 return asset;
631 });
632 }
633
634 void
636 {
637 using namespace test::jtx;
638
639 struct CaseArgs
640 {
641 FeatureBitset features =
642 testable_amendments() | featureSingleAssetVault;
643 };
644
645 auto testCase = [&, this](
646 std::function<void(
647 Env & env,
648 Account const& issuer,
649 Account const& owner,
650 Asset const& asset,
651 Vault& vault)> test,
652 CaseArgs args = {}) {
653 Env env{*this, args.features};
654 Account issuer{"issuer"};
655 Account owner{"owner"};
656 Vault vault{env};
657 env.fund(XRP(1000), issuer, owner);
658 env.close();
659
660 env(fset(issuer, asfAllowTrustLineClawback));
661 env(fset(issuer, asfRequireAuth));
662 env.close();
663
664 PrettyAsset asset = issuer["IOU"];
665 env(trust(owner, asset(1000)));
666 env(trust(issuer, asset(0), owner, tfSetfAuth));
667 env(pay(issuer, owner, asset(1000)));
668 env.close();
669
670 test(env, issuer, owner, asset, vault);
671 };
672
673 testCase(
674 [&](Env& env,
675 Account const& issuer,
676 Account const& owner,
677 Asset const& asset,
678 Vault& vault) {
679 testcase("disabled single asset vault");
680
681 auto [tx, keylet] =
682 vault.create({.owner = owner, .asset = asset});
683 env(tx, ter{temDISABLED});
684
685 {
686 auto tx = vault.set({.owner = owner, .id = keylet.key});
687 env(tx, ter{temDISABLED});
688 }
689
690 {
691 auto tx = vault.deposit(
692 {.depositor = owner,
693 .id = keylet.key,
694 .amount = asset(10)});
695 env(tx, ter{temDISABLED});
696 }
697
698 {
699 auto tx = vault.withdraw(
700 {.depositor = owner,
701 .id = keylet.key,
702 .amount = asset(10)});
703 env(tx, ter{temDISABLED});
704 }
705
706 {
707 auto tx = vault.clawback(
708 {.issuer = issuer,
709 .id = keylet.key,
710 .holder = owner,
711 .amount = asset(10)});
712 env(tx, ter{temDISABLED});
713 }
714
715 {
716 auto tx = vault.del({.owner = owner, .id = keylet.key});
717 env(tx, ter{temDISABLED});
718 }
719 },
720 {.features = testable_amendments() - featureSingleAssetVault});
721
722 testCase([&](Env& env,
723 Account const& issuer,
724 Account const& owner,
725 Asset const& asset,
726 Vault& vault) {
727 testcase("invalid flags");
728
729 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
730 tx[sfFlags] = tfClearDeepFreeze;
731 env(tx, ter{temINVALID_FLAG});
732
733 {
734 auto tx = vault.set({.owner = owner, .id = keylet.key});
735 tx[sfFlags] = tfClearDeepFreeze;
736 env(tx, ter{temINVALID_FLAG});
737 }
738
739 {
740 auto tx = vault.deposit(
741 {.depositor = owner,
742 .id = keylet.key,
743 .amount = asset(10)});
744 tx[sfFlags] = tfClearDeepFreeze;
745 env(tx, ter{temINVALID_FLAG});
746 }
747
748 {
749 auto tx = vault.withdraw(
750 {.depositor = owner,
751 .id = keylet.key,
752 .amount = asset(10)});
753 tx[sfFlags] = tfClearDeepFreeze;
754 env(tx, ter{temINVALID_FLAG});
755 }
756
757 {
758 auto tx = vault.clawback(
759 {.issuer = issuer,
760 .id = keylet.key,
761 .holder = owner,
762 .amount = asset(10)});
763 tx[sfFlags] = tfClearDeepFreeze;
764 env(tx, ter{temINVALID_FLAG});
765 }
766
767 {
768 auto tx = vault.del({.owner = owner, .id = keylet.key});
769 tx[sfFlags] = tfClearDeepFreeze;
770 env(tx, ter{temINVALID_FLAG});
771 }
772 });
773
774 testCase([&](Env& env,
775 Account const& issuer,
776 Account const& owner,
777 Asset const& asset,
778 Vault& vault) {
779 testcase("invalid fee");
780
781 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
782 tx[jss::Fee] = "-1";
783 env(tx, ter{temBAD_FEE});
784
785 {
786 auto tx = vault.set({.owner = owner, .id = keylet.key});
787 tx[jss::Fee] = "-1";
788 env(tx, ter{temBAD_FEE});
789 }
790
791 {
792 auto tx = vault.deposit(
793 {.depositor = owner,
794 .id = keylet.key,
795 .amount = asset(10)});
796 tx[jss::Fee] = "-1";
797 env(tx, ter{temBAD_FEE});
798 }
799
800 {
801 auto tx = vault.withdraw(
802 {.depositor = owner,
803 .id = keylet.key,
804 .amount = asset(10)});
805 tx[jss::Fee] = "-1";
806 env(tx, ter{temBAD_FEE});
807 }
808
809 {
810 auto tx = vault.clawback(
811 {.issuer = issuer,
812 .id = keylet.key,
813 .holder = owner,
814 .amount = asset(10)});
815 tx[jss::Fee] = "-1";
816 env(tx, ter{temBAD_FEE});
817 }
818
819 {
820 auto tx = vault.del({.owner = owner, .id = keylet.key});
821 tx[jss::Fee] = "-1";
822 env(tx, ter{temBAD_FEE});
823 }
824 });
825
826 testCase(
827 [&](Env& env,
828 Account const&,
829 Account const& owner,
830 Asset const&,
831 Vault& vault) {
832 testcase("disabled permissioned domain");
833
834 auto [tx, keylet] =
835 vault.create({.owner = owner, .asset = xrpIssue()});
836 tx[sfDomainID] = to_string(base_uint<256>(42ul));
837 env(tx, ter{temDISABLED});
838
839 {
840 auto tx = vault.set({.owner = owner, .id = keylet.key});
841 tx[sfDomainID] = to_string(base_uint<256>(42ul));
842 env(tx, ter{temDISABLED});
843 }
844
845 {
846 auto tx = vault.set({.owner = owner, .id = keylet.key});
847 tx[sfDomainID] = "0";
848 env(tx, ter{temDISABLED});
849 }
850 },
851 {.features = (testable_amendments() | featureSingleAssetVault) -
852 featurePermissionedDomains});
853
854 testCase([&](Env& env,
855 Account const& issuer,
856 Account const& owner,
857 Asset const& asset,
858 Vault& vault) {
859 testcase("use zero vault");
860
861 auto [tx, keylet] =
862 vault.create({.owner = owner, .asset = xrpIssue()});
863
864 {
865 auto tx = vault.set({
866 .owner = owner,
867 .id = beast::zero,
868 });
869 env(tx, ter{temMALFORMED});
870 }
871
872 {
873 auto tx = vault.deposit(
874 {.depositor = owner,
875 .id = beast::zero,
876 .amount = asset(10)});
877 env(tx, ter(temMALFORMED));
878 }
879
880 {
881 auto tx = vault.withdraw(
882 {.depositor = owner,
883 .id = beast::zero,
884 .amount = asset(10)});
885 env(tx, ter{temMALFORMED});
886 }
887
888 {
889 auto tx = vault.clawback(
890 {.issuer = issuer,
891 .id = beast::zero,
892 .holder = owner,
893 .amount = asset(10)});
894 env(tx, ter{temMALFORMED});
895 }
896
897 {
898 auto tx = vault.del({
899 .owner = owner,
900 .id = beast::zero,
901 });
902 env(tx, ter{temMALFORMED});
903 }
904 });
905
906 testCase([&](Env& env,
907 Account const& issuer,
908 Account const& owner,
909 Asset const& asset,
910 Vault& vault) {
911 testcase("clawback from self");
912
913 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
914
915 {
916 auto tx = vault.clawback(
917 {.issuer = issuer,
918 .id = keylet.key,
919 .holder = issuer,
920 .amount = asset(10)});
921 env(tx, ter{temMALFORMED});
922 }
923 });
924
925 testCase([&](Env& env,
926 Account const&,
927 Account const& owner,
928 Asset const& asset,
929 Vault& vault) {
930 testcase("withdraw to bad destination");
931
932 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
933
934 {
935 auto tx = vault.withdraw(
936 {.depositor = owner,
937 .id = keylet.key,
938 .amount = asset(10)});
939 tx[jss::Destination] = "0";
940 env(tx, ter{temMALFORMED});
941 }
942 });
943
944 testCase([&](Env& env,
945 Account const&,
946 Account const& owner,
947 Asset const& asset,
948 Vault& vault) {
949 testcase("create with Scale");
950
951 {
952 auto [tx, keylet] =
953 vault.create({.owner = owner, .asset = asset});
954 tx[sfScale] = 255;
955 env(tx, ter(temMALFORMED));
956 }
957
958 {
959 auto [tx, keylet] =
960 vault.create({.owner = owner, .asset = asset});
961 tx[sfScale] = 19;
962 env(tx, ter(temMALFORMED));
963 }
964
965 // accepted range from 0 to 18
966 {
967 auto [tx, keylet] =
968 vault.create({.owner = owner, .asset = asset});
969 tx[sfScale] = 18;
970 env(tx);
971 env.close();
972 auto const sleVault = env.le(keylet);
973 BEAST_EXPECT(sleVault);
974 BEAST_EXPECT((*sleVault)[sfScale] == 18);
975 }
976
977 {
978 auto [tx, keylet] =
979 vault.create({.owner = owner, .asset = asset});
980 tx[sfScale] = 0;
981 env(tx);
982 env.close();
983 auto const sleVault = env.le(keylet);
984 BEAST_EXPECT(sleVault);
985 BEAST_EXPECT((*sleVault)[sfScale] == 0);
986 }
987
988 {
989 auto [tx, keylet] =
990 vault.create({.owner = owner, .asset = asset});
991 env(tx);
992 env.close();
993 auto const sleVault = env.le(keylet);
994 BEAST_EXPECT(sleVault);
995 BEAST_EXPECT((*sleVault)[sfScale] == 6);
996 }
997 });
998
999 testCase([&](Env& env,
1000 Account const&,
1001 Account const& owner,
1002 Asset const& asset,
1003 Vault& vault) {
1004 testcase("create or set invalid data");
1005
1006 auto [tx1, keylet] = vault.create({.owner = owner, .asset = asset});
1007
1008 {
1009 auto tx = tx1;
1010 tx[sfData] = "";
1011 env(tx, ter(temMALFORMED));
1012 }
1013
1014 {
1015 auto tx = tx1;
1016 // A hexadecimal string of 257 bytes.
1017 tx[sfData] = std::string(514, 'A');
1018 env(tx, ter(temMALFORMED));
1019 }
1020
1021 {
1022 auto tx = vault.set({.owner = owner, .id = keylet.key});
1023 tx[sfData] = "";
1024 env(tx, ter{temMALFORMED});
1025 }
1026
1027 {
1028 auto tx = vault.set({.owner = owner, .id = keylet.key});
1029 // A hexadecimal string of 257 bytes.
1030 tx[sfData] = std::string(514, 'A');
1031 env(tx, ter{temMALFORMED});
1032 }
1033 });
1034
1035 testCase([&](Env& env,
1036 Account const&,
1037 Account const& owner,
1038 Asset const& asset,
1039 Vault& vault) {
1040 testcase("set nothing updated");
1041
1042 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1043
1044 {
1045 auto tx = vault.set({.owner = owner, .id = keylet.key});
1046 env(tx, ter{temMALFORMED});
1047 }
1048 });
1049
1050 testCase([&](Env& env,
1051 Account const&,
1052 Account const& owner,
1053 Asset const& asset,
1054 Vault& vault) {
1055 testcase("create with invalid metadata");
1056
1057 auto [tx1, keylet] = vault.create({.owner = owner, .asset = asset});
1058
1059 {
1060 auto tx = tx1;
1061 tx[sfMPTokenMetadata] = "";
1062 env(tx, ter(temMALFORMED));
1063 }
1064
1065 {
1066 auto tx = tx1;
1067 // This metadata is for the share token.
1068 // A hexadecimal string of 1025 bytes.
1069 tx[sfMPTokenMetadata] = std::string(2050, 'B');
1070 env(tx, ter(temMALFORMED));
1071 }
1072 });
1073
1074 testCase([&](Env& env,
1075 Account const&,
1076 Account const& owner,
1077 Asset const& asset,
1078 Vault& vault) {
1079 testcase("set negative maximum");
1080
1081 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1082
1083 {
1084 auto tx = vault.set({.owner = owner, .id = keylet.key});
1085 tx[sfAssetsMaximum] = negativeAmount(asset).number();
1086 env(tx, ter{temMALFORMED});
1087 }
1088 });
1089
1090 testCase([&](Env& env,
1091 Account const&,
1092 Account const& owner,
1093 Asset const& asset,
1094 Vault& vault) {
1095 testcase("invalid deposit amount");
1096
1097 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1098
1099 {
1100 auto tx = vault.deposit(
1101 {.depositor = owner,
1102 .id = keylet.key,
1103 .amount = negativeAmount(asset)});
1104 env(tx, ter(temBAD_AMOUNT));
1105 }
1106
1107 {
1108 auto tx = vault.deposit(
1109 {.depositor = owner, .id = keylet.key, .amount = asset(0)});
1110 env(tx, ter(temBAD_AMOUNT));
1111 }
1112 });
1113
1114 testCase([&](Env& env,
1115 Account const&,
1116 Account const& owner,
1117 Asset const& asset,
1118 Vault& vault) {
1119 testcase("invalid set immutable flag");
1120
1121 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1122
1123 {
1124 auto tx = vault.set({.owner = owner, .id = keylet.key});
1125 tx[sfFlags] = tfVaultPrivate;
1126 env(tx, ter(temINVALID_FLAG));
1127 }
1128 });
1129
1130 testCase([&](Env& env,
1131 Account const&,
1132 Account const& owner,
1133 Asset const& asset,
1134 Vault& vault) {
1135 testcase("invalid withdraw amount");
1136
1137 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1138
1139 {
1140 auto tx = vault.withdraw(
1141 {.depositor = owner,
1142 .id = keylet.key,
1143 .amount = negativeAmount(asset)});
1144 env(tx, ter(temBAD_AMOUNT));
1145 }
1146
1147 {
1148 auto tx = vault.withdraw(
1149 {.depositor = owner, .id = keylet.key, .amount = asset(0)});
1150 env(tx, ter(temBAD_AMOUNT));
1151 }
1152 });
1153
1154 testCase([&](Env& env,
1155 Account const& issuer,
1156 Account const& owner,
1157 Asset const& asset,
1158 Vault& vault) {
1159 testcase("invalid clawback");
1160
1161 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1162
1163 {
1164 auto tx = vault.clawback(
1165 {.issuer = owner,
1166 .id = keylet.key,
1167 .holder = issuer,
1168 .amount = asset(50)});
1169 env(tx, ter(temMALFORMED));
1170 }
1171
1172 {
1173 auto tx = vault.clawback(
1174 {.issuer = issuer,
1175 .id = keylet.key,
1176 .holder = owner,
1177 .amount = negativeAmount(asset)});
1178 env(tx, ter(temBAD_AMOUNT));
1179 }
1180 });
1181
1182 testCase([&](Env& env,
1183 Account const&,
1184 Account const& owner,
1185 Asset const& asset,
1186 Vault& vault) {
1187 testcase("invalid create");
1188
1189 auto [tx1, keylet] = vault.create({.owner = owner, .asset = asset});
1190
1191 {
1192 auto tx = tx1;
1193 tx[sfWithdrawalPolicy] = 0;
1194 env(tx, ter(temMALFORMED));
1195 }
1196
1197 {
1198 auto tx = tx1;
1199 tx[sfDomainID] = to_string(base_uint<256>(42ul));
1200 env(tx, ter{temMALFORMED});
1201 }
1202
1203 {
1204 auto tx = tx1;
1205 tx[sfAssetsMaximum] = negativeAmount(asset).number();
1206 env(tx, ter{temMALFORMED});
1207 }
1208
1209 {
1210 auto tx = tx1;
1211 tx[sfFlags] = tfVaultPrivate;
1212 tx[sfDomainID] = "0";
1213 env(tx, ter{temMALFORMED});
1214 }
1215 });
1216 }
1217
1218 // Test for non-asset specific behaviors.
1219 void
1221 {
1222 using namespace test::jtx;
1223
1224 auto testCase = [this](std::function<void(
1225 Env & env,
1226 Account const& issuer,
1227 Account const& owner,
1228 Account const& depositor,
1229 Asset const& asset,
1230 Vault& vault)> test) {
1231 Env env{*this, testable_amendments() | featureSingleAssetVault};
1232 Account issuer{"issuer"};
1233 Account owner{"owner"};
1234 Account depositor{"depositor"};
1235 env.fund(XRP(1000), issuer, owner, depositor);
1236 env.close();
1237 Vault vault{env};
1238 Asset asset = xrpIssue();
1239
1240 test(env, issuer, owner, depositor, asset, vault);
1241 };
1242
1243 testCase([this](
1244 Env& env,
1245 Account const& issuer,
1246 Account const& owner,
1247 Account const& depositor,
1248 PrettyAsset const& asset,
1249 Vault& vault) {
1250 testcase("nothing to set");
1251 auto tx = vault.set({.owner = owner, .id = keylet::skip().key});
1252 tx[sfAssetsMaximum] = asset(0).number();
1253 env(tx, ter(tecNO_ENTRY));
1254 });
1255
1256 testCase([this](
1257 Env& env,
1258 Account const& issuer,
1259 Account const& owner,
1260 Account const& depositor,
1261 PrettyAsset const& asset,
1262 Vault& vault) {
1263 testcase("nothing to deposit to");
1264 auto tx = vault.deposit(
1265 {.depositor = depositor,
1266 .id = keylet::skip().key,
1267 .amount = asset(10)});
1268 env(tx, ter(tecNO_ENTRY));
1269 });
1270
1271 testCase([this](
1272 Env& env,
1273 Account const& issuer,
1274 Account const& owner,
1275 Account const& depositor,
1276 PrettyAsset const& asset,
1277 Vault& vault) {
1278 testcase("nothing to withdraw from");
1279 auto tx = vault.withdraw(
1280 {.depositor = depositor,
1281 .id = keylet::skip().key,
1282 .amount = asset(10)});
1283 env(tx, ter(tecNO_ENTRY));
1284 });
1285
1286 testCase([this](
1287 Env& env,
1288 Account const& issuer,
1289 Account const& owner,
1290 Account const& depositor,
1291 Asset const& asset,
1292 Vault& vault) {
1293 testcase("nothing to delete");
1294 auto tx = vault.del({.owner = owner, .id = keylet::skip().key});
1295 env(tx, ter(tecNO_ENTRY));
1296 });
1297
1298 testCase([this](
1299 Env& env,
1300 Account const& issuer,
1301 Account const& owner,
1302 Account const& depositor,
1303 Asset const& asset,
1304 Vault& vault) {
1305 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1306 testcase("transaction is good");
1307 env(tx);
1308 });
1309
1310 testCase([this](
1311 Env& env,
1312 Account const& issuer,
1313 Account const& owner,
1314 Account const& depositor,
1315 Asset const& asset,
1316 Vault& vault) {
1317 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1318 tx[sfWithdrawalPolicy] = 1;
1319 testcase("explicitly select withdrawal policy");
1320 env(tx);
1321 });
1322
1323 testCase([this](
1324 Env& env,
1325 Account const& issuer,
1326 Account const& owner,
1327 Account const& depositor,
1328 Asset const& asset,
1329 Vault& vault) {
1330 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1331 testcase("insufficient fee");
1332 env(tx, fee(env.current()->fees().base), ter(telINSUF_FEE_P));
1333 });
1334
1335 testCase([this](
1336 Env& env,
1337 Account const& issuer,
1338 Account const& owner,
1339 Account const& depositor,
1340 Asset const& asset,
1341 Vault& vault) {
1342 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1343 testcase("insufficient reserve");
1344 // It is possible to construct a complicated mathematical
1345 // expression for this amount, but it is sadly not easy.
1346 env(pay(owner, issuer, XRP(775)));
1347 env.close();
1348 env(tx, ter(tecINSUFFICIENT_RESERVE));
1349 });
1350
1351 testCase([this](
1352 Env& env,
1353 Account const& issuer,
1354 Account const& owner,
1355 Account const& depositor,
1356 Asset const& asset,
1357 Vault& vault) {
1358 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1359 tx[sfFlags] = tfVaultPrivate;
1360 tx[sfDomainID] = to_string(base_uint<256>(42ul));
1361 testcase("non-existing domain");
1362 env(tx, ter{tecOBJECT_NOT_FOUND});
1363 });
1364
1365 testCase([this](
1366 Env& env,
1367 Account const& issuer,
1368 Account const& owner,
1369 Account const& depositor,
1370 Asset const& asset,
1371 Vault& vault) {
1372 testcase("cannot set Scale=0");
1373 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1374 tx[sfScale] = 0;
1375 env(tx, ter{temMALFORMED});
1376 });
1377
1378 testCase([this](
1379 Env& env,
1380 Account const& issuer,
1381 Account const& owner,
1382 Account const& depositor,
1383 Asset const& asset,
1384 Vault& vault) {
1385 testcase("cannot set Scale=1");
1386 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1387 tx[sfScale] = 1;
1388 env(tx, ter{temMALFORMED});
1389 });
1390 }
1391
1392 void
1394 {
1395 using namespace test::jtx;
1396 {
1397 {
1398 testcase("IOU fail because MPT is disabled");
1399 Env env{
1400 *this,
1401 (testable_amendments() - featureMPTokensV1) |
1402 featureSingleAssetVault};
1403 Account issuer{"issuer"};
1404 Account owner{"owner"};
1405 env.fund(XRP(1000), issuer, owner);
1406 env.close();
1407
1408 Vault vault{env};
1409 Asset asset = issuer["IOU"].asset();
1410 auto [tx, keylet] =
1411 vault.create({.owner = owner, .asset = asset});
1412
1413 env(tx, ter(temDISABLED));
1414 env.close();
1415 }
1416
1417 {
1418 testcase("IOU fail create frozen");
1419 Env env{*this, testable_amendments() | featureSingleAssetVault};
1420 Account issuer{"issuer"};
1421 Account owner{"owner"};
1422 env.fund(XRP(1000), issuer, owner);
1423 env.close();
1424 env(fset(issuer, asfGlobalFreeze));
1425 env.close();
1426
1427 Vault vault{env};
1428 Asset asset = issuer["IOU"].asset();
1429 auto [tx, keylet] =
1430 vault.create({.owner = owner, .asset = asset});
1431
1432 env(tx, ter(tecFROZEN));
1433 env.close();
1434 }
1435
1436 {
1437 testcase("IOU fail create no ripling");
1438 Env env{*this, testable_amendments() | featureSingleAssetVault};
1439 Account issuer{"issuer"};
1440 Account owner{"owner"};
1441 env.fund(XRP(1000), issuer, owner);
1442 env.close();
1443 env(fclear(issuer, asfDefaultRipple));
1444 env.close();
1445
1446 Vault vault{env};
1447 Asset asset = issuer["IOU"].asset();
1448 auto [tx, keylet] =
1449 vault.create({.owner = owner, .asset = asset});
1450 env(tx, ter(terNO_RIPPLE));
1451 env.close();
1452 }
1453
1454 {
1455 testcase("IOU no issuer");
1456 Env env{*this, testable_amendments() | featureSingleAssetVault};
1457 Account issuer{"issuer"};
1458 Account owner{"owner"};
1459 env.fund(XRP(1000), owner);
1460 env.close();
1461
1462 Vault vault{env};
1463 Asset asset = issuer["IOU"].asset();
1464 {
1465 auto [tx, keylet] =
1466 vault.create({.owner = owner, .asset = asset});
1467 env(tx, ter(terNO_ACCOUNT));
1468 env.close();
1469 }
1470 }
1471 }
1472
1473 {
1474 testcase("IOU fail create vault for AMM LPToken");
1475 Env env{*this, testable_amendments() | featureSingleAssetVault};
1476 Account const gw("gateway");
1477 Account const alice("alice");
1478 Account const carol("carol");
1479 IOU const USD = gw["USD"];
1480
1481 auto const [asset1, asset2] =
1482 std::pair<STAmount, STAmount>(XRP(10000), USD(10000));
1483 auto tofund = [&](STAmount const& a) -> STAmount {
1484 if (a.native())
1485 {
1486 auto const defXRP = XRP(30000);
1487 if (a <= defXRP)
1488 return defXRP;
1489 return a + XRP(1000);
1490 }
1491 auto const defIOU = STAmount{a.issue(), 30000};
1492 if (a <= defIOU)
1493 return defIOU;
1494 return a + STAmount{a.issue(), 1000};
1495 };
1496 auto const toFund1 = tofund(asset1);
1497 auto const toFund2 = tofund(asset2);
1498 BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2);
1499
1500 if (!asset1.native() && !asset2.native())
1501 fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All);
1502 else if (asset1.native())
1503 fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All);
1504 else if (asset2.native())
1505 fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All);
1506
1507 AMM ammAlice(
1508 env, alice, asset1, asset2, CreateArg{.log = false, .tfee = 0});
1509
1510 Account const owner{"owner"};
1511 env.fund(XRP(1000000), owner);
1512
1513 Vault vault{env};
1514 auto [tx, k] =
1515 vault.create({.owner = owner, .asset = ammAlice.lptIssue()});
1516 env(tx, ter{tecWRONG_ASSET});
1517 env.close();
1518 }
1519 }
1520
1521 void
1523 {
1524 using namespace test::jtx;
1525
1526 auto testCase = [this](std::function<void(
1527 Env & env,
1528 Account const& issuer,
1529 Account const& owner,
1530 Account const& depositor,
1531 Asset const& asset,
1532 Vault& vault)> test) {
1533 Env env{*this, testable_amendments() | featureSingleAssetVault};
1534 Account issuer{"issuer"};
1535 Account owner{"owner"};
1536 Account depositor{"depositor"};
1537 env.fund(XRP(1000), issuer, owner, depositor);
1538 env.close();
1539 Vault vault{env};
1540 MPTTester mptt{env, issuer, mptInitNoFund};
1541 // Locked because that is the default flag.
1542 mptt.create();
1543 Asset asset = mptt.issuanceID();
1544
1545 test(env, issuer, owner, depositor, asset, vault);
1546 };
1547
1548 testCase([this](
1549 Env& env,
1550 Account const& issuer,
1551 Account const& owner,
1552 Account const& depositor,
1553 Asset const& asset,
1554 Vault& vault) {
1555 testcase("MPT no authorization");
1556 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1557 env(tx, ter(tecNO_AUTH));
1558 });
1559
1560 testCase([this](
1561 Env& env,
1562 Account const& issuer,
1563 Account const& owner,
1564 Account const& depositor,
1565 Asset const& asset,
1566 Vault& vault) {
1567 testcase("MPT cannot set Scale=0");
1568 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1569 tx[sfScale] = 0;
1570 env(tx, ter{temMALFORMED});
1571 });
1572
1573 testCase([this](
1574 Env& env,
1575 Account const& issuer,
1576 Account const& owner,
1577 Account const& depositor,
1578 Asset const& asset,
1579 Vault& vault) {
1580 testcase("MPT cannot set Scale=1");
1581 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1582 tx[sfScale] = 1;
1583 env(tx, ter{temMALFORMED});
1584 });
1585 }
1586
1587 void
1589 {
1590 using namespace test::jtx;
1591
1592 Env env{*this, testable_amendments() | featureSingleAssetVault};
1593 Account issuer{"issuer"};
1594 Account owner{"owner"};
1595 Account depositor{"depositor"};
1596 env.fund(XRP(1000), issuer, owner, depositor);
1597 env.close();
1598
1599 Vault vault{env};
1600 PrettyAsset asset = issuer["IOU"];
1601 env.trust(asset(1000), owner);
1602 env(pay(issuer, owner, asset(100)));
1603 env.trust(asset(1000), depositor);
1604 env(pay(issuer, depositor, asset(100)));
1605 env.close();
1606
1607 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1608 tx[sfFlags] = tfVaultShareNonTransferable;
1609 env(tx);
1610 env.close();
1611
1612 {
1613 testcase("nontransferable deposits");
1614 auto tx1 = vault.deposit(
1615 {.depositor = depositor,
1616 .id = keylet.key,
1617 .amount = asset(40)});
1618 env(tx1);
1619
1620 auto tx2 = vault.deposit(
1621 {.depositor = owner, .id = keylet.key, .amount = asset(60)});
1622 env(tx2);
1623 env.close();
1624 }
1625
1626 auto const vaultAccount = //
1627 [&env, key = keylet.key, this]() -> AccountID {
1628 auto jvVault = env.rpc("vault_info", strHex(key));
1629
1630 BEAST_EXPECT(
1631 jvVault[jss::result][jss::vault][sfAssetsTotal] == "100");
1632 BEAST_EXPECT(
1633 jvVault[jss::result][jss::vault][jss::shares]
1634 [sfOutstandingAmount] == "100000000");
1635
1636 // Vault pseudo-account
1637 return parseBase58<AccountID>(
1638 jvVault[jss::result][jss::vault][jss::Account]
1639 .asString())
1640 .value();
1641 }();
1642
1643 auto const MptID = makeMptID(1, vaultAccount);
1644 Asset shares = MptID;
1645
1646 {
1647 testcase("nontransferable shares cannot be moved");
1648 env(pay(owner, depositor, shares(10)), ter{tecNO_AUTH});
1649 env(pay(depositor, owner, shares(10)), ter{tecNO_AUTH});
1650 }
1651
1652 {
1653 testcase("nontransferable shares can be used to withdraw");
1654 auto tx1 = vault.withdraw(
1655 {.depositor = depositor,
1656 .id = keylet.key,
1657 .amount = asset(20)});
1658 env(tx1);
1659
1660 auto tx2 = vault.withdraw(
1661 {.depositor = owner, .id = keylet.key, .amount = asset(30)});
1662 env(tx2);
1663 env.close();
1664 }
1665
1666 {
1667 testcase("nontransferable shares balance check");
1668 auto jvVault = env.rpc("vault_info", strHex(keylet.key));
1669 BEAST_EXPECT(
1670 jvVault[jss::result][jss::vault][sfAssetsTotal] == "50");
1671 BEAST_EXPECT(
1672 jvVault[jss::result][jss::vault][jss::shares]
1673 [sfOutstandingAmount] == "50000000");
1674 }
1675
1676 {
1677 testcase("nontransferable shares withdraw rest");
1678 auto tx1 = vault.withdraw(
1679 {.depositor = depositor,
1680 .id = keylet.key,
1681 .amount = asset(20)});
1682 env(tx1);
1683
1684 auto tx2 = vault.withdraw(
1685 {.depositor = owner, .id = keylet.key, .amount = asset(30)});
1686 env(tx2);
1687 env.close();
1688 }
1689
1690 {
1691 testcase("nontransferable shares delete empty vault");
1692 auto tx = vault.del({.owner = owner, .id = keylet.key});
1693 env(tx);
1694 BEAST_EXPECT(!env.le(keylet));
1695 }
1696 }
1697
1698 void
1700 {
1701 using namespace test::jtx;
1702
1703 struct CaseArgs
1704 {
1705 bool enableClawback = true;
1706 bool requireAuth = true;
1707 int initialXRP = 1000;
1708 };
1709
1710 auto testCase = [this](
1711 std::function<void(
1712 Env & env,
1713 Account const& issuer,
1714 Account const& owner,
1715 Account const& depositor,
1716 Asset const& asset,
1717 Vault& vault,
1718 MPTTester& mptt)> test,
1719 CaseArgs args = {}) {
1720 Env env{*this, testable_amendments() | featureSingleAssetVault};
1721 Account issuer{"issuer"};
1722 Account owner{"owner"};
1723 Account depositor{"depositor"};
1724 env.fund(XRP(args.initialXRP), issuer, owner, depositor);
1725 env.close();
1726 Vault vault{env};
1727
1728 MPTTester mptt{env, issuer, mptInitNoFund};
1729 auto const none = LedgerSpecificFlags(0);
1730 mptt.create(
1731 {.flags = tfMPTCanTransfer | tfMPTCanLock |
1732 (args.enableClawback ? tfMPTCanClawback : none) |
1733 (args.requireAuth ? tfMPTRequireAuth : none)});
1734 PrettyAsset asset = mptt.issuanceID();
1735 mptt.authorize({.account = owner});
1736 mptt.authorize({.account = depositor});
1737 if (args.requireAuth)
1738 {
1739 mptt.authorize({.account = issuer, .holder = owner});
1740 mptt.authorize({.account = issuer, .holder = depositor});
1741 }
1742
1743 env(pay(issuer, depositor, asset(1000)));
1744 env.close();
1745
1746 test(env, issuer, owner, depositor, asset, vault, mptt);
1747 };
1748
1749 testCase([this](
1750 Env& env,
1751 Account const& issuer,
1752 Account const& owner,
1753 Account const& depositor,
1754 PrettyAsset const& asset,
1755 Vault& vault,
1756 MPTTester& mptt) {
1757 testcase("MPT nothing to clawback from");
1758 auto tx = vault.clawback(
1759 {.issuer = issuer,
1760 .id = keylet::skip().key,
1761 .holder = depositor,
1762 .amount = asset(10)});
1763 env(tx, ter(tecNO_ENTRY));
1764 });
1765
1766 testCase([this](
1767 Env& env,
1768 Account const& issuer,
1769 Account const& owner,
1770 Account const& depositor,
1771 Asset const& asset,
1772 Vault& vault,
1773 MPTTester& mptt) {
1774 testcase("MPT global lock blocks create");
1775 mptt.set({.account = issuer, .flags = tfMPTLock});
1776 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1777 env(tx, ter(tecLOCKED));
1778 });
1779
1780 testCase([this](
1781 Env& env,
1782 Account const& issuer,
1783 Account const& owner,
1784 Account const& depositor,
1785 Asset const& asset,
1786 Vault& vault,
1787 MPTTester& mptt) {
1788 testcase("MPT global lock blocks deposit");
1789 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1790 env(tx);
1791 env.close();
1792
1793 mptt.set({.account = issuer, .flags = tfMPTLock});
1794 env.close();
1795
1796 tx = vault.deposit(
1797 {.depositor = depositor,
1798 .id = keylet.key,
1799 .amount = asset(100)});
1800 env(tx, ter{tecLOCKED});
1801 env.close();
1802
1803 // Can delete empty vault, even if global lock
1804 tx = vault.del({.owner = owner, .id = keylet.key});
1805 env(tx);
1806 });
1807
1808 testCase([this](
1809 Env& env,
1810 Account const& issuer,
1811 Account const& owner,
1812 Account const& depositor,
1813 Asset const& asset,
1814 Vault& vault,
1815 MPTTester& mptt) {
1816 testcase("MPT global lock blocks withdrawal");
1817 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1818 env(tx);
1819 env.close();
1820 tx = vault.deposit(
1821 {.depositor = depositor,
1822 .id = keylet.key,
1823 .amount = asset(100)});
1824 env(tx);
1825 env.close();
1826
1827 // Check that the OutstandingAmount field of MPTIssuance
1828 // accounts for the issued shares.
1829 auto v = env.le(keylet);
1830 BEAST_EXPECT(v);
1831 MPTID share = (*v)[sfShareMPTID];
1832 auto issuance = env.le(keylet::mptIssuance(share));
1833 BEAST_EXPECT(issuance);
1834 Number outstandingShares = issuance->at(sfOutstandingAmount);
1835 BEAST_EXPECT(outstandingShares == 100);
1836
1837 mptt.set({.account = issuer, .flags = tfMPTLock});
1838 env.close();
1839
1840 tx = vault.withdraw(
1841 {.depositor = depositor,
1842 .id = keylet.key,
1843 .amount = asset(100)});
1844 env(tx, ter(tecLOCKED));
1845
1846 tx[sfDestination] = issuer.human();
1847 env(tx, ter(tecLOCKED));
1848
1849 // Clawback is still permitted, even with global lock
1850 tx = vault.clawback(
1851 {.issuer = issuer,
1852 .id = keylet.key,
1853 .holder = depositor,
1854 .amount = asset(0)});
1855 env(tx);
1856 env.close();
1857
1858 // Clawback removed shares MPToken
1859 auto const mptSle = env.le(keylet::mptoken(share, depositor.id()));
1860 BEAST_EXPECT(mptSle == nullptr);
1861
1862 // Can delete empty vault, even if global lock
1863 tx = vault.del({.owner = owner, .id = keylet.key});
1864 env(tx);
1865 });
1866
1867 testCase([this](
1868 Env& env,
1869 Account const& issuer,
1870 Account const& owner,
1871 Account const& depositor,
1872 PrettyAsset const& asset,
1873 Vault& vault,
1874 MPTTester& mptt) {
1875 testcase("MPT only issuer can clawback");
1876
1877 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
1878 env(tx);
1879 env.close();
1880
1881 tx = vault.deposit(
1882 {.depositor = depositor,
1883 .id = keylet.key,
1884 .amount = asset(100)});
1885 env(tx);
1886 env.close();
1887
1888 {
1889 auto tx = vault.clawback(
1890 {.issuer = owner, .id = keylet.key, .holder = depositor});
1891 env(tx, ter(tecNO_PERMISSION));
1892 }
1893 });
1894
1895 testCase(
1896 [this](
1897 Env& env,
1898 Account const& issuer,
1899 Account const& owner,
1900 Account const& depositor,
1901 PrettyAsset const& asset,
1902 Vault& vault,
1903 MPTTester& mptt) {
1904 testcase("MPT depositor without MPToken, auth required");
1905
1906 auto [tx, keylet] =
1907 vault.create({.owner = owner, .asset = asset});
1908 env(tx);
1909 env.close();
1910
1911 tx = vault.deposit(
1912 {.depositor = depositor,
1913 .id = keylet.key,
1914 .amount = asset(1000)});
1915 env(tx);
1916 env.close();
1917
1918 {
1919 // Remove depositor MPToken and it will not be re-created
1920 mptt.authorize(
1921 {.account = depositor, .flags = tfMPTUnauthorize});
1922 env.close();
1923
1924 auto const mptoken =
1925 keylet::mptoken(mptt.issuanceID(), depositor);
1926 auto const sleMPT1 = env.le(mptoken);
1927 BEAST_EXPECT(sleMPT1 == nullptr);
1928
1929 tx = vault.withdraw(
1930 {.depositor = depositor,
1931 .id = keylet.key,
1932 .amount = asset(100)});
1933 env(tx, ter{tecNO_AUTH});
1934 env.close();
1935
1936 auto const sleMPT2 = env.le(mptoken);
1937 BEAST_EXPECT(sleMPT2 == nullptr);
1938 }
1939
1940 {
1941 // Set destination to 3rd party without MPToken
1942 Account charlie{"charlie"};
1943 env.fund(XRP(1000), charlie);
1944 env.close();
1945
1946 tx = vault.withdraw(
1947 {.depositor = depositor,
1948 .id = keylet.key,
1949 .amount = asset(100)});
1950 tx[sfDestination] = charlie.human();
1951 env(tx, ter(tecNO_AUTH));
1952 }
1953 },
1954 {.requireAuth = true});
1955
1956 testCase(
1957 [this](
1958 Env& env,
1959 Account const& issuer,
1960 Account const& owner,
1961 Account const& depositor,
1962 PrettyAsset const& asset,
1963 Vault& vault,
1964 MPTTester& mptt) {
1965 testcase("MPT depositor without MPToken, no auth required");
1966
1967 auto [tx, keylet] =
1968 vault.create({.owner = owner, .asset = asset});
1969 env(tx);
1970 env.close();
1971 auto v = env.le(keylet);
1972 BEAST_EXPECT(v);
1973
1974 tx = vault.deposit(
1975 {.depositor = depositor,
1976 .id = keylet.key,
1977 .amount = asset(1000)}); // all assets held by depositor
1978 env(tx);
1979 env.close();
1980
1981 {
1982 // Remove depositor's MPToken and it will be re-created
1983 mptt.authorize(
1984 {.account = depositor, .flags = tfMPTUnauthorize});
1985 env.close();
1986
1987 auto const mptoken =
1988 keylet::mptoken(mptt.issuanceID(), depositor);
1989 auto const sleMPT1 = env.le(mptoken);
1990 BEAST_EXPECT(sleMPT1 == nullptr);
1991
1992 tx = vault.withdraw(
1993 {.depositor = depositor,
1994 .id = keylet.key,
1995 .amount = asset(100)});
1996 env(tx);
1997 env.close();
1998
1999 auto const sleMPT2 = env.le(mptoken);
2000 BEAST_EXPECT(sleMPT2 != nullptr);
2001 BEAST_EXPECT(sleMPT2->at(sfMPTAmount) == 100);
2002 }
2003
2004 {
2005 // Remove 3rd party MPToken and it will not be re-created
2006 mptt.authorize(
2007 {.account = owner, .flags = tfMPTUnauthorize});
2008 env.close();
2009
2010 auto const mptoken =
2011 keylet::mptoken(mptt.issuanceID(), owner);
2012 auto const sleMPT1 = env.le(mptoken);
2013 BEAST_EXPECT(sleMPT1 == nullptr);
2014
2015 tx = vault.withdraw(
2016 {.depositor = depositor,
2017 .id = keylet.key,
2018 .amount = asset(100)});
2019 tx[sfDestination] = owner.human();
2020 env(tx, ter(tecNO_AUTH));
2021 env.close();
2022
2023 auto const sleMPT2 = env.le(mptoken);
2024 BEAST_EXPECT(sleMPT2 == nullptr);
2025 }
2026 },
2027 {.requireAuth = false});
2028
2029 auto const [acctReserve, incReserve] = [this]() -> std::pair<int, int> {
2030 Env env{*this, testable_amendments()};
2031 return {
2032 env.current()->fees().accountReserve(0).drops() /
2034 env.current()->fees().increment.drops() /
2036 }();
2037
2038 testCase(
2039 [&, this](
2040 Env& env,
2041 Account const& issuer,
2042 Account const& owner,
2043 Account const& depositor,
2044 PrettyAsset const& asset,
2045 Vault& vault,
2046 MPTTester& mptt) {
2047 testcase("MPT failed reserve to re-create MPToken");
2048
2049 auto [tx, keylet] =
2050 vault.create({.owner = owner, .asset = asset});
2051 env(tx);
2052 env.close();
2053 auto v = env.le(keylet);
2054 BEAST_EXPECT(v);
2055
2056 env(pay(depositor, owner, asset(1000)));
2057 env.close();
2058
2059 tx = vault.deposit(
2060 {.depositor = owner,
2061 .id = keylet.key,
2062 .amount = asset(1000)}); // all assets held by owner
2063 env(tx);
2064 env.close();
2065
2066 {
2067 // Remove owners's MPToken and it will not be re-created
2068 mptt.authorize(
2069 {.account = owner, .flags = tfMPTUnauthorize});
2070 env.close();
2071
2072 auto const mptoken =
2073 keylet::mptoken(mptt.issuanceID(), owner);
2074 auto const sleMPT = env.le(mptoken);
2075 BEAST_EXPECT(sleMPT == nullptr);
2076
2077 // No reserve to create MPToken for asset in VaultWithdraw
2078 tx = vault.withdraw(
2079 {.depositor = owner,
2080 .id = keylet.key,
2081 .amount = asset(100)});
2082 env(tx, ter{tecINSUFFICIENT_RESERVE});
2083 env.close();
2084
2085 env(pay(depositor, owner, XRP(incReserve)));
2086 env.close();
2087
2088 // Withdraw can now create asset MPToken, tx will succeed
2089 env(tx);
2090 env.close();
2091 }
2092 },
2093 {.requireAuth = false,
2094 .initialXRP = acctReserve + incReserve * 4 - 1});
2095
2096 testCase([this](
2097 Env& env,
2098 Account const& issuer,
2099 Account const& owner,
2100 Account const& depositor,
2101 PrettyAsset const& asset,
2102 Vault& vault,
2103 MPTTester& mptt) {
2104 testcase("MPT issuance deleted");
2105
2106 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2107 env(tx);
2108 env.close();
2109
2110 tx = vault.deposit(
2111 {.depositor = depositor,
2112 .id = keylet.key,
2113 .amount = asset(1000)});
2114 env(tx);
2115 env.close();
2116
2117 {
2118 auto tx = vault.clawback(
2119 {.issuer = issuer,
2120 .id = keylet.key,
2121 .holder = depositor,
2122 .amount = asset(0)});
2123 env(tx);
2124 }
2125
2126 mptt.destroy({.issuer = issuer, .id = mptt.issuanceID()});
2127 env.close();
2128
2129 {
2130 auto [tx, keylet] =
2131 vault.create({.owner = depositor, .asset = asset});
2132 env(tx, ter{tecOBJECT_NOT_FOUND});
2133 }
2134
2135 {
2136 auto tx = vault.deposit(
2137 {.depositor = depositor,
2138 .id = keylet.key,
2139 .amount = asset(10)});
2140 env(tx, ter{tecOBJECT_NOT_FOUND});
2141 }
2142
2143 {
2144 auto tx = vault.withdraw(
2145 {.depositor = depositor,
2146 .id = keylet.key,
2147 .amount = asset(10)});
2148 env(tx, ter{tecOBJECT_NOT_FOUND});
2149 }
2150
2151 {
2152 auto tx = vault.clawback(
2153 {.issuer = issuer,
2154 .id = keylet.key,
2155 .holder = depositor,
2156 .amount = asset(0)});
2157 env(tx, ter{tecOBJECT_NOT_FOUND});
2158 }
2159
2160 env(vault.del({.owner = owner, .id = keylet.key}));
2161 });
2162
2163 testCase([this](
2164 Env& env,
2165 Account const& issuer,
2166 Account const& owner,
2167 Account const& depositor,
2168 PrettyAsset const& asset,
2169 Vault& vault,
2170 MPTTester& mptt) {
2171 testcase("MPT vault owner can receive shares unless unauthorized");
2172
2173 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2174 env(tx);
2175 env.close();
2176
2177 tx = vault.deposit(
2178 {.depositor = depositor,
2179 .id = keylet.key,
2180 .amount = asset(1000)});
2181 env(tx);
2182 env.close();
2183
2184 auto const issuanceId = [&env](ripple::Keylet keylet) -> MPTID {
2185 auto const vault = env.le(keylet);
2186 return vault->at(sfShareMPTID);
2187 }(keylet);
2188 PrettyAsset shares = MPTIssue(issuanceId);
2189
2190 {
2191 // owner has MPToken for shares they did not explicitly create
2192 env(pay(depositor, owner, shares(1)));
2193 env.close();
2194
2195 tx = vault.withdraw(
2196 {.depositor = owner,
2197 .id = keylet.key,
2198 .amount = shares(1)});
2199 env(tx);
2200 env.close();
2201
2202 // owner's MPToken for vault shares not destroyed by withdraw
2203 env(pay(depositor, owner, shares(1)));
2204 env.close();
2205
2206 tx = vault.clawback(
2207 {.issuer = issuer,
2208 .id = keylet.key,
2209 .holder = owner,
2210 .amount = asset(0)});
2211 env(tx);
2212 env.close();
2213
2214 // owner's MPToken for vault shares not destroyed by clawback
2215 env(pay(depositor, owner, shares(1)));
2216 env.close();
2217
2218 // pay back, so we can destroy owner's MPToken now
2219 env(pay(owner, depositor, shares(1)));
2220 env.close();
2221
2222 {
2223 // explicitly destroy vault owners MPToken with zero balance
2224 Json::Value jv;
2225 jv[sfAccount] = owner.human();
2226 jv[sfMPTokenIssuanceID] = to_string(issuanceId);
2227 jv[sfFlags] = tfMPTUnauthorize;
2228 jv[sfTransactionType] = jss::MPTokenAuthorize;
2229 env(jv);
2230 env.close();
2231 }
2232
2233 // owner no longer has MPToken for vault shares
2234 tx = pay(depositor, owner, shares(1));
2235 env(tx, ter{tecNO_AUTH});
2236 env.close();
2237
2238 // destroy all remaining shares, so we can delete vault
2239 tx = vault.clawback(
2240 {.issuer = issuer,
2241 .id = keylet.key,
2242 .holder = depositor,
2243 .amount = asset(0)});
2244 env(tx);
2245 env.close();
2246
2247 // will soft fail destroying MPToken for vault owner
2248 env(vault.del({.owner = owner, .id = keylet.key}));
2249 env.close();
2250 }
2251 });
2252
2253 testCase(
2254 [this](
2255 Env& env,
2256 Account const& issuer,
2257 Account const& owner,
2258 Account const& depositor,
2259 PrettyAsset const& asset,
2260 Vault& vault,
2261 MPTTester& mptt) {
2262 testcase("MPT clawback disabled");
2263
2264 auto [tx, keylet] =
2265 vault.create({.owner = owner, .asset = asset});
2266 env(tx);
2267 env.close();
2268
2269 tx = vault.deposit(
2270 {.depositor = depositor,
2271 .id = keylet.key,
2272 .amount = asset(1000)});
2273 env(tx);
2274 env.close();
2275
2276 {
2277 auto tx = vault.clawback(
2278 {.issuer = issuer,
2279 .id = keylet.key,
2280 .holder = depositor,
2281 .amount = asset(0)});
2282 env(tx, ter{tecNO_PERMISSION});
2283 }
2284 },
2285 {.enableClawback = false});
2286
2287 testCase([this](
2288 Env& env,
2289 Account const& issuer,
2290 Account const& owner,
2291 Account const& depositor,
2292 Asset const& asset,
2293 Vault& vault,
2294 MPTTester& mptt) {
2295 testcase("MPT un-authorization");
2296 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2297 env(tx);
2298 env.close();
2299 tx = vault.deposit(
2300 {.depositor = depositor,
2301 .id = keylet.key,
2302 .amount = asset(1000)});
2303 env(tx);
2304 env.close();
2305
2306 mptt.authorize(
2307 {.account = issuer,
2308 .holder = depositor,
2309 .flags = tfMPTUnauthorize});
2310 env.close();
2311
2312 {
2313 auto tx = vault.withdraw(
2314 {.depositor = depositor,
2315 .id = keylet.key,
2316 .amount = asset(100)});
2317 env(tx, ter(tecNO_AUTH));
2318
2319 // Withdrawal to other (authorized) accounts works
2320 tx[sfDestination] = issuer.human();
2321 env(tx);
2322 env.close();
2323
2324 tx[sfDestination] = owner.human();
2325 env(tx);
2326 env.close();
2327 }
2328
2329 {
2330 // Cannot deposit some more
2331 auto tx = vault.deposit(
2332 {.depositor = depositor,
2333 .id = keylet.key,
2334 .amount = asset(100)});
2335 env(tx, ter(tecNO_AUTH));
2336 }
2337
2338 // Clawback works
2339 tx = vault.clawback(
2340 {.issuer = issuer,
2341 .id = keylet.key,
2342 .holder = depositor,
2343 .amount = asset(800)});
2344 env(tx);
2345 env.close();
2346
2347 env(vault.del({.owner = owner, .id = keylet.key}));
2348 });
2349
2350 testCase([this](
2351 Env& env,
2352 Account const& issuer,
2353 Account const& owner,
2354 Account const& depositor,
2355 Asset const& asset,
2356 Vault& vault,
2357 MPTTester& mptt) {
2358 testcase("MPT lock of vault pseudo-account");
2359 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2360 env(tx);
2361 env.close();
2362
2363 auto const vaultAccount =
2364 [&env, keylet = keylet, this]() -> AccountID {
2365 auto const vault = env.le(keylet);
2366 BEAST_EXPECT(vault != nullptr);
2367 return vault->at(sfAccount);
2368 }();
2369
2370 tx = vault.deposit(
2371 {.depositor = depositor,
2372 .id = keylet.key,
2373 .amount = asset(100)});
2374 env(tx);
2375 env.close();
2376
2377 tx = [&]() {
2378 Json::Value jv;
2379 jv[jss::Account] = issuer.human();
2380 jv[sfMPTokenIssuanceID] =
2381 to_string(asset.get<MPTIssue>().getMptID());
2382 jv[jss::Holder] = toBase58(vaultAccount);
2383 jv[jss::TransactionType] = jss::MPTokenIssuanceSet;
2384 jv[jss::Flags] = tfMPTLock;
2385 return jv;
2386 }();
2387 env(tx);
2388 env.close();
2389
2390 tx = vault.deposit(
2391 {.depositor = depositor,
2392 .id = keylet.key,
2393 .amount = asset(100)});
2394 env(tx, ter(tecLOCKED));
2395
2396 tx = vault.withdraw(
2397 {.depositor = depositor,
2398 .id = keylet.key,
2399 .amount = asset(100)});
2400 env(tx, ter(tecLOCKED));
2401
2402 // Clawback works, even when locked
2403 tx = vault.clawback(
2404 {.issuer = issuer,
2405 .id = keylet.key,
2406 .holder = depositor,
2407 .amount = asset(100)});
2408 env(tx);
2409
2410 // Can delete an empty vault even when asset is locked.
2411 tx = vault.del({.owner = owner, .id = keylet.key});
2412 env(tx);
2413 });
2414
2415 {
2416 testcase("MPT shares to a vault");
2417
2418 Env env{*this, testable_amendments() | featureSingleAssetVault};
2419 Account owner{"owner"};
2420 Account issuer{"issuer"};
2421 env.fund(XRP(1000000), owner, issuer);
2422 env.close();
2423 Vault vault{env};
2424
2425 MPTTester mptt{env, issuer, mptInitNoFund};
2426 mptt.create(
2429 mptt.authorize({.account = owner});
2430 mptt.authorize({.account = issuer, .holder = owner});
2431 PrettyAsset asset = mptt.issuanceID();
2432 env(pay(issuer, owner, asset(100)));
2433 auto [tx1, k1] = vault.create({.owner = owner, .asset = asset});
2434 env(tx1);
2435 env.close();
2436
2437 auto const shares = [&env, keylet = k1, this]() -> Asset {
2438 auto const vault = env.le(keylet);
2439 BEAST_EXPECT(vault != nullptr);
2440 return MPTIssue(vault->at(sfShareMPTID));
2441 }();
2442
2443 auto [tx2, k2] = vault.create({.owner = owner, .asset = shares});
2444 env(tx2, ter{tecWRONG_ASSET});
2445 env.close();
2446 }
2447 }
2448
2449 void
2451 {
2452 using namespace test::jtx;
2453
2454 struct CaseArgs
2455 {
2456 int initialXRP = 1000;
2457 double transferRate = 1.0;
2458 };
2459
2460 auto testCase =
2461 [&, this](
2462 std::function<void(
2463 Env & env,
2464 Account const& owner,
2465 Account const& issuer,
2466 Account const& charlie,
2467 std::function<Account(ripple::Keylet)> vaultAccount,
2468 Vault& vault,
2469 PrettyAsset const& asset,
2470 std::function<MPTID(ripple::Keylet)> issuanceId)> test,
2471 CaseArgs args = {}) {
2472 Env env{*this, testable_amendments() | featureSingleAssetVault};
2473 Account const owner{"owner"};
2474 Account const issuer{"issuer"};
2475 Account const charlie{"charlie"};
2476 Vault vault{env};
2477 env.fund(XRP(args.initialXRP), issuer, owner, charlie);
2478 env(fset(issuer, asfAllowTrustLineClawback));
2479 env.close();
2480
2481 PrettyAsset const asset = issuer["IOU"];
2482 env.trust(asset(1000), owner);
2483 env.trust(asset(1000), charlie);
2484 env(pay(issuer, owner, asset(200)));
2485 env(rate(issuer, args.transferRate));
2486 env.close();
2487
2488 auto const vaultAccount =
2489 [&env](ripple::Keylet keylet) -> Account {
2490 return Account("vault", env.le(keylet)->at(sfAccount));
2491 };
2492 auto const issuanceId = [&env](ripple::Keylet keylet) -> MPTID {
2493 return env.le(keylet)->at(sfShareMPTID);
2494 };
2495
2496 test(
2497 env,
2498 owner,
2499 issuer,
2500 charlie,
2501 vaultAccount,
2502 vault,
2503 asset,
2504 issuanceId);
2505 };
2506
2507 testCase([&, this](
2508 Env& env,
2509 Account const& owner,
2510 Account const& issuer,
2511 Account const&,
2512 auto vaultAccount,
2513 Vault& vault,
2514 PrettyAsset const& asset,
2515 auto&&...) {
2516 testcase("IOU cannot use different asset");
2517 PrettyAsset const foo = issuer["FOO"];
2518
2519 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2520 env(tx);
2521 env.close();
2522
2523 {
2524 // Cannot create new trustline to a vault
2525 auto tx = [&, account = vaultAccount(keylet)]() {
2526 Json::Value jv;
2527 jv[jss::Account] = issuer.human();
2528 {
2529 auto& ja = jv[jss::LimitAmount] =
2530 foo(0).value().getJson(JsonOptions::none);
2531 ja[jss::issuer] = toBase58(account);
2532 }
2533 jv[jss::TransactionType] = jss::TrustSet;
2534 jv[jss::Flags] = tfSetFreeze;
2535 return jv;
2536 }();
2537 env(tx, ter{tecNO_PERMISSION});
2538 env.close();
2539 }
2540
2541 {
2542 auto tx = vault.deposit(
2543 {.depositor = issuer, .id = keylet.key, .amount = foo(20)});
2544 env(tx, ter{tecWRONG_ASSET});
2545 env.close();
2546 }
2547
2548 {
2549 auto tx = vault.withdraw(
2550 {.depositor = issuer, .id = keylet.key, .amount = foo(20)});
2551 env(tx, ter{tecWRONG_ASSET});
2552 env.close();
2553 }
2554
2555 env(vault.del({.owner = owner, .id = keylet.key}));
2556 env.close();
2557 });
2558
2559 testCase([&, this](
2560 Env& env,
2561 Account const& owner,
2562 Account const& issuer,
2563 Account const& charlie,
2564 auto vaultAccount,
2565 Vault& vault,
2566 PrettyAsset const& asset,
2567 auto issuanceId) {
2568 testcase("IOU frozen trust line to vault account");
2569
2570 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2571 env(tx);
2572 env.close();
2573
2574 env(vault.deposit(
2575 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2576 env.close();
2577
2578 Asset const share = Asset(issuanceId(keylet));
2579
2580 // Freeze the trustline to the vault
2581 auto trustSet = [&, account = vaultAccount(keylet)]() {
2582 Json::Value jv;
2583 jv[jss::Account] = issuer.human();
2584 {
2585 auto& ja = jv[jss::LimitAmount] =
2586 asset(0).value().getJson(JsonOptions::none);
2587 ja[jss::issuer] = toBase58(account);
2588 }
2589 jv[jss::TransactionType] = jss::TrustSet;
2590 jv[jss::Flags] = tfSetFreeze;
2591 return jv;
2592 }();
2593 env(trustSet);
2594 env.close();
2595
2596 {
2597 // Note, the "frozen" state of the trust line to vault account
2598 // is reported as "locked" state of the vault shares, because
2599 // this state is attached to shares by means of the transitive
2600 // isFrozen.
2601 auto tx = vault.deposit(
2602 {.depositor = owner,
2603 .id = keylet.key,
2604 .amount = asset(80)});
2605 env(tx, ter{tecLOCKED});
2606 }
2607
2608 {
2609 auto tx = vault.withdraw(
2610 {.depositor = owner,
2611 .id = keylet.key,
2612 .amount = asset(100)});
2613 env(tx, ter{tecLOCKED});
2614
2615 // also when trying to withdraw to a 3rd party
2616 tx[sfDestination] = charlie.human();
2617 env(tx, ter{tecLOCKED});
2618 env.close();
2619 }
2620
2621 {
2622 // Clawback works, even when locked
2623 auto tx = vault.clawback(
2624 {.issuer = issuer,
2625 .id = keylet.key,
2626 .holder = owner,
2627 .amount = asset(50)});
2628 env(tx);
2629 env.close();
2630 }
2631
2632 // Clear the frozen state
2633 trustSet[jss::Flags] = tfClearFreeze;
2634 env(trustSet);
2635 env.close();
2636
2637 env(vault.withdraw(
2638 {.depositor = owner,
2639 .id = keylet.key,
2640 .amount = share(50'000'000)}));
2641
2642 env(vault.del({.owner = owner, .id = keylet.key}));
2643 env.close();
2644 });
2645
2646 testCase(
2647 [&, this](
2648 Env& env,
2649 Account const& owner,
2650 Account const& issuer,
2651 Account const& charlie,
2652 auto vaultAccount,
2653 Vault& vault,
2654 PrettyAsset const& asset,
2655 auto issuanceId) {
2656 testcase("IOU transfer fees not applied");
2657
2658 auto [tx, keylet] =
2659 vault.create({.owner = owner, .asset = asset});
2660 env(tx);
2661 env.close();
2662
2663 env(vault.deposit(
2664 {.depositor = owner,
2665 .id = keylet.key,
2666 .amount = asset(100)}));
2667 env.close();
2668
2669 auto const issue = asset.raw().get<Issue>();
2670 Asset const share = Asset(issuanceId(keylet));
2671
2672 // transfer fees ignored on deposit
2673 BEAST_EXPECT(env.balance(owner, issue) == asset(100));
2674 BEAST_EXPECT(
2675 env.balance(vaultAccount(keylet), issue) == asset(100));
2676
2677 {
2678 auto tx = vault.clawback(
2679 {.issuer = issuer,
2680 .id = keylet.key,
2681 .holder = owner,
2682 .amount = asset(50)});
2683 env(tx);
2684 env.close();
2685 }
2686
2687 // transfer fees ignored on clawback
2688 BEAST_EXPECT(env.balance(owner, issue) == asset(100));
2689 BEAST_EXPECT(
2690 env.balance(vaultAccount(keylet), issue) == asset(50));
2691
2692 env(vault.withdraw(
2693 {.depositor = owner,
2694 .id = keylet.key,
2695 .amount = share(20'000'000)}));
2696
2697 // transfer fees ignored on withdraw
2698 BEAST_EXPECT(env.balance(owner, issue) == asset(120));
2699 BEAST_EXPECT(
2700 env.balance(vaultAccount(keylet), issue) == asset(30));
2701
2702 {
2703 auto tx = vault.withdraw(
2704 {.depositor = owner,
2705 .id = keylet.key,
2706 .amount = share(30'000'000)});
2707 tx[sfDestination] = charlie.human();
2708 env(tx);
2709 }
2710
2711 // transfer fees ignored on withdraw to 3rd party
2712 BEAST_EXPECT(env.balance(owner, issue) == asset(120));
2713 BEAST_EXPECT(env.balance(charlie, issue) == asset(30));
2714 BEAST_EXPECT(
2715 env.balance(vaultAccount(keylet), issue) == asset(0));
2716
2717 env(vault.del({.owner = owner, .id = keylet.key}));
2718 env.close();
2719 },
2720 CaseArgs{.transferRate = 1.25});
2721
2722 testCase([&, this](
2723 Env& env,
2724 Account const& owner,
2725 Account const& issuer,
2726 Account const& charlie,
2727 auto,
2728 Vault& vault,
2729 PrettyAsset const& asset,
2730 auto&&...) {
2731 testcase("IOU frozen trust line to depositor");
2732
2733 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2734 env(tx);
2735 env.close();
2736
2737 env(vault.deposit(
2738 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2739 env.close();
2740
2741 // Withdraw to 3rd party works
2742 auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
2743 auto tx = vault.withdraw(
2744 {.depositor = owner,
2745 .id = keylet.key,
2746 .amount = asset(10)});
2747 tx[sfDestination] = charlie.human();
2748 return tx;
2749 }(keylet);
2750 env(withdrawToCharlie);
2751
2752 // Freeze the owner
2753 env(trust(issuer, asset(0), owner, tfSetFreeze));
2754 env.close();
2755
2756 // Cannot withdraw
2757 auto const withdraw = vault.withdraw(
2758 {.depositor = owner, .id = keylet.key, .amount = asset(10)});
2759 env(withdraw, ter{tecFROZEN});
2760
2761 // Cannot withdraw to 3rd party
2762 env(withdrawToCharlie, ter{tecLOCKED});
2763 env.close();
2764
2765 {
2766 // Cannot deposit some more
2767 auto tx = vault.deposit(
2768 {.depositor = owner,
2769 .id = keylet.key,
2770 .amount = asset(10)});
2771 env(tx, ter{tecFROZEN});
2772 }
2773
2774 {
2775 // Clawback still works
2776 auto tx = vault.clawback(
2777 {.issuer = issuer,
2778 .id = keylet.key,
2779 .holder = owner,
2780 .amount = asset(0)});
2781 env(tx);
2782 env.close();
2783 }
2784
2785 env(vault.del({.owner = owner, .id = keylet.key}));
2786 env.close();
2787 });
2788
2789 testCase([&, this](
2790 Env& env,
2791 Account const& owner,
2792 Account const& issuer,
2793 Account const& charlie,
2794 auto,
2795 Vault& vault,
2796 PrettyAsset const& asset,
2797 auto&&...) {
2798 testcase("IOU no trust line to 3rd party");
2799
2800 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2801 env(tx);
2802 env.close();
2803
2804 env(vault.deposit(
2805 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2806 env.close();
2807
2808 Account const erin{"erin"};
2809 env.fund(XRP(1000), erin);
2810 env.close();
2811
2812 // Withdraw to 3rd party without trust line
2813 auto const tx1 = [&](ripple::Keylet keylet) {
2814 auto tx = vault.withdraw(
2815 {.depositor = owner,
2816 .id = keylet.key,
2817 .amount = asset(10)});
2818 tx[sfDestination] = erin.human();
2819 return tx;
2820 }(keylet);
2821 env(tx1, ter{tecNO_LINE});
2822 });
2823
2824 testCase([&, this](
2825 Env& env,
2826 Account const& owner,
2827 Account const& issuer,
2828 Account const& charlie,
2829 auto,
2830 Vault& vault,
2831 PrettyAsset const& asset,
2832 auto&&...) {
2833 testcase("IOU no trust line to depositor");
2834
2835 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2836 env(tx);
2837 env.close();
2838
2839 // reset limit, so deposit of all funds will delete the trust line
2840 env.trust(asset(0), owner);
2841 env.close();
2842
2843 env(vault.deposit(
2844 {.depositor = owner, .id = keylet.key, .amount = asset(200)}));
2845 env.close();
2846
2847 auto trustline =
2848 env.le(keylet::line(owner, asset.raw().get<Issue>()));
2849 BEAST_EXPECT(trustline == nullptr);
2850
2851 // Withdraw without trust line, will succeed
2852 auto const tx1 = [&](ripple::Keylet keylet) {
2853 auto tx = vault.withdraw(
2854 {.depositor = owner,
2855 .id = keylet.key,
2856 .amount = asset(10)});
2857 return tx;
2858 }(keylet);
2859 env(tx1);
2860 });
2861
2862 auto const [acctReserve, incReserve] = [this]() -> std::pair<int, int> {
2863 Env env{*this, testable_amendments()};
2864 return {
2865 env.current()->fees().accountReserve(0).drops() /
2867 env.current()->fees().increment.drops() /
2869 }();
2870
2871 testCase(
2872 [&, this](
2873 Env& env,
2874 Account const& owner,
2875 Account const& issuer,
2876 Account const& charlie,
2877 auto,
2878 Vault& vault,
2879 PrettyAsset const& asset,
2880 auto&&...) {
2881 testcase("IOU no trust line to depositor no reserve");
2882 auto [tx, keylet] =
2883 vault.create({.owner = owner, .asset = asset});
2884 env(tx);
2885 env.close();
2886
2887 // reset limit, so deposit of all funds will delete the trust
2888 // line
2889 env.trust(asset(0), owner);
2890 env.close();
2891
2892 env(vault.deposit(
2893 {.depositor = owner,
2894 .id = keylet.key,
2895 .amount = asset(200)}));
2896 env.close();
2897
2898 auto trustline =
2899 env.le(keylet::line(owner, asset.raw().get<Issue>()));
2900 BEAST_EXPECT(trustline == nullptr);
2901
2902 // Fail because not enough reserve to create trust line
2903 tx = vault.withdraw(
2904 {.depositor = owner,
2905 .id = keylet.key,
2906 .amount = asset(10)});
2907 env(tx, ter{tecNO_LINE_INSUF_RESERVE});
2908 env.close();
2909
2910 env(pay(charlie, owner, XRP(incReserve)));
2911 env.close();
2912
2913 // Withdraw can now create trust line, will succeed
2914 env(tx);
2915 env.close();
2916 },
2917 CaseArgs{.initialXRP = acctReserve + incReserve * 4 - 1});
2918
2919 testCase(
2920 [&, this](
2921 Env& env,
2922 Account const& owner,
2923 Account const& issuer,
2924 Account const& charlie,
2925 auto,
2926 Vault& vault,
2927 PrettyAsset const& asset,
2928 auto&&...) {
2929 testcase("IOU no reserve for share MPToken");
2930 auto [tx, keylet] =
2931 vault.create({.owner = owner, .asset = asset});
2932 env(tx);
2933 env.close();
2934
2935 env(pay(owner, charlie, asset(100)));
2936 env.close();
2937
2938 // Use up some reserve on tickets
2939 env(ticket::create(charlie, 2));
2940 env.close();
2941
2942 // Fail because not enough reserve to create MPToken for shares
2943 tx = vault.deposit(
2944 {.depositor = charlie,
2945 .id = keylet.key,
2946 .amount = asset(100)});
2947 env(tx, ter{tecINSUFFICIENT_RESERVE});
2948 env.close();
2949
2950 env(pay(issuer, charlie, XRP(incReserve)));
2951 env.close();
2952
2953 // Deposit can now create MPToken, will succeed
2954 env(tx);
2955 env.close();
2956 },
2957 CaseArgs{.initialXRP = acctReserve + incReserve * 4 - 1});
2958
2959 testCase([&, this](
2960 Env& env,
2961 Account const& owner,
2962 Account const& issuer,
2963 Account const& charlie,
2964 auto,
2965 Vault& vault,
2966 PrettyAsset const& asset,
2967 auto&&...) {
2968 testcase("IOU frozen trust line to 3rd party");
2969
2970 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2971 env(tx);
2972 env.close();
2973
2974 env(vault.deposit(
2975 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2976 env.close();
2977
2978 // Withdraw to 3rd party works
2979 auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
2980 auto tx = vault.withdraw(
2981 {.depositor = owner,
2982 .id = keylet.key,
2983 .amount = asset(10)});
2984 tx[sfDestination] = charlie.human();
2985 return tx;
2986 }(keylet);
2987 env(withdrawToCharlie);
2988
2989 // Freeze the 3rd party
2990 env(trust(issuer, asset(0), charlie, tfSetFreeze));
2991 env.close();
2992
2993 // Can withdraw
2994 auto const withdraw = vault.withdraw(
2995 {.depositor = owner, .id = keylet.key, .amount = asset(10)});
2996 env(withdraw);
2997 env.close();
2998
2999 // Cannot withdraw to 3rd party
3000 env(withdrawToCharlie, ter{tecFROZEN});
3001 env.close();
3002
3003 env(vault.clawback(
3004 {.issuer = issuer,
3005 .id = keylet.key,
3006 .holder = owner,
3007 .amount = asset(0)}));
3008 env.close();
3009
3010 env(vault.del({.owner = owner, .id = keylet.key}));
3011 env.close();
3012 });
3013
3014 testCase([&, this](
3015 Env& env,
3016 Account const& owner,
3017 Account const& issuer,
3018 Account const& charlie,
3019 auto,
3020 Vault& vault,
3021 PrettyAsset const& asset,
3022 auto&&...) {
3023 testcase("IOU global freeze");
3024
3025 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
3026 env(tx);
3027 env.close();
3028
3029 env(vault.deposit(
3030 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
3031 env.close();
3032
3033 env(fset(issuer, asfGlobalFreeze));
3034 env.close();
3035
3036 {
3037 // Cannot withdraw
3038 auto tx = vault.withdraw(
3039 {.depositor = owner,
3040 .id = keylet.key,
3041 .amount = asset(10)});
3042 env(tx, ter{tecFROZEN});
3043
3044 // Cannot withdraw to 3rd party
3045 tx[sfDestination] = charlie.human();
3046 env(tx, ter{tecFROZEN});
3047 env.close();
3048
3049 // Cannot deposit some more
3050 tx = vault.deposit(
3051 {.depositor = owner,
3052 .id = keylet.key,
3053 .amount = asset(10)});
3054
3055 env(tx, ter{tecFROZEN});
3056 }
3057
3058 // Clawback is permitted
3059 env(vault.clawback(
3060 {.issuer = issuer,
3061 .id = keylet.key,
3062 .holder = owner,
3063 .amount = asset(0)}));
3064 env.close();
3065
3066 env(vault.del({.owner = owner, .id = keylet.key}));
3067 env.close();
3068 });
3069 }
3070
3071 void
3073 {
3074 using namespace test::jtx;
3075
3076 testcase("private vault");
3077
3078 Env env{*this, testable_amendments() | featureSingleAssetVault};
3079 Account issuer{"issuer"};
3080 Account owner{"owner"};
3081 Account depositor{"depositor"};
3082 Account charlie{"charlie"};
3083 Account pdOwner{"pdOwner"};
3084 Account credIssuer1{"credIssuer1"};
3085 Account credIssuer2{"credIssuer2"};
3086 std::string const credType = "credential";
3087 Vault vault{env};
3088 env.fund(
3089 XRP(1000),
3090 issuer,
3091 owner,
3092 depositor,
3093 charlie,
3094 pdOwner,
3095 credIssuer1,
3096 credIssuer2);
3097 env.close();
3098 env(fset(issuer, asfAllowTrustLineClawback));
3099 env.close();
3100 env.require(flags(issuer, asfAllowTrustLineClawback));
3101
3102 PrettyAsset asset = issuer["IOU"];
3103 env.trust(asset(1000), owner);
3104 env(pay(issuer, owner, asset(500)));
3105 env.trust(asset(1000), depositor);
3106 env(pay(issuer, depositor, asset(500)));
3107 env.trust(asset(1000), charlie);
3108 env(pay(issuer, charlie, asset(5)));
3109 env.close();
3110
3111 auto [tx, keylet] = vault.create(
3112 {.owner = owner, .asset = asset, .flags = tfVaultPrivate});
3113 env(tx);
3114 env.close();
3115 BEAST_EXPECT(env.le(keylet));
3116
3117 {
3118 testcase("private vault owner can deposit");
3119 auto tx = vault.deposit(
3120 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
3121 env(tx);
3122 }
3123
3124 {
3125 testcase("private vault depositor not authorized yet");
3126 auto tx = vault.deposit(
3127 {.depositor = depositor,
3128 .id = keylet.key,
3129 .amount = asset(50)});
3130 env(tx, ter{tecNO_AUTH});
3131 }
3132
3133 {
3134 testcase("private vault cannot set non-existing domain");
3135 auto tx = vault.set({.owner = owner, .id = keylet.key});
3136 tx[sfDomainID] = to_string(base_uint<256>(42ul));
3137 env(tx, ter{tecOBJECT_NOT_FOUND});
3138 }
3139
3140 {
3141 testcase("private vault set domainId");
3142
3143 {
3144 pdomain::Credentials const credentials1{
3145 {.issuer = credIssuer1, .credType = credType}};
3146
3147 env(pdomain::setTx(pdOwner, credentials1));
3148 auto const domainId1 = [&]() {
3149 auto tx = env.tx()->getJson(JsonOptions::none);
3150 return pdomain::getNewDomain(env.meta());
3151 }();
3152
3153 auto tx = vault.set({.owner = owner, .id = keylet.key});
3154 tx[sfDomainID] = to_string(domainId1);
3155 env(tx);
3156 env.close();
3157
3158 // Update domain second time, should be harmless
3159 env(tx);
3160 env.close();
3161 }
3162
3163 {
3164 pdomain::Credentials const credentials{
3165 {.issuer = credIssuer1, .credType = credType},
3166 {.issuer = credIssuer2, .credType = credType}};
3167
3168 env(pdomain::setTx(pdOwner, credentials));
3169 auto const domainId = [&]() {
3170 auto tx = env.tx()->getJson(JsonOptions::none);
3171 return pdomain::getNewDomain(env.meta());
3172 }();
3173
3174 auto tx = vault.set({.owner = owner, .id = keylet.key});
3175 tx[sfDomainID] = to_string(domainId);
3176 env(tx);
3177 env.close();
3178
3179 // Should be idempotent
3180 tx = vault.set({.owner = owner, .id = keylet.key});
3181 tx[sfDomainID] = to_string(domainId);
3182 env(tx);
3183 env.close();
3184 }
3185 }
3186
3187 {
3188 testcase("private vault depositor still not authorized");
3189 auto tx = vault.deposit(
3190 {.depositor = depositor,
3191 .id = keylet.key,
3192 .amount = asset(50)});
3193 env(tx, ter{tecNO_AUTH});
3194 env.close();
3195 }
3196
3197 auto const credKeylet =
3198 credentials::keylet(depositor, credIssuer1, credType);
3199 {
3200 testcase("private vault depositor now authorized");
3201 env(credentials::create(depositor, credIssuer1, credType));
3202 env(credentials::accept(depositor, credIssuer1, credType));
3203 env(credentials::create(charlie, credIssuer1, credType));
3204 // charlie's credential not accepted
3205 env.close();
3206 auto credSle = env.le(credKeylet);
3207 BEAST_EXPECT(credSle != nullptr);
3208
3209 auto tx = vault.deposit(
3210 {.depositor = depositor,
3211 .id = keylet.key,
3212 .amount = asset(50)});
3213 env(tx);
3214 env.close();
3215
3216 tx = vault.deposit(
3217 {.depositor = charlie, .id = keylet.key, .amount = asset(50)});
3218 env(tx, ter{tecNO_AUTH});
3219 env.close();
3220 }
3221
3222 {
3223 testcase("private vault depositor lost authorization");
3224 env(credentials::deleteCred(
3225 credIssuer1, depositor, credIssuer1, credType));
3226 env(credentials::deleteCred(
3227 credIssuer1, charlie, credIssuer1, credType));
3228 env.close();
3229 auto credSle = env.le(credKeylet);
3230 BEAST_EXPECT(credSle == nullptr);
3231
3232 auto tx = vault.deposit(
3233 {.depositor = depositor,
3234 .id = keylet.key,
3235 .amount = asset(50)});
3236 env(tx, ter{tecNO_AUTH});
3237 env.close();
3238 }
3239
3240 auto const shares = [&env, keylet = keylet, this]() -> Asset {
3241 auto const vault = env.le(keylet);
3242 BEAST_EXPECT(vault != nullptr);
3243 return MPTIssue(vault->at(sfShareMPTID));
3244 }();
3245
3246 {
3247 testcase("private vault expired authorization");
3248 uint32_t const closeTime = env.current()
3249 ->info()
3250 .parentCloseTime.time_since_epoch()
3251 .count();
3252 {
3253 auto tx0 =
3254 credentials::create(depositor, credIssuer2, credType);
3255 tx0[sfExpiration] = closeTime + 20;
3256 env(tx0);
3257 tx0 = credentials::create(charlie, credIssuer2, credType);
3258 tx0[sfExpiration] = closeTime + 20;
3259 env(tx0);
3260 env.close();
3261
3262 env(credentials::accept(depositor, credIssuer2, credType));
3263 env(credentials::accept(charlie, credIssuer2, credType));
3264 env.close();
3265 }
3266
3267 {
3268 auto tx1 = vault.deposit(
3269 {.depositor = depositor,
3270 .id = keylet.key,
3271 .amount = asset(50)});
3272 env(tx1);
3273 env.close();
3274
3275 auto const tokenKeylet = keylet::mptoken(
3276 shares.get<MPTIssue>().getMptID(), depositor.id());
3277 BEAST_EXPECT(env.le(tokenKeylet) != nullptr);
3278 }
3279
3280 {
3281 // time advance
3282 env.close();
3283 env.close();
3284 env.close();
3285
3286 auto const credsKeylet =
3287 credentials::keylet(depositor, credIssuer2, credType);
3288 BEAST_EXPECT(env.le(credsKeylet) != nullptr);
3289
3290 auto tx2 = vault.deposit(
3291 {.depositor = depositor,
3292 .id = keylet.key,
3293 .amount = asset(1)});
3294 env(tx2, ter{tecEXPIRED});
3295 env.close();
3296
3297 BEAST_EXPECT(env.le(credsKeylet) == nullptr);
3298 }
3299
3300 {
3301 auto const credsKeylet =
3302 credentials::keylet(charlie, credIssuer2, credType);
3303 BEAST_EXPECT(env.le(credsKeylet) != nullptr);
3304 auto const tokenKeylet = keylet::mptoken(
3305 shares.get<MPTIssue>().getMptID(), charlie.id());
3306 BEAST_EXPECT(env.le(tokenKeylet) == nullptr);
3307
3308 auto tx3 = vault.deposit(
3309 {.depositor = charlie,
3310 .id = keylet.key,
3311 .amount = asset(2)});
3312 env(tx3, ter{tecEXPIRED});
3313
3314 env.close();
3315 BEAST_EXPECT(env.le(credsKeylet) == nullptr);
3316 BEAST_EXPECT(env.le(tokenKeylet) == nullptr);
3317 }
3318 }
3319
3320 {
3321 testcase("private vault reset domainId");
3322 auto tx = vault.set({.owner = owner, .id = keylet.key});
3323 tx[sfDomainID] = "0";
3324 env(tx);
3325 env.close();
3326
3327 tx = vault.deposit(
3328 {.depositor = depositor,
3329 .id = keylet.key,
3330 .amount = asset(50)});
3331 env(tx, ter{tecNO_AUTH});
3332 env.close();
3333
3334 tx = vault.withdraw(
3335 {.depositor = depositor,
3336 .id = keylet.key,
3337 .amount = asset(50)});
3338 env(tx);
3339 env.close();
3340
3341 tx = vault.clawback(
3342 {.issuer = issuer,
3343 .id = keylet.key,
3344 .holder = depositor,
3345 .amount = asset(0)});
3346 env(tx);
3347
3348 tx = vault.clawback(
3349 {.issuer = issuer,
3350 .id = keylet.key,
3351 .holder = owner,
3352 .amount = asset(0)});
3353 env(tx);
3354 env.close();
3355
3356 tx = vault.del({
3357 .owner = owner,
3358 .id = keylet.key,
3359 });
3360 env(tx);
3361 }
3362 }
3363
3364 void
3366 {
3367 using namespace test::jtx;
3368
3369 testcase("private XRP vault");
3370
3371 Env env{*this, testable_amendments() | featureSingleAssetVault};
3372 Account owner{"owner"};
3373 Account depositor{"depositor"};
3374 Account alice{"charlie"};
3375 std::string const credType = "credential";
3376 Vault vault{env};
3377 env.fund(XRP(100000), owner, depositor, alice);
3378 env.close();
3379
3380 PrettyAsset asset = xrpIssue();
3381 auto [tx, keylet] = vault.create(
3382 {.owner = owner, .asset = asset, .flags = tfVaultPrivate});
3383 env(tx);
3384 env.close();
3385
3386 auto const [vaultAccount, issuanceId] =
3387 [&env, keylet = keylet, this]() -> std::tuple<AccountID, uint192> {
3388 auto const vault = env.le(keylet);
3389 BEAST_EXPECT(vault != nullptr);
3390 return {vault->at(sfAccount), vault->at(sfShareMPTID)};
3391 }();
3392 BEAST_EXPECT(env.le(keylet::account(vaultAccount)));
3393 BEAST_EXPECT(env.le(keylet::mptIssuance(issuanceId)));
3394 PrettyAsset shares{issuanceId};
3395
3396 {
3397 testcase("private XRP vault owner can deposit");
3398 auto tx = vault.deposit(
3399 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
3400 env(tx);
3401 env.close();
3402 }
3403
3404 {
3405 testcase("private XRP vault cannot pay shares to depositor yet");
3406 env(pay(owner, depositor, shares(1)), ter{tecNO_AUTH});
3407 }
3408
3409 {
3410 testcase("private XRP vault depositor not authorized yet");
3411 auto tx = vault.deposit(
3412 {.depositor = depositor,
3413 .id = keylet.key,
3414 .amount = asset(50)});
3415 env(tx, ter{tecNO_AUTH});
3416 }
3417
3418 {
3419 testcase("private XRP vault set DomainID");
3420 pdomain::Credentials const credentials{
3421 {.issuer = owner, .credType = credType}};
3422
3423 env(pdomain::setTx(owner, credentials));
3424 auto const domainId = [&]() {
3425 auto tx = env.tx()->getJson(JsonOptions::none);
3426 return pdomain::getNewDomain(env.meta());
3427 }();
3428
3429 auto tx = vault.set({.owner = owner, .id = keylet.key});
3430 tx[sfDomainID] = to_string(domainId);
3431 env(tx);
3432 env.close();
3433 }
3434
3435 auto const credKeylet = credentials::keylet(depositor, owner, credType);
3436 {
3437 testcase("private XRP vault depositor now authorized");
3438 env(credentials::create(depositor, owner, credType));
3439 env(credentials::accept(depositor, owner, credType));
3440 env.close();
3441
3442 BEAST_EXPECT(env.le(credKeylet));
3443 auto tx = vault.deposit(
3444 {.depositor = depositor,
3445 .id = keylet.key,
3446 .amount = asset(50)});
3447 env(tx);
3448 env.close();
3449 }
3450
3451 {
3452 testcase("private XRP vault can pay shares to depositor");
3453 env(pay(owner, depositor, shares(1)));
3454 }
3455
3456 {
3457 testcase("private XRP vault cannot pay shares to 3rd party");
3458 Json::Value jv;
3459 jv[sfAccount] = alice.human();
3460 jv[sfTransactionType] = jss::MPTokenAuthorize;
3461 jv[sfMPTokenIssuanceID] = to_string(issuanceId);
3462 env(jv);
3463 env.close();
3464
3465 env(pay(owner, alice, shares(1)), ter{tecNO_AUTH});
3466 }
3467 }
3468
3469 void
3471 {
3472 using namespace test::jtx;
3473
3474 testcase("fail pseudo-account allocation");
3475 Env env{*this, testable_amendments() | featureSingleAssetVault};
3476 Account const owner{"owner"};
3477 Vault vault{env};
3478 env.fund(XRP(1000), owner);
3479
3480 auto const keylet = keylet::vault(owner.id(), env.seq(owner));
3481 for (int i = 0; i < 256; ++i)
3482 {
3483 AccountID const accountId =
3484 ripple::pseudoAccountAddress(*env.current(), keylet.key);
3485
3486 env(pay(env.master.id(), accountId, XRP(1000)),
3487 seq(autofill),
3488 fee(autofill),
3489 sig(autofill));
3490 }
3491
3492 auto [tx, keylet1] =
3493 vault.create({.owner = owner, .asset = xrpIssue()});
3494 BEAST_EXPECT(keylet.key == keylet1.key);
3495 env(tx, ter{terADDRESS_COLLISION});
3496 }
3497
3498 void
3500 {
3501 using namespace test::jtx;
3502
3503 struct Data
3504 {
3505 Account const& owner;
3506 Account const& issuer;
3507 Account const& depositor;
3508 Account const& vaultAccount;
3509 MPTIssue shares;
3510 PrettyAsset const& share;
3511 Vault& vault;
3512 ripple::Keylet keylet;
3513 Issue assets;
3514 PrettyAsset const& asset;
3515 std::function<bool(std::function<bool(SLE&, SLE&)>)> peek;
3516 };
3517
3518 auto testCase = [&, this](
3519 std::uint8_t scale,
3520 std::function<void(Env & env, Data data)> test) {
3521 Env env{*this, testable_amendments() | featureSingleAssetVault};
3522 Account const owner{"owner"};
3523 Account const issuer{"issuer"};
3524 Account const depositor{"depositor"};
3525 Vault vault{env};
3526 env.fund(XRP(1000), issuer, owner, depositor);
3527 env(fset(issuer, asfAllowTrustLineClawback));
3528 env.close();
3529
3530 PrettyAsset const asset = issuer["IOU"];
3531 env.trust(asset(1000), owner);
3532 env.trust(asset(1000), depositor);
3533 env(pay(issuer, owner, asset(200)));
3534 env(pay(issuer, depositor, asset(200)));
3535 env.close();
3536
3537 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
3538 tx[sfScale] = scale;
3539 env(tx);
3540
3541 auto const [vaultAccount, issuanceId] =
3542 [&env](ripple::Keylet keylet) -> std::tuple<Account, MPTID> {
3543 auto const vault = env.le(keylet);
3544 return {
3545 Account("vault", vault->at(sfAccount)),
3546 vault->at(sfShareMPTID)};
3547 }(keylet);
3548 MPTIssue shares(issuanceId);
3549 env.memoize(vaultAccount);
3550
3551 auto const peek =
3552 [=, &env, this](std::function<bool(SLE&, SLE&)> fn) -> bool {
3553 return env.app().openLedger().modify(
3554 [&](OpenView& view, beast::Journal j) -> bool {
3555 Sandbox sb(&view, tapNONE);
3556 auto vault = sb.peek(keylet::vault(keylet.key));
3557 if (!BEAST_EXPECT(vault != nullptr))
3558 return false;
3559 auto shares = sb.peek(
3560 keylet::mptIssuance(vault->at(sfShareMPTID)));
3561 if (!BEAST_EXPECT(shares != nullptr))
3562 return false;
3563 if (fn(*vault, *shares))
3564 {
3565 sb.update(vault);
3566 sb.update(shares);
3567 sb.apply(view);
3568 return true;
3569 }
3570 return false;
3571 });
3572 };
3573
3574 test(
3575 env,
3576 {.owner = owner,
3577 .issuer = issuer,
3578 .depositor = depositor,
3579 .vaultAccount = vaultAccount,
3580 .shares = shares,
3581 .share = PrettyAsset(shares),
3582 .vault = vault,
3583 .keylet = keylet,
3584 .assets = asset.raw().get<Issue>(),
3585 .asset = asset,
3586 .peek = peek});
3587 };
3588
3589 testCase(18, [&, this](Env& env, Data d) {
3590 testcase("Scale deposit overflow on first deposit");
3591 auto tx = d.vault.deposit(
3592 {.depositor = d.depositor,
3593 .id = d.keylet.key,
3594 .amount = d.asset(10)});
3595 env(tx, ter{tecPATH_DRY});
3596 env.close();
3597 });
3598
3599 testCase(18, [&, this](Env& env, Data d) {
3600 testcase("Scale deposit overflow on second deposit");
3601
3602 {
3603 auto tx = d.vault.deposit(
3604 {.depositor = d.depositor,
3605 .id = d.keylet.key,
3606 .amount = d.asset(5)});
3607 env(tx);
3608 env.close();
3609 }
3610
3611 {
3612 auto tx = d.vault.deposit(
3613 {.depositor = d.depositor,
3614 .id = d.keylet.key,
3615 .amount = d.asset(10)});
3616 env(tx, ter{tecPATH_DRY});
3617 env.close();
3618 }
3619 });
3620
3621 testCase(18, [&, this](Env& env, Data d) {
3622 testcase("Scale deposit overflow on total shares");
3623
3624 {
3625 auto tx = d.vault.deposit(
3626 {.depositor = d.depositor,
3627 .id = d.keylet.key,
3628 .amount = d.asset(5)});
3629 env(tx);
3630 env.close();
3631 }
3632
3633 {
3634 auto tx = d.vault.deposit(
3635 {.depositor = d.depositor,
3636 .id = d.keylet.key,
3637 .amount = d.asset(5)});
3638 env(tx, ter{tecPATH_DRY});
3639 env.close();
3640 }
3641 });
3642
3643 testCase(1, [&, this](Env& env, Data d) {
3644 testcase("Scale deposit exact");
3645
3646 auto const start = env.balance(d.depositor, d.assets).number();
3647 auto tx = d.vault.deposit(
3648 {.depositor = d.depositor,
3649 .id = d.keylet.key,
3650 .amount = d.asset(1)});
3651 env(tx);
3652 env.close();
3653 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(10));
3654 BEAST_EXPECT(
3655 env.balance(d.depositor, d.assets) ==
3656 STAmount(d.asset, start - 1));
3657 });
3658
3659 testCase(1, [&, this](Env& env, Data d) {
3660 testcase("Scale deposit insignificant amount");
3661
3662 auto tx = d.vault.deposit(
3663 {.depositor = d.depositor,
3664 .id = d.keylet.key,
3665 .amount = STAmount(d.asset, Number(9, -2))});
3666 env(tx, ter{tecPRECISION_LOSS});
3667 });
3668
3669 testCase(1, [&, this](Env& env, Data d) {
3670 testcase("Scale deposit exact, using full precision");
3671
3672 auto const start = env.balance(d.depositor, d.assets).number();
3673 auto tx = d.vault.deposit(
3674 {.depositor = d.depositor,
3675 .id = d.keylet.key,
3676 .amount = STAmount(d.asset, Number(15, -1))});
3677 env(tx);
3678 env.close();
3679 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(15));
3680 BEAST_EXPECT(
3681 env.balance(d.depositor, d.assets) ==
3682 STAmount(d.asset, start - Number(15, -1)));
3683 });
3684
3685 testCase(1, [&, this](Env& env, Data d) {
3686 testcase("Scale deposit exact, truncating from .5");
3687
3688 auto const start = env.balance(d.depositor, d.assets).number();
3689 // Each of the cases below will transfer exactly 1.2 IOU to the
3690 // vault and receive 12 shares in exchange
3691 {
3692 auto tx = d.vault.deposit(
3693 {.depositor = d.depositor,
3694 .id = d.keylet.key,
3695 .amount = STAmount(d.asset, Number(125, -2))});
3696 env(tx);
3697 env.close();
3698 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3699 BEAST_EXPECT(
3700 env.balance(d.depositor, d.assets) ==
3701 STAmount(d.asset, start - Number(12, -1)));
3702 }
3703
3704 {
3705 auto tx = d.vault.deposit(
3706 {.depositor = d.depositor,
3707 .id = d.keylet.key,
3708 .amount = STAmount(d.asset, Number(1201, -3))});
3709 env(tx);
3710 env.close();
3711 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(24));
3712 BEAST_EXPECT(
3713 env.balance(d.depositor, d.assets) ==
3714 STAmount(d.asset, start - Number(24, -1)));
3715 }
3716
3717 {
3718 auto tx = d.vault.deposit(
3719 {.depositor = d.depositor,
3720 .id = d.keylet.key,
3721 .amount = STAmount(d.asset, Number(1299, -3))});
3722 env(tx);
3723 env.close();
3724 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(36));
3725 BEAST_EXPECT(
3726 env.balance(d.depositor, d.assets) ==
3727 STAmount(d.asset, start - Number(36, -1)));
3728 }
3729 });
3730
3731 testCase(1, [&, this](Env& env, Data d) {
3732 testcase("Scale deposit exact, truncating from .01");
3733
3734 auto const start = env.balance(d.depositor, d.assets).number();
3735 // round to 12
3736 auto tx = d.vault.deposit(
3737 {.depositor = d.depositor,
3738 .id = d.keylet.key,
3739 .amount = STAmount(d.asset, Number(1201, -3))});
3740 env(tx);
3741 env.close();
3742 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3743 BEAST_EXPECT(
3744 env.balance(d.depositor, d.assets) ==
3745 STAmount(d.asset, start - Number(12, -1)));
3746
3747 {
3748 // round to 6
3749 auto tx = d.vault.deposit(
3750 {.depositor = d.depositor,
3751 .id = d.keylet.key,
3752 .amount = STAmount(d.asset, Number(69, -2))});
3753 env(tx);
3754 env.close();
3755 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
3756 BEAST_EXPECT(
3757 env.balance(d.depositor, d.assets) ==
3758 STAmount(d.asset, start - Number(18, -1)));
3759 }
3760 });
3761
3762 testCase(1, [&, this](Env& env, Data d) {
3763 testcase("Scale deposit exact, truncating from .99");
3764
3765 auto const start = env.balance(d.depositor, d.assets).number();
3766 // round to 12
3767 auto tx = d.vault.deposit(
3768 {.depositor = d.depositor,
3769 .id = d.keylet.key,
3770 .amount = STAmount(d.asset, Number(1299, -3))});
3771 env(tx);
3772 env.close();
3773 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3774 BEAST_EXPECT(
3775 env.balance(d.depositor, d.assets) ==
3776 STAmount(d.asset, start - Number(12, -1)));
3777
3778 {
3779 // round to 6
3780 auto tx = d.vault.deposit(
3781 {.depositor = d.depositor,
3782 .id = d.keylet.key,
3783 .amount = STAmount(d.asset, Number(62, -2))});
3784 env(tx);
3785 env.close();
3786 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
3787 BEAST_EXPECT(
3788 env.balance(d.depositor, d.assets) ==
3789 STAmount(d.asset, start - Number(18, -1)));
3790 }
3791 });
3792
3793 testCase(1, [&, this](Env& env, Data d) {
3794 // initial setup: deposit 100 IOU, receive 1000 shares
3795 auto const start = env.balance(d.depositor, d.assets).number();
3796 auto tx = d.vault.deposit(
3797 {.depositor = d.depositor,
3798 .id = d.keylet.key,
3799 .amount = STAmount(d.asset, Number(100, 0))});
3800 env(tx);
3801 env.close();
3802 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
3803 BEAST_EXPECT(
3804 env.balance(d.depositor, d.assets) ==
3805 STAmount(d.asset, start - Number(100, 0)));
3806 BEAST_EXPECT(
3807 env.balance(d.vaultAccount, d.assets) ==
3808 STAmount(d.asset, Number(100, 0)));
3809 BEAST_EXPECT(
3810 env.balance(d.vaultAccount, d.shares) ==
3811 STAmount(d.share, Number(-1000, 0)));
3812
3813 {
3814 testcase("Scale redeem exact");
3815 // sharesToAssetsWithdraw:
3816 // assets = assetsTotal * (shares / sharesTotal)
3817 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
3818
3819 auto const start = env.balance(d.depositor, d.assets).number();
3820 auto tx = d.vault.withdraw(
3821 {.depositor = d.depositor,
3822 .id = d.keylet.key,
3823 .amount = STAmount(d.share, Number(100, 0))});
3824 env(tx);
3825 env.close();
3826 BEAST_EXPECT(
3827 env.balance(d.depositor, d.shares) == d.share(900));
3828 BEAST_EXPECT(
3829 env.balance(d.depositor, d.assets) ==
3830 STAmount(d.asset, start + Number(10, 0)));
3831 BEAST_EXPECT(
3832 env.balance(d.vaultAccount, d.assets) ==
3833 STAmount(d.asset, Number(90, 0)));
3834 BEAST_EXPECT(
3835 env.balance(d.vaultAccount, d.shares) ==
3836 STAmount(d.share, Number(-900, 0)));
3837 }
3838
3839 {
3840 testcase("Scale redeem with rounding");
3841 // sharesToAssetsWithdraw:
3842 // assets = assetsTotal * (shares / sharesTotal)
3843 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
3844
3845 auto const start = env.balance(d.depositor, d.assets).number();
3846 d.peek([](SLE& vault, auto&) -> bool {
3847 vault[sfAssetsAvailable] = Number(1);
3848 return true;
3849 });
3850
3851 // Note, this transaction fails first (because of above change
3852 // in the open ledger) but then succeeds when the ledger is
3853 // closed (because a modification like above is not persistent),
3854 // which is why the checks below are expected to pass.
3855 auto tx = d.vault.withdraw(
3856 {.depositor = d.depositor,
3857 .id = d.keylet.key,
3858 .amount = STAmount(d.share, Number(25, 0))});
3859 env(tx, ter{tecINSUFFICIENT_FUNDS});
3860 env.close();
3861 BEAST_EXPECT(
3862 env.balance(d.depositor, d.shares) == d.share(900 - 25));
3863 BEAST_EXPECT(
3864 env.balance(d.depositor, d.assets) ==
3865 STAmount(d.asset, start + Number(25, -1)));
3866 BEAST_EXPECT(
3867 env.balance(d.vaultAccount, d.assets) ==
3868 STAmount(d.asset, Number(900 - 25, -1)));
3869 BEAST_EXPECT(
3870 env.balance(d.vaultAccount, d.shares) ==
3871 STAmount(d.share, -Number(900 - 25, 0)));
3872 }
3873
3874 {
3875 testcase("Scale redeem exact");
3876 // sharesToAssetsWithdraw:
3877 // assets = assetsTotal * (shares / sharesTotal)
3878 // assets = 87.5 * 21 / 875 = 87.5 * 0.024 = 2.1
3879
3880 auto const start = env.balance(d.depositor, d.assets).number();
3881
3882 tx = d.vault.withdraw(
3883 {.depositor = d.depositor,
3884 .id = d.keylet.key,
3885 .amount = STAmount(d.share, Number(21, 0))});
3886 env(tx);
3887 env.close();
3888 BEAST_EXPECT(
3889 env.balance(d.depositor, d.shares) == d.share(875 - 21));
3890 BEAST_EXPECT(
3891 env.balance(d.depositor, d.assets) ==
3892 STAmount(d.asset, start + Number(21, -1)));
3893 BEAST_EXPECT(
3894 env.balance(d.vaultAccount, d.assets) ==
3895 STAmount(d.asset, Number(875 - 21, -1)));
3896 BEAST_EXPECT(
3897 env.balance(d.vaultAccount, d.shares) ==
3898 STAmount(d.share, -Number(875 - 21, 0)));
3899 }
3900
3901 {
3902 testcase("Scale redeem rest");
3903 auto const rest = env.balance(d.depositor, d.shares).number();
3904
3905 tx = d.vault.withdraw(
3906 {.depositor = d.depositor,
3907 .id = d.keylet.key,
3908 .amount = STAmount(d.share, rest)});
3909 env(tx);
3910 env.close();
3911 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
3912 BEAST_EXPECT(
3913 env.balance(d.vaultAccount, d.assets).number() == 0);
3914 BEAST_EXPECT(
3915 env.balance(d.vaultAccount, d.shares).number() == 0);
3916 }
3917 });
3918
3919 testCase(18, [&, this](Env& env, Data d) {
3920 testcase("Scale withdraw overflow");
3921
3922 {
3923 auto tx = d.vault.deposit(
3924 {.depositor = d.depositor,
3925 .id = d.keylet.key,
3926 .amount = d.asset(5)});
3927 env(tx);
3928 env.close();
3929 }
3930
3931 {
3932 auto tx = d.vault.withdraw(
3933 {.depositor = d.depositor,
3934 .id = d.keylet.key,
3935 .amount = STAmount(d.asset, Number(10, 0))});
3936 env(tx, ter{tecPATH_DRY});
3937 env.close();
3938 }
3939 });
3940
3941 testCase(1, [&, this](Env& env, Data d) {
3942 // initial setup: deposit 100 IOU, receive 1000 shares
3943 auto const start = env.balance(d.depositor, d.assets).number();
3944 auto tx = d.vault.deposit(
3945 {.depositor = d.depositor,
3946 .id = d.keylet.key,
3947 .amount = STAmount(d.asset, Number(100, 0))});
3948 env(tx);
3949 env.close();
3950 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
3951 BEAST_EXPECT(
3952 env.balance(d.depositor, d.assets) ==
3953 STAmount(d.asset, start - Number(100, 0)));
3954 BEAST_EXPECT(
3955 env.balance(d.vaultAccount, d.assets) ==
3956 STAmount(d.asset, Number(100, 0)));
3957 BEAST_EXPECT(
3958 env.balance(d.vaultAccount, d.shares) ==
3959 STAmount(d.share, Number(-1000, 0)));
3960
3961 {
3962 testcase("Scale withdraw exact");
3963 // assetsToSharesWithdraw:
3964 // shares = sharesTotal * (assets / assetsTotal)
3965 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
3966 // sharesToAssetsWithdraw:
3967 // assets = assetsTotal * (shares / sharesTotal)
3968 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
3969
3970 auto const start = env.balance(d.depositor, d.assets).number();
3971 auto tx = d.vault.withdraw(
3972 {.depositor = d.depositor,
3973 .id = d.keylet.key,
3974 .amount = STAmount(d.asset, Number(10, 0))});
3975 env(tx);
3976 env.close();
3977 BEAST_EXPECT(
3978 env.balance(d.depositor, d.shares) == d.share(900));
3979 BEAST_EXPECT(
3980 env.balance(d.depositor, d.assets) ==
3981 STAmount(d.asset, start + Number(10, 0)));
3982 BEAST_EXPECT(
3983 env.balance(d.vaultAccount, d.assets) ==
3984 STAmount(d.asset, Number(90, 0)));
3985 BEAST_EXPECT(
3986 env.balance(d.vaultAccount, d.shares) ==
3987 STAmount(d.share, Number(-900, 0)));
3988 }
3989
3990 {
3991 testcase("Scale withdraw insignificant amount");
3992 auto tx = d.vault.withdraw(
3993 {.depositor = d.depositor,
3994 .id = d.keylet.key,
3995 .amount = STAmount(d.asset, Number(4, -2))});
3996 env(tx, ter{tecPRECISION_LOSS});
3997 }
3998
3999 {
4000 testcase("Scale withdraw with rounding assets");
4001 // assetsToSharesWithdraw:
4002 // shares = sharesTotal * (assets / assetsTotal)
4003 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
4004 // sharesToAssetsWithdraw:
4005 // assets = assetsTotal * (shares / sharesTotal)
4006 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
4007
4008 auto const start = env.balance(d.depositor, d.assets).number();
4009 d.peek([](SLE& vault, auto&) -> bool {
4010 vault[sfAssetsAvailable] = Number(1);
4011 return true;
4012 });
4013
4014 // Note, this transaction fails first (because of above change
4015 // in the open ledger) but then succeeds when the ledger is
4016 // closed (because a modification like above is not persistent),
4017 // which is why the checks below are expected to pass.
4018 auto tx = d.vault.withdraw(
4019 {.depositor = d.depositor,
4020 .id = d.keylet.key,
4021 .amount = STAmount(d.asset, Number(25, -1))});
4022 env(tx, ter{tecINSUFFICIENT_FUNDS});
4023 env.close();
4024 BEAST_EXPECT(
4025 env.balance(d.depositor, d.shares) == d.share(900 - 25));
4026 BEAST_EXPECT(
4027 env.balance(d.depositor, d.assets) ==
4028 STAmount(d.asset, start + Number(25, -1)));
4029 BEAST_EXPECT(
4030 env.balance(d.vaultAccount, d.assets) ==
4031 STAmount(d.asset, Number(900 - 25, -1)));
4032 BEAST_EXPECT(
4033 env.balance(d.vaultAccount, d.shares) ==
4034 STAmount(d.share, -Number(900 - 25, 0)));
4035 }
4036
4037 {
4038 testcase("Scale withdraw with rounding shares up");
4039 // assetsToSharesWithdraw:
4040 // shares = sharesTotal * (assets / assetsTotal)
4041 // shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
4042 // sharesToAssetsWithdraw:
4043 // assets = assetsTotal * (shares / sharesTotal)
4044 // assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
4045
4046 auto const start = env.balance(d.depositor, d.assets).number();
4047 auto tx = d.vault.withdraw(
4048 {.depositor = d.depositor,
4049 .id = d.keylet.key,
4050 .amount = STAmount(d.asset, Number(375, -2))});
4051 env(tx);
4052 env.close();
4053 BEAST_EXPECT(
4054 env.balance(d.depositor, d.shares) == d.share(875 - 38));
4055 BEAST_EXPECT(
4056 env.balance(d.depositor, d.assets) ==
4057 STAmount(d.asset, start + Number(38, -1)));
4058 BEAST_EXPECT(
4059 env.balance(d.vaultAccount, d.assets) ==
4060 STAmount(d.asset, Number(875 - 38, -1)));
4061 BEAST_EXPECT(
4062 env.balance(d.vaultAccount, d.shares) ==
4063 STAmount(d.share, -Number(875 - 38, 0)));
4064 }
4065
4066 {
4067 testcase("Scale withdraw with rounding shares down");
4068 // assetsToSharesWithdraw:
4069 // shares = sharesTotal * (assets / assetsTotal)
4070 // shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
4071 // sharesToAssetsWithdraw:
4072 // assets = assetsTotal * (shares / sharesTotal)
4073 // assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
4074
4075 auto const start = env.balance(d.depositor, d.assets).number();
4076 auto tx = d.vault.withdraw(
4077 {.depositor = d.depositor,
4078 .id = d.keylet.key,
4079 .amount = STAmount(d.asset, Number(372, -2))});
4080 env(tx);
4081 env.close();
4082 BEAST_EXPECT(
4083 env.balance(d.depositor, d.shares) == d.share(837 - 37));
4084 BEAST_EXPECT(
4085 env.balance(d.depositor, d.assets) ==
4086 STAmount(d.asset, start + Number(37, -1)));
4087 BEAST_EXPECT(
4088 env.balance(d.vaultAccount, d.assets) ==
4089 STAmount(d.asset, Number(837 - 37, -1)));
4090 BEAST_EXPECT(
4091 env.balance(d.vaultAccount, d.shares) ==
4092 STAmount(d.share, -Number(837 - 37, 0)));
4093 }
4094
4095 {
4096 testcase("Scale withdraw tiny amount");
4097
4098 auto const start = env.balance(d.depositor, d.assets).number();
4099 auto tx = d.vault.withdraw(
4100 {.depositor = d.depositor,
4101 .id = d.keylet.key,
4102 .amount = STAmount(d.asset, Number(9, -2))});
4103 env(tx);
4104 env.close();
4105 BEAST_EXPECT(
4106 env.balance(d.depositor, d.shares) == d.share(800 - 1));
4107 BEAST_EXPECT(
4108 env.balance(d.depositor, d.assets) ==
4109 STAmount(d.asset, start + Number(1, -1)));
4110 BEAST_EXPECT(
4111 env.balance(d.vaultAccount, d.assets) ==
4112 STAmount(d.asset, Number(800 - 1, -1)));
4113 BEAST_EXPECT(
4114 env.balance(d.vaultAccount, d.shares) ==
4115 STAmount(d.share, -Number(800 - 1, 0)));
4116 }
4117
4118 {
4119 testcase("Scale withdraw rest");
4120 auto const rest =
4121 env.balance(d.vaultAccount, d.assets).number();
4122
4123 tx = d.vault.withdraw(
4124 {.depositor = d.depositor,
4125 .id = d.keylet.key,
4126 .amount = STAmount(d.asset, rest)});
4127 env(tx);
4128 env.close();
4129 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
4130 BEAST_EXPECT(
4131 env.balance(d.vaultAccount, d.assets).number() == 0);
4132 BEAST_EXPECT(
4133 env.balance(d.vaultAccount, d.shares).number() == 0);
4134 }
4135 });
4136
4137 testCase(18, [&, this](Env& env, Data d) {
4138 testcase("Scale clawback overflow");
4139
4140 {
4141 auto tx = d.vault.deposit(
4142 {.depositor = d.depositor,
4143 .id = d.keylet.key,
4144 .amount = d.asset(5)});
4145 env(tx);
4146 env.close();
4147 }
4148
4149 {
4150 auto tx = d.vault.clawback(
4151 {.issuer = d.issuer,
4152 .id = d.keylet.key,
4153 .holder = d.depositor,
4154 .amount = STAmount(d.asset, Number(10, 0))});
4155 env(tx, ter{tecPATH_DRY});
4156 env.close();
4157 }
4158 });
4159
4160 testCase(1, [&, this](Env& env, Data d) {
4161 // initial setup: deposit 100 IOU, receive 1000 shares
4162 auto const start = env.balance(d.depositor, d.assets).number();
4163 auto tx = d.vault.deposit(
4164 {.depositor = d.depositor,
4165 .id = d.keylet.key,
4166 .amount = STAmount(d.asset, Number(100, 0))});
4167 env(tx);
4168 env.close();
4169 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
4170 BEAST_EXPECT(
4171 env.balance(d.depositor, d.assets) ==
4172 STAmount(d.asset, start - Number(100, 0)));
4173 BEAST_EXPECT(
4174 env.balance(d.vaultAccount, d.assets) ==
4175 STAmount(d.asset, Number(100, 0)));
4176 BEAST_EXPECT(
4177 env.balance(d.vaultAccount, d.shares) ==
4178 STAmount(d.share, -Number(1000, 0)));
4179 {
4180 testcase("Scale clawback exact");
4181 // assetsToSharesWithdraw:
4182 // shares = sharesTotal * (assets / assetsTotal)
4183 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
4184 // sharesToAssetsWithdraw:
4185 // assets = assetsTotal * (shares / sharesTotal)
4186 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
4187
4188 auto const start = env.balance(d.depositor, d.assets).number();
4189 auto tx = d.vault.clawback(
4190 {.issuer = d.issuer,
4191 .id = d.keylet.key,
4192 .holder = d.depositor,
4193 .amount = STAmount(d.asset, Number(10, 0))});
4194 env(tx);
4195 env.close();
4196 BEAST_EXPECT(
4197 env.balance(d.depositor, d.shares) == d.share(900));
4198 BEAST_EXPECT(
4199 env.balance(d.depositor, d.assets) ==
4200 STAmount(d.asset, start));
4201 BEAST_EXPECT(
4202 env.balance(d.vaultAccount, d.assets) ==
4203 STAmount(d.asset, Number(90, 0)));
4204 BEAST_EXPECT(
4205 env.balance(d.vaultAccount, d.shares) ==
4206 STAmount(d.share, -Number(900, 0)));
4207 }
4208
4209 {
4210 testcase("Scale clawback insignificant amount");
4211 auto tx = d.vault.clawback(
4212 {.issuer = d.issuer,
4213 .id = d.keylet.key,
4214 .holder = d.depositor,
4215 .amount = STAmount(d.asset, Number(4, -2))});
4216 env(tx, ter{tecPRECISION_LOSS});
4217 }
4218
4219 {
4220 testcase("Scale clawback with rounding assets");
4221 // assetsToSharesWithdraw:
4222 // shares = sharesTotal * (assets / assetsTotal)
4223 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
4224 // sharesToAssetsWithdraw:
4225 // assets = assetsTotal * (shares / sharesTotal)
4226 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
4227
4228 auto const start = env.balance(d.depositor, d.assets).number();
4229 auto tx = d.vault.clawback(
4230 {.issuer = d.issuer,
4231 .id = d.keylet.key,
4232 .holder = d.depositor,
4233 .amount = STAmount(d.asset, Number(25, -1))});
4234 env(tx);
4235 env.close();
4236 BEAST_EXPECT(
4237 env.balance(d.depositor, d.shares) == d.share(900 - 25));
4238 BEAST_EXPECT(
4239 env.balance(d.depositor, d.assets) ==
4240 STAmount(d.asset, start));
4241 BEAST_EXPECT(
4242 env.balance(d.vaultAccount, d.assets) ==
4243 STAmount(d.asset, Number(900 - 25, -1)));
4244 BEAST_EXPECT(
4245 env.balance(d.vaultAccount, d.shares) ==
4246 STAmount(d.share, -Number(900 - 25, 0)));
4247 }
4248
4249 {
4250 testcase("Scale clawback with rounding shares up");
4251 // assetsToSharesWithdraw:
4252 // shares = sharesTotal * (assets / assetsTotal)
4253 // shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
4254 // sharesToAssetsWithdraw:
4255 // assets = assetsTotal * (shares / sharesTotal)
4256 // assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
4257
4258 auto const start = env.balance(d.depositor, d.assets).number();
4259 auto tx = d.vault.clawback(
4260 {.issuer = d.issuer,
4261 .id = d.keylet.key,
4262 .holder = d.depositor,
4263 .amount = STAmount(d.asset, Number(375, -2))});
4264 env(tx);
4265 env.close();
4266 BEAST_EXPECT(
4267 env.balance(d.depositor, d.shares) == d.share(875 - 38));
4268 BEAST_EXPECT(
4269 env.balance(d.depositor, d.assets) ==
4270 STAmount(d.asset, start));
4271 BEAST_EXPECT(
4272 env.balance(d.vaultAccount, d.assets) ==
4273 STAmount(d.asset, Number(875 - 38, -1)));
4274 BEAST_EXPECT(
4275 env.balance(d.vaultAccount, d.shares) ==
4276 STAmount(d.share, -Number(875 - 38, 0)));
4277 }
4278
4279 {
4280 testcase("Scale clawback with rounding shares down");
4281 // assetsToSharesWithdraw:
4282 // shares = sharesTotal * (assets / assetsTotal)
4283 // shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
4284 // sharesToAssetsWithdraw:
4285 // assets = assetsTotal * (shares / sharesTotal)
4286 // assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
4287
4288 auto const start = env.balance(d.depositor, d.assets).number();
4289 auto tx = d.vault.clawback(
4290 {.issuer = d.issuer,
4291 .id = d.keylet.key,
4292 .holder = d.depositor,
4293 .amount = STAmount(d.asset, Number(372, -2))});
4294 env(tx);
4295 env.close();
4296 BEAST_EXPECT(
4297 env.balance(d.depositor, d.shares) == d.share(837 - 37));
4298 BEAST_EXPECT(
4299 env.balance(d.depositor, d.assets) ==
4300 STAmount(d.asset, start));
4301 BEAST_EXPECT(
4302 env.balance(d.vaultAccount, d.assets) ==
4303 STAmount(d.asset, Number(837 - 37, -1)));
4304 BEAST_EXPECT(
4305 env.balance(d.vaultAccount, d.shares) ==
4306 STAmount(d.share, -Number(837 - 37, 0)));
4307 }
4308
4309 {
4310 testcase("Scale clawback tiny amount");
4311
4312 auto const start = env.balance(d.depositor, d.assets).number();
4313 auto tx = d.vault.clawback(
4314 {.issuer = d.issuer,
4315 .id = d.keylet.key,
4316 .holder = d.depositor,
4317 .amount = STAmount(d.asset, Number(9, -2))});
4318 env(tx);
4319 env.close();
4320 BEAST_EXPECT(
4321 env.balance(d.depositor, d.shares) == d.share(800 - 1));
4322 BEAST_EXPECT(
4323 env.balance(d.depositor, d.assets) ==
4324 STAmount(d.asset, start));
4325 BEAST_EXPECT(
4326 env.balance(d.vaultAccount, d.assets) ==
4327 STAmount(d.asset, Number(800 - 1, -1)));
4328 BEAST_EXPECT(
4329 env.balance(d.vaultAccount, d.shares) ==
4330 STAmount(d.share, -Number(800 - 1, 0)));
4331 }
4332
4333 {
4334 testcase("Scale clawback rest");
4335 auto const rest =
4336 env.balance(d.vaultAccount, d.assets).number();
4337 d.peek([](SLE& vault, auto&) -> bool {
4338 vault[sfAssetsAvailable] = Number(5);
4339 return true;
4340 });
4341
4342 // Note, this transaction yields two different results:
4343 // * in the open ledger, with AssetsAvailable = 5
4344 // * when the ledger is closed with unmodified AssetsAvailable
4345 // because a modification like above is not persistent.
4346 tx = d.vault.clawback(
4347 {.issuer = d.issuer,
4348 .id = d.keylet.key,
4349 .holder = d.depositor,
4350 .amount = STAmount(d.asset, rest)});
4351 env(tx);
4352 env.close();
4353 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
4354 BEAST_EXPECT(
4355 env.balance(d.vaultAccount, d.assets).number() == 0);
4356 BEAST_EXPECT(
4357 env.balance(d.vaultAccount, d.shares).number() == 0);
4358 }
4359 });
4360 }
4361
4362 void
4364 {
4365 using namespace test::jtx;
4366
4367 testcase("RPC");
4368 Env env{*this, testable_amendments() | featureSingleAssetVault};
4369 Account const owner{"owner"};
4370 Account const issuer{"issuer"};
4371 Vault vault{env};
4372 env.fund(XRP(1000), issuer, owner);
4373 env.close();
4374
4375 PrettyAsset asset = issuer["IOU"];
4376 env.trust(asset(1000), owner);
4377 env(pay(issuer, owner, asset(200)));
4378 env.close();
4379
4380 auto const sequence = env.seq(owner);
4381 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
4382 env(tx);
4383 env.close();
4384
4385 // Set some fields
4386 {
4387 auto tx1 = vault.deposit(
4388 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
4389 env(tx1);
4390
4391 auto tx2 = vault.set({.owner = owner, .id = keylet.key});
4392 tx2[sfAssetsMaximum] = asset(1000).number();
4393 env(tx2);
4394 env.close();
4395 }
4396
4397 auto const sleVault = [&env, keylet = keylet, this]() {
4398 auto const vault = env.le(keylet);
4399 BEAST_EXPECT(vault != nullptr);
4400 return vault;
4401 }();
4402
4403 auto const check = [&, keylet = keylet, sle = sleVault, this](
4404 Json::Value const& vault,
4405 Json::Value const& issuance = Json::nullValue) {
4406 BEAST_EXPECT(vault.isObject());
4407
4408 constexpr auto checkString =
4409 [](auto& node, SField const& field, std::string v) -> bool {
4410 return node.isMember(field.fieldName) &&
4411 node[field.fieldName].isString() &&
4412 node[field.fieldName] == v;
4413 };
4414 constexpr auto checkObject =
4415 [](auto& node, SField const& field, Json::Value v) -> bool {
4416 return node.isMember(field.fieldName) &&
4417 node[field.fieldName].isObject() &&
4418 node[field.fieldName] == v;
4419 };
4420 constexpr auto checkInt =
4421 [](auto& node, SField const& field, int v) -> bool {
4422 return node.isMember(field.fieldName) &&
4423 ((node[field.fieldName].isInt() &&
4424 node[field.fieldName] == Json::Int(v)) ||
4425 (node[field.fieldName].isUInt() &&
4426 node[field.fieldName] == Json::UInt(v)));
4427 };
4428
4429 BEAST_EXPECT(vault["LedgerEntryType"].asString() == "Vault");
4430 BEAST_EXPECT(vault[jss::index].asString() == strHex(keylet.key));
4431 BEAST_EXPECT(checkInt(vault, sfFlags, 0));
4432 // Ignore all other standard fields, this test doesn't care
4433
4434 BEAST_EXPECT(
4435 checkString(vault, sfAccount, toBase58(sle->at(sfAccount))));
4436 BEAST_EXPECT(
4437 checkObject(vault, sfAsset, to_json(sle->at(sfAsset))));
4438 BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
4439 BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
4440 BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
4441 BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
4442
4443 auto const strShareID = strHex(sle->at(sfShareMPTID));
4444 BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));
4445 BEAST_EXPECT(checkString(vault, sfOwner, toBase58(owner.id())));
4446 BEAST_EXPECT(checkInt(vault, sfSequence, sequence));
4447 BEAST_EXPECT(checkInt(
4448 vault, sfWithdrawalPolicy, vaultStrategyFirstComeFirstServe));
4449
4450 if (issuance.isObject())
4451 {
4452 BEAST_EXPECT(
4453 issuance["LedgerEntryType"].asString() ==
4454 "MPTokenIssuance");
4455 BEAST_EXPECT(
4456 issuance[jss::mpt_issuance_id].asString() == strShareID);
4457 BEAST_EXPECT(checkInt(issuance, sfSequence, 1));
4458 BEAST_EXPECT(checkInt(
4459 issuance,
4460 sfFlags,
4462 BEAST_EXPECT(
4463 checkString(issuance, sfOutstandingAmount, "50000000"));
4464 }
4465 };
4466
4467 {
4468 testcase("RPC ledger_entry selected by key");
4469 Json::Value jvParams;
4470 jvParams[jss::ledger_index] = jss::validated;
4471 jvParams[jss::vault] = strHex(keylet.key);
4472 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4473
4474 BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error));
4475 BEAST_EXPECT(jvVault[jss::result].isMember(jss::node));
4476 check(jvVault[jss::result][jss::node]);
4477 }
4478
4479 {
4480 testcase("RPC ledger_entry selected by owner and seq");
4481 Json::Value jvParams;
4482 jvParams[jss::ledger_index] = jss::validated;
4483 jvParams[jss::vault][jss::owner] = owner.human();
4484 jvParams[jss::vault][jss::seq] = sequence;
4485 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4486
4487 BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error));
4488 BEAST_EXPECT(jvVault[jss::result].isMember(jss::node));
4489 check(jvVault[jss::result][jss::node]);
4490 }
4491
4492 {
4493 testcase("RPC ledger_entry cannot find vault by key");
4494 Json::Value jvParams;
4495 jvParams[jss::ledger_index] = jss::validated;
4496 jvParams[jss::vault] = to_string(uint256(42));
4497 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4498 BEAST_EXPECT(
4499 jvVault[jss::result][jss::error].asString() == "entryNotFound");
4500 }
4501
4502 {
4503 testcase("RPC ledger_entry cannot find vault by owner and seq");
4504 Json::Value jvParams;
4505 jvParams[jss::ledger_index] = jss::validated;
4506 jvParams[jss::vault][jss::owner] = issuer.human();
4507 jvParams[jss::vault][jss::seq] = 1'000'000;
4508 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4509 BEAST_EXPECT(
4510 jvVault[jss::result][jss::error].asString() == "entryNotFound");
4511 }
4512
4513 {
4514 testcase("RPC ledger_entry malformed key");
4515 Json::Value jvParams;
4516 jvParams[jss::ledger_index] = jss::validated;
4517 jvParams[jss::vault] = 42;
4518 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4519 BEAST_EXPECT(
4520 jvVault[jss::result][jss::error].asString() ==
4521 "malformedRequest");
4522 }
4523
4524 {
4525 testcase("RPC ledger_entry malformed owner");
4526 Json::Value jvParams;
4527 jvParams[jss::ledger_index] = jss::validated;
4528 jvParams[jss::vault][jss::owner] = 42;
4529 jvParams[jss::vault][jss::seq] = sequence;
4530 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4531 BEAST_EXPECT(
4532 jvVault[jss::result][jss::error].asString() ==
4533 "malformedOwner");
4534 }
4535
4536 {
4537 testcase("RPC ledger_entry malformed seq");
4538 Json::Value jvParams;
4539 jvParams[jss::ledger_index] = jss::validated;
4540 jvParams[jss::vault][jss::owner] = issuer.human();
4541 jvParams[jss::vault][jss::seq] = "foo";
4542 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4543 BEAST_EXPECT(
4544 jvVault[jss::result][jss::error].asString() ==
4545 "malformedRequest");
4546 }
4547
4548 {
4549 testcase("RPC ledger_entry negative seq");
4550 Json::Value jvParams;
4551 jvParams[jss::ledger_index] = jss::validated;
4552 jvParams[jss::vault][jss::owner] = issuer.human();
4553 jvParams[jss::vault][jss::seq] = -1;
4554 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4555 BEAST_EXPECT(
4556 jvVault[jss::result][jss::error].asString() ==
4557 "malformedRequest");
4558 }
4559
4560 {
4561 testcase("RPC ledger_entry oversized seq");
4562 Json::Value jvParams;
4563 jvParams[jss::ledger_index] = jss::validated;
4564 jvParams[jss::vault][jss::owner] = issuer.human();
4565 jvParams[jss::vault][jss::seq] = 1e20;
4566 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4567 BEAST_EXPECT(
4568 jvVault[jss::result][jss::error].asString() ==
4569 "malformedRequest");
4570 }
4571
4572 {
4573 testcase("RPC ledger_entry bool seq");
4574 Json::Value jvParams;
4575 jvParams[jss::ledger_index] = jss::validated;
4576 jvParams[jss::vault][jss::owner] = issuer.human();
4577 jvParams[jss::vault][jss::seq] = true;
4578 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4579 BEAST_EXPECT(
4580 jvVault[jss::result][jss::error].asString() ==
4581 "malformedRequest");
4582 }
4583
4584 {
4585 testcase("RPC account_objects");
4586
4587 Json::Value jvParams;
4588 jvParams[jss::account] = owner.human();
4589 jvParams[jss::type] = jss::vault;
4590 auto jv = env.rpc(
4591 "json", "account_objects", to_string(jvParams))[jss::result];
4592
4593 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
4594 check(jv[jss::account_objects][0u]);
4595 }
4596
4597 {
4598 testcase("RPC ledger_data");
4599
4600 Json::Value jvParams;
4601 jvParams[jss::ledger_index] = jss::validated;
4602 jvParams[jss::binary] = false;
4603 jvParams[jss::type] = jss::vault;
4604 Json::Value jv =
4605 env.rpc("json", "ledger_data", to_string(jvParams));
4606 BEAST_EXPECT(jv[jss::result][jss::state].size() == 1);
4607 check(jv[jss::result][jss::state][0u]);
4608 }
4609
4610 {
4611 testcase("RPC vault_info command line");
4612 Json::Value jv =
4613 env.rpc("vault_info", strHex(keylet.key), "validated");
4614
4615 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4616 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4617 check(
4618 jv[jss::result][jss::vault],
4619 jv[jss::result][jss::vault][jss::shares]);
4620 }
4621
4622 {
4623 testcase("RPC vault_info json");
4624 Json::Value jvParams;
4625 jvParams[jss::ledger_index] = jss::validated;
4626 jvParams[jss::vault_id] = strHex(keylet.key);
4627 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4628
4629 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4630 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4631 check(
4632 jv[jss::result][jss::vault],
4633 jv[jss::result][jss::vault][jss::shares]);
4634 }
4635
4636 {
4637 testcase("RPC vault_info invalid vault_id");
4638 Json::Value jvParams;
4639 jvParams[jss::ledger_index] = jss::validated;
4640 jvParams[jss::vault_id] = "foobar";
4641 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4642 BEAST_EXPECT(
4643 jv[jss::result][jss::error].asString() == "malformedRequest");
4644 }
4645
4646 {
4647 testcase("RPC vault_info json invalid index");
4648 Json::Value jvParams;
4649 jvParams[jss::ledger_index] = jss::validated;
4650 jvParams[jss::vault_id] = 0;
4651 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4652 BEAST_EXPECT(
4653 jv[jss::result][jss::error].asString() == "malformedRequest");
4654 }
4655
4656 {
4657 testcase("RPC vault_info json by owner and sequence");
4658 Json::Value jvParams;
4659 jvParams[jss::ledger_index] = jss::validated;
4660 jvParams[jss::owner] = owner.human();
4661 jvParams[jss::seq] = sequence;
4662 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4663
4664 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4665 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4666 check(
4667 jv[jss::result][jss::vault],
4668 jv[jss::result][jss::vault][jss::shares]);
4669 }
4670
4671 {
4672 testcase("RPC vault_info json malformed sequence");
4673 Json::Value jvParams;
4674 jvParams[jss::ledger_index] = jss::validated;
4675 jvParams[jss::owner] = owner.human();
4676 jvParams[jss::seq] = "foobar";
4677 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4678 BEAST_EXPECT(
4679 jv[jss::result][jss::error].asString() == "malformedRequest");
4680 }
4681
4682 {
4683 testcase("RPC vault_info json invalid sequence");
4684 Json::Value jvParams;
4685 jvParams[jss::ledger_index] = jss::validated;
4686 jvParams[jss::owner] = owner.human();
4687 jvParams[jss::seq] = 0;
4688 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4689 BEAST_EXPECT(
4690 jv[jss::result][jss::error].asString() == "malformedRequest");
4691 }
4692
4693 {
4694 testcase("RPC vault_info json negative sequence");
4695 Json::Value jvParams;
4696 jvParams[jss::ledger_index] = jss::validated;
4697 jvParams[jss::owner] = owner.human();
4698 jvParams[jss::seq] = -1;
4699 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4700 BEAST_EXPECT(
4701 jv[jss::result][jss::error].asString() == "malformedRequest");
4702 }
4703
4704 {
4705 testcase("RPC vault_info json oversized sequence");
4706 Json::Value jvParams;
4707 jvParams[jss::ledger_index] = jss::validated;
4708 jvParams[jss::owner] = owner.human();
4709 jvParams[jss::seq] = 1e20;
4710 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4711 BEAST_EXPECT(
4712 jv[jss::result][jss::error].asString() == "malformedRequest");
4713 }
4714
4715 {
4716 testcase("RPC vault_info json bool sequence");
4717 Json::Value jvParams;
4718 jvParams[jss::ledger_index] = jss::validated;
4719 jvParams[jss::owner] = owner.human();
4720 jvParams[jss::seq] = true;
4721 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4722 BEAST_EXPECT(
4723 jv[jss::result][jss::error].asString() == "malformedRequest");
4724 }
4725
4726 {
4727 testcase("RPC vault_info json malformed owner");
4728 Json::Value jvParams;
4729 jvParams[jss::ledger_index] = jss::validated;
4730 jvParams[jss::owner] = "foobar";
4731 jvParams[jss::seq] = sequence;
4732 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4733 BEAST_EXPECT(
4734 jv[jss::result][jss::error].asString() == "malformedRequest");
4735 }
4736
4737 {
4738 testcase("RPC vault_info json invalid combination only owner");
4739 Json::Value jvParams;
4740 jvParams[jss::ledger_index] = jss::validated;
4741 jvParams[jss::owner] = owner.human();
4742 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4743 BEAST_EXPECT(
4744 jv[jss::result][jss::error].asString() == "malformedRequest");
4745 }
4746
4747 {
4748 testcase("RPC vault_info json invalid combination only seq");
4749 Json::Value jvParams;
4750 jvParams[jss::ledger_index] = jss::validated;
4751 jvParams[jss::seq] = sequence;
4752 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4753 BEAST_EXPECT(
4754 jv[jss::result][jss::error].asString() == "malformedRequest");
4755 }
4756
4757 {
4758 testcase("RPC vault_info json invalid combination seq vault_id");
4759 Json::Value jvParams;
4760 jvParams[jss::ledger_index] = jss::validated;
4761 jvParams[jss::vault_id] = strHex(keylet.key);
4762 jvParams[jss::seq] = sequence;
4763 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4764 BEAST_EXPECT(
4765 jv[jss::result][jss::error].asString() == "malformedRequest");
4766 }
4767
4768 {
4769 testcase("RPC vault_info json invalid combination owner vault_id");
4770 Json::Value jvParams;
4771 jvParams[jss::ledger_index] = jss::validated;
4772 jvParams[jss::vault_id] = strHex(keylet.key);
4773 jvParams[jss::owner] = owner.human();
4774 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4775 BEAST_EXPECT(
4776 jv[jss::result][jss::error].asString() == "malformedRequest");
4777 }
4778
4779 {
4780 testcase(
4781 "RPC vault_info json invalid combination owner seq "
4782 "vault_id");
4783 Json::Value jvParams;
4784 jvParams[jss::ledger_index] = jss::validated;
4785 jvParams[jss::vault_id] = strHex(keylet.key);
4786 jvParams[jss::seq] = sequence;
4787 jvParams[jss::owner] = owner.human();
4788 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4789 BEAST_EXPECT(
4790 jv[jss::result][jss::error].asString() == "malformedRequest");
4791 }
4792
4793 {
4794 testcase("RPC vault_info json no input");
4795 Json::Value jvParams;
4796 jvParams[jss::ledger_index] = jss::validated;
4797 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4798 BEAST_EXPECT(
4799 jv[jss::result][jss::error].asString() == "malformedRequest");
4800 }
4801
4802 {
4803 testcase("RPC vault_info command line invalid index");
4804 Json::Value jv = env.rpc("vault_info", "foobar", "validated");
4805 BEAST_EXPECT(jv[jss::error].asString() == "invalidParams");
4806 }
4807
4808 {
4809 testcase("RPC vault_info command line invalid index");
4810 Json::Value jv = env.rpc("vault_info", "0", "validated");
4811 BEAST_EXPECT(
4812 jv[jss::result][jss::error].asString() == "malformedRequest");
4813 }
4814
4815 {
4816 testcase("RPC vault_info command line invalid index");
4817 Json::Value jv =
4818 env.rpc("vault_info", strHex(uint256(42)), "validated");
4819 BEAST_EXPECT(
4820 jv[jss::result][jss::error].asString() == "entryNotFound");
4821 }
4822
4823 {
4824 testcase("RPC vault_info command line invalid ledger");
4825 Json::Value jv = env.rpc("vault_info", strHex(keylet.key), "0");
4826 BEAST_EXPECT(
4827 jv[jss::result][jss::error].asString() == "lgrNotFound");
4828 }
4829 }
4830
4831 void
4833 {
4834 using namespace test::jtx;
4835
4836 Env env(*this, testable_amendments());
4837 Account alice{"alice"};
4838 Account bob{"bob"};
4839 Account carol{"carol"};
4840
4841 struct CaseArgs
4842 {
4843 PrettyAsset asset = xrpIssue();
4844 };
4845
4846 auto const xrpBalance =
4847 [this](
4848 Env const& env, Account const& account) -> std::optional<long> {
4849 auto sle = env.le(keylet::account(account.id()));
4850 if (BEAST_EXPECT(sle != nullptr))
4851 return sle->getFieldAmount(sfBalance).xrp().drops();
4852 return std::nullopt;
4853 };
4854
4855 auto testCase = [&, this](auto test, CaseArgs args = {}) {
4856 Env env{*this, testable_amendments() | featureSingleAssetVault};
4857
4858 Vault vault{env};
4859
4860 // use different initial amount to distinguish the source balance
4861 env.fund(XRP(10000), alice);
4862 env.fund(XRP(20000), bob);
4863 env.fund(XRP(30000), carol);
4864 env.close();
4865
4866 env(delegate::set(
4867 carol,
4868 alice,
4869 {"Payment",
4870 "VaultCreate",
4871 "VaultSet",
4872 "VaultDelete",
4873 "VaultDeposit",
4874 "VaultWithdraw",
4875 "VaultClawback"}));
4876
4877 test(env, vault, args.asset);
4878 };
4879
4880 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
4881 testcase("delegated vault creation");
4882 auto startBalance = xrpBalance(env, carol);
4883 if (!BEAST_EXPECT(startBalance.has_value()))
4884 return;
4885
4886 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
4887 env(tx, delegate::as(alice));
4888 env.close();
4889 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance);
4890 });
4891
4892 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
4893 testcase("delegated deposit and withdrawal");
4894 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
4895 env(tx);
4896 env.close();
4897
4898 auto const amount = 1513;
4899 auto const baseFee = env.current()->fees().base;
4900
4901 auto startBalance = xrpBalance(env, carol);
4902 if (!BEAST_EXPECT(startBalance.has_value()))
4903 return;
4904
4905 tx = vault.deposit(
4906 {.depositor = carol,
4907 .id = keylet.key,
4908 .amount = asset(amount)});
4909 env(tx, delegate::as(alice));
4910 env.close();
4911 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount);
4912
4913 tx = vault.withdraw(
4914 {.depositor = carol,
4915 .id = keylet.key,
4916 .amount = asset(amount - 1)});
4917 env(tx, delegate::as(alice));
4918 env.close();
4919 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - 1);
4920
4921 tx = vault.withdraw(
4922 {.depositor = carol, .id = keylet.key, .amount = asset(1)});
4923 env(tx);
4924 env.close();
4925 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee);
4926 });
4927
4928 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
4929 testcase("delegated withdrawal same as base fee and deletion");
4930 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
4931 env(tx);
4932 env.close();
4933
4934 auto const amount = 25537;
4935 auto const baseFee = env.current()->fees().base;
4936
4937 auto startBalance = xrpBalance(env, carol);
4938 if (!BEAST_EXPECT(startBalance.has_value()))
4939 return;
4940
4941 tx = vault.deposit(
4942 {.depositor = carol,
4943 .id = keylet.key,
4944 .amount = asset(amount)});
4945 env(tx);
4946 env.close();
4947 BEAST_EXPECT(
4948 xrpBalance(env, carol) == *startBalance - amount - baseFee);
4949
4950 tx = vault.withdraw(
4951 {.depositor = carol,
4952 .id = keylet.key,
4953 .amount = asset(baseFee)});
4954 env(tx, delegate::as(alice));
4955 env.close();
4956 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount);
4957
4958 tx = vault.withdraw(
4959 {.depositor = carol,
4960 .id = keylet.key,
4961 .amount = asset(amount - baseFee)});
4962 env(tx, delegate::as(alice));
4963 env.close();
4964 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee);
4965
4966 tx = vault.del({.owner = carol, .id = keylet.key});
4967 env(tx, delegate::as(alice));
4968 env.close();
4969 });
4970 }
4971
4972public:
4973 void
4974 run() override
4975 {
4976 testSequences();
4977 testPreflight();
4981 testWithMPT();
4982 testWithIOU();
4987 testScaleIOU();
4988 testRPC();
4989 testDelegate();
4990 }
4991};
4992
4993BEAST_DEFINE_TESTSUITE_PRIO(Vault, app, ripple, 1);
4994
4995} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
constexpr TIss const & get() const
A currency issued by an account.
Definition Issue.h:14
bool native() const
Definition Issue.cpp:47
constexpr MPTID const & getMptID() const
Definition MPTIssue.h:27
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:46
Identifies fields.
Definition SField.h:127
Issue const & issue() const
Definition STAmount.h:477
Discardable, editable view to a ledger.
Definition Sandbox.h:16
void apply(RawView &to)
Definition Sandbox.h:36
ripple::test::jtx::PrettyAsset PrettyAsset
void testNonTransferableShares()
static auto constexpr negativeAmount
void run() override
Runs the suite.
constexpr value_type drops() const
Returns the number of drops.
Definition XRPAmount.h:158
Integers of any length that is a multiple of 32-bits.
Definition base_uint.h:67
void update(std::shared_ptr< SLE > const &sle) override
Indicate changes to a peeked SLE.
std::shared_ptr< SLE > peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
T is_same_v
@ nullValue
'null' value
Definition json_value.h:19
int Int
unsigned int UInt
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:521
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:225
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:507
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:545
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet const & skip() noexcept
The index of the "short" skip list.
Definition Indexes.cpp:177
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
constexpr std::uint32_t asfDepositAuth
Definition TxFlags.h:66
@ telINSUF_FEE_P
Definition TER.h:38
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
constexpr std::uint32_t asfRequireDest
Definition TxFlags.h:58
base_uint< 256 > uint256
Definition base_uint.h:539
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:45
@ lsfMPTCanTransfer
@ lsfMPTCanEscrow
@ lsfMPTCanClawback
constexpr std::uint32_t const tfVaultPrivate
Definition TxFlags.h:251
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
Definition View.cpp:2466
Json::Value to_json(Asset const &asset)
Definition Asset.h:104
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t const tfMPTUnauthorize
Definition TxFlags.h:153
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
constexpr std::uint32_t asfDefaultRipple
Definition TxFlags.h:65
constexpr std::uint32_t tfClearFreeze
Definition TxFlags.h:100
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
Definition View.cpp:743
@ tecNO_ENTRY
Definition TER.h:288
@ tecNO_LINE_INSUF_RESERVE
Definition TER.h:274
@ tecLIMIT_EXCEEDED
Definition TER.h:343
@ tecOBJECT_NOT_FOUND
Definition TER.h:308
@ tecFROZEN
Definition TER.h:285
@ tecINSUFFICIENT_FUNDS
Definition TER.h:307
@ tecNO_PERMISSION
Definition TER.h:287
@ tecDST_TAG_NEEDED
Definition TER.h:291
@ tecPRECISION_LOSS
Definition TER.h:345
@ tecHAS_OBLIGATIONS
Definition TER.h:299
@ tecWRONG_ASSET
Definition TER.h:342
@ tecNO_LINE
Definition TER.h:283
@ tecPATH_DRY
Definition TER.h:276
@ tecINSUFFICIENT_RESERVE
Definition TER.h:289
@ tecEXPIRED
Definition TER.h:296
@ tecNO_AUTH
Definition TER.h:282
@ tecLOCKED
Definition TER.h:340
constexpr std::uint32_t const tfMPTLock
Definition TxFlags.h:157
@ tesSUCCESS
Definition TER.h:226
constexpr std::uint32_t const tfVaultShareNonTransferable
Definition TxFlags.h:253
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1050
constexpr std::uint32_t tfClearDeepFreeze
Definition TxFlags.h:102
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:106
constexpr std::uint32_t asfAllowTrustLineClawback
Definition TxFlags.h:75
@ tapNONE
Definition ApplyView.h:12
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:151
@ terADDRESS_COLLISION
Definition TER.h:209
@ terNO_ACCOUNT
Definition TER.h:198
@ terNO_RIPPLE
Definition TER.h:205
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:130
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:129
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:134
constexpr XRPAmount DROPS_PER_XRP
Number of drops per 1 XRP.
Definition XRPAmount.h:240
@ temBAD_AMOUNT
Definition TER.h:70
@ temBAD_FEE
Definition TER.h:73
@ temMALFORMED
Definition TER.h:68
@ temINVALID_FLAG
Definition TER.h:92
@ temDISABLED
Definition TER.h:95
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...