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