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