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 - 1), 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 // Use one reserve so the next transaction fails
2078 env(ticket::create(owner, 1));
2079 env.close();
2080
2081 // No reserve to create MPToken for asset in VaultWithdraw
2082 tx = vault.withdraw(
2083 {.depositor = owner,
2084 .id = keylet.key,
2085 .amount = asset(100)});
2086 env(tx, ter{tecINSUFFICIENT_RESERVE});
2087 env.close();
2088
2089 env(pay(depositor, owner, XRP(incReserve)));
2090 env.close();
2091
2092 // Withdraw can now create asset MPToken, tx will succeed
2093 env(tx);
2094 env.close();
2095 }
2096 },
2097 {.requireAuth = false,
2098 .initialXRP = acctReserve + incReserve * 4 + 1});
2099
2100 testCase([this](
2101 Env& env,
2102 Account const& issuer,
2103 Account const& owner,
2104 Account const& depositor,
2105 PrettyAsset const& asset,
2106 Vault& vault,
2107 MPTTester& mptt) {
2108 testcase("MPT issuance deleted");
2109
2110 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2111 env(tx);
2112 env.close();
2113
2114 tx = vault.deposit(
2115 {.depositor = depositor,
2116 .id = keylet.key,
2117 .amount = asset(1000)});
2118 env(tx);
2119 env.close();
2120
2121 {
2122 auto tx = vault.clawback(
2123 {.issuer = issuer,
2124 .id = keylet.key,
2125 .holder = depositor,
2126 .amount = asset(0)});
2127 env(tx);
2128 }
2129
2130 mptt.destroy({.issuer = issuer, .id = mptt.issuanceID()});
2131 env.close();
2132
2133 {
2134 auto [tx, keylet] =
2135 vault.create({.owner = depositor, .asset = asset});
2136 env(tx, ter{tecOBJECT_NOT_FOUND});
2137 }
2138
2139 {
2140 auto tx = vault.deposit(
2141 {.depositor = depositor,
2142 .id = keylet.key,
2143 .amount = asset(10)});
2144 env(tx, ter{tecOBJECT_NOT_FOUND});
2145 }
2146
2147 {
2148 auto tx = vault.withdraw(
2149 {.depositor = depositor,
2150 .id = keylet.key,
2151 .amount = asset(10)});
2152 env(tx, ter{tecOBJECT_NOT_FOUND});
2153 }
2154
2155 {
2156 auto tx = vault.clawback(
2157 {.issuer = issuer,
2158 .id = keylet.key,
2159 .holder = depositor,
2160 .amount = asset(0)});
2161 env(tx, ter{tecOBJECT_NOT_FOUND});
2162 }
2163
2164 env(vault.del({.owner = owner, .id = keylet.key}));
2165 });
2166
2167 testCase([this](
2168 Env& env,
2169 Account const& issuer,
2170 Account const& owner,
2171 Account const& depositor,
2172 PrettyAsset const& asset,
2173 Vault& vault,
2174 MPTTester& mptt) {
2175 testcase("MPT vault owner can receive shares unless unauthorized");
2176
2177 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2178 env(tx);
2179 env.close();
2180
2181 tx = vault.deposit(
2182 {.depositor = depositor,
2183 .id = keylet.key,
2184 .amount = asset(1000)});
2185 env(tx);
2186 env.close();
2187
2188 auto const issuanceId = [&env](ripple::Keylet keylet) -> MPTID {
2189 auto const vault = env.le(keylet);
2190 return vault->at(sfShareMPTID);
2191 }(keylet);
2192 PrettyAsset shares = MPTIssue(issuanceId);
2193
2194 {
2195 // owner has MPToken for shares they did not explicitly create
2196 env(pay(depositor, owner, shares(1)));
2197 env.close();
2198
2199 tx = vault.withdraw(
2200 {.depositor = owner,
2201 .id = keylet.key,
2202 .amount = shares(1)});
2203 env(tx);
2204 env.close();
2205
2206 // owner's MPToken for vault shares not destroyed by withdraw
2207 env(pay(depositor, owner, shares(1)));
2208 env.close();
2209
2210 tx = vault.clawback(
2211 {.issuer = issuer,
2212 .id = keylet.key,
2213 .holder = owner,
2214 .amount = asset(0)});
2215 env(tx);
2216 env.close();
2217
2218 // owner's MPToken for vault shares not destroyed by clawback
2219 env(pay(depositor, owner, shares(1)));
2220 env.close();
2221
2222 // pay back, so we can destroy owner's MPToken now
2223 env(pay(owner, depositor, shares(1)));
2224 env.close();
2225
2226 {
2227 // explicitly destroy vault owners MPToken with zero balance
2228 Json::Value jv;
2229 jv[sfAccount] = owner.human();
2230 jv[sfMPTokenIssuanceID] = to_string(issuanceId);
2231 jv[sfFlags] = tfMPTUnauthorize;
2232 jv[sfTransactionType] = jss::MPTokenAuthorize;
2233 env(jv);
2234 env.close();
2235 }
2236
2237 // owner no longer has MPToken for vault shares
2238 tx = pay(depositor, owner, shares(1));
2239 env(tx, ter{tecNO_AUTH});
2240 env.close();
2241
2242 // destroy all remaining shares, so we can delete vault
2243 tx = vault.clawback(
2244 {.issuer = issuer,
2245 .id = keylet.key,
2246 .holder = depositor,
2247 .amount = asset(0)});
2248 env(tx);
2249 env.close();
2250
2251 // will soft fail destroying MPToken for vault owner
2252 env(vault.del({.owner = owner, .id = keylet.key}));
2253 env.close();
2254 }
2255 });
2256
2257 testCase(
2258 [this](
2259 Env& env,
2260 Account const& issuer,
2261 Account const& owner,
2262 Account const& depositor,
2263 PrettyAsset const& asset,
2264 Vault& vault,
2265 MPTTester& mptt) {
2266 testcase("MPT clawback disabled");
2267
2268 auto [tx, keylet] =
2269 vault.create({.owner = owner, .asset = asset});
2270 env(tx);
2271 env.close();
2272
2273 tx = vault.deposit(
2274 {.depositor = depositor,
2275 .id = keylet.key,
2276 .amount = asset(1000)});
2277 env(tx);
2278 env.close();
2279
2280 {
2281 auto tx = vault.clawback(
2282 {.issuer = issuer,
2283 .id = keylet.key,
2284 .holder = depositor,
2285 .amount = asset(0)});
2286 env(tx, ter{tecNO_PERMISSION});
2287 }
2288 },
2289 {.enableClawback = false});
2290
2291 testCase([this](
2292 Env& env,
2293 Account const& issuer,
2294 Account const& owner,
2295 Account const& depositor,
2296 Asset const& asset,
2297 Vault& vault,
2298 MPTTester& mptt) {
2299 testcase("MPT un-authorization");
2300 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2301 env(tx);
2302 env.close();
2303 tx = vault.deposit(
2304 {.depositor = depositor,
2305 .id = keylet.key,
2306 .amount = asset(1000)});
2307 env(tx);
2308 env.close();
2309
2310 mptt.authorize(
2311 {.account = issuer,
2312 .holder = depositor,
2313 .flags = tfMPTUnauthorize});
2314 env.close();
2315
2316 {
2317 auto tx = vault.withdraw(
2318 {.depositor = depositor,
2319 .id = keylet.key,
2320 .amount = asset(100)});
2321 env(tx, ter(tecNO_AUTH));
2322
2323 // Withdrawal to other (authorized) accounts works
2324 tx[sfDestination] = issuer.human();
2325 env(tx);
2326 env.close();
2327
2328 tx[sfDestination] = owner.human();
2329 env(tx);
2330 env.close();
2331 }
2332
2333 {
2334 // Cannot deposit some more
2335 auto tx = vault.deposit(
2336 {.depositor = depositor,
2337 .id = keylet.key,
2338 .amount = asset(100)});
2339 env(tx, ter(tecNO_AUTH));
2340 }
2341
2342 // Clawback works
2343 tx = vault.clawback(
2344 {.issuer = issuer,
2345 .id = keylet.key,
2346 .holder = depositor,
2347 .amount = asset(800)});
2348 env(tx);
2349 env.close();
2350
2351 env(vault.del({.owner = owner, .id = keylet.key}));
2352 });
2353
2354 testCase([this](
2355 Env& env,
2356 Account const& issuer,
2357 Account const& owner,
2358 Account const& depositor,
2359 Asset const& asset,
2360 Vault& vault,
2361 MPTTester& mptt) {
2362 testcase("MPT lock of vault pseudo-account");
2363 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2364 env(tx);
2365 env.close();
2366
2367 auto const vaultAccount =
2368 [&env, keylet = keylet, this]() -> AccountID {
2369 auto const vault = env.le(keylet);
2370 BEAST_EXPECT(vault != nullptr);
2371 return vault->at(sfAccount);
2372 }();
2373
2374 tx = vault.deposit(
2375 {.depositor = depositor,
2376 .id = keylet.key,
2377 .amount = asset(100)});
2378 env(tx);
2379 env.close();
2380
2381 tx = [&]() {
2382 Json::Value jv;
2383 jv[jss::Account] = issuer.human();
2384 jv[sfMPTokenIssuanceID] =
2385 to_string(asset.get<MPTIssue>().getMptID());
2386 jv[jss::Holder] = toBase58(vaultAccount);
2387 jv[jss::TransactionType] = jss::MPTokenIssuanceSet;
2388 jv[jss::Flags] = tfMPTLock;
2389 return jv;
2390 }();
2391 env(tx);
2392 env.close();
2393
2394 tx = vault.deposit(
2395 {.depositor = depositor,
2396 .id = keylet.key,
2397 .amount = asset(100)});
2398 env(tx, ter(tecLOCKED));
2399
2400 tx = vault.withdraw(
2401 {.depositor = depositor,
2402 .id = keylet.key,
2403 .amount = asset(100)});
2404 env(tx, ter(tecLOCKED));
2405
2406 // Clawback works, even when locked
2407 tx = vault.clawback(
2408 {.issuer = issuer,
2409 .id = keylet.key,
2410 .holder = depositor,
2411 .amount = asset(100)});
2412 env(tx);
2413
2414 // Can delete an empty vault even when asset is locked.
2415 tx = vault.del({.owner = owner, .id = keylet.key});
2416 env(tx);
2417 });
2418
2419 {
2420 testcase("MPT shares to a vault");
2421
2422 Env env{*this, testable_amendments() | featureSingleAssetVault};
2423 Account owner{"owner"};
2424 Account issuer{"issuer"};
2425 env.fund(XRP(1000000), owner, issuer);
2426 env.close();
2427 Vault vault{env};
2428
2429 MPTTester mptt{env, issuer, mptInitNoFund};
2430 mptt.create(
2433 mptt.authorize({.account = owner});
2434 mptt.authorize({.account = issuer, .holder = owner});
2435 PrettyAsset asset = mptt.issuanceID();
2436 env(pay(issuer, owner, asset(100)));
2437 auto [tx1, k1] = vault.create({.owner = owner, .asset = asset});
2438 env(tx1);
2439 env.close();
2440
2441 auto const shares = [&env, keylet = k1, this]() -> Asset {
2442 auto const vault = env.le(keylet);
2443 BEAST_EXPECT(vault != nullptr);
2444 return MPTIssue(vault->at(sfShareMPTID));
2445 }();
2446
2447 auto [tx2, k2] = vault.create({.owner = owner, .asset = shares});
2448 env(tx2, ter{tecWRONG_ASSET});
2449 env.close();
2450 }
2451 }
2452
2453 void
2455 {
2456 using namespace test::jtx;
2457
2458 struct CaseArgs
2459 {
2460 int initialXRP = 1000;
2461 Number initialIOU = 200;
2462 double transferRate = 1.0;
2463 };
2464
2465 auto testCase =
2466 [&, this](
2467 std::function<void(
2468 Env & env,
2469 Account const& owner,
2470 Account const& issuer,
2471 Account const& charlie,
2472 std::function<Account(ripple::Keylet)> vaultAccount,
2473 Vault& vault,
2474 PrettyAsset const& asset,
2475 std::function<MPTID(ripple::Keylet)> issuanceId)> test,
2476 CaseArgs args = {}) {
2477 Env env{*this, testable_amendments() | featureSingleAssetVault};
2478 Account const owner{"owner"};
2479 Account const issuer{"issuer"};
2480 Account const charlie{"charlie"};
2481 Vault vault{env};
2482 env.fund(XRP(args.initialXRP), issuer, owner, charlie);
2483 env(fset(issuer, asfAllowTrustLineClawback));
2484 env.close();
2485
2486 PrettyAsset const asset = issuer["IOU"];
2487 env.trust(asset(1000), owner);
2488 env.trust(asset(1000), charlie);
2489 env(pay(issuer, owner, asset(args.initialIOU)));
2490 env(rate(issuer, args.transferRate));
2491 env.close();
2492
2493 auto const vaultAccount =
2494 [&env](ripple::Keylet keylet) -> Account {
2495 return Account("vault", env.le(keylet)->at(sfAccount));
2496 };
2497 auto const issuanceId = [&env](ripple::Keylet keylet) -> MPTID {
2498 return env.le(keylet)->at(sfShareMPTID);
2499 };
2500
2501 test(
2502 env,
2503 owner,
2504 issuer,
2505 charlie,
2506 vaultAccount,
2507 vault,
2508 asset,
2509 issuanceId);
2510 };
2511
2512 testCase([&, this](
2513 Env& env,
2514 Account const& owner,
2515 Account const& issuer,
2516 Account const&,
2517 auto vaultAccount,
2518 Vault& vault,
2519 PrettyAsset const& asset,
2520 auto&&...) {
2521 testcase("IOU cannot use different asset");
2522 PrettyAsset const foo = issuer["FOO"];
2523
2524 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2525 env(tx);
2526 env.close();
2527
2528 {
2529 // Cannot create new trustline to a vault
2530 auto tx = [&, account = vaultAccount(keylet)]() {
2531 Json::Value jv;
2532 jv[jss::Account] = issuer.human();
2533 {
2534 auto& ja = jv[jss::LimitAmount] =
2535 foo(0).value().getJson(JsonOptions::none);
2536 ja[jss::issuer] = toBase58(account);
2537 }
2538 jv[jss::TransactionType] = jss::TrustSet;
2539 jv[jss::Flags] = tfSetFreeze;
2540 return jv;
2541 }();
2542 env(tx, ter{tecNO_PERMISSION});
2543 env.close();
2544 }
2545
2546 {
2547 auto tx = vault.deposit(
2548 {.depositor = issuer, .id = keylet.key, .amount = foo(20)});
2549 env(tx, ter{tecWRONG_ASSET});
2550 env.close();
2551 }
2552
2553 {
2554 auto tx = vault.withdraw(
2555 {.depositor = issuer, .id = keylet.key, .amount = foo(20)});
2556 env(tx, ter{tecWRONG_ASSET});
2557 env.close();
2558 }
2559
2560 env(vault.del({.owner = owner, .id = keylet.key}));
2561 env.close();
2562 });
2563
2564 testCase([&, this](
2565 Env& env,
2566 Account const& owner,
2567 Account const& issuer,
2568 Account const& charlie,
2569 auto vaultAccount,
2570 Vault& vault,
2571 PrettyAsset const& asset,
2572 auto issuanceId) {
2573 testcase("IOU frozen trust line to vault account");
2574
2575 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2576 env(tx);
2577 env.close();
2578
2579 env(vault.deposit(
2580 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2581 env.close();
2582
2583 Asset const share = Asset(issuanceId(keylet));
2584
2585 // Freeze the trustline to the vault
2586 auto trustSet = [&, account = vaultAccount(keylet)]() {
2587 Json::Value jv;
2588 jv[jss::Account] = issuer.human();
2589 {
2590 auto& ja = jv[jss::LimitAmount] =
2591 asset(0).value().getJson(JsonOptions::none);
2592 ja[jss::issuer] = toBase58(account);
2593 }
2594 jv[jss::TransactionType] = jss::TrustSet;
2595 jv[jss::Flags] = tfSetFreeze;
2596 return jv;
2597 }();
2598 env(trustSet);
2599 env.close();
2600
2601 {
2602 // Note, the "frozen" state of the trust line to vault account
2603 // is reported as "locked" state of the vault shares, because
2604 // this state is attached to shares by means of the transitive
2605 // isFrozen.
2606 auto tx = vault.deposit(
2607 {.depositor = owner,
2608 .id = keylet.key,
2609 .amount = asset(80)});
2610 env(tx, ter{tecLOCKED});
2611 }
2612
2613 {
2614 auto tx = vault.withdraw(
2615 {.depositor = owner,
2616 .id = keylet.key,
2617 .amount = asset(100)});
2618 env(tx, ter{tecLOCKED});
2619
2620 // also when trying to withdraw to a 3rd party
2621 tx[sfDestination] = charlie.human();
2622 env(tx, ter{tecLOCKED});
2623 env.close();
2624 }
2625
2626 {
2627 // Clawback works, even when locked
2628 auto tx = vault.clawback(
2629 {.issuer = issuer,
2630 .id = keylet.key,
2631 .holder = owner,
2632 .amount = asset(50)});
2633 env(tx);
2634 env.close();
2635 }
2636
2637 // Clear the frozen state
2638 trustSet[jss::Flags] = tfClearFreeze;
2639 env(trustSet);
2640 env.close();
2641
2642 env(vault.withdraw(
2643 {.depositor = owner,
2644 .id = keylet.key,
2645 .amount = share(50'000'000)}));
2646
2647 env(vault.del({.owner = owner, .id = keylet.key}));
2648 env.close();
2649 });
2650
2651 testCase(
2652 [&, this](
2653 Env& env,
2654 Account const& owner,
2655 Account const& issuer,
2656 Account const& charlie,
2657 auto vaultAccount,
2658 Vault& vault,
2659 PrettyAsset const& asset,
2660 auto issuanceId) {
2661 testcase("IOU transfer fees not applied");
2662
2663 auto [tx, keylet] =
2664 vault.create({.owner = owner, .asset = asset});
2665 env(tx);
2666 env.close();
2667
2668 env(vault.deposit(
2669 {.depositor = owner,
2670 .id = keylet.key,
2671 .amount = asset(100)}));
2672 env.close();
2673
2674 auto const issue = asset.raw().get<Issue>();
2675 Asset const share = Asset(issuanceId(keylet));
2676
2677 // transfer fees ignored on deposit
2678 BEAST_EXPECT(env.balance(owner, issue) == asset(100));
2679 BEAST_EXPECT(
2680 env.balance(vaultAccount(keylet), issue) == asset(100));
2681
2682 {
2683 auto tx = vault.clawback(
2684 {.issuer = issuer,
2685 .id = keylet.key,
2686 .holder = owner,
2687 .amount = asset(50)});
2688 env(tx);
2689 env.close();
2690 }
2691
2692 // transfer fees ignored on clawback
2693 BEAST_EXPECT(env.balance(owner, issue) == asset(100));
2694 BEAST_EXPECT(
2695 env.balance(vaultAccount(keylet), issue) == asset(50));
2696
2697 env(vault.withdraw(
2698 {.depositor = owner,
2699 .id = keylet.key,
2700 .amount = share(20'000'000)}));
2701
2702 // transfer fees ignored on withdraw
2703 BEAST_EXPECT(env.balance(owner, issue) == asset(120));
2704 BEAST_EXPECT(
2705 env.balance(vaultAccount(keylet), issue) == asset(30));
2706
2707 {
2708 auto tx = vault.withdraw(
2709 {.depositor = owner,
2710 .id = keylet.key,
2711 .amount = share(30'000'000)});
2712 tx[sfDestination] = charlie.human();
2713 env(tx);
2714 }
2715
2716 // transfer fees ignored on withdraw to 3rd party
2717 BEAST_EXPECT(env.balance(owner, issue) == asset(120));
2718 BEAST_EXPECT(env.balance(charlie, issue) == asset(30));
2719 BEAST_EXPECT(
2720 env.balance(vaultAccount(keylet), issue) == asset(0));
2721
2722 env(vault.del({.owner = owner, .id = keylet.key}));
2723 env.close();
2724 },
2725 CaseArgs{.transferRate = 1.25});
2726
2727 testCase([&, this](
2728 Env& env,
2729 Account const& owner,
2730 Account const& issuer,
2731 Account const& charlie,
2732 auto,
2733 Vault& vault,
2734 PrettyAsset const& asset,
2735 auto&&...) {
2736 testcase("IOU frozen trust line to depositor");
2737
2738 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2739 env(tx);
2740 env.close();
2741
2742 env(vault.deposit(
2743 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2744 env.close();
2745
2746 // Withdraw to 3rd party works
2747 auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
2748 auto tx = vault.withdraw(
2749 {.depositor = owner,
2750 .id = keylet.key,
2751 .amount = asset(10)});
2752 tx[sfDestination] = charlie.human();
2753 return tx;
2754 }(keylet);
2755 env(withdrawToCharlie);
2756
2757 // Freeze the owner
2758 env(trust(issuer, asset(0), owner, tfSetFreeze));
2759 env.close();
2760
2761 // Cannot withdraw
2762 auto const withdraw = vault.withdraw(
2763 {.depositor = owner, .id = keylet.key, .amount = asset(10)});
2764 env(withdraw, ter{tecFROZEN});
2765
2766 // Cannot withdraw to 3rd party
2767 env(withdrawToCharlie, ter{tecLOCKED});
2768 env.close();
2769
2770 {
2771 // Cannot deposit some more
2772 auto tx = vault.deposit(
2773 {.depositor = owner,
2774 .id = keylet.key,
2775 .amount = asset(10)});
2776 env(tx, ter{tecFROZEN});
2777 }
2778
2779 {
2780 // Clawback still works
2781 auto tx = vault.clawback(
2782 {.issuer = issuer,
2783 .id = keylet.key,
2784 .holder = owner,
2785 .amount = asset(0)});
2786 env(tx);
2787 env.close();
2788 }
2789
2790 env(vault.del({.owner = owner, .id = keylet.key}));
2791 env.close();
2792 });
2793
2794 testCase([&, this](
2795 Env& env,
2796 Account const& owner,
2797 Account const& issuer,
2798 Account const& charlie,
2799 auto,
2800 Vault& vault,
2801 PrettyAsset const& asset,
2802 auto&&...) {
2803 testcase("IOU no trust line to 3rd party");
2804
2805 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2806 env(tx);
2807 env.close();
2808
2809 env(vault.deposit(
2810 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
2811 env.close();
2812
2813 Account const erin{"erin"};
2814 env.fund(XRP(1000), erin);
2815 env.close();
2816
2817 // Withdraw to 3rd party without trust line
2818 auto const tx1 = [&](ripple::Keylet keylet) {
2819 auto tx = vault.withdraw(
2820 {.depositor = owner,
2821 .id = keylet.key,
2822 .amount = asset(10)});
2823 tx[sfDestination] = erin.human();
2824 return tx;
2825 }(keylet);
2826 env(tx1, ter{tecNO_LINE});
2827 });
2828
2829 testCase([&, this](
2830 Env& env,
2831 Account const& owner,
2832 Account const& issuer,
2833 Account const& charlie,
2834 auto,
2835 Vault& vault,
2836 PrettyAsset const& asset,
2837 auto&&...) {
2838 testcase("IOU no trust line to depositor");
2839
2840 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
2841 env(tx);
2842 env.close();
2843
2844 // reset limit, so deposit of all funds will delete the trust line
2845 env.trust(asset(0), owner);
2846 env.close();
2847
2848 env(vault.deposit(
2849 {.depositor = owner, .id = keylet.key, .amount = asset(200)}));
2850 env.close();
2851
2852 auto trustline =
2853 env.le(keylet::line(owner, asset.raw().get<Issue>()));
2854 BEAST_EXPECT(trustline == nullptr);
2855
2856 // Withdraw without trust line, will succeed
2857 auto const tx1 = [&](ripple::Keylet keylet) {
2858 auto tx = vault.withdraw(
2859 {.depositor = owner,
2860 .id = keylet.key,
2861 .amount = asset(10)});
2862 return tx;
2863 }(keylet);
2864 env(tx1);
2865 });
2866
2867 testCase(
2868 [&, this](
2869 Env& env,
2870 Account const& owner,
2871 Account const& issuer,
2872 Account const& charlie,
2873 auto const& vaultAccount,
2874 Vault& vault,
2875 PrettyAsset const& asset,
2876 auto&&...) {
2877 testcase("IOU calculation rounding");
2878
2879 auto [tx, keylet] =
2880 vault.create({.owner = owner, .asset = asset});
2881 tx[sfScale] = 1;
2882 env(tx);
2883 env.close();
2884
2885 auto const startingOwnerBalance = env.balance(owner, asset);
2886 BEAST_EXPECT(
2887 (startingOwnerBalance.value() ==
2888 STAmount{asset, 11875, -2}));
2889
2890 // This operation (first deposit 100, then 3.75 x 5) is known to
2891 // have triggered calculation rounding errors in Number
2892 // (addition and division), causing the last deposit to be
2893 // blocked by Vault invariants.
2894 env(vault.deposit(
2895 {.depositor = owner,
2896 .id = keylet.key,
2897 .amount = asset(100)}));
2898
2899 auto const tx1 = vault.deposit(
2900 {.depositor = owner,
2901 .id = keylet.key,
2902 .amount = asset(Number(375, -2))});
2903 for (auto i = 0; i < 5; ++i)
2904 {
2905 env(tx1);
2906 }
2907 env.close();
2908
2909 {
2910 STAmount const xfer{asset, 1185, -1};
2911 BEAST_EXPECT(
2912 env.balance(owner, asset) ==
2913 startingOwnerBalance.value() - xfer);
2914 BEAST_EXPECT(
2915 env.balance(vaultAccount(keylet), asset) == xfer);
2916
2917 auto const vault = env.le(keylet);
2918 BEAST_EXPECT(vault->at(sfAssetsAvailable) == xfer);
2919 BEAST_EXPECT(vault->at(sfAssetsTotal) == xfer);
2920 }
2921
2922 // Total vault balance should be 118.5 IOU. Withdraw and delete
2923 // the vault to verify this exact amount was deposited and the
2924 // owner has matching shares
2925 env(vault.withdraw(
2926 {.depositor = owner,
2927 .id = keylet.key,
2928 .amount = asset(Number(1000 + 37 * 5, -1))}));
2929
2930 {
2931 BEAST_EXPECT(
2932 env.balance(owner, asset) ==
2933 startingOwnerBalance.value());
2934 BEAST_EXPECT(
2935 env.balance(vaultAccount(keylet), asset) ==
2936 beast::zero);
2937 auto const vault = env.le(keylet);
2938 BEAST_EXPECT(vault->at(sfAssetsAvailable) == beast::zero);
2939 BEAST_EXPECT(vault->at(sfAssetsTotal) == beast::zero);
2940 }
2941
2942 env(vault.del({.owner = owner, .id = keylet.key}));
2943 env.close();
2944 },
2945 {.initialIOU = Number(11875, -2)});
2946
2947 auto const [acctReserve, incReserve] = [this]() -> std::pair<int, int> {
2948 Env env{*this, testable_amendments()};
2949 return {
2950 env.current()->fees().accountReserve(0).drops() /
2952 env.current()->fees().increment.drops() /
2954 }();
2955
2956 testCase(
2957 [&, this](
2958 Env& env,
2959 Account const& owner,
2960 Account const& issuer,
2961 Account const& charlie,
2962 auto,
2963 Vault& vault,
2964 PrettyAsset const& asset,
2965 auto&&...) {
2966 testcase("IOU no trust line to depositor no reserve");
2967 auto [tx, keylet] =
2968 vault.create({.owner = owner, .asset = asset});
2969 env(tx);
2970 env.close();
2971
2972 // reset limit, so deposit of all funds will delete the trust
2973 // line
2974 env.trust(asset(0), owner);
2975 env.close();
2976
2977 env(vault.deposit(
2978 {.depositor = owner,
2979 .id = keylet.key,
2980 .amount = asset(200)}));
2981 env.close();
2982
2983 auto trustline =
2984 env.le(keylet::line(owner, asset.raw().get<Issue>()));
2985 BEAST_EXPECT(trustline == nullptr);
2986
2987 env(ticket::create(owner, 1));
2988 env.close();
2989
2990 // Fail because not enough reserve to create trust line
2991 tx = vault.withdraw(
2992 {.depositor = owner,
2993 .id = keylet.key,
2994 .amount = asset(10)});
2995 env(tx, ter{tecNO_LINE_INSUF_RESERVE});
2996 env.close();
2997
2998 env(pay(charlie, owner, XRP(incReserve)));
2999 env.close();
3000
3001 // Withdraw can now create trust line, will succeed
3002 env(tx);
3003 env.close();
3004 },
3005 CaseArgs{.initialXRP = acctReserve + incReserve * 4 + 1});
3006
3007 testCase(
3008 [&, this](
3009 Env& env,
3010 Account const& owner,
3011 Account const& issuer,
3012 Account const& charlie,
3013 auto,
3014 Vault& vault,
3015 PrettyAsset const& asset,
3016 auto&&...) {
3017 testcase("IOU no reserve for share MPToken");
3018 auto [tx, keylet] =
3019 vault.create({.owner = owner, .asset = asset});
3020 env(tx);
3021 env.close();
3022
3023 env(pay(owner, charlie, asset(100)));
3024 env.close();
3025
3026 env(ticket::create(charlie, 3));
3027 env.close();
3028
3029 // Fail because not enough reserve to create MPToken for shares
3030 tx = vault.deposit(
3031 {.depositor = charlie,
3032 .id = keylet.key,
3033 .amount = asset(100)});
3034 env(tx, ter{tecINSUFFICIENT_RESERVE});
3035 env.close();
3036
3037 env(pay(issuer, charlie, XRP(incReserve)));
3038 env.close();
3039
3040 // Deposit can now create MPToken, will succeed
3041 env(tx);
3042 env.close();
3043 },
3044 CaseArgs{.initialXRP = acctReserve + incReserve * 4 + 1});
3045
3046 testCase([&, this](
3047 Env& env,
3048 Account const& owner,
3049 Account const& issuer,
3050 Account const& charlie,
3051 auto,
3052 Vault& vault,
3053 PrettyAsset const& asset,
3054 auto&&...) {
3055 testcase("IOU frozen trust line to 3rd party");
3056
3057 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
3058 env(tx);
3059 env.close();
3060
3061 env(vault.deposit(
3062 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
3063 env.close();
3064
3065 // Withdraw to 3rd party works
3066 auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
3067 auto tx = vault.withdraw(
3068 {.depositor = owner,
3069 .id = keylet.key,
3070 .amount = asset(10)});
3071 tx[sfDestination] = charlie.human();
3072 return tx;
3073 }(keylet);
3074 env(withdrawToCharlie);
3075
3076 // Freeze the 3rd party
3077 env(trust(issuer, asset(0), charlie, tfSetFreeze));
3078 env.close();
3079
3080 // Can withdraw
3081 auto const withdraw = vault.withdraw(
3082 {.depositor = owner, .id = keylet.key, .amount = asset(10)});
3083 env(withdraw);
3084 env.close();
3085
3086 // Cannot withdraw to 3rd party
3087 env(withdrawToCharlie, ter{tecFROZEN});
3088 env.close();
3089
3090 env(vault.clawback(
3091 {.issuer = issuer,
3092 .id = keylet.key,
3093 .holder = owner,
3094 .amount = asset(0)}));
3095 env.close();
3096
3097 env(vault.del({.owner = owner, .id = keylet.key}));
3098 env.close();
3099 });
3100
3101 testCase([&, this](
3102 Env& env,
3103 Account const& owner,
3104 Account const& issuer,
3105 Account const& charlie,
3106 auto,
3107 Vault& vault,
3108 PrettyAsset const& asset,
3109 auto&&...) {
3110 testcase("IOU global freeze");
3111
3112 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
3113 env(tx);
3114 env.close();
3115
3116 env(vault.deposit(
3117 {.depositor = owner, .id = keylet.key, .amount = asset(100)}));
3118 env.close();
3119
3120 env(fset(issuer, asfGlobalFreeze));
3121 env.close();
3122
3123 {
3124 // Cannot withdraw
3125 auto tx = vault.withdraw(
3126 {.depositor = owner,
3127 .id = keylet.key,
3128 .amount = asset(10)});
3129 env(tx, ter{tecFROZEN});
3130
3131 // Cannot withdraw to 3rd party
3132 tx[sfDestination] = charlie.human();
3133 env(tx, ter{tecFROZEN});
3134 env.close();
3135
3136 // Cannot deposit some more
3137 tx = vault.deposit(
3138 {.depositor = owner,
3139 .id = keylet.key,
3140 .amount = asset(10)});
3141
3142 env(tx, ter{tecFROZEN});
3143 }
3144
3145 // Clawback is permitted
3146 env(vault.clawback(
3147 {.issuer = issuer,
3148 .id = keylet.key,
3149 .holder = owner,
3150 .amount = asset(0)}));
3151 env.close();
3152
3153 env(vault.del({.owner = owner, .id = keylet.key}));
3154 env.close();
3155 });
3156 }
3157
3158 void
3160 {
3161 using namespace test::jtx;
3162
3163 testcase("private vault");
3164
3165 Env env{*this, testable_amendments() | featureSingleAssetVault};
3166 Account issuer{"issuer"};
3167 Account owner{"owner"};
3168 Account depositor{"depositor"};
3169 Account charlie{"charlie"};
3170 Account pdOwner{"pdOwner"};
3171 Account credIssuer1{"credIssuer1"};
3172 Account credIssuer2{"credIssuer2"};
3173 std::string const credType = "credential";
3174 Vault vault{env};
3175 env.fund(
3176 XRP(1000),
3177 issuer,
3178 owner,
3179 depositor,
3180 charlie,
3181 pdOwner,
3182 credIssuer1,
3183 credIssuer2);
3184 env.close();
3185 env(fset(issuer, asfAllowTrustLineClawback));
3186 env.close();
3187 env.require(flags(issuer, asfAllowTrustLineClawback));
3188
3189 PrettyAsset asset = issuer["IOU"];
3190 env.trust(asset(1000), owner);
3191 env(pay(issuer, owner, asset(500)));
3192 env.trust(asset(1000), depositor);
3193 env(pay(issuer, depositor, asset(500)));
3194 env.trust(asset(1000), charlie);
3195 env(pay(issuer, charlie, asset(5)));
3196 env.close();
3197
3198 auto [tx, keylet] = vault.create(
3199 {.owner = owner, .asset = asset, .flags = tfVaultPrivate});
3200 env(tx);
3201 env.close();
3202 BEAST_EXPECT(env.le(keylet));
3203
3204 {
3205 testcase("private vault owner can deposit");
3206 auto tx = vault.deposit(
3207 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
3208 env(tx);
3209 }
3210
3211 {
3212 testcase("private vault depositor not authorized yet");
3213 auto tx = vault.deposit(
3214 {.depositor = depositor,
3215 .id = keylet.key,
3216 .amount = asset(50)});
3217 env(tx, ter{tecNO_AUTH});
3218 }
3219
3220 {
3221 testcase("private vault cannot set non-existing domain");
3222 auto tx = vault.set({.owner = owner, .id = keylet.key});
3223 tx[sfDomainID] = to_string(base_uint<256>(42ul));
3224 env(tx, ter{tecOBJECT_NOT_FOUND});
3225 }
3226
3227 {
3228 testcase("private vault set domainId");
3229
3230 {
3231 pdomain::Credentials const credentials1{
3232 {.issuer = credIssuer1, .credType = credType}};
3233
3234 env(pdomain::setTx(pdOwner, credentials1));
3235 auto const domainId1 = [&]() {
3236 auto tx = env.tx()->getJson(JsonOptions::none);
3237 return pdomain::getNewDomain(env.meta());
3238 }();
3239
3240 auto tx = vault.set({.owner = owner, .id = keylet.key});
3241 tx[sfDomainID] = to_string(domainId1);
3242 env(tx);
3243 env.close();
3244
3245 // Update domain second time, should be harmless
3246 env(tx);
3247 env.close();
3248 }
3249
3250 {
3251 pdomain::Credentials const credentials{
3252 {.issuer = credIssuer1, .credType = credType},
3253 {.issuer = credIssuer2, .credType = credType}};
3254
3255 env(pdomain::setTx(pdOwner, credentials));
3256 auto const domainId = [&]() {
3257 auto tx = env.tx()->getJson(JsonOptions::none);
3258 return pdomain::getNewDomain(env.meta());
3259 }();
3260
3261 auto tx = vault.set({.owner = owner, .id = keylet.key});
3262 tx[sfDomainID] = to_string(domainId);
3263 env(tx);
3264 env.close();
3265
3266 // Should be idempotent
3267 tx = vault.set({.owner = owner, .id = keylet.key});
3268 tx[sfDomainID] = to_string(domainId);
3269 env(tx);
3270 env.close();
3271 }
3272 }
3273
3274 {
3275 testcase("private vault depositor still not authorized");
3276 auto tx = vault.deposit(
3277 {.depositor = depositor,
3278 .id = keylet.key,
3279 .amount = asset(50)});
3280 env(tx, ter{tecNO_AUTH});
3281 env.close();
3282 }
3283
3284 auto const credKeylet =
3285 credentials::keylet(depositor, credIssuer1, credType);
3286 {
3287 testcase("private vault depositor now authorized");
3288 env(credentials::create(depositor, credIssuer1, credType));
3289 env(credentials::accept(depositor, credIssuer1, credType));
3290 env(credentials::create(charlie, credIssuer1, credType));
3291 // charlie's credential not accepted
3292 env.close();
3293 auto credSle = env.le(credKeylet);
3294 BEAST_EXPECT(credSle != nullptr);
3295
3296 auto tx = vault.deposit(
3297 {.depositor = depositor,
3298 .id = keylet.key,
3299 .amount = asset(50)});
3300 env(tx);
3301 env.close();
3302
3303 tx = vault.deposit(
3304 {.depositor = charlie, .id = keylet.key, .amount = asset(50)});
3305 env(tx, ter{tecNO_AUTH});
3306 env.close();
3307 }
3308
3309 {
3310 testcase("private vault depositor lost authorization");
3311 env(credentials::deleteCred(
3312 credIssuer1, depositor, credIssuer1, credType));
3313 env(credentials::deleteCred(
3314 credIssuer1, charlie, credIssuer1, credType));
3315 env.close();
3316 auto credSle = env.le(credKeylet);
3317 BEAST_EXPECT(credSle == nullptr);
3318
3319 auto tx = vault.deposit(
3320 {.depositor = depositor,
3321 .id = keylet.key,
3322 .amount = asset(50)});
3323 env(tx, ter{tecNO_AUTH});
3324 env.close();
3325 }
3326
3327 auto const shares = [&env, keylet = keylet, this]() -> Asset {
3328 auto const vault = env.le(keylet);
3329 BEAST_EXPECT(vault != nullptr);
3330 return MPTIssue(vault->at(sfShareMPTID));
3331 }();
3332
3333 {
3334 testcase("private vault expired authorization");
3335 uint32_t const closeTime = env.current()
3336 ->info()
3337 .parentCloseTime.time_since_epoch()
3338 .count();
3339 {
3340 auto tx0 =
3341 credentials::create(depositor, credIssuer2, credType);
3342 tx0[sfExpiration] = closeTime + 20;
3343 env(tx0);
3344 tx0 = credentials::create(charlie, credIssuer2, credType);
3345 tx0[sfExpiration] = closeTime + 20;
3346 env(tx0);
3347 env.close();
3348
3349 env(credentials::accept(depositor, credIssuer2, credType));
3350 env(credentials::accept(charlie, credIssuer2, credType));
3351 env.close();
3352 }
3353
3354 {
3355 auto tx1 = vault.deposit(
3356 {.depositor = depositor,
3357 .id = keylet.key,
3358 .amount = asset(50)});
3359 env(tx1);
3360 env.close();
3361
3362 auto const tokenKeylet = keylet::mptoken(
3363 shares.get<MPTIssue>().getMptID(), depositor.id());
3364 BEAST_EXPECT(env.le(tokenKeylet) != nullptr);
3365 }
3366
3367 {
3368 // time advance
3369 env.close();
3370 env.close();
3371 env.close();
3372
3373 auto const credsKeylet =
3374 credentials::keylet(depositor, credIssuer2, credType);
3375 BEAST_EXPECT(env.le(credsKeylet) != nullptr);
3376
3377 auto tx2 = vault.deposit(
3378 {.depositor = depositor,
3379 .id = keylet.key,
3380 .amount = asset(1)});
3381 env(tx2, ter{tecEXPIRED});
3382 env.close();
3383
3384 BEAST_EXPECT(env.le(credsKeylet) == nullptr);
3385 }
3386
3387 {
3388 auto const credsKeylet =
3389 credentials::keylet(charlie, credIssuer2, credType);
3390 BEAST_EXPECT(env.le(credsKeylet) != nullptr);
3391 auto const tokenKeylet = keylet::mptoken(
3392 shares.get<MPTIssue>().getMptID(), charlie.id());
3393 BEAST_EXPECT(env.le(tokenKeylet) == nullptr);
3394
3395 auto tx3 = vault.deposit(
3396 {.depositor = charlie,
3397 .id = keylet.key,
3398 .amount = asset(2)});
3399 env(tx3, ter{tecEXPIRED});
3400
3401 env.close();
3402 BEAST_EXPECT(env.le(credsKeylet) == nullptr);
3403 BEAST_EXPECT(env.le(tokenKeylet) == nullptr);
3404 }
3405 }
3406
3407 {
3408 testcase("private vault reset domainId");
3409 auto tx = vault.set({.owner = owner, .id = keylet.key});
3410 tx[sfDomainID] = "0";
3411 env(tx);
3412 env.close();
3413
3414 tx = vault.deposit(
3415 {.depositor = depositor,
3416 .id = keylet.key,
3417 .amount = asset(50)});
3418 env(tx, ter{tecNO_AUTH});
3419 env.close();
3420
3421 tx = vault.withdraw(
3422 {.depositor = depositor,
3423 .id = keylet.key,
3424 .amount = asset(50)});
3425 env(tx);
3426 env.close();
3427
3428 tx = vault.clawback(
3429 {.issuer = issuer,
3430 .id = keylet.key,
3431 .holder = depositor,
3432 .amount = asset(0)});
3433 env(tx);
3434
3435 tx = vault.clawback(
3436 {.issuer = issuer,
3437 .id = keylet.key,
3438 .holder = owner,
3439 .amount = asset(0)});
3440 env(tx);
3441 env.close();
3442
3443 tx = vault.del({
3444 .owner = owner,
3445 .id = keylet.key,
3446 });
3447 env(tx);
3448 }
3449 }
3450
3451 void
3453 {
3454 using namespace test::jtx;
3455
3456 testcase("private XRP vault");
3457
3458 Env env{*this, testable_amendments() | featureSingleAssetVault};
3459 Account owner{"owner"};
3460 Account depositor{"depositor"};
3461 Account alice{"charlie"};
3462 std::string const credType = "credential";
3463 Vault vault{env};
3464 env.fund(XRP(100000), owner, depositor, alice);
3465 env.close();
3466
3467 PrettyAsset asset = xrpIssue();
3468 auto [tx, keylet] = vault.create(
3469 {.owner = owner, .asset = asset, .flags = tfVaultPrivate});
3470 env(tx);
3471 env.close();
3472
3473 auto const [vaultAccount, issuanceId] =
3474 [&env, keylet = keylet, this]() -> std::tuple<AccountID, uint192> {
3475 auto const vault = env.le(keylet);
3476 BEAST_EXPECT(vault != nullptr);
3477 return {vault->at(sfAccount), vault->at(sfShareMPTID)};
3478 }();
3479 BEAST_EXPECT(env.le(keylet::account(vaultAccount)));
3480 BEAST_EXPECT(env.le(keylet::mptIssuance(issuanceId)));
3481 PrettyAsset shares{issuanceId};
3482
3483 {
3484 testcase("private XRP vault owner can deposit");
3485 auto tx = vault.deposit(
3486 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
3487 env(tx);
3488 env.close();
3489 }
3490
3491 {
3492 testcase("private XRP vault cannot pay shares to depositor yet");
3493 env(pay(owner, depositor, shares(1)), ter{tecNO_AUTH});
3494 }
3495
3496 {
3497 testcase("private XRP vault depositor not authorized yet");
3498 auto tx = vault.deposit(
3499 {.depositor = depositor,
3500 .id = keylet.key,
3501 .amount = asset(50)});
3502 env(tx, ter{tecNO_AUTH});
3503 }
3504
3505 {
3506 testcase("private XRP vault set DomainID");
3507 pdomain::Credentials const credentials{
3508 {.issuer = owner, .credType = credType}};
3509
3510 env(pdomain::setTx(owner, credentials));
3511 auto const domainId = [&]() {
3512 auto tx = env.tx()->getJson(JsonOptions::none);
3513 return pdomain::getNewDomain(env.meta());
3514 }();
3515
3516 auto tx = vault.set({.owner = owner, .id = keylet.key});
3517 tx[sfDomainID] = to_string(domainId);
3518 env(tx);
3519 env.close();
3520 }
3521
3522 auto const credKeylet = credentials::keylet(depositor, owner, credType);
3523 {
3524 testcase("private XRP vault depositor now authorized");
3525 env(credentials::create(depositor, owner, credType));
3526 env(credentials::accept(depositor, owner, credType));
3527 env.close();
3528
3529 BEAST_EXPECT(env.le(credKeylet));
3530 auto tx = vault.deposit(
3531 {.depositor = depositor,
3532 .id = keylet.key,
3533 .amount = asset(50)});
3534 env(tx);
3535 env.close();
3536 }
3537
3538 {
3539 testcase("private XRP vault can pay shares to depositor");
3540 env(pay(owner, depositor, shares(1)));
3541 }
3542
3543 {
3544 testcase("private XRP vault cannot pay shares to 3rd party");
3545 Json::Value jv;
3546 jv[sfAccount] = alice.human();
3547 jv[sfTransactionType] = jss::MPTokenAuthorize;
3548 jv[sfMPTokenIssuanceID] = to_string(issuanceId);
3549 env(jv);
3550 env.close();
3551
3552 env(pay(owner, alice, shares(1)), ter{tecNO_AUTH});
3553 }
3554 }
3555
3556 void
3558 {
3559 using namespace test::jtx;
3560
3561 testcase("fail pseudo-account allocation");
3562 Env env{*this, testable_amendments() | featureSingleAssetVault};
3563 Account const owner{"owner"};
3564 Vault vault{env};
3565 env.fund(XRP(1000), owner);
3566
3567 auto const keylet = keylet::vault(owner.id(), env.seq(owner));
3568 for (int i = 0; i < 256; ++i)
3569 {
3570 AccountID const accountId =
3571 ripple::pseudoAccountAddress(*env.current(), keylet.key);
3572
3573 env(pay(env.master.id(), accountId, XRP(1000)),
3574 seq(autofill),
3575 fee(autofill),
3576 sig(autofill));
3577 }
3578
3579 auto [tx, keylet1] =
3580 vault.create({.owner = owner, .asset = xrpIssue()});
3581 BEAST_EXPECT(keylet.key == keylet1.key);
3582 env(tx, ter{terADDRESS_COLLISION});
3583 }
3584
3585 void
3587 {
3588 using namespace test::jtx;
3589
3590 struct Data
3591 {
3592 Account const& owner;
3593 Account const& issuer;
3594 Account const& depositor;
3595 Account const& vaultAccount;
3596 MPTIssue shares;
3597 PrettyAsset const& share;
3598 Vault& vault;
3599 ripple::Keylet keylet;
3600 Issue assets;
3601 PrettyAsset const& asset;
3602 std::function<bool(std::function<bool(SLE&, SLE&)>)> peek;
3603 };
3604
3605 auto testCase = [&, this](
3606 std::uint8_t scale,
3607 std::function<void(Env & env, Data data)> test) {
3608 Env env{*this, testable_amendments() | featureSingleAssetVault};
3609 Account const owner{"owner"};
3610 Account const issuer{"issuer"};
3611 Account const depositor{"depositor"};
3612 Vault vault{env};
3613 env.fund(XRP(1000), issuer, owner, depositor);
3614 env(fset(issuer, asfAllowTrustLineClawback));
3615 env.close();
3616
3617 PrettyAsset const asset = issuer["IOU"];
3618 env.trust(asset(1000), owner);
3619 env.trust(asset(1000), depositor);
3620 env(pay(issuer, owner, asset(200)));
3621 env(pay(issuer, depositor, asset(200)));
3622 env.close();
3623
3624 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
3625 tx[sfScale] = scale;
3626 env(tx);
3627
3628 auto const [vaultAccount, issuanceId] =
3629 [&env](ripple::Keylet keylet) -> std::tuple<Account, MPTID> {
3630 auto const vault = env.le(keylet);
3631 return {
3632 Account("vault", vault->at(sfAccount)),
3633 vault->at(sfShareMPTID)};
3634 }(keylet);
3635 MPTIssue shares(issuanceId);
3636 env.memoize(vaultAccount);
3637
3638 auto const peek =
3639 [=, &env, this](std::function<bool(SLE&, SLE&)> fn) -> bool {
3640 return env.app().openLedger().modify(
3641 [&](OpenView& view, beast::Journal j) -> bool {
3642 Sandbox sb(&view, tapNONE);
3643 auto vault = sb.peek(keylet::vault(keylet.key));
3644 if (!BEAST_EXPECT(vault != nullptr))
3645 return false;
3646 auto shares = sb.peek(
3647 keylet::mptIssuance(vault->at(sfShareMPTID)));
3648 if (!BEAST_EXPECT(shares != nullptr))
3649 return false;
3650 if (fn(*vault, *shares))
3651 {
3652 sb.update(vault);
3653 sb.update(shares);
3654 sb.apply(view);
3655 return true;
3656 }
3657 return false;
3658 });
3659 };
3660
3661 test(
3662 env,
3663 {.owner = owner,
3664 .issuer = issuer,
3665 .depositor = depositor,
3666 .vaultAccount = vaultAccount,
3667 .shares = shares,
3668 .share = PrettyAsset(shares),
3669 .vault = vault,
3670 .keylet = keylet,
3671 .assets = asset.raw().get<Issue>(),
3672 .asset = asset,
3673 .peek = peek});
3674 };
3675
3676 testCase(18, [&, this](Env& env, Data d) {
3677 testcase("Scale deposit overflow on first deposit");
3678 auto tx = d.vault.deposit(
3679 {.depositor = d.depositor,
3680 .id = d.keylet.key,
3681 .amount = d.asset(10)});
3682 env(tx, ter{tecPATH_DRY});
3683 env.close();
3684 });
3685
3686 testCase(18, [&, this](Env& env, Data d) {
3687 testcase("Scale deposit overflow on second deposit");
3688
3689 {
3690 auto tx = d.vault.deposit(
3691 {.depositor = d.depositor,
3692 .id = d.keylet.key,
3693 .amount = d.asset(5)});
3694 env(tx);
3695 env.close();
3696 }
3697
3698 {
3699 auto tx = d.vault.deposit(
3700 {.depositor = d.depositor,
3701 .id = d.keylet.key,
3702 .amount = d.asset(10)});
3703 env(tx, ter{tecPATH_DRY});
3704 env.close();
3705 }
3706 });
3707
3708 testCase(18, [&, this](Env& env, Data d) {
3709 testcase("Scale deposit overflow on total shares");
3710
3711 {
3712 auto tx = d.vault.deposit(
3713 {.depositor = d.depositor,
3714 .id = d.keylet.key,
3715 .amount = d.asset(5)});
3716 env(tx);
3717 env.close();
3718 }
3719
3720 {
3721 auto tx = d.vault.deposit(
3722 {.depositor = d.depositor,
3723 .id = d.keylet.key,
3724 .amount = d.asset(5)});
3725 env(tx, ter{tecPATH_DRY});
3726 env.close();
3727 }
3728 });
3729
3730 testCase(1, [&, this](Env& env, Data d) {
3731 testcase("Scale deposit exact");
3732
3733 auto const start = env.balance(d.depositor, d.assets).number();
3734 auto tx = d.vault.deposit(
3735 {.depositor = d.depositor,
3736 .id = d.keylet.key,
3737 .amount = d.asset(1)});
3738 env(tx);
3739 env.close();
3740 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(10));
3741 BEAST_EXPECT(
3742 env.balance(d.depositor, d.assets) ==
3743 STAmount(d.asset, start - 1));
3744 });
3745
3746 testCase(1, [&, this](Env& env, Data d) {
3747 testcase("Scale deposit insignificant amount");
3748
3749 auto tx = d.vault.deposit(
3750 {.depositor = d.depositor,
3751 .id = d.keylet.key,
3752 .amount = STAmount(d.asset, Number(9, -2))});
3753 env(tx, ter{tecPRECISION_LOSS});
3754 });
3755
3756 testCase(1, [&, this](Env& env, Data d) {
3757 testcase("Scale deposit exact, using full precision");
3758
3759 auto const start = env.balance(d.depositor, d.assets).number();
3760 auto tx = d.vault.deposit(
3761 {.depositor = d.depositor,
3762 .id = d.keylet.key,
3763 .amount = STAmount(d.asset, Number(15, -1))});
3764 env(tx);
3765 env.close();
3766 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(15));
3767 BEAST_EXPECT(
3768 env.balance(d.depositor, d.assets) ==
3769 STAmount(d.asset, start - Number(15, -1)));
3770 });
3771
3772 testCase(1, [&, this](Env& env, Data d) {
3773 testcase("Scale deposit exact, truncating from .5");
3774
3775 auto const start = env.balance(d.depositor, d.assets).number();
3776 // Each of the cases below will transfer exactly 1.2 IOU to the
3777 // vault and receive 12 shares in exchange
3778 {
3779 auto tx = d.vault.deposit(
3780 {.depositor = d.depositor,
3781 .id = d.keylet.key,
3782 .amount = STAmount(d.asset, Number(125, -2))});
3783 env(tx);
3784 env.close();
3785 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3786 BEAST_EXPECT(
3787 env.balance(d.depositor, d.assets) ==
3788 STAmount(d.asset, start - Number(12, -1)));
3789 }
3790
3791 {
3792 auto tx = d.vault.deposit(
3793 {.depositor = d.depositor,
3794 .id = d.keylet.key,
3795 .amount = STAmount(d.asset, Number(1201, -3))});
3796 env(tx);
3797 env.close();
3798 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(24));
3799 BEAST_EXPECT(
3800 env.balance(d.depositor, d.assets) ==
3801 STAmount(d.asset, start - Number(24, -1)));
3802 }
3803
3804 {
3805 auto tx = d.vault.deposit(
3806 {.depositor = d.depositor,
3807 .id = d.keylet.key,
3808 .amount = STAmount(d.asset, Number(1299, -3))});
3809 env(tx);
3810 env.close();
3811 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(36));
3812 BEAST_EXPECT(
3813 env.balance(d.depositor, d.assets) ==
3814 STAmount(d.asset, start - Number(36, -1)));
3815 }
3816 });
3817
3818 testCase(1, [&, this](Env& env, Data d) {
3819 testcase("Scale deposit exact, truncating from .01");
3820
3821 auto const start = env.balance(d.depositor, d.assets).number();
3822 // round to 12
3823 auto tx = d.vault.deposit(
3824 {.depositor = d.depositor,
3825 .id = d.keylet.key,
3826 .amount = STAmount(d.asset, Number(1201, -3))});
3827 env(tx);
3828 env.close();
3829 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3830 BEAST_EXPECT(
3831 env.balance(d.depositor, d.assets) ==
3832 STAmount(d.asset, start - Number(12, -1)));
3833
3834 {
3835 // round to 6
3836 auto tx = d.vault.deposit(
3837 {.depositor = d.depositor,
3838 .id = d.keylet.key,
3839 .amount = STAmount(d.asset, Number(69, -2))});
3840 env(tx);
3841 env.close();
3842 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
3843 BEAST_EXPECT(
3844 env.balance(d.depositor, d.assets) ==
3845 STAmount(d.asset, start - Number(18, -1)));
3846 }
3847 });
3848
3849 testCase(1, [&, this](Env& env, Data d) {
3850 testcase("Scale deposit exact, truncating from .99");
3851
3852 auto const start = env.balance(d.depositor, d.assets).number();
3853 // round to 12
3854 auto tx = d.vault.deposit(
3855 {.depositor = d.depositor,
3856 .id = d.keylet.key,
3857 .amount = STAmount(d.asset, Number(1299, -3))});
3858 env(tx);
3859 env.close();
3860 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(12));
3861 BEAST_EXPECT(
3862 env.balance(d.depositor, d.assets) ==
3863 STAmount(d.asset, start - Number(12, -1)));
3864
3865 {
3866 // round to 6
3867 auto tx = d.vault.deposit(
3868 {.depositor = d.depositor,
3869 .id = d.keylet.key,
3870 .amount = STAmount(d.asset, Number(62, -2))});
3871 env(tx);
3872 env.close();
3873 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(18));
3874 BEAST_EXPECT(
3875 env.balance(d.depositor, d.assets) ==
3876 STAmount(d.asset, start - Number(18, -1)));
3877 }
3878 });
3879
3880 testCase(1, [&, this](Env& env, Data d) {
3881 // initial setup: deposit 100 IOU, receive 1000 shares
3882 auto const start = env.balance(d.depositor, d.assets).number();
3883 auto tx = d.vault.deposit(
3884 {.depositor = d.depositor,
3885 .id = d.keylet.key,
3886 .amount = STAmount(d.asset, Number(100, 0))});
3887 env(tx);
3888 env.close();
3889 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
3890 BEAST_EXPECT(
3891 env.balance(d.depositor, d.assets) ==
3892 STAmount(d.asset, start - Number(100, 0)));
3893 BEAST_EXPECT(
3894 env.balance(d.vaultAccount, d.assets) ==
3895 STAmount(d.asset, Number(100, 0)));
3896 BEAST_EXPECT(
3897 env.balance(d.vaultAccount, d.shares) ==
3898 STAmount(d.share, Number(-1000, 0)));
3899
3900 {
3901 testcase("Scale redeem exact");
3902 // sharesToAssetsWithdraw:
3903 // assets = assetsTotal * (shares / sharesTotal)
3904 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
3905
3906 auto const start = env.balance(d.depositor, d.assets).number();
3907 auto tx = d.vault.withdraw(
3908 {.depositor = d.depositor,
3909 .id = d.keylet.key,
3910 .amount = STAmount(d.share, Number(100, 0))});
3911 env(tx);
3912 env.close();
3913 BEAST_EXPECT(
3914 env.balance(d.depositor, d.shares) == d.share(900));
3915 BEAST_EXPECT(
3916 env.balance(d.depositor, d.assets) ==
3917 STAmount(d.asset, start + Number(10, 0)));
3918 BEAST_EXPECT(
3919 env.balance(d.vaultAccount, d.assets) ==
3920 STAmount(d.asset, Number(90, 0)));
3921 BEAST_EXPECT(
3922 env.balance(d.vaultAccount, d.shares) ==
3923 STAmount(d.share, Number(-900, 0)));
3924 }
3925
3926 {
3927 testcase("Scale redeem with rounding");
3928 // sharesToAssetsWithdraw:
3929 // assets = assetsTotal * (shares / sharesTotal)
3930 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
3931
3932 auto const start = env.balance(d.depositor, d.assets).number();
3933 d.peek([](SLE& vault, auto&) -> bool {
3934 vault[sfAssetsAvailable] = Number(1);
3935 return true;
3936 });
3937
3938 // Note, this transaction fails first (because of above change
3939 // in the open ledger) but then succeeds when the ledger is
3940 // closed (because a modification like above is not persistent),
3941 // which is why the checks below are expected to pass.
3942 auto tx = d.vault.withdraw(
3943 {.depositor = d.depositor,
3944 .id = d.keylet.key,
3945 .amount = STAmount(d.share, Number(25, 0))});
3946 env(tx, ter{tecINSUFFICIENT_FUNDS});
3947 env.close();
3948 BEAST_EXPECT(
3949 env.balance(d.depositor, d.shares) == d.share(900 - 25));
3950 BEAST_EXPECT(
3951 env.balance(d.depositor, d.assets) ==
3952 STAmount(d.asset, start + Number(25, -1)));
3953 BEAST_EXPECT(
3954 env.balance(d.vaultAccount, d.assets) ==
3955 STAmount(d.asset, Number(900 - 25, -1)));
3956 BEAST_EXPECT(
3957 env.balance(d.vaultAccount, d.shares) ==
3958 STAmount(d.share, -Number(900 - 25, 0)));
3959 }
3960
3961 {
3962 testcase("Scale redeem exact");
3963 // sharesToAssetsWithdraw:
3964 // assets = assetsTotal * (shares / sharesTotal)
3965 // assets = 87.5 * 21 / 875 = 87.5 * 0.024 = 2.1
3966
3967 auto const start = env.balance(d.depositor, d.assets).number();
3968
3969 tx = d.vault.withdraw(
3970 {.depositor = d.depositor,
3971 .id = d.keylet.key,
3972 .amount = STAmount(d.share, Number(21, 0))});
3973 env(tx);
3974 env.close();
3975 BEAST_EXPECT(
3976 env.balance(d.depositor, d.shares) == d.share(875 - 21));
3977 BEAST_EXPECT(
3978 env.balance(d.depositor, d.assets) ==
3979 STAmount(d.asset, start + Number(21, -1)));
3980 BEAST_EXPECT(
3981 env.balance(d.vaultAccount, d.assets) ==
3982 STAmount(d.asset, Number(875 - 21, -1)));
3983 BEAST_EXPECT(
3984 env.balance(d.vaultAccount, d.shares) ==
3985 STAmount(d.share, -Number(875 - 21, 0)));
3986 }
3987
3988 {
3989 testcase("Scale redeem rest");
3990 auto const rest = env.balance(d.depositor, d.shares).number();
3991
3992 tx = d.vault.withdraw(
3993 {.depositor = d.depositor,
3994 .id = d.keylet.key,
3995 .amount = STAmount(d.share, rest)});
3996 env(tx);
3997 env.close();
3998 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
3999 BEAST_EXPECT(
4000 env.balance(d.vaultAccount, d.assets).number() == 0);
4001 BEAST_EXPECT(
4002 env.balance(d.vaultAccount, d.shares).number() == 0);
4003 }
4004 });
4005
4006 testCase(18, [&, this](Env& env, Data d) {
4007 testcase("Scale withdraw overflow");
4008
4009 {
4010 auto tx = d.vault.deposit(
4011 {.depositor = d.depositor,
4012 .id = d.keylet.key,
4013 .amount = d.asset(5)});
4014 env(tx);
4015 env.close();
4016 }
4017
4018 {
4019 auto tx = d.vault.withdraw(
4020 {.depositor = d.depositor,
4021 .id = d.keylet.key,
4022 .amount = STAmount(d.asset, Number(10, 0))});
4023 env(tx, ter{tecPATH_DRY});
4024 env.close();
4025 }
4026 });
4027
4028 testCase(1, [&, this](Env& env, Data d) {
4029 // initial setup: deposit 100 IOU, receive 1000 shares
4030 auto const start = env.balance(d.depositor, d.assets).number();
4031 auto tx = d.vault.deposit(
4032 {.depositor = d.depositor,
4033 .id = d.keylet.key,
4034 .amount = STAmount(d.asset, Number(100, 0))});
4035 env(tx);
4036 env.close();
4037 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
4038 BEAST_EXPECT(
4039 env.balance(d.depositor, d.assets) ==
4040 STAmount(d.asset, start - Number(100, 0)));
4041 BEAST_EXPECT(
4042 env.balance(d.vaultAccount, d.assets) ==
4043 STAmount(d.asset, Number(100, 0)));
4044 BEAST_EXPECT(
4045 env.balance(d.vaultAccount, d.shares) ==
4046 STAmount(d.share, Number(-1000, 0)));
4047
4048 {
4049 testcase("Scale withdraw exact");
4050 // assetsToSharesWithdraw:
4051 // shares = sharesTotal * (assets / assetsTotal)
4052 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
4053 // sharesToAssetsWithdraw:
4054 // assets = assetsTotal * (shares / sharesTotal)
4055 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
4056
4057 auto const start = env.balance(d.depositor, d.assets).number();
4058 auto tx = d.vault.withdraw(
4059 {.depositor = d.depositor,
4060 .id = d.keylet.key,
4061 .amount = STAmount(d.asset, Number(10, 0))});
4062 env(tx);
4063 env.close();
4064 BEAST_EXPECT(
4065 env.balance(d.depositor, d.shares) == d.share(900));
4066 BEAST_EXPECT(
4067 env.balance(d.depositor, d.assets) ==
4068 STAmount(d.asset, start + Number(10, 0)));
4069 BEAST_EXPECT(
4070 env.balance(d.vaultAccount, d.assets) ==
4071 STAmount(d.asset, Number(90, 0)));
4072 BEAST_EXPECT(
4073 env.balance(d.vaultAccount, d.shares) ==
4074 STAmount(d.share, Number(-900, 0)));
4075 }
4076
4077 {
4078 testcase("Scale withdraw insignificant amount");
4079 auto tx = d.vault.withdraw(
4080 {.depositor = d.depositor,
4081 .id = d.keylet.key,
4082 .amount = STAmount(d.asset, Number(4, -2))});
4083 env(tx, ter{tecPRECISION_LOSS});
4084 }
4085
4086 {
4087 testcase("Scale withdraw with rounding assets");
4088 // assetsToSharesWithdraw:
4089 // shares = sharesTotal * (assets / assetsTotal)
4090 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
4091 // sharesToAssetsWithdraw:
4092 // assets = assetsTotal * (shares / sharesTotal)
4093 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
4094
4095 auto const start = env.balance(d.depositor, d.assets).number();
4096 d.peek([](SLE& vault, auto&) -> bool {
4097 vault[sfAssetsAvailable] = Number(1);
4098 return true;
4099 });
4100
4101 // Note, this transaction fails first (because of above change
4102 // in the open ledger) but then succeeds when the ledger is
4103 // closed (because a modification like above is not persistent),
4104 // which is why the checks below are expected to pass.
4105 auto tx = d.vault.withdraw(
4106 {.depositor = d.depositor,
4107 .id = d.keylet.key,
4108 .amount = STAmount(d.asset, Number(25, -1))});
4109 env(tx, ter{tecINSUFFICIENT_FUNDS});
4110 env.close();
4111 BEAST_EXPECT(
4112 env.balance(d.depositor, d.shares) == d.share(900 - 25));
4113 BEAST_EXPECT(
4114 env.balance(d.depositor, d.assets) ==
4115 STAmount(d.asset, start + Number(25, -1)));
4116 BEAST_EXPECT(
4117 env.balance(d.vaultAccount, d.assets) ==
4118 STAmount(d.asset, Number(900 - 25, -1)));
4119 BEAST_EXPECT(
4120 env.balance(d.vaultAccount, d.shares) ==
4121 STAmount(d.share, -Number(900 - 25, 0)));
4122 }
4123
4124 {
4125 testcase("Scale withdraw with rounding shares up");
4126 // assetsToSharesWithdraw:
4127 // shares = sharesTotal * (assets / assetsTotal)
4128 // shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
4129 // sharesToAssetsWithdraw:
4130 // assets = assetsTotal * (shares / sharesTotal)
4131 // assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
4132
4133 auto const start = env.balance(d.depositor, d.assets).number();
4134 auto tx = d.vault.withdraw(
4135 {.depositor = d.depositor,
4136 .id = d.keylet.key,
4137 .amount = STAmount(d.asset, Number(375, -2))});
4138 env(tx);
4139 env.close();
4140 BEAST_EXPECT(
4141 env.balance(d.depositor, d.shares) == d.share(875 - 38));
4142 BEAST_EXPECT(
4143 env.balance(d.depositor, d.assets) ==
4144 STAmount(d.asset, start + Number(38, -1)));
4145 BEAST_EXPECT(
4146 env.balance(d.vaultAccount, d.assets) ==
4147 STAmount(d.asset, Number(875 - 38, -1)));
4148 BEAST_EXPECT(
4149 env.balance(d.vaultAccount, d.shares) ==
4150 STAmount(d.share, -Number(875 - 38, 0)));
4151 }
4152
4153 {
4154 testcase("Scale withdraw with rounding shares down");
4155 // assetsToSharesWithdraw:
4156 // shares = sharesTotal * (assets / assetsTotal)
4157 // shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
4158 // sharesToAssetsWithdraw:
4159 // assets = assetsTotal * (shares / sharesTotal)
4160 // assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
4161
4162 auto const start = env.balance(d.depositor, d.assets).number();
4163 auto tx = d.vault.withdraw(
4164 {.depositor = d.depositor,
4165 .id = d.keylet.key,
4166 .amount = STAmount(d.asset, Number(372, -2))});
4167 env(tx);
4168 env.close();
4169 BEAST_EXPECT(
4170 env.balance(d.depositor, d.shares) == d.share(837 - 37));
4171 BEAST_EXPECT(
4172 env.balance(d.depositor, d.assets) ==
4173 STAmount(d.asset, start + Number(37, -1)));
4174 BEAST_EXPECT(
4175 env.balance(d.vaultAccount, d.assets) ==
4176 STAmount(d.asset, Number(837 - 37, -1)));
4177 BEAST_EXPECT(
4178 env.balance(d.vaultAccount, d.shares) ==
4179 STAmount(d.share, -Number(837 - 37, 0)));
4180 }
4181
4182 {
4183 testcase("Scale withdraw tiny amount");
4184
4185 auto const start = env.balance(d.depositor, d.assets).number();
4186 auto tx = d.vault.withdraw(
4187 {.depositor = d.depositor,
4188 .id = d.keylet.key,
4189 .amount = STAmount(d.asset, Number(9, -2))});
4190 env(tx);
4191 env.close();
4192 BEAST_EXPECT(
4193 env.balance(d.depositor, d.shares) == d.share(800 - 1));
4194 BEAST_EXPECT(
4195 env.balance(d.depositor, d.assets) ==
4196 STAmount(d.asset, start + Number(1, -1)));
4197 BEAST_EXPECT(
4198 env.balance(d.vaultAccount, d.assets) ==
4199 STAmount(d.asset, Number(800 - 1, -1)));
4200 BEAST_EXPECT(
4201 env.balance(d.vaultAccount, d.shares) ==
4202 STAmount(d.share, -Number(800 - 1, 0)));
4203 }
4204
4205 {
4206 testcase("Scale withdraw rest");
4207 auto const rest =
4208 env.balance(d.vaultAccount, d.assets).number();
4209
4210 tx = d.vault.withdraw(
4211 {.depositor = d.depositor,
4212 .id = d.keylet.key,
4213 .amount = STAmount(d.asset, rest)});
4214 env(tx);
4215 env.close();
4216 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
4217 BEAST_EXPECT(
4218 env.balance(d.vaultAccount, d.assets).number() == 0);
4219 BEAST_EXPECT(
4220 env.balance(d.vaultAccount, d.shares).number() == 0);
4221 }
4222 });
4223
4224 testCase(18, [&, this](Env& env, Data d) {
4225 testcase("Scale clawback overflow");
4226
4227 {
4228 auto tx = d.vault.deposit(
4229 {.depositor = d.depositor,
4230 .id = d.keylet.key,
4231 .amount = d.asset(5)});
4232 env(tx);
4233 env.close();
4234 }
4235
4236 {
4237 auto tx = d.vault.clawback(
4238 {.issuer = d.issuer,
4239 .id = d.keylet.key,
4240 .holder = d.depositor,
4241 .amount = STAmount(d.asset, Number(10, 0))});
4242 env(tx, ter{tecPATH_DRY});
4243 env.close();
4244 }
4245 });
4246
4247 testCase(1, [&, this](Env& env, Data d) {
4248 // initial setup: deposit 100 IOU, receive 1000 shares
4249 auto const start = env.balance(d.depositor, d.assets).number();
4250 auto tx = d.vault.deposit(
4251 {.depositor = d.depositor,
4252 .id = d.keylet.key,
4253 .amount = STAmount(d.asset, Number(100, 0))});
4254 env(tx);
4255 env.close();
4256 BEAST_EXPECT(env.balance(d.depositor, d.shares) == d.share(1000));
4257 BEAST_EXPECT(
4258 env.balance(d.depositor, d.assets) ==
4259 STAmount(d.asset, start - Number(100, 0)));
4260 BEAST_EXPECT(
4261 env.balance(d.vaultAccount, d.assets) ==
4262 STAmount(d.asset, Number(100, 0)));
4263 BEAST_EXPECT(
4264 env.balance(d.vaultAccount, d.shares) ==
4265 STAmount(d.share, -Number(1000, 0)));
4266 {
4267 testcase("Scale clawback exact");
4268 // assetsToSharesWithdraw:
4269 // shares = sharesTotal * (assets / assetsTotal)
4270 // shares = 1000 * 10 / 100 = 1000 * 0.1 = 100
4271 // sharesToAssetsWithdraw:
4272 // assets = assetsTotal * (shares / sharesTotal)
4273 // assets = 100 * 100 / 1000 = 100 * 0.1 = 10
4274
4275 auto const start = env.balance(d.depositor, d.assets).number();
4276 auto tx = d.vault.clawback(
4277 {.issuer = d.issuer,
4278 .id = d.keylet.key,
4279 .holder = d.depositor,
4280 .amount = STAmount(d.asset, Number(10, 0))});
4281 env(tx);
4282 env.close();
4283 BEAST_EXPECT(
4284 env.balance(d.depositor, d.shares) == d.share(900));
4285 BEAST_EXPECT(
4286 env.balance(d.depositor, d.assets) ==
4287 STAmount(d.asset, start));
4288 BEAST_EXPECT(
4289 env.balance(d.vaultAccount, d.assets) ==
4290 STAmount(d.asset, Number(90, 0)));
4291 BEAST_EXPECT(
4292 env.balance(d.vaultAccount, d.shares) ==
4293 STAmount(d.share, -Number(900, 0)));
4294 }
4295
4296 {
4297 testcase("Scale clawback insignificant amount");
4298 auto tx = d.vault.clawback(
4299 {.issuer = d.issuer,
4300 .id = d.keylet.key,
4301 .holder = d.depositor,
4302 .amount = STAmount(d.asset, Number(4, -2))});
4303 env(tx, ter{tecPRECISION_LOSS});
4304 }
4305
4306 {
4307 testcase("Scale clawback with rounding assets");
4308 // assetsToSharesWithdraw:
4309 // shares = sharesTotal * (assets / assetsTotal)
4310 // shares = 900 * 2.5 / 90 = 900 * 0.02777... = 25
4311 // sharesToAssetsWithdraw:
4312 // assets = assetsTotal * (shares / sharesTotal)
4313 // assets = 90 * 25 / 900 = 90 * 0.02777... = 2.5
4314
4315 auto const start = env.balance(d.depositor, d.assets).number();
4316 auto tx = d.vault.clawback(
4317 {.issuer = d.issuer,
4318 .id = d.keylet.key,
4319 .holder = d.depositor,
4320 .amount = STAmount(d.asset, Number(25, -1))});
4321 env(tx);
4322 env.close();
4323 BEAST_EXPECT(
4324 env.balance(d.depositor, d.shares) == d.share(900 - 25));
4325 BEAST_EXPECT(
4326 env.balance(d.depositor, d.assets) ==
4327 STAmount(d.asset, start));
4328 BEAST_EXPECT(
4329 env.balance(d.vaultAccount, d.assets) ==
4330 STAmount(d.asset, Number(900 - 25, -1)));
4331 BEAST_EXPECT(
4332 env.balance(d.vaultAccount, d.shares) ==
4333 STAmount(d.share, -Number(900 - 25, 0)));
4334 }
4335
4336 {
4337 testcase("Scale clawback with rounding shares up");
4338 // assetsToSharesWithdraw:
4339 // shares = sharesTotal * (assets / assetsTotal)
4340 // shares = 875 * 3.75 / 87.5 = 875 * 0.042857... = 37.5
4341 // sharesToAssetsWithdraw:
4342 // assets = assetsTotal * (shares / sharesTotal)
4343 // assets = 87.5 * 38 / 875 = 87.5 * 0.043428... = 3.8
4344
4345 auto const start = env.balance(d.depositor, d.assets).number();
4346 auto tx = d.vault.clawback(
4347 {.issuer = d.issuer,
4348 .id = d.keylet.key,
4349 .holder = d.depositor,
4350 .amount = STAmount(d.asset, Number(375, -2))});
4351 env(tx);
4352 env.close();
4353 BEAST_EXPECT(
4354 env.balance(d.depositor, d.shares) == d.share(875 - 38));
4355 BEAST_EXPECT(
4356 env.balance(d.depositor, d.assets) ==
4357 STAmount(d.asset, start));
4358 BEAST_EXPECT(
4359 env.balance(d.vaultAccount, d.assets) ==
4360 STAmount(d.asset, Number(875 - 38, -1)));
4361 BEAST_EXPECT(
4362 env.balance(d.vaultAccount, d.shares) ==
4363 STAmount(d.share, -Number(875 - 38, 0)));
4364 }
4365
4366 {
4367 testcase("Scale clawback with rounding shares down");
4368 // assetsToSharesWithdraw:
4369 // shares = sharesTotal * (assets / assetsTotal)
4370 // shares = 837 * 3.72 / 83.7 = 837 * 0.04444... = 37.2
4371 // sharesToAssetsWithdraw:
4372 // assets = assetsTotal * (shares / sharesTotal)
4373 // assets = 83.7 * 37 / 837 = 83.7 * 0.044205... = 3.7
4374
4375 auto const start = env.balance(d.depositor, d.assets).number();
4376 auto tx = d.vault.clawback(
4377 {.issuer = d.issuer,
4378 .id = d.keylet.key,
4379 .holder = d.depositor,
4380 .amount = STAmount(d.asset, Number(372, -2))});
4381 env(tx);
4382 env.close();
4383 BEAST_EXPECT(
4384 env.balance(d.depositor, d.shares) == d.share(837 - 37));
4385 BEAST_EXPECT(
4386 env.balance(d.depositor, d.assets) ==
4387 STAmount(d.asset, start));
4388 BEAST_EXPECT(
4389 env.balance(d.vaultAccount, d.assets) ==
4390 STAmount(d.asset, Number(837 - 37, -1)));
4391 BEAST_EXPECT(
4392 env.balance(d.vaultAccount, d.shares) ==
4393 STAmount(d.share, -Number(837 - 37, 0)));
4394 }
4395
4396 {
4397 testcase("Scale clawback tiny amount");
4398
4399 auto const start = env.balance(d.depositor, d.assets).number();
4400 auto tx = d.vault.clawback(
4401 {.issuer = d.issuer,
4402 .id = d.keylet.key,
4403 .holder = d.depositor,
4404 .amount = STAmount(d.asset, Number(9, -2))});
4405 env(tx);
4406 env.close();
4407 BEAST_EXPECT(
4408 env.balance(d.depositor, d.shares) == d.share(800 - 1));
4409 BEAST_EXPECT(
4410 env.balance(d.depositor, d.assets) ==
4411 STAmount(d.asset, start));
4412 BEAST_EXPECT(
4413 env.balance(d.vaultAccount, d.assets) ==
4414 STAmount(d.asset, Number(800 - 1, -1)));
4415 BEAST_EXPECT(
4416 env.balance(d.vaultAccount, d.shares) ==
4417 STAmount(d.share, -Number(800 - 1, 0)));
4418 }
4419
4420 {
4421 testcase("Scale clawback rest");
4422 auto const rest =
4423 env.balance(d.vaultAccount, d.assets).number();
4424 d.peek([](SLE& vault, auto&) -> bool {
4425 vault[sfAssetsAvailable] = Number(5);
4426 return true;
4427 });
4428
4429 // Note, this transaction yields two different results:
4430 // * in the open ledger, with AssetsAvailable = 5
4431 // * when the ledger is closed with unmodified AssetsAvailable
4432 // because a modification like above is not persistent.
4433 tx = d.vault.clawback(
4434 {.issuer = d.issuer,
4435 .id = d.keylet.key,
4436 .holder = d.depositor,
4437 .amount = STAmount(d.asset, rest)});
4438 env(tx);
4439 env.close();
4440 BEAST_EXPECT(env.balance(d.depositor, d.shares).number() == 0);
4441 BEAST_EXPECT(
4442 env.balance(d.vaultAccount, d.assets).number() == 0);
4443 BEAST_EXPECT(
4444 env.balance(d.vaultAccount, d.shares).number() == 0);
4445 }
4446 });
4447 }
4448
4449 void
4451 {
4452 using namespace test::jtx;
4453
4454 testcase("RPC");
4455 Env env{*this, testable_amendments() | featureSingleAssetVault};
4456 Account const owner{"owner"};
4457 Account const issuer{"issuer"};
4458 Vault vault{env};
4459 env.fund(XRP(1000), issuer, owner);
4460 env.close();
4461
4462 PrettyAsset asset = issuer["IOU"];
4463 env.trust(asset(1000), owner);
4464 env(pay(issuer, owner, asset(200)));
4465 env.close();
4466
4467 auto const sequence = env.seq(owner);
4468 auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
4469 env(tx);
4470 env.close();
4471
4472 // Set some fields
4473 {
4474 auto tx1 = vault.deposit(
4475 {.depositor = owner, .id = keylet.key, .amount = asset(50)});
4476 env(tx1);
4477
4478 auto tx2 = vault.set({.owner = owner, .id = keylet.key});
4479 tx2[sfAssetsMaximum] = asset(1000).number();
4480 env(tx2);
4481 env.close();
4482 }
4483
4484 auto const sleVault = [&env, keylet = keylet, this]() {
4485 auto const vault = env.le(keylet);
4486 BEAST_EXPECT(vault != nullptr);
4487 return vault;
4488 }();
4489
4490 auto const check = [&, keylet = keylet, sle = sleVault, this](
4491 Json::Value const& vault,
4492 Json::Value const& issuance = Json::nullValue) {
4493 BEAST_EXPECT(vault.isObject());
4494
4495 constexpr auto checkString =
4496 [](auto& node, SField const& field, std::string v) -> bool {
4497 return node.isMember(field.fieldName) &&
4498 node[field.fieldName].isString() &&
4499 node[field.fieldName] == v;
4500 };
4501 constexpr auto checkObject =
4502 [](auto& node, SField const& field, Json::Value v) -> bool {
4503 return node.isMember(field.fieldName) &&
4504 node[field.fieldName].isObject() &&
4505 node[field.fieldName] == v;
4506 };
4507 constexpr auto checkInt =
4508 [](auto& node, SField const& field, int v) -> bool {
4509 return node.isMember(field.fieldName) &&
4510 ((node[field.fieldName].isInt() &&
4511 node[field.fieldName] == Json::Int(v)) ||
4512 (node[field.fieldName].isUInt() &&
4513 node[field.fieldName] == Json::UInt(v)));
4514 };
4515
4516 BEAST_EXPECT(vault["LedgerEntryType"].asString() == "Vault");
4517 BEAST_EXPECT(vault[jss::index].asString() == strHex(keylet.key));
4518 BEAST_EXPECT(checkInt(vault, sfFlags, 0));
4519 // Ignore all other standard fields, this test doesn't care
4520
4521 BEAST_EXPECT(
4522 checkString(vault, sfAccount, toBase58(sle->at(sfAccount))));
4523 BEAST_EXPECT(
4524 checkObject(vault, sfAsset, to_json(sle->at(sfAsset))));
4525 BEAST_EXPECT(checkString(vault, sfAssetsAvailable, "50"));
4526 BEAST_EXPECT(checkString(vault, sfAssetsMaximum, "1000"));
4527 BEAST_EXPECT(checkString(vault, sfAssetsTotal, "50"));
4528 BEAST_EXPECT(checkString(vault, sfLossUnrealized, "0"));
4529
4530 auto const strShareID = strHex(sle->at(sfShareMPTID));
4531 BEAST_EXPECT(checkString(vault, sfShareMPTID, strShareID));
4532 BEAST_EXPECT(checkString(vault, sfOwner, toBase58(owner.id())));
4533 BEAST_EXPECT(checkInt(vault, sfSequence, sequence));
4534 BEAST_EXPECT(checkInt(
4535 vault, sfWithdrawalPolicy, vaultStrategyFirstComeFirstServe));
4536
4537 if (issuance.isObject())
4538 {
4539 BEAST_EXPECT(
4540 issuance["LedgerEntryType"].asString() ==
4541 "MPTokenIssuance");
4542 BEAST_EXPECT(
4543 issuance[jss::mpt_issuance_id].asString() == strShareID);
4544 BEAST_EXPECT(checkInt(issuance, sfSequence, 1));
4545 BEAST_EXPECT(checkInt(
4546 issuance,
4547 sfFlags,
4549 BEAST_EXPECT(
4550 checkString(issuance, sfOutstandingAmount, "50000000"));
4551 }
4552 };
4553
4554 {
4555 testcase("RPC ledger_entry selected by key");
4556 Json::Value jvParams;
4557 jvParams[jss::ledger_index] = jss::validated;
4558 jvParams[jss::vault] = strHex(keylet.key);
4559 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4560
4561 BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error));
4562 BEAST_EXPECT(jvVault[jss::result].isMember(jss::node));
4563 check(jvVault[jss::result][jss::node]);
4564 }
4565
4566 {
4567 testcase("RPC ledger_entry selected by owner and seq");
4568 Json::Value jvParams;
4569 jvParams[jss::ledger_index] = jss::validated;
4570 jvParams[jss::vault][jss::owner] = owner.human();
4571 jvParams[jss::vault][jss::seq] = sequence;
4572 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4573
4574 BEAST_EXPECT(!jvVault[jss::result].isMember(jss::error));
4575 BEAST_EXPECT(jvVault[jss::result].isMember(jss::node));
4576 check(jvVault[jss::result][jss::node]);
4577 }
4578
4579 {
4580 testcase("RPC ledger_entry cannot find vault by key");
4581 Json::Value jvParams;
4582 jvParams[jss::ledger_index] = jss::validated;
4583 jvParams[jss::vault] = to_string(uint256(42));
4584 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4585 BEAST_EXPECT(
4586 jvVault[jss::result][jss::error].asString() == "entryNotFound");
4587 }
4588
4589 {
4590 testcase("RPC ledger_entry cannot find vault by owner and seq");
4591 Json::Value jvParams;
4592 jvParams[jss::ledger_index] = jss::validated;
4593 jvParams[jss::vault][jss::owner] = issuer.human();
4594 jvParams[jss::vault][jss::seq] = 1'000'000;
4595 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4596 BEAST_EXPECT(
4597 jvVault[jss::result][jss::error].asString() == "entryNotFound");
4598 }
4599
4600 {
4601 testcase("RPC ledger_entry malformed key");
4602 Json::Value jvParams;
4603 jvParams[jss::ledger_index] = jss::validated;
4604 jvParams[jss::vault] = 42;
4605 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4606 BEAST_EXPECT(
4607 jvVault[jss::result][jss::error].asString() ==
4608 "malformedRequest");
4609 }
4610
4611 {
4612 testcase("RPC ledger_entry malformed owner");
4613 Json::Value jvParams;
4614 jvParams[jss::ledger_index] = jss::validated;
4615 jvParams[jss::vault][jss::owner] = 42;
4616 jvParams[jss::vault][jss::seq] = sequence;
4617 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4618 BEAST_EXPECT(
4619 jvVault[jss::result][jss::error].asString() ==
4620 "malformedOwner");
4621 }
4622
4623 {
4624 testcase("RPC ledger_entry malformed seq");
4625 Json::Value jvParams;
4626 jvParams[jss::ledger_index] = jss::validated;
4627 jvParams[jss::vault][jss::owner] = issuer.human();
4628 jvParams[jss::vault][jss::seq] = "foo";
4629 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4630 BEAST_EXPECT(
4631 jvVault[jss::result][jss::error].asString() ==
4632 "malformedRequest");
4633 }
4634
4635 {
4636 testcase("RPC ledger_entry negative seq");
4637 Json::Value jvParams;
4638 jvParams[jss::ledger_index] = jss::validated;
4639 jvParams[jss::vault][jss::owner] = issuer.human();
4640 jvParams[jss::vault][jss::seq] = -1;
4641 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4642 BEAST_EXPECT(
4643 jvVault[jss::result][jss::error].asString() ==
4644 "malformedRequest");
4645 }
4646
4647 {
4648 testcase("RPC ledger_entry oversized seq");
4649 Json::Value jvParams;
4650 jvParams[jss::ledger_index] = jss::validated;
4651 jvParams[jss::vault][jss::owner] = issuer.human();
4652 jvParams[jss::vault][jss::seq] = 1e20;
4653 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4654 BEAST_EXPECT(
4655 jvVault[jss::result][jss::error].asString() ==
4656 "malformedRequest");
4657 }
4658
4659 {
4660 testcase("RPC ledger_entry bool seq");
4661 Json::Value jvParams;
4662 jvParams[jss::ledger_index] = jss::validated;
4663 jvParams[jss::vault][jss::owner] = issuer.human();
4664 jvParams[jss::vault][jss::seq] = true;
4665 auto jvVault = env.rpc("json", "ledger_entry", to_string(jvParams));
4666 BEAST_EXPECT(
4667 jvVault[jss::result][jss::error].asString() ==
4668 "malformedRequest");
4669 }
4670
4671 {
4672 testcase("RPC account_objects");
4673
4674 Json::Value jvParams;
4675 jvParams[jss::account] = owner.human();
4676 jvParams[jss::type] = jss::vault;
4677 auto jv = env.rpc(
4678 "json", "account_objects", to_string(jvParams))[jss::result];
4679
4680 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
4681 check(jv[jss::account_objects][0u]);
4682 }
4683
4684 {
4685 testcase("RPC ledger_data");
4686
4687 Json::Value jvParams;
4688 jvParams[jss::ledger_index] = jss::validated;
4689 jvParams[jss::binary] = false;
4690 jvParams[jss::type] = jss::vault;
4691 Json::Value jv =
4692 env.rpc("json", "ledger_data", to_string(jvParams));
4693 BEAST_EXPECT(jv[jss::result][jss::state].size() == 1);
4694 check(jv[jss::result][jss::state][0u]);
4695 }
4696
4697 {
4698 testcase("RPC vault_info command line");
4699 Json::Value jv =
4700 env.rpc("vault_info", strHex(keylet.key), "validated");
4701
4702 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4703 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4704 check(
4705 jv[jss::result][jss::vault],
4706 jv[jss::result][jss::vault][jss::shares]);
4707 }
4708
4709 {
4710 testcase("RPC vault_info json");
4711 Json::Value jvParams;
4712 jvParams[jss::ledger_index] = jss::validated;
4713 jvParams[jss::vault_id] = strHex(keylet.key);
4714 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4715
4716 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4717 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4718 check(
4719 jv[jss::result][jss::vault],
4720 jv[jss::result][jss::vault][jss::shares]);
4721 }
4722
4723 {
4724 testcase("RPC vault_info invalid vault_id");
4725 Json::Value jvParams;
4726 jvParams[jss::ledger_index] = jss::validated;
4727 jvParams[jss::vault_id] = "foobar";
4728 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4729 BEAST_EXPECT(
4730 jv[jss::result][jss::error].asString() == "malformedRequest");
4731 }
4732
4733 {
4734 testcase("RPC vault_info json invalid index");
4735 Json::Value jvParams;
4736 jvParams[jss::ledger_index] = jss::validated;
4737 jvParams[jss::vault_id] = 0;
4738 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4739 BEAST_EXPECT(
4740 jv[jss::result][jss::error].asString() == "malformedRequest");
4741 }
4742
4743 {
4744 testcase("RPC vault_info json by owner and sequence");
4745 Json::Value jvParams;
4746 jvParams[jss::ledger_index] = jss::validated;
4747 jvParams[jss::owner] = owner.human();
4748 jvParams[jss::seq] = sequence;
4749 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4750
4751 BEAST_EXPECT(!jv[jss::result].isMember(jss::error));
4752 BEAST_EXPECT(jv[jss::result].isMember(jss::vault));
4753 check(
4754 jv[jss::result][jss::vault],
4755 jv[jss::result][jss::vault][jss::shares]);
4756 }
4757
4758 {
4759 testcase("RPC vault_info json malformed sequence");
4760 Json::Value jvParams;
4761 jvParams[jss::ledger_index] = jss::validated;
4762 jvParams[jss::owner] = owner.human();
4763 jvParams[jss::seq] = "foobar";
4764 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4765 BEAST_EXPECT(
4766 jv[jss::result][jss::error].asString() == "malformedRequest");
4767 }
4768
4769 {
4770 testcase("RPC vault_info json invalid sequence");
4771 Json::Value jvParams;
4772 jvParams[jss::ledger_index] = jss::validated;
4773 jvParams[jss::owner] = owner.human();
4774 jvParams[jss::seq] = 0;
4775 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4776 BEAST_EXPECT(
4777 jv[jss::result][jss::error].asString() == "malformedRequest");
4778 }
4779
4780 {
4781 testcase("RPC vault_info json negative sequence");
4782 Json::Value jvParams;
4783 jvParams[jss::ledger_index] = jss::validated;
4784 jvParams[jss::owner] = owner.human();
4785 jvParams[jss::seq] = -1;
4786 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4787 BEAST_EXPECT(
4788 jv[jss::result][jss::error].asString() == "malformedRequest");
4789 }
4790
4791 {
4792 testcase("RPC vault_info json oversized sequence");
4793 Json::Value jvParams;
4794 jvParams[jss::ledger_index] = jss::validated;
4795 jvParams[jss::owner] = owner.human();
4796 jvParams[jss::seq] = 1e20;
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 json bool sequence");
4804 Json::Value jvParams;
4805 jvParams[jss::ledger_index] = jss::validated;
4806 jvParams[jss::owner] = owner.human();
4807 jvParams[jss::seq] = true;
4808 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4809 BEAST_EXPECT(
4810 jv[jss::result][jss::error].asString() == "malformedRequest");
4811 }
4812
4813 {
4814 testcase("RPC vault_info json malformed owner");
4815 Json::Value jvParams;
4816 jvParams[jss::ledger_index] = jss::validated;
4817 jvParams[jss::owner] = "foobar";
4818 jvParams[jss::seq] = sequence;
4819 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4820 BEAST_EXPECT(
4821 jv[jss::result][jss::error].asString() == "malformedRequest");
4822 }
4823
4824 {
4825 testcase("RPC vault_info json invalid combination only owner");
4826 Json::Value jvParams;
4827 jvParams[jss::ledger_index] = jss::validated;
4828 jvParams[jss::owner] = owner.human();
4829 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4830 BEAST_EXPECT(
4831 jv[jss::result][jss::error].asString() == "malformedRequest");
4832 }
4833
4834 {
4835 testcase("RPC vault_info json invalid combination only seq");
4836 Json::Value jvParams;
4837 jvParams[jss::ledger_index] = jss::validated;
4838 jvParams[jss::seq] = sequence;
4839 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4840 BEAST_EXPECT(
4841 jv[jss::result][jss::error].asString() == "malformedRequest");
4842 }
4843
4844 {
4845 testcase("RPC vault_info json invalid combination seq vault_id");
4846 Json::Value jvParams;
4847 jvParams[jss::ledger_index] = jss::validated;
4848 jvParams[jss::vault_id] = strHex(keylet.key);
4849 jvParams[jss::seq] = sequence;
4850 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4851 BEAST_EXPECT(
4852 jv[jss::result][jss::error].asString() == "malformedRequest");
4853 }
4854
4855 {
4856 testcase("RPC vault_info json invalid combination owner vault_id");
4857 Json::Value jvParams;
4858 jvParams[jss::ledger_index] = jss::validated;
4859 jvParams[jss::vault_id] = strHex(keylet.key);
4860 jvParams[jss::owner] = owner.human();
4861 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4862 BEAST_EXPECT(
4863 jv[jss::result][jss::error].asString() == "malformedRequest");
4864 }
4865
4866 {
4867 testcase(
4868 "RPC vault_info json invalid combination owner seq "
4869 "vault_id");
4870 Json::Value jvParams;
4871 jvParams[jss::ledger_index] = jss::validated;
4872 jvParams[jss::vault_id] = strHex(keylet.key);
4873 jvParams[jss::seq] = sequence;
4874 jvParams[jss::owner] = owner.human();
4875 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4876 BEAST_EXPECT(
4877 jv[jss::result][jss::error].asString() == "malformedRequest");
4878 }
4879
4880 {
4881 testcase("RPC vault_info json no input");
4882 Json::Value jvParams;
4883 jvParams[jss::ledger_index] = jss::validated;
4884 auto jv = env.rpc("json", "vault_info", to_string(jvParams));
4885 BEAST_EXPECT(
4886 jv[jss::result][jss::error].asString() == "malformedRequest");
4887 }
4888
4889 {
4890 testcase("RPC vault_info command line invalid index");
4891 Json::Value jv = env.rpc("vault_info", "foobar", "validated");
4892 BEAST_EXPECT(jv[jss::error].asString() == "invalidParams");
4893 }
4894
4895 {
4896 testcase("RPC vault_info command line invalid index");
4897 Json::Value jv = env.rpc("vault_info", "0", "validated");
4898 BEAST_EXPECT(
4899 jv[jss::result][jss::error].asString() == "malformedRequest");
4900 }
4901
4902 {
4903 testcase("RPC vault_info command line invalid index");
4904 Json::Value jv =
4905 env.rpc("vault_info", strHex(uint256(42)), "validated");
4906 BEAST_EXPECT(
4907 jv[jss::result][jss::error].asString() == "entryNotFound");
4908 }
4909
4910 {
4911 testcase("RPC vault_info command line invalid ledger");
4912 Json::Value jv = env.rpc("vault_info", strHex(keylet.key), "0");
4913 BEAST_EXPECT(
4914 jv[jss::result][jss::error].asString() == "lgrNotFound");
4915 }
4916 }
4917
4918 void
4920 {
4921 using namespace test::jtx;
4922
4923 Env env(*this, testable_amendments());
4924 Account alice{"alice"};
4925 Account bob{"bob"};
4926 Account carol{"carol"};
4927
4928 struct CaseArgs
4929 {
4930 PrettyAsset asset = xrpIssue();
4931 };
4932
4933 auto const xrpBalance =
4934 [this](
4935 Env const& env, Account const& account) -> std::optional<long> {
4936 auto sle = env.le(keylet::account(account.id()));
4937 if (BEAST_EXPECT(sle != nullptr))
4938 return sle->getFieldAmount(sfBalance).xrp().drops();
4939 return std::nullopt;
4940 };
4941
4942 auto testCase = [&, this](auto test, CaseArgs args = {}) {
4943 Env env{*this, testable_amendments() | featureSingleAssetVault};
4944
4945 Vault vault{env};
4946
4947 // use different initial amount to distinguish the source balance
4948 env.fund(XRP(10000), alice);
4949 env.fund(XRP(20000), bob);
4950 env.fund(XRP(30000), carol);
4951 env.close();
4952
4953 env(delegate::set(
4954 carol,
4955 alice,
4956 {"Payment",
4957 "VaultCreate",
4958 "VaultSet",
4959 "VaultDelete",
4960 "VaultDeposit",
4961 "VaultWithdraw",
4962 "VaultClawback"}));
4963
4964 test(env, vault, args.asset);
4965 };
4966
4967 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
4968 testcase("delegated vault creation");
4969 auto startBalance = xrpBalance(env, carol);
4970 if (!BEAST_EXPECT(startBalance.has_value()))
4971 return;
4972
4973 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
4974 env(tx, delegate::as(alice));
4975 env.close();
4976 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance);
4977 });
4978
4979 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
4980 testcase("delegated deposit and withdrawal");
4981 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
4982 env(tx);
4983 env.close();
4984
4985 auto const amount = 1513;
4986 auto const baseFee = env.current()->fees().base;
4987
4988 auto startBalance = xrpBalance(env, carol);
4989 if (!BEAST_EXPECT(startBalance.has_value()))
4990 return;
4991
4992 tx = vault.deposit(
4993 {.depositor = carol,
4994 .id = keylet.key,
4995 .amount = asset(amount)});
4996 env(tx, delegate::as(alice));
4997 env.close();
4998 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount);
4999
5000 tx = vault.withdraw(
5001 {.depositor = carol,
5002 .id = keylet.key,
5003 .amount = asset(amount - 1)});
5004 env(tx, delegate::as(alice));
5005 env.close();
5006 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - 1);
5007
5008 tx = vault.withdraw(
5009 {.depositor = carol, .id = keylet.key, .amount = asset(1)});
5010 env(tx);
5011 env.close();
5012 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee);
5013 });
5014
5015 testCase([&, this](Env& env, Vault& vault, PrettyAsset const& asset) {
5016 testcase("delegated withdrawal same as base fee and deletion");
5017 auto [tx, keylet] = vault.create({.owner = carol, .asset = asset});
5018 env(tx);
5019 env.close();
5020
5021 auto const amount = 25537;
5022 auto const baseFee = env.current()->fees().base;
5023
5024 auto startBalance = xrpBalance(env, carol);
5025 if (!BEAST_EXPECT(startBalance.has_value()))
5026 return;
5027
5028 tx = vault.deposit(
5029 {.depositor = carol,
5030 .id = keylet.key,
5031 .amount = asset(amount)});
5032 env(tx);
5033 env.close();
5034 BEAST_EXPECT(
5035 xrpBalance(env, carol) == *startBalance - amount - baseFee);
5036
5037 tx = vault.withdraw(
5038 {.depositor = carol,
5039 .id = keylet.key,
5040 .amount = asset(baseFee)});
5041 env(tx, delegate::as(alice));
5042 env.close();
5043 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - amount);
5044
5045 tx = vault.withdraw(
5046 {.depositor = carol,
5047 .id = keylet.key,
5048 .amount = asset(amount - baseFee)});
5049 env(tx, delegate::as(alice));
5050 env.close();
5051 BEAST_EXPECT(xrpBalance(env, carol) == *startBalance - baseFee);
5052
5053 tx = vault.del({.owner = carol, .id = keylet.key});
5054 env(tx, delegate::as(alice));
5055 env.close();
5056 });
5057 }
5058
5059public:
5060 void
5061 run() override
5062 {
5063 testSequences();
5064 testPreflight();
5068 testWithMPT();
5069 testWithIOU();
5074 testScaleIOU();
5075 testRPC();
5076 testDelegate();
5077 }
5078};
5079
5080BEAST_DEFINE_TESTSUITE_PRIO(Vault, app, ripple, 1);
5081
5082} // namespace ripple
Represents a JSON value.
Definition json_value.h:131
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:20
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 ...