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