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