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