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