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