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