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