rippled
Loading...
Searching...
No Matches
Invariants_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/AMM.h>
3#include <test/jtx/Env.h>
4
5#include <xrpld/app/tx/apply.h>
6#include <xrpld/app/tx/detail/ApplyContext.h>
7
8#include <xrpl/beast/unit_test/suite.h>
9#include <xrpl/beast/utility/Journal.h>
10#include <xrpl/protocol/AccountID.h>
11#include <xrpl/protocol/Indexes.h>
12#include <xrpl/protocol/InnerObjectFormats.h>
13#include <xrpl/protocol/MPTIssue.h>
14#include <xrpl/protocol/SField.h>
15#include <xrpl/protocol/STLedgerEntry.h>
16#include <xrpl/protocol/STNumber.h>
17#include <xrpl/protocol/TER.h>
18#include <xrpl/protocol/TxFormats.h>
19#include <xrpl/protocol/XRPAmount.h>
20
21#include <boost/algorithm/string/predicate.hpp>
22
23namespace ripple {
24namespace test {
25
27{
28 // The optional Preclose function is used to process additional transactions
29 // on the ledger after creating two accounts, but before closing it, and
30 // before the Precheck function. These should only be valid functions, and
31 // not direct manipulations. Preclose is not commonly used.
32 using Preclose = std::function<bool(
33 test::jtx::Account const& a,
34 test::jtx::Account const& b,
35 test::jtx::Env& env)>;
36
37 // this is common setup/method for running a failing invariant check. The
38 // precheck function is used to manipulate the ApplyContext with view
39 // changes that will cause the check to fail.
40 using Precheck = std::function<bool(
41 test::jtx::Account const& a,
42 test::jtx::Account const& b,
43 ApplyContext& ac)>;
44
61 enum class TxAccount : int { None = 0, A1, A2 };
62 void
64 std::vector<std::string> const& expect_logs,
65 Precheck const& precheck,
67 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
70 Preclose const& preclose = {},
71 TxAccount setTxAccount = TxAccount::None)
72 {
73 using namespace test::jtx;
74 FeatureBitset amendments = testable_amendments() |
75 featureInvariantsV1_1 | featureSingleAssetVault;
76 Env env{*this, amendments};
77
78 Account const A1{"A1"};
79 Account const A2{"A2"};
80 env.fund(XRP(1000), A1, A2);
81 if (preclose)
82 BEAST_EXPECT(preclose(A1, A2, env));
83 env.close();
84
85 OpenView ov{*env.current()};
86 test::StreamSink sink{beast::severities::kWarning};
87 beast::Journal jlog{sink};
88 if (setTxAccount != TxAccount::None)
89 tx.setAccountID(
90 sfAccount, setTxAccount == TxAccount::A1 ? A1.id() : A2.id());
91 ApplyContext ac{
92 env.app(),
93 ov,
94 tx,
96 env.current()->fees().base,
97 tapNONE,
98 jlog};
99
100 BEAST_EXPECT(precheck(A1, A2, ac));
101
102 // invoke check twice to cover tec and tef cases
103 if (!BEAST_EXPECT(ters.size() == 2))
104 return;
105
106 TER terActual = tesSUCCESS;
107 for (TER const& terExpect : ters)
108 {
109 terActual = ac.checkInvariants(terActual, fee);
110 BEAST_EXPECT(terExpect == terActual);
111 auto const messages = sink.messages().str();
112 BEAST_EXPECT(
113 messages.starts_with("Invariant failed:") ||
114 messages.starts_with("Transaction caused an exception"));
115 // std::cerr << messages << '\n';
116 for (auto const& m : expect_logs)
117 {
118 if (messages.find(m) == std::string::npos)
119 {
120 // uncomment if you want to log the invariant failure
121 // std::cerr << " --> " << m << std::endl;
122 fail();
123 }
124 }
125 }
126 }
127
128 void
130 {
131 using namespace test::jtx;
132 testcase << "XRP created";
134 {{"XRP net change was positive: 500"}},
135 [](Account const& A1, Account const&, ApplyContext& ac) {
136 // put a single account in the view and "manufacture" some XRP
137 auto const sle = ac.view().peek(keylet::account(A1.id()));
138 if (!sle)
139 return false;
140 auto amt = sle->getFieldAmount(sfBalance);
141 sle->setFieldAmount(sfBalance, amt + STAmount{500});
142 ac.view().update(sle);
143 return true;
144 });
145 }
146
147 void
149 {
150 using namespace test::jtx;
151 testcase << "account root removed";
152
153 // An account was deleted, but not by an AccountDelete transaction.
155 {{"an account root was deleted"}},
156 [](Account const& A1, Account const&, ApplyContext& ac) {
157 // remove an account from the view
158 auto const sle = ac.view().peek(keylet::account(A1.id()));
159 if (!sle)
160 return false;
161 ac.view().erase(sle);
162 return true;
163 });
164
165 // Successful AccountDelete transaction that didn't delete an account.
166 //
167 // Note that this is a case where a second invocation of the invariant
168 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
169 // After a discussion with the team, we believe that's okay.
171 {{"account deletion succeeded without deleting an account"}},
172 [](Account const&, Account const&, ApplyContext& ac) {
173 return true;
174 },
175 XRPAmount{},
176 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
178
179 // Successful AccountDelete that deleted more than one account.
181 {{"account deletion succeeded but deleted multiple accounts"}},
182 [](Account const& A1, Account const& A2, ApplyContext& ac) {
183 // remove two accounts from the view
184 auto const sleA1 = ac.view().peek(keylet::account(A1.id()));
185 auto const sleA2 = ac.view().peek(keylet::account(A2.id()));
186 if (!sleA1 || !sleA2)
187 return false;
188 ac.view().erase(sleA1);
189 ac.view().erase(sleA2);
190 return true;
191 },
192 XRPAmount{},
193 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
194 }
195
196 void
198 {
199 using namespace test::jtx;
200 testcase << "account root deletion left artifact";
201
202 for (auto const& keyletInfo : directAccountKeylets)
203 {
204 // TODO: Use structured binding once LLVM 16 is the minimum
205 // supported version. See also:
206 // https://github.com/llvm/llvm-project/issues/48582
207 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
208 if (!keyletInfo.includeInTests)
209 continue;
210 auto const& keyletfunc = keyletInfo.function;
211 auto const& type = keyletInfo.expectedLEName;
212
213 using namespace std::string_literals;
214
216 {{"account deletion left behind a "s + type.c_str() +
217 " object"}},
218 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
219 // Add an object to the ledger for account A1, then delete
220 // A1
221 auto const a1 = A1.id();
222 auto const sleA1 = ac.view().peek(keylet::account(a1));
223 if (!sleA1)
224 return false;
225
226 auto const key = std::invoke(keyletfunc, a1);
227 auto const newSLE = std::make_shared<SLE>(key);
228 ac.view().insert(newSLE);
229 ac.view().erase(sleA1);
230
231 return true;
232 },
233 XRPAmount{},
234 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
235 };
236
237 // NFT special case
239 {{"account deletion left behind a NFTokenPage object"}},
240 [&](Account const& A1, Account const&, ApplyContext& ac) {
241 // remove an account from the view
242 auto const sle = ac.view().peek(keylet::account(A1.id()));
243 if (!sle)
244 return false;
245 ac.view().erase(sle);
246 return true;
247 },
248 XRPAmount{},
249 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
251 [&](Account const& A1, Account const&, Env& env) {
252 // Preclose callback to mint the NFT which will be deleted in
253 // the Precheck callback above.
254 env(token::mint(A1));
255
256 return true;
257 });
258
259 // AMM special cases
260 AccountID ammAcctID;
261 uint256 ammKey;
262 Issue ammIssue;
264 {{"account deletion left behind a DirectoryNode object"}},
265 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
266 // Delete the AMM account without cleaning up the directory or
267 // deleting the AMM object
268 auto const sle = ac.view().peek(keylet::account(ammAcctID));
269 if (!sle)
270 return false;
271
272 BEAST_EXPECT(sle->at(~sfAMMID));
273 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
274
275 ac.view().erase(sle);
276
277 return true;
278 },
279 XRPAmount{},
280 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
282 [&](Account const& A1, Account const& A2, Env& env) {
283 // Preclose callback to create the AMM which will be partially
284 // deleted in the Precheck callback above.
285 AMM const amm(env, A1, XRP(100), A1["USD"](50));
286 ammAcctID = amm.ammAccount();
287 ammKey = amm.ammID();
288 ammIssue = amm.lptIssue();
289 return true;
290 });
292 {{"account deletion left behind a AMM object"}},
293 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
294 // Delete all the AMM's trust lines, remove the AMM from the AMM
295 // account's directory (this deletes the directory), and delete
296 // the AMM account. Do not delete the AMM object.
297 auto const sle = ac.view().peek(keylet::account(ammAcctID));
298 if (!sle)
299 return false;
300
301 BEAST_EXPECT(sle->at(~sfAMMID));
302 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
303
304 for (auto const& trustKeylet :
305 {keylet::line(ammAcctID, A1["USD"]),
306 keylet::line(A1, ammIssue)})
307 {
308 if (auto const line = ac.view().peek(trustKeylet); !line)
309 {
310 return false;
311 }
312 else
313 {
314 STAmount const lowLimit = line->at(sfLowLimit);
315 STAmount const highLimit = line->at(sfHighLimit);
316 BEAST_EXPECT(
318 ac.view(),
319 line,
320 lowLimit.getIssuer(),
321 highLimit.getIssuer(),
322 ac.journal) == tesSUCCESS);
323 }
324 }
325
326 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
327 if (!BEAST_EXPECT(ammSle))
328 return false;
329 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
330
331 BEAST_EXPECT(ac.view().dirRemove(
332 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
333 BEAST_EXPECT(
334 !ac.view().exists(ownerDirKeylet) ||
335 ac.view().emptyDirDelete(ownerDirKeylet));
336
337 ac.view().erase(sle);
338
339 return true;
340 },
341 XRPAmount{},
342 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
344 [&](Account const& A1, Account const& A2, Env& env) {
345 // Preclose callback to create the AMM which will be partially
346 // deleted in the Precheck callback above.
347 AMM const amm(env, A1, XRP(100), A1["USD"](50));
348 ammAcctID = amm.ammAccount();
349 ammKey = amm.ammID();
350 ammIssue = amm.lptIssue();
351 return true;
352 });
353 }
354
355 void
357 {
358 using namespace test::jtx;
359 testcase << "ledger entry types don't match";
361 {{"ledger entry type mismatch"},
362 {"XRP net change of -1000000000 doesn't match fee 0"}},
363 [](Account const& A1, Account const&, ApplyContext& ac) {
364 // replace an entry in the table with an SLE of a different type
365 auto const sle = ac.view().peek(keylet::account(A1.id()));
366 if (!sle)
367 return false;
368 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
369 ac.rawView().rawReplace(sleNew);
370 return true;
371 });
372
374 {{"invalid ledger entry type added"}},
375 [](Account const& A1, Account const&, ApplyContext& ac) {
376 // add an entry in the table with an SLE of an invalid type
377 auto const sle = ac.view().peek(keylet::account(A1.id()));
378 if (!sle)
379 return false;
380
381 // make a dummy escrow ledger entry, then change the type to an
382 // unsupported value so that the valid type invariant check
383 // will fail.
384 auto const sleNew = std::make_shared<SLE>(
385 keylet::escrow(A1, (*sle)[sfSequence] + 2));
386
387 // We don't use ltNICKNAME directly since it's marked deprecated
388 // to prevent accidental use elsewhere.
389 sleNew->type_ = static_cast<LedgerEntryType>('n');
390 ac.view().insert(sleNew);
391 return true;
392 });
393 }
394
395 void
397 {
398 using namespace test::jtx;
399 testcase << "trust lines with XRP not allowed";
401 {{"an XRP trust line was created"}},
402 [](Account const& A1, Account const& A2, ApplyContext& ac) {
403 // create simple trust SLE with xrp currency
404 auto const sleNew = std::make_shared<SLE>(
405 keylet::line(A1, A2, xrpIssue().currency));
406 ac.view().insert(sleNew);
407 return true;
408 });
409 }
410
411 void
413 {
414 using namespace test::jtx;
415 testcase << "trust lines with deep freeze flag without freeze "
416 "not allowed";
418 {{"a trust line with deep freeze flag without normal freeze was "
419 "created"}},
420 [](Account const& A1, Account const& A2, ApplyContext& ac) {
421 auto const sleNew = std::make_shared<SLE>(
422 keylet::line(A1, A2, A1["USD"].currency));
423 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
424 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
425
426 std::uint32_t uFlags = 0u;
427 uFlags |= lsfLowDeepFreeze;
428 sleNew->setFieldU32(sfFlags, uFlags);
429 ac.view().insert(sleNew);
430 return true;
431 });
432
434 {{"a trust line with deep freeze flag without normal freeze was "
435 "created"}},
436 [](Account const& A1, Account const& A2, ApplyContext& ac) {
437 auto const sleNew = std::make_shared<SLE>(
438 keylet::line(A1, A2, A1["USD"].currency));
439 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
440 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
441 std::uint32_t uFlags = 0u;
442 uFlags |= lsfHighDeepFreeze;
443 sleNew->setFieldU32(sfFlags, uFlags);
444 ac.view().insert(sleNew);
445 return true;
446 });
447
449 {{"a trust line with deep freeze flag without normal freeze was "
450 "created"}},
451 [](Account const& A1, Account const& A2, ApplyContext& ac) {
452 auto const sleNew = std::make_shared<SLE>(
453 keylet::line(A1, A2, A1["USD"].currency));
454 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
455 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
456 std::uint32_t uFlags = 0u;
458 sleNew->setFieldU32(sfFlags, uFlags);
459 ac.view().insert(sleNew);
460 return true;
461 });
462
464 {{"a trust line with deep freeze flag without normal freeze was "
465 "created"}},
466 [](Account const& A1, Account const& A2, ApplyContext& ac) {
467 auto const sleNew = std::make_shared<SLE>(
468 keylet::line(A1, A2, A1["USD"].currency));
469 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
470 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
471 std::uint32_t uFlags = 0u;
473 sleNew->setFieldU32(sfFlags, uFlags);
474 ac.view().insert(sleNew);
475 return true;
476 });
477
479 {{"a trust line with deep freeze flag without normal freeze was "
480 "created"}},
481 [](Account const& A1, Account const& A2, ApplyContext& ac) {
482 auto const sleNew = std::make_shared<SLE>(
483 keylet::line(A1, A2, A1["USD"].currency));
484 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
485 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
486 std::uint32_t uFlags = 0u;
488 sleNew->setFieldU32(sfFlags, uFlags);
489 ac.view().insert(sleNew);
490 return true;
491 });
492 }
493
494 void
496 {
497 using namespace test::jtx;
498 testcase << "transfers when frozen";
499
500 Account G1{"G1"};
501 // Helper function to establish the trustlines
502 auto const createTrustlines =
503 [&](Account const& A1, Account const& A2, Env& env) {
504 // Preclose callback to establish trust lines with gateway
505 env.fund(XRP(1000), G1);
506
507 env.trust(G1["USD"](10000), A1);
508 env.trust(G1["USD"](10000), A2);
509 env.close();
510
511 env(pay(G1, A1, G1["USD"](1000)));
512 env(pay(G1, A2, G1["USD"](1000)));
513 env.close();
514
515 return true;
516 };
517
518 auto const A1FrozenByIssuer =
519 [&](Account const& A1, Account const& A2, Env& env) {
520 createTrustlines(A1, A2, env);
521 env(trust(G1, A1["USD"](10000), tfSetFreeze));
522 env.close();
523
524 return true;
525 };
526
527 auto const A1DeepFrozenByIssuer =
528 [&](Account const& A1, Account const& A2, Env& env) {
529 A1FrozenByIssuer(A1, A2, env);
530 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
531 env.close();
532
533 return true;
534 };
535
536 auto const changeBalances = [&](Account const& A1,
537 Account const& A2,
538 ApplyContext& ac,
539 int A1Balance,
540 int A2Balance) {
541 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
542 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
543
544 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
545 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
546
547 ac.view().update(sleA1);
548 ac.view().update(sleA2);
549 };
550
551 // test: imitating frozen A1 making a payment to A2.
553 {{"Attempting to move frozen funds"}},
554 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
555 changeBalances(A1, A2, ac, -900, -1100);
556 return true;
557 },
558 XRPAmount{},
559 STTx{ttPAYMENT, [](STObject& tx) {}},
561 A1FrozenByIssuer);
562
563 // test: imitating deep frozen A1 making a payment to A2.
565 {{"Attempting to move frozen funds"}},
566 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
567 changeBalances(A1, A2, ac, -900, -1100);
568 return true;
569 },
570 XRPAmount{},
571 STTx{ttPAYMENT, [](STObject& tx) {}},
573 A1DeepFrozenByIssuer);
574
575 // test: imitating A2 making a payment to deep frozen A1.
577 {{"Attempting to move frozen funds"}},
578 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
579 changeBalances(A1, A2, ac, -1100, -900);
580 return true;
581 },
582 XRPAmount{},
583 STTx{ttPAYMENT, [](STObject& tx) {}},
585 A1DeepFrozenByIssuer);
586 }
587
588 void
590 {
591 using namespace test::jtx;
592 testcase << "XRP balance checks";
593
595 {{"Cannot return non-native STAmount as XRPAmount"}},
596 [](Account const& A1, Account const& A2, ApplyContext& ac) {
597 // non-native balance
598 auto const sle = ac.view().peek(keylet::account(A1.id()));
599 if (!sle)
600 return false;
601 STAmount const nonNative(A2["USD"](51));
602 sle->setFieldAmount(sfBalance, nonNative);
603 ac.view().update(sle);
604 return true;
605 });
606
608 {{"incorrect account XRP balance"},
609 {"XRP net change was positive: 99999999000000001"}},
610 [this](Account const& A1, Account const&, ApplyContext& ac) {
611 // balance exceeds genesis amount
612 auto const sle = ac.view().peek(keylet::account(A1.id()));
613 if (!sle)
614 return false;
615 // Use `drops(1)` to bypass a call to STAmount::canonicalize
616 // with an invalid value
617 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
618 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
619 ac.view().update(sle);
620 return true;
621 });
622
624 {{"incorrect account XRP balance"},
625 {"XRP net change of -1000000001 doesn't match fee 0"}},
626 [this](Account const& A1, Account const&, ApplyContext& ac) {
627 // balance is negative
628 auto const sle = ac.view().peek(keylet::account(A1.id()));
629 if (!sle)
630 return false;
631 sle->setFieldAmount(sfBalance, STAmount{1, true});
632 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
633 ac.view().update(sle);
634 return true;
635 });
636 }
637
638 void
640 {
641 using namespace test::jtx;
642 using namespace std::string_literals;
643 testcase << "Transaction fee checks";
644
646 {{"fee paid was negative: -1"},
647 {"XRP net change of 0 doesn't match fee -1"}},
648 [](Account const&, Account const&, ApplyContext&) { return true; },
649 XRPAmount{-1});
650
652 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
653 {"XRP net change of 0 doesn't match fee "s +
655 [](Account const&, Account const&, ApplyContext&) { return true; },
657
659 {{"fee paid is 20 exceeds fee specified in transaction."},
660 {"XRP net change of 0 doesn't match fee 20"}},
661 [](Account const&, Account const&, ApplyContext&) { return true; },
662 XRPAmount{20},
663 STTx{ttACCOUNT_SET, [](STObject& tx) {
664 tx.setFieldAmount(sfFee, XRPAmount{10});
665 }});
666 }
667
668 void
670 {
671 using namespace test::jtx;
672 testcase << "no bad offers";
673
675 {{"offer with a bad amount"}},
676 [](Account const& A1, Account const&, ApplyContext& ac) {
677 // offer with negative takerpays
678 auto const sle = ac.view().peek(keylet::account(A1.id()));
679 if (!sle)
680 return false;
681 auto sleNew = std::make_shared<SLE>(
682 keylet::offer(A1.id(), (*sle)[sfSequence]));
683 sleNew->setAccountID(sfAccount, A1.id());
684 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
685 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
686 ac.view().insert(sleNew);
687 return true;
688 });
689
691 {{"offer with a bad amount"}},
692 [](Account const& A1, Account const&, ApplyContext& ac) {
693 // offer with negative takergets
694 auto const sle = ac.view().peek(keylet::account(A1.id()));
695 if (!sle)
696 return false;
697 auto sleNew = std::make_shared<SLE>(
698 keylet::offer(A1.id(), (*sle)[sfSequence]));
699 sleNew->setAccountID(sfAccount, A1.id());
700 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
701 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
702 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
703 ac.view().insert(sleNew);
704 return true;
705 });
706
708 {{"offer with a bad amount"}},
709 [](Account const& A1, Account const&, ApplyContext& ac) {
710 // offer XRP to XRP
711 auto const sle = ac.view().peek(keylet::account(A1.id()));
712 if (!sle)
713 return false;
714 auto sleNew = std::make_shared<SLE>(
715 keylet::offer(A1.id(), (*sle)[sfSequence]));
716 sleNew->setAccountID(sfAccount, A1.id());
717 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
718 sleNew->setFieldAmount(sfTakerPays, XRP(10));
719 sleNew->setFieldAmount(sfTakerGets, XRP(11));
720 ac.view().insert(sleNew);
721 return true;
722 });
723 }
724
725 void
727 {
728 using namespace test::jtx;
729 testcase << "no zero escrow";
730
732 {{"XRP net change of -1000000 doesn't match fee 0"},
733 {"escrow specifies invalid amount"}},
734 [](Account const& A1, Account const&, ApplyContext& ac) {
735 // escrow with negative amount
736 auto const sle = ac.view().peek(keylet::account(A1.id()));
737 if (!sle)
738 return false;
739 auto sleNew = std::make_shared<SLE>(
740 keylet::escrow(A1, (*sle)[sfSequence] + 2));
741 sleNew->setFieldAmount(sfAmount, XRP(-1));
742 ac.view().insert(sleNew);
743 return true;
744 });
745
747 {{"XRP net change was positive: 100000000000000001"},
748 {"escrow specifies invalid amount"}},
749 [](Account const& A1, Account const&, ApplyContext& ac) {
750 // escrow with too-large amount
751 auto const sle = ac.view().peek(keylet::account(A1.id()));
752 if (!sle)
753 return false;
754 auto sleNew = std::make_shared<SLE>(
755 keylet::escrow(A1, (*sle)[sfSequence] + 2));
756 // Use `drops(1)` to bypass a call to STAmount::canonicalize
757 // with an invalid value
758 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
759 ac.view().insert(sleNew);
760 return true;
761 });
762
763 // IOU < 0
765 {{"escrow specifies invalid amount"}},
766 [](Account const& A1, Account const&, ApplyContext& ac) {
767 // escrow with too-little iou
768 auto const sle = ac.view().peek(keylet::account(A1.id()));
769 if (!sle)
770 return false;
771 auto sleNew = std::make_shared<SLE>(
772 keylet::escrow(A1, (*sle)[sfSequence] + 2));
773
774 Issue const usd{
775 Currency(0x5553440000000000), AccountID(0x4985601)};
776 STAmount amt(usd, -1);
777 sleNew->setFieldAmount(sfAmount, amt);
778 ac.view().insert(sleNew);
779 return true;
780 });
781
782 // IOU bad currency
784 {{"escrow specifies invalid amount"}},
785 [](Account const& A1, Account const&, ApplyContext& ac) {
786 // escrow with bad iou currency
787 auto const sle = ac.view().peek(keylet::account(A1.id()));
788 if (!sle)
789 return false;
790 auto sleNew = std::make_shared<SLE>(
791 keylet::escrow(A1, (*sle)[sfSequence] + 2));
792
793 Issue const bad{badCurrency(), AccountID(0x4985601)};
794 STAmount amt(bad, 1);
795 sleNew->setFieldAmount(sfAmount, amt);
796 ac.view().insert(sleNew);
797 return true;
798 });
799
800 // MPT < 0
802 {{"escrow specifies invalid amount"}},
803 [](Account const& A1, Account const&, ApplyContext& ac) {
804 // escrow with too-little mpt
805 auto const sle = ac.view().peek(keylet::account(A1.id()));
806 if (!sle)
807 return false;
808 auto sleNew = std::make_shared<SLE>(
809 keylet::escrow(A1, (*sle)[sfSequence] + 2));
810
811 MPTIssue const mpt{
812 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
813 STAmount amt(mpt, -1);
814 sleNew->setFieldAmount(sfAmount, amt);
815 ac.view().insert(sleNew);
816 return true;
817 });
818
819 // MPT OutstandingAmount < 0
821 {{"escrow specifies invalid amount"}},
822 [](Account const& A1, Account const&, ApplyContext& ac) {
823 // mpissuance outstanding is negative
824 auto const sle = ac.view().peek(keylet::account(A1.id()));
825 if (!sle)
826 return false;
827
828 MPTIssue const mpt{
829 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
830 auto sleNew =
832 sleNew->setFieldU64(sfOutstandingAmount, -1);
833 ac.view().insert(sleNew);
834 return true;
835 });
836
837 // MPT LockedAmount < 0
839 {{"escrow specifies invalid amount"}},
840 [](Account const& A1, Account const&, ApplyContext& ac) {
841 // mpissuance locked is less than locked
842 auto const sle = ac.view().peek(keylet::account(A1.id()));
843 if (!sle)
844 return false;
845
846 MPTIssue const mpt{
847 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
848 auto sleNew =
850 sleNew->setFieldU64(sfLockedAmount, -1);
851 ac.view().insert(sleNew);
852 return true;
853 });
854
855 // MPT OutstandingAmount < LockedAmount
857 {{"escrow specifies invalid amount"}},
858 [](Account const& A1, Account const&, ApplyContext& ac) {
859 // mpissuance outstanding is less than locked
860 auto const sle = ac.view().peek(keylet::account(A1.id()));
861 if (!sle)
862 return false;
863
864 MPTIssue const mpt{
865 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
866 auto sleNew =
868 sleNew->setFieldU64(sfOutstandingAmount, 1);
869 sleNew->setFieldU64(sfLockedAmount, 10);
870 ac.view().insert(sleNew);
871 return true;
872 });
873
874 // MPT MPTAmount < 0
876 {{"escrow specifies invalid amount"}},
877 [](Account const& A1, Account const&, ApplyContext& ac) {
878 // mptoken amount is negative
879 auto const sle = ac.view().peek(keylet::account(A1.id()));
880 if (!sle)
881 return false;
882
883 MPTIssue const mpt{
884 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
885 auto sleNew =
886 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
887 sleNew->setFieldU64(sfMPTAmount, -1);
888 ac.view().insert(sleNew);
889 return true;
890 });
891
892 // MPT LockedAmount < 0
894 {{"escrow specifies invalid amount"}},
895 [](Account const& A1, Account const&, ApplyContext& ac) {
896 // mptoken locked amount is negative
897 auto const sle = ac.view().peek(keylet::account(A1.id()));
898 if (!sle)
899 return false;
900
901 MPTIssue const mpt{
902 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
903 auto sleNew =
904 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
905 sleNew->setFieldU64(sfLockedAmount, -1);
906 ac.view().insert(sleNew);
907 return true;
908 });
909 }
910
911 void
913 {
914 using namespace test::jtx;
915 testcase << "valid new account root";
916
918 {{"account root created illegally"}},
919 [](Account const&, Account const&, ApplyContext& ac) {
920 // Insert a new account root created by a non-payment into
921 // the view.
922 Account const A3{"A3"};
923 Keylet const acctKeylet = keylet::account(A3);
924 auto const sleNew = std::make_shared<SLE>(acctKeylet);
925 ac.view().insert(sleNew);
926 return true;
927 });
928
930 {{"multiple accounts created in a single transaction"}},
931 [](Account const&, Account const&, ApplyContext& ac) {
932 // Insert two new account roots into the view.
933 {
934 Account const A3{"A3"};
935 Keylet const acctKeylet = keylet::account(A3);
936 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
937 ac.view().insert(sleA3);
938 }
939 {
940 Account const A4{"A4"};
941 Keylet const acctKeylet = keylet::account(A4);
942 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
943 ac.view().insert(sleA4);
944 }
945 return true;
946 });
947
949 {{"account created with wrong starting sequence number"}},
950 [](Account const&, Account const&, ApplyContext& ac) {
951 // Insert a new account root with the wrong starting sequence.
952 Account const A3{"A3"};
953 Keylet const acctKeylet = keylet::account(A3);
954 auto const sleNew = std::make_shared<SLE>(acctKeylet);
955 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
956 ac.view().insert(sleNew);
957 return true;
958 },
959 XRPAmount{},
960 STTx{ttPAYMENT, [](STObject& tx) {}});
961
963 {{"pseudo-account created by a wrong transaction type"}},
964 [](Account const&, Account const&, ApplyContext& ac) {
965 Account const A3{"A3"};
966 Keylet const acctKeylet = keylet::account(A3);
967 auto const sleNew = std::make_shared<SLE>(acctKeylet);
968 sleNew->setFieldU32(sfSequence, 0);
969 sleNew->setFieldH256(sfAMMID, uint256(1));
970 sleNew->setFieldU32(
971 sfFlags,
973 ac.view().insert(sleNew);
974 return true;
975 },
976 XRPAmount{},
977 STTx{ttPAYMENT, [](STObject& tx) {}});
978
980 {{"account created with wrong starting sequence number"}},
981 [](Account const&, Account const&, ApplyContext& ac) {
982 Account const A3{"A3"};
983 Keylet const acctKeylet = keylet::account(A3);
984 auto const sleNew = std::make_shared<SLE>(acctKeylet);
985 sleNew->setFieldU32(sfSequence, ac.view().seq());
986 sleNew->setFieldH256(sfAMMID, uint256(1));
987 sleNew->setFieldU32(
988 sfFlags,
990 ac.view().insert(sleNew);
991 return true;
992 },
993 XRPAmount{},
994 STTx{ttAMM_CREATE, [](STObject& tx) {}});
995
997 {{"pseudo-account created with wrong flags"}},
998 [](Account const&, Account const&, ApplyContext& ac) {
999 Account const A3{"A3"};
1000 Keylet const acctKeylet = keylet::account(A3);
1001 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1002 sleNew->setFieldU32(sfSequence, 0);
1003 sleNew->setFieldH256(sfAMMID, uint256(1));
1004 sleNew->setFieldU32(
1006 ac.view().insert(sleNew);
1007 return true;
1008 },
1009 XRPAmount{},
1010 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
1011
1013 {{"pseudo-account created with wrong flags"}},
1014 [](Account const&, Account const&, ApplyContext& ac) {
1015 Account const A3{"A3"};
1016 Keylet const acctKeylet = keylet::account(A3);
1017 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1018 sleNew->setFieldU32(sfSequence, 0);
1019 sleNew->setFieldH256(sfAMMID, uint256(1));
1020 sleNew->setFieldU32(
1021 sfFlags,
1024 ac.view().insert(sleNew);
1025 return true;
1026 },
1027 XRPAmount{},
1028 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1029 }
1030
1031 void
1033 {
1034 using namespace test::jtx;
1035 testcase << "NFTokenPage";
1036
1037 // lambda that returns an STArray of NFTokenIDs.
1038 uint256 const firstNFTID(
1039 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
1040 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
1041 SOTemplate const* nfTokenTemplate =
1043 sfNFToken);
1044
1045 uint256 nftID(firstNFTID);
1046 STArray ret;
1047 for (int i = 0; i < nftCount; ++i)
1048 {
1049 STObject newNFToken(
1050 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
1051 object.setFieldH256(sfNFTokenID, nftID);
1052 });
1053 ret.push_back(std::move(newNFToken));
1054 ++nftID;
1055 }
1056 return ret;
1057 };
1058
1060 {{"NFT page has invalid size"}},
1061 [&makeNFTokenIDs](
1062 Account const& A1, Account const&, ApplyContext& ac) {
1064 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
1065
1066 ac.view().insert(nftPage);
1067 return true;
1068 });
1069
1071 {{"NFT page has invalid size"}},
1072 [&makeNFTokenIDs](
1073 Account const& A1, Account const&, ApplyContext& ac) {
1075 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
1076
1077 ac.view().insert(nftPage);
1078 return true;
1079 });
1080
1082 {{"NFTs on page are not sorted"}},
1083 [&makeNFTokenIDs](
1084 Account const& A1, Account const&, ApplyContext& ac) {
1085 STArray nfTokens = makeNFTokenIDs(2);
1086 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
1087
1089 nftPage->setFieldArray(sfNFTokens, nfTokens);
1090
1091 ac.view().insert(nftPage);
1092 return true;
1093 });
1094
1096 {{"NFT contains empty URI"}},
1097 [&makeNFTokenIDs](
1098 Account const& A1, Account const&, ApplyContext& ac) {
1099 STArray nfTokens = makeNFTokenIDs(1);
1100 nfTokens[0].setFieldVL(sfURI, Blob{});
1101
1103 nftPage->setFieldArray(sfNFTokens, nfTokens);
1104
1105 ac.view().insert(nftPage);
1106 return true;
1107 });
1108
1110 {{"NFT page is improperly linked"}},
1111 [&makeNFTokenIDs](
1112 Account const& A1, Account const&, ApplyContext& ac) {
1114 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1115 nftPage->setFieldH256(
1116 sfPreviousPageMin, keylet::nftpage_max(A1).key);
1117
1118 ac.view().insert(nftPage);
1119 return true;
1120 });
1121
1123 {{"NFT page is improperly linked"}},
1124 [&makeNFTokenIDs](
1125 Account const& A1, Account const& A2, ApplyContext& ac) {
1127 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1128 nftPage->setFieldH256(
1129 sfPreviousPageMin, keylet::nftpage_min(A2).key);
1130
1131 ac.view().insert(nftPage);
1132 return true;
1133 });
1134
1136 {{"NFT page is improperly linked"}},
1137 [&makeNFTokenIDs](
1138 Account const& A1, Account const&, ApplyContext& ac) {
1140 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1141 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1142
1143 ac.view().insert(nftPage);
1144 return true;
1145 });
1146
1148 {{"NFT page is improperly linked"}},
1149 [&makeNFTokenIDs](
1150 Account const& A1, Account const& A2, ApplyContext& ac) {
1151 STArray nfTokens = makeNFTokenIDs(1);
1154 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1155 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1156 nftPage->setFieldH256(
1157 sfNextPageMin, keylet::nftpage_max(A2).key);
1158
1159 ac.view().insert(nftPage);
1160 return true;
1161 });
1162
1164 {{"NFT found in incorrect page"}},
1165 [&makeNFTokenIDs](
1166 Account const& A1, Account const&, ApplyContext& ac) {
1167 STArray nfTokens = makeNFTokenIDs(2);
1170 (nfTokens[1].getFieldH256(sfNFTokenID))));
1171 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1172
1173 ac.view().insert(nftPage);
1174 return true;
1175 });
1176 }
1177
1178 void
1180 ApplyContext& ac,
1182 test::jtx::Account const& A1,
1183 test::jtx::Account const& A2)
1184 {
1185 sle->setAccountID(sfOwner, A1);
1186 sle->setFieldU32(sfSequence, 10);
1187
1188 STArray credentials(sfAcceptedCredentials, 2);
1189 for (std::size_t n = 0; n < 2; ++n)
1190 {
1191 auto cred = STObject::makeInnerObject(sfCredential);
1192 cred.setAccountID(sfIssuer, A2);
1193 auto credType = "cred_type" + std::to_string(n);
1194 cred.setFieldVL(
1195 sfCredentialType, Slice(credType.c_str(), credType.size()));
1196 credentials.push_back(std::move(cred));
1197 }
1198 sle->setFieldArray(sfAcceptedCredentials, credentials);
1199 ac.view().insert(sle);
1200 };
1201
1202 void
1204 {
1205 using namespace test::jtx;
1206
1207 testcase << "PermissionedDomain";
1209 {{"permissioned domain with no rules."}},
1210 [](Account const& A1, Account const&, ApplyContext& ac) {
1211 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1212 auto slePd = std::make_shared<SLE>(pdKeylet);
1213 slePd->setAccountID(sfOwner, A1);
1214 slePd->setFieldU32(sfSequence, 10);
1215
1216 ac.view().insert(slePd);
1217 return true;
1218 },
1219 XRPAmount{},
1220 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1222
1223 testcase << "PermissionedDomain 2";
1224
1225 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1227 {{"permissioned domain bad credentials size " +
1228 std::to_string(tooBig)}},
1229 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1230 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1231 auto slePd = std::make_shared<SLE>(pdKeylet);
1232 slePd->setAccountID(sfOwner, A1);
1233 slePd->setFieldU32(sfSequence, 10);
1234
1235 STArray credentials(sfAcceptedCredentials, tooBig);
1236 for (std::size_t n = 0; n < tooBig; ++n)
1237 {
1238 auto cred = STObject::makeInnerObject(sfCredential);
1239 cred.setAccountID(sfIssuer, A2);
1240 auto credType =
1241 std::string("cred_type") + std::to_string(n);
1242 cred.setFieldVL(
1243 sfCredentialType,
1244 Slice(credType.c_str(), credType.size()));
1245 credentials.push_back(std::move(cred));
1246 }
1247 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1248 ac.view().insert(slePd);
1249 return true;
1250 },
1251 XRPAmount{},
1252 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1254
1255 testcase << "PermissionedDomain 3";
1257 {{"permissioned domain credentials aren't sorted"}},
1258 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1259 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1260 auto slePd = std::make_shared<SLE>(pdKeylet);
1261 slePd->setAccountID(sfOwner, A1);
1262 slePd->setFieldU32(sfSequence, 10);
1263
1264 STArray credentials(sfAcceptedCredentials, 2);
1265 for (std::size_t n = 0; n < 2; ++n)
1266 {
1267 auto cred = STObject::makeInnerObject(sfCredential);
1268 cred.setAccountID(sfIssuer, A2);
1269 auto credType =
1270 std::string("cred_type") + std::to_string(9 - n);
1271 cred.setFieldVL(
1272 sfCredentialType,
1273 Slice(credType.c_str(), credType.size()));
1274 credentials.push_back(std::move(cred));
1275 }
1276 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1277 ac.view().insert(slePd);
1278 return true;
1279 },
1280 XRPAmount{},
1281 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1283
1284 testcase << "PermissionedDomain 4";
1286 {{"permissioned domain credentials aren't unique"}},
1287 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1288 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1289 auto slePd = std::make_shared<SLE>(pdKeylet);
1290 slePd->setAccountID(sfOwner, A1);
1291 slePd->setFieldU32(sfSequence, 10);
1292
1293 STArray credentials(sfAcceptedCredentials, 2);
1294 for (std::size_t n = 0; n < 2; ++n)
1295 {
1296 auto cred = STObject::makeInnerObject(sfCredential);
1297 cred.setAccountID(sfIssuer, A2);
1298 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1299 credentials.push_back(std::move(cred));
1300 }
1301 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1302 ac.view().insert(slePd);
1303 return true;
1304 },
1305 XRPAmount{},
1306 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1308
1309 testcase << "PermissionedDomain Set 1";
1311 {{"permissioned domain with no rules."}},
1312 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1313 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1314 auto slePd = std::make_shared<SLE>(pdKeylet);
1315
1316 // create PD
1317 createPermissionedDomain(ac, slePd, A1, A2);
1318
1319 // update PD with empty rules
1320 {
1321 STArray credentials(sfAcceptedCredentials, 2);
1322 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1323 ac.view().update(slePd);
1324 }
1325
1326 return true;
1327 },
1328 XRPAmount{},
1329 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1331
1332 testcase << "PermissionedDomain Set 2";
1334 {{"permissioned domain bad credentials size " +
1335 std::to_string(tooBig)}},
1336 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1337 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1338 auto slePd = std::make_shared<SLE>(pdKeylet);
1339
1340 // create PD
1341 createPermissionedDomain(ac, slePd, A1, A2);
1342
1343 // update PD
1344 {
1345 STArray credentials(sfAcceptedCredentials, tooBig);
1346
1347 for (std::size_t n = 0; n < tooBig; ++n)
1348 {
1349 auto cred = STObject::makeInnerObject(sfCredential);
1350 cred.setAccountID(sfIssuer, A2);
1351 auto credType = "cred_type2" + std::to_string(n);
1352 cred.setFieldVL(
1353 sfCredentialType,
1354 Slice(credType.c_str(), credType.size()));
1355 credentials.push_back(std::move(cred));
1356 }
1357
1358 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1359 ac.view().update(slePd);
1360 }
1361
1362 return true;
1363 },
1364 XRPAmount{},
1365 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1367
1368 testcase << "PermissionedDomain Set 3";
1370 {{"permissioned domain credentials aren't sorted"}},
1371 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1372 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1373 auto slePd = std::make_shared<SLE>(pdKeylet);
1374
1375 // create PD
1376 createPermissionedDomain(ac, slePd, A1, A2);
1377
1378 // update PD
1379 {
1380 STArray credentials(sfAcceptedCredentials, 2);
1381 for (std::size_t n = 0; n < 2; ++n)
1382 {
1383 auto cred = STObject::makeInnerObject(sfCredential);
1384 cred.setAccountID(sfIssuer, A2);
1385 auto credType =
1386 std::string("cred_type2") + std::to_string(9 - n);
1387 cred.setFieldVL(
1388 sfCredentialType,
1389 Slice(credType.c_str(), credType.size()));
1390 credentials.push_back(std::move(cred));
1391 }
1392
1393 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1394 ac.view().update(slePd);
1395 }
1396
1397 return true;
1398 },
1399 XRPAmount{},
1400 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1402
1403 testcase << "PermissionedDomain Set 4";
1405 {{"permissioned domain credentials aren't unique"}},
1406 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1407 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1408 auto slePd = std::make_shared<SLE>(pdKeylet);
1409
1410 // create PD
1411 createPermissionedDomain(ac, slePd, A1, A2);
1412
1413 // update PD
1414 {
1415 STArray credentials(sfAcceptedCredentials, 2);
1416 for (std::size_t n = 0; n < 2; ++n)
1417 {
1418 auto cred = STObject::makeInnerObject(sfCredential);
1419 cred.setAccountID(sfIssuer, A2);
1420 cred.setFieldVL(
1421 sfCredentialType, Slice("cred_type", 9));
1422 credentials.push_back(std::move(cred));
1423 }
1424 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1425 ac.view().update(slePd);
1426 }
1427
1428 return true;
1429 },
1430 XRPAmount{},
1431 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1433 }
1434
1435 void
1437 {
1438 testcase << "valid pseudo accounts";
1439
1440 using namespace jtx;
1441
1442 AccountID pseudoAccountID;
1443 Preclose createPseudo =
1444 [&, this](Account const& a, Account const& b, Env& env) {
1445 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1446
1447 // Create vault
1448 Vault vault{env};
1449 auto [tx, vKeylet] =
1450 vault.create({.owner = a, .asset = xrpAsset});
1451 env(tx);
1452 env.close();
1453 if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
1454 {
1455 pseudoAccountID = vSle->at(sfAccount);
1456 }
1457
1458 return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
1459 };
1460
1461 /* Cases to check
1462 "pseudo-account has 0 pseudo-account fields set"
1463 "pseudo-account has 2 pseudo-account fields set"
1464 "pseudo-account sequence changed"
1465 "pseudo-account flags are not set"
1466 "pseudo-account has a regular key"
1467 */
1468 struct Mod
1469 {
1470 std::string expectedFailure;
1471 std::function<void(SLE::pointer&)> func;
1472 };
1473 auto const mods = std::to_array<Mod>({
1474 {
1475 "pseudo-account has 0 pseudo-account fields set",
1476 [this](SLE::pointer& sle) {
1477 BEAST_EXPECT(sle->at(~sfVaultID));
1478 sle->at(~sfVaultID) = std::nullopt;
1479 },
1480 },
1481 {
1482 "pseudo-account sequence changed",
1483 [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
1484 },
1485 {
1486 "pseudo-account flags are not set",
1487 [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
1488 },
1489 {
1490 "pseudo-account has a regular key",
1491 [](SLE::pointer& sle) {
1492 sle->at(sfRegularKey) = Account("regular").id();
1493 },
1494 },
1495 });
1496
1497 for (auto const& mod : mods)
1498 {
1500 {{mod.expectedFailure}},
1501 [&](Account const& A1, Account const&, ApplyContext& ac) {
1502 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1503 if (!sle)
1504 return false;
1505 mod.func(sle);
1506 ac.view().update(sle);
1507 return true;
1508 },
1509 XRPAmount{},
1510 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1512 createPseudo);
1513 }
1514 for (auto const pField : getPseudoAccountFields())
1515 {
1516 // createPseudo creates a vault, so sfVaultID will be set, and
1517 // setting it again will not cause an error
1518 if (pField == &sfVaultID)
1519 continue;
1521 {{"pseudo-account has 2 pseudo-account fields set"}},
1522 [&](Account const& A1, Account const&, ApplyContext& ac) {
1523 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1524 if (!sle)
1525 return false;
1526
1527 auto const vaultID = ~sle->at(~sfVaultID);
1528 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
1529 sle->setFieldH256(*pField, *vaultID);
1530
1531 ac.view().update(sle);
1532 return true;
1533 },
1534 XRPAmount{},
1535 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1537 createPseudo);
1538 }
1539
1540 // Take one of the regular accounts and set the sequence to 0, which
1541 // will make it look like a pseudo-account
1543 {{"pseudo-account has 0 pseudo-account fields set"},
1544 {"pseudo-account sequence changed"},
1545 {"pseudo-account flags are not set"}},
1546 [&](Account const& A1, Account const&, ApplyContext& ac) {
1547 auto sle = ac.view().peek(keylet::account(A1.id()));
1548 if (!sle)
1549 return false;
1550 sle->at(sfSequence) = 0;
1551 ac.view().update(sle);
1552 return true;
1553 });
1554 }
1555
1556 void
1558 {
1559 using namespace test::jtx;
1560 testcase << "PermissionedDEX";
1561
1563 {{"domain doesn't exist"}},
1564 [](Account const& A1, Account const&, ApplyContext& ac) {
1565 Keylet const offerKey = keylet::offer(A1.id(), 10);
1566 auto sleOffer = std::make_shared<SLE>(offerKey);
1567 sleOffer->setAccountID(sfAccount, A1);
1568 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1569 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1570 ac.view().insert(sleOffer);
1571 return true;
1572 },
1573 XRPAmount{},
1574 STTx{
1575 ttOFFER_CREATE,
1576 [](STObject& tx) {
1577 tx.setFieldH256(
1578 sfDomainID,
1579 uint256{
1580 "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1581 "70F3649CE134E5"});
1582 Account const A1{"A1"};
1583 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1584 tx.setFieldAmount(sfTakerGets, XRP(1));
1585 }},
1587
1588 // missing domain ID in offer object
1590 {{"hybrid offer is malformed"}},
1591 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1592 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1593 auto slePd = std::make_shared<SLE>(pdKeylet);
1594 createPermissionedDomain(ac, slePd, A1, A2);
1595
1596 Keylet const offerKey = keylet::offer(A2.id(), 10);
1597 auto sleOffer = std::make_shared<SLE>(offerKey);
1598 sleOffer->setAccountID(sfAccount, A2);
1599 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1600 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1601 sleOffer->setFlag(lsfHybrid);
1602
1603 STArray bookArr;
1604 bookArr.push_back(STObject::makeInnerObject(sfBook));
1605 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1606 ac.view().insert(sleOffer);
1607 return true;
1608 },
1609 XRPAmount{},
1610 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1612
1613 // more than one entry in sfAdditionalBooks
1615 {{"hybrid offer is malformed"}},
1616 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1617 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1618 auto slePd = std::make_shared<SLE>(pdKeylet);
1619 createPermissionedDomain(ac, slePd, A1, A2);
1620
1621 Keylet const offerKey = keylet::offer(A2.id(), 10);
1622 auto sleOffer = std::make_shared<SLE>(offerKey);
1623 sleOffer->setAccountID(sfAccount, A2);
1624 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1625 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1626 sleOffer->setFlag(lsfHybrid);
1627 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1628
1629 STArray bookArr;
1630 bookArr.push_back(STObject::makeInnerObject(sfBook));
1631 bookArr.push_back(STObject::makeInnerObject(sfBook));
1632 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1633 ac.view().insert(sleOffer);
1634 return true;
1635 },
1636 XRPAmount{},
1637 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1639
1640 // hybrid offer missing sfAdditionalBooks
1642 {{"hybrid offer is malformed"}},
1643 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1644 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1645 auto slePd = std::make_shared<SLE>(pdKeylet);
1646 createPermissionedDomain(ac, slePd, A1, A2);
1647
1648 Keylet const offerKey = keylet::offer(A2.id(), 10);
1649 auto sleOffer = std::make_shared<SLE>(offerKey);
1650 sleOffer->setAccountID(sfAccount, A2);
1651 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1652 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1653 sleOffer->setFlag(lsfHybrid);
1654 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1655 ac.view().insert(sleOffer);
1656 return true;
1657 },
1658 XRPAmount{},
1659 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1661
1663 {{"transaction consumed wrong domains"}},
1664 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1665 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1666 auto slePd = std::make_shared<SLE>(pdKeylet);
1667 createPermissionedDomain(ac, slePd, A1, A2);
1668
1669 Keylet const badDomainKeylet =
1671 auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
1672 createPermissionedDomain(ac, sleBadPd, A1, A2);
1673
1674 Keylet const offerKey = keylet::offer(A2.id(), 10);
1675 auto sleOffer = std::make_shared<SLE>(offerKey);
1676 sleOffer->setAccountID(sfAccount, A2);
1677 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1678 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1679 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1680 ac.view().insert(sleOffer);
1681 return true;
1682 },
1683 XRPAmount{},
1684 STTx{
1685 ttOFFER_CREATE,
1686 [&](STObject& tx) {
1687 Account const A1{"A1"};
1688 Keylet const badDomainKey =
1690 tx.setFieldH256(sfDomainID, badDomainKey.key);
1691 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1692 tx.setFieldAmount(sfTakerGets, XRP(1));
1693 }},
1695
1697 {{"domain transaction affected regular offers"}},
1698 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1699 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1700 auto slePd = std::make_shared<SLE>(pdKeylet);
1701 createPermissionedDomain(ac, slePd, A1, A2);
1702
1703 Keylet const offerKey = keylet::offer(A2.id(), 10);
1704 auto sleOffer = std::make_shared<SLE>(offerKey);
1705 sleOffer->setAccountID(sfAccount, A2);
1706 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1707 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1708 ac.view().insert(sleOffer);
1709 return true;
1710 },
1711 XRPAmount{},
1712 STTx{
1713 ttOFFER_CREATE,
1714 [&](STObject& tx) {
1715 Account const A1{"A1"};
1716 Keylet const domainKey =
1718 tx.setFieldH256(sfDomainID, domainKey.key);
1719 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1720 tx.setFieldAmount(sfTakerGets, XRP(1));
1721 }},
1723 }
1724
1725 void
1727 {
1728 using namespace test::jtx;
1729
1730 struct AccountAmount
1731 {
1732 AccountID account;
1733 int amount;
1734 };
1735 struct Adjustments
1736 {
1737 std::optional<int> assetsTotal = {};
1738 std::optional<int> assetsAvailable = {};
1739 std::optional<int> lossUnrealized = {};
1740 std::optional<int> assetsMaximum = {};
1741 std::optional<int> sharesTotal = {};
1742 std::optional<int> vaultAssets = {};
1743 std::optional<AccountAmount> accountAssets = {};
1744 std::optional<AccountAmount> accountShares = {};
1745 };
1746 auto constexpr adjust = [&](ApplyView& ac,
1747 ripple::Keylet keylet,
1748 Adjustments args) {
1749 auto sleVault = ac.peek(keylet);
1750 if (!sleVault)
1751 return false;
1752
1753 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
1754 auto sleShares = ac.peek(keylet::mptIssuance(mptIssuanceID));
1755 if (!sleShares)
1756 return false;
1757
1758 // These two fields are adjusted in absolute terms
1759 if (args.lossUnrealized)
1760 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
1761 if (args.assetsMaximum)
1762 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
1763
1764 // Remaining fields are adjusted in terms of difference
1765 if (args.assetsTotal)
1766 (*sleVault)[sfAssetsTotal] =
1767 *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
1768 if (args.assetsAvailable)
1769 (*sleVault)[sfAssetsAvailable] =
1770 *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
1771 ac.update(sleVault);
1772
1773 if (args.sharesTotal)
1774 {
1775 (*sleShares)[sfOutstandingAmount] =
1776 *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
1777 ac.update(sleShares);
1778 }
1779
1780 auto const assets = *(*sleVault)[sfAsset];
1781 auto const pseudoId = *(*sleVault)[sfAccount];
1782 if (args.vaultAssets)
1783 {
1784 if (assets.native())
1785 {
1786 auto slePseudoAccount = ac.peek(keylet::account(pseudoId));
1787 if (!slePseudoAccount)
1788 return false;
1789 (*slePseudoAccount)[sfBalance] =
1790 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
1791 ac.update(slePseudoAccount);
1792 }
1793 else if (assets.holds<MPTIssue>())
1794 {
1795 auto const mptId = assets.get<MPTIssue>().getMptID();
1796 auto sleMPToken = ac.peek(keylet::mptoken(mptId, pseudoId));
1797 if (!sleMPToken)
1798 return false;
1799 (*sleMPToken)[sfMPTAmount] =
1800 *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
1801 ac.update(sleMPToken);
1802 }
1803 else
1804 return false; // Not supporting testing with IOU
1805 }
1806
1807 if (args.accountAssets)
1808 {
1809 auto const& pair = *args.accountAssets;
1810 if (assets.native())
1811 {
1812 auto sleAccount = ac.peek(keylet::account(pair.account));
1813 if (!sleAccount)
1814 return false;
1815 (*sleAccount)[sfBalance] =
1816 *(*sleAccount)[sfBalance] + pair.amount;
1817 ac.update(sleAccount);
1818 }
1819 else if (assets.holds<MPTIssue>())
1820 {
1821 auto const mptID = assets.get<MPTIssue>().getMptID();
1822 auto sleMPToken =
1823 ac.peek(keylet::mptoken(mptID, pair.account));
1824 if (!sleMPToken)
1825 return false;
1826 (*sleMPToken)[sfMPTAmount] =
1827 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1828 ac.update(sleMPToken);
1829 }
1830 else
1831 return false; // Not supporting testing with IOU
1832 }
1833
1834 if (args.accountShares)
1835 {
1836 auto const& pair = *args.accountShares;
1837 auto sleMPToken =
1838 ac.peek(keylet::mptoken(mptIssuanceID, pair.account));
1839 if (!sleMPToken)
1840 return false;
1841 (*sleMPToken)[sfMPTAmount] =
1842 *(*sleMPToken)[sfMPTAmount] + pair.amount;
1843 ac.update(sleMPToken);
1844 }
1845 return true;
1846 };
1847
1848 constexpr auto args =
1849 [](AccountID id, int adjustment, auto fn) -> Adjustments {
1850 Adjustments sample = {
1851 .assetsTotal = adjustment,
1852 .assetsAvailable = adjustment,
1853 .lossUnrealized = 0,
1854 .sharesTotal = adjustment,
1855 .vaultAssets = adjustment,
1856 .accountAssets = //
1857 AccountAmount{id, -adjustment},
1858 .accountShares = //
1859 AccountAmount{id, adjustment}};
1860 fn(sample);
1861 return sample;
1862 };
1863
1864 Account A3{"A3"};
1865 Account A4{"A4"};
1866 auto const precloseXrp =
1867 [&](Account const& A1, Account const& A2, Env& env) -> bool {
1868 env.fund(XRP(1000), A3, A4);
1869 Vault vault{env};
1870 auto [tx, keylet] =
1871 vault.create({.owner = A1, .asset = xrpIssue()});
1872 env(tx);
1873 env(vault.deposit(
1874 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
1875 env(vault.deposit(
1876 {.depositor = A2, .id = keylet.key, .amount = XRP(10)}));
1877 env(vault.deposit(
1878 {.depositor = A3, .id = keylet.key, .amount = XRP(10)}));
1879 return true;
1880 };
1881
1882 testcase << "Vault general checks";
1884 {"vault deletion succeeded without deleting a vault"},
1885 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1886 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1887 auto sleVault = ac.view().peek(keylet);
1888 if (!sleVault)
1889 return false;
1890 ac.view().update(sleVault);
1891 return true;
1892 },
1893 XRPAmount{},
1894 STTx{ttVAULT_DELETE, [](STObject&) {}},
1896 [&](Account const& A1, Account const& A2, Env& env) {
1897 Vault vault{env};
1898 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1899 env(tx);
1900 return true;
1901 });
1902
1904 {"vault updated by a wrong transaction type"},
1905 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1906 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1907 auto sleVault = ac.view().peek(keylet);
1908 if (!sleVault)
1909 return false;
1910 ac.view().erase(sleVault);
1911 return true;
1912 },
1913 XRPAmount{},
1914 STTx{ttPAYMENT, [](STObject&) {}},
1916 [&](Account const& A1, Account const& A2, Env& env) {
1917 Vault vault{env};
1918 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1919 env(tx);
1920 return true;
1921 });
1922
1924 {"vault updated by a wrong transaction type"},
1925 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1926 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1927 auto sleVault = ac.view().peek(keylet);
1928 if (!sleVault)
1929 return false;
1930 ac.view().update(sleVault);
1931 return true;
1932 },
1933 XRPAmount{},
1934 STTx{ttPAYMENT, [](STObject&) {}},
1936 [&](Account const& A1, Account const& A2, Env& env) {
1937 Vault vault{env};
1938 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1939 env(tx);
1940 return true;
1941 });
1942
1944 {"vault updated by a wrong transaction type"},
1945 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1946 auto const sequence = ac.view().seq();
1947 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
1948 auto sleVault = std::make_shared<SLE>(vaultKeylet);
1949 auto const vaultPage = ac.view().dirInsert(
1950 keylet::ownerDir(A1.id()),
1951 sleVault->key(),
1952 describeOwnerDir(A1.id()));
1953 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
1954 ac.view().insert(sleVault);
1955 return true;
1956 },
1957 XRPAmount{},
1958 STTx{ttPAYMENT, [](STObject&) {}},
1960
1962 {"vault deleted by a wrong transaction type"},
1963 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1964 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1965 auto sleVault = ac.view().peek(keylet);
1966 if (!sleVault)
1967 return false;
1968 ac.view().erase(sleVault);
1969 return true;
1970 },
1971 XRPAmount{},
1972 STTx{ttVAULT_SET, [](STObject&) {}},
1974 [&](Account const& A1, Account const& A2, Env& env) {
1975 Vault vault{env};
1976 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
1977 env(tx);
1978 return true;
1979 });
1980
1982 {"vault operation updated more than single vault"},
1983 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1984 {
1985 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
1986 auto sleVault = ac.view().peek(keylet);
1987 if (!sleVault)
1988 return false;
1989 ac.view().erase(sleVault);
1990 }
1991 {
1992 auto const keylet = keylet::vault(A2.id(), ac.view().seq());
1993 auto sleVault = ac.view().peek(keylet);
1994 if (!sleVault)
1995 return false;
1996 ac.view().erase(sleVault);
1997 }
1998 return true;
1999 },
2000 XRPAmount{},
2001 STTx{ttVAULT_DELETE, [](STObject&) {}},
2003 [&](Account const& A1, Account const& A2, Env& env) {
2004 Vault vault{env};
2005 {
2006 auto [tx, _] =
2007 vault.create({.owner = A1, .asset = xrpIssue()});
2008 env(tx);
2009 }
2010 {
2011 auto [tx, _] =
2012 vault.create({.owner = A2, .asset = xrpIssue()});
2013 env(tx);
2014 }
2015 return true;
2016 });
2017
2019 {"vault operation updated more than single vault"},
2020 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2021 auto const sequence = ac.view().seq();
2022 auto const insertVault = [&](Account const A) {
2023 auto const vaultKeylet = keylet::vault(A.id(), sequence);
2024 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2025 auto const vaultPage = ac.view().dirInsert(
2026 keylet::ownerDir(A.id()),
2027 sleVault->key(),
2028 describeOwnerDir(A.id()));
2029 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2030 ac.view().insert(sleVault);
2031 };
2032 insertVault(A1);
2033 insertVault(A2);
2034 return true;
2035 },
2036 XRPAmount{},
2037 STTx{ttVAULT_CREATE, [](STObject&) {}},
2039
2041 {"deleted vault must also delete shares"},
2042 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2043 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2044 auto sleVault = ac.view().peek(keylet);
2045 if (!sleVault)
2046 return false;
2047 ac.view().erase(sleVault);
2048 return true;
2049 },
2050 XRPAmount{},
2051 STTx{ttVAULT_DELETE, [](STObject&) {}},
2053 [&](Account const& A1, Account const& A2, Env& env) {
2054 Vault vault{env};
2055 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2056 env(tx);
2057 return true;
2058 });
2059
2061 {"deleted vault must have no shares outstanding",
2062 "deleted vault must have no assets outstanding",
2063 "deleted vault must have no assets available"},
2064 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2065 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2066 auto sleVault = ac.view().peek(keylet);
2067 if (!sleVault)
2068 return false;
2069 auto sleShares = ac.view().peek(
2070 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2071 if (!sleShares)
2072 return false;
2073 ac.view().erase(sleVault);
2074 ac.view().erase(sleShares);
2075 return true;
2076 },
2077 XRPAmount{},
2078 STTx{ttVAULT_DELETE, [](STObject&) {}},
2080 [&](Account const& A1, Account const& A2, Env& env) {
2081 Vault vault{env};
2082 auto [tx, keylet] =
2083 vault.create({.owner = A1, .asset = xrpIssue()});
2084 env(tx);
2085 env(vault.deposit(
2086 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2087 return true;
2088 });
2089
2091 {"vault operation succeeded without modifying a vault"},
2092 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2093 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2094 auto sleVault = ac.view().peek(keylet);
2095 if (!sleVault)
2096 return false;
2097 auto sleShares = ac.view().peek(
2098 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2099 if (!sleShares)
2100 return false;
2101 // Note, such an "orphaned" update of MPT issuance attached to a
2102 // vault is invalid; ttVAULT_SET must also update Vault object.
2103 sleShares->setFieldH256(sfDomainID, uint256(13));
2104 ac.view().update(sleShares);
2105 return true;
2106 },
2107 XRPAmount{},
2108 STTx{ttVAULT_SET, [](STObject& tx) {}},
2110 precloseXrp,
2112
2114 {"vault operation succeeded without modifying a vault"},
2115 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2116 return true;
2117 },
2118 XRPAmount{},
2119 STTx{ttVAULT_CREATE, [](STObject&) {}},
2121 [&](Account const& A1, Account const& A2, Env& env) {
2122 Vault vault{env};
2123 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2124 env(tx);
2125 return true;
2126 });
2127
2129 {"vault operation succeeded without modifying a vault"},
2130 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2131 return true;
2132 },
2133 XRPAmount{},
2134 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2136 [&](Account const& A1, Account const& A2, Env& env) {
2137 Vault vault{env};
2138 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2139 env(tx);
2140 return true;
2141 });
2142
2144 {"vault operation succeeded without modifying a vault"},
2145 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2146 return true;
2147 },
2148 XRPAmount{},
2149 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2151 [&](Account const& A1, Account const& A2, Env& env) {
2152 Vault vault{env};
2153 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2154 env(tx);
2155 return true;
2156 });
2157
2159 {"vault operation succeeded without modifying a vault"},
2160 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2161 return true;
2162 },
2163 XRPAmount{},
2164 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
2166 [&](Account const& A1, Account const& A2, Env& env) {
2167 Vault vault{env};
2168 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2169 env(tx);
2170 return true;
2171 });
2172
2174 {"vault operation succeeded without modifying a vault"},
2175 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2176 return true;
2177 },
2178 XRPAmount{},
2179 STTx{ttVAULT_DELETE, [](STObject&) {}},
2181 [&](Account const& A1, Account const& A2, Env& env) {
2182 Vault vault{env};
2183 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2184 env(tx);
2185 return true;
2186 });
2187
2189 {"updated vault must have shares"},
2190 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2191 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2192 auto sleVault = ac.view().peek(keylet);
2193 if (!sleVault)
2194 return false;
2195 (*sleVault)[sfAssetsMaximum] = 200;
2196 ac.view().update(sleVault);
2197
2198 auto sleShares = ac.view().peek(
2199 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2200 if (!sleShares)
2201 return false;
2202 ac.view().erase(sleShares);
2203 return true;
2204 },
2205 XRPAmount{},
2206 STTx{ttVAULT_SET, [](STObject&) {}},
2208 [&](Account const& A1, Account const& A2, Env& env) {
2209 Vault vault{env};
2210 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2211 env(tx);
2212 return true;
2213 });
2214
2216 {"vault operation succeeded without updating shares",
2217 "assets available must not be greater than assets outstanding"},
2218 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2219 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2220 auto sleVault = ac.view().peek(keylet);
2221 if (!sleVault)
2222 return false;
2223 (*sleVault)[sfAssetsTotal] = 9;
2224 ac.view().update(sleVault);
2225 return true;
2226 },
2227 XRPAmount{},
2228 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2230 [&](Account const& A1, Account const& A2, Env& env) {
2231 Vault vault{env};
2232 auto [tx, keylet] =
2233 vault.create({.owner = A1, .asset = xrpIssue()});
2234 env(tx);
2235 env(vault.deposit(
2236 {.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2237 return true;
2238 });
2239
2241 {"set must not change assets outstanding",
2242 "set must not change assets available",
2243 "set must not change shares outstanding",
2244 "set must not change vault balance",
2245 "assets available must be positive",
2246 "assets available must not be greater than assets outstanding",
2247 "assets outstanding must be positive"},
2248 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2249 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2250 auto sleVault = ac.view().peek(keylet);
2251 if (!sleVault)
2252 return false;
2253 auto slePseudoAccount =
2254 ac.view().peek(keylet::account(*(*sleVault)[sfAccount]));
2255 if (!slePseudoAccount)
2256 return false;
2257 (*slePseudoAccount)[sfBalance] =
2258 *(*slePseudoAccount)[sfBalance] - 10;
2259 ac.view().update(slePseudoAccount);
2260
2261 // Move 10 drops to A4 to enforce total XRP balance
2262 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2263 if (!sleA4)
2264 return false;
2265 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2266 ac.view().update(sleA4);
2267
2268 return adjust(
2269 ac.view(),
2270 keylet,
2271 args(A2.id(), 0, [&](Adjustments& sample) {
2272 sample.assetsAvailable = (DROPS_PER_XRP * -100).value();
2273 sample.assetsTotal = (DROPS_PER_XRP * -200).value();
2274 sample.sharesTotal = -1;
2275 }));
2276 },
2277 XRPAmount{},
2278 STTx{ttVAULT_SET, [](STObject& tx) {}},
2280 precloseXrp,
2282
2284 {"violation of vault immutable data"},
2285 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2286 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2287 auto sleVault = ac.view().peek(keylet);
2288 if (!sleVault)
2289 return false;
2290 sleVault->setFieldIssue(
2291 sfAsset, STIssue{sfAsset, MPTIssue(MPTID(42))});
2292 ac.view().update(sleVault);
2293 return true;
2294 },
2295 XRPAmount{},
2296 STTx{ttVAULT_SET, [](STObject& tx) {}},
2298 precloseXrp);
2299
2301 {"violation of vault immutable data"},
2302 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2303 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2304 auto sleVault = ac.view().peek(keylet);
2305 if (!sleVault)
2306 return false;
2307 sleVault->setAccountID(sfAccount, A2.id());
2308 ac.view().update(sleVault);
2309 return true;
2310 },
2311 XRPAmount{},
2312 STTx{ttVAULT_SET, [](STObject& tx) {}},
2314 precloseXrp);
2315
2317 {"violation of vault immutable data"},
2318 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2319 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2320 auto sleVault = ac.view().peek(keylet);
2321 if (!sleVault)
2322 return false;
2323 (*sleVault)[sfShareMPTID] = MPTID(42);
2324 ac.view().update(sleVault);
2325 return true;
2326 },
2327 XRPAmount{},
2328 STTx{ttVAULT_SET, [](STObject& tx) {}},
2330 precloseXrp);
2331
2333 {"vault transaction must not change loss unrealized",
2334 "set must not change assets outstanding"},
2335 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2336 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2337 return adjust(
2338 ac.view(),
2339 keylet,
2340 args(A2.id(), 0, [&](Adjustments& sample) {
2341 sample.lossUnrealized = 13;
2342 sample.assetsTotal = 20;
2343 }));
2344 },
2345 XRPAmount{},
2346 STTx{ttVAULT_SET, [](STObject& tx) {}},
2348 precloseXrp,
2350
2352 {"loss unrealized must not exceed the difference "
2353 "between assets outstanding and available",
2354 "vault transaction must not change loss unrealized"},
2355 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2356 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2357 return adjust(
2358 ac.view(),
2359 keylet,
2360 args(A2.id(), 100, [&](Adjustments& sample) {
2361 sample.lossUnrealized = 13;
2362 }));
2363 },
2364 XRPAmount{},
2365 STTx{
2366 ttVAULT_DEPOSIT,
2367 [](STObject& tx) {
2368 tx.setFieldAmount(sfAmount, XRPAmount(200));
2369 }},
2371 precloseXrp,
2373
2375 {"set assets outstanding must not exceed assets maximum"},
2376 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2377 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2378 return adjust(
2379 ac.view(),
2380 keylet,
2381 args(A2.id(), 0, [&](Adjustments& sample) {
2382 sample.assetsMaximum = 1;
2383 }));
2384 },
2385 XRPAmount{},
2386 STTx{ttVAULT_SET, [](STObject& tx) {}},
2388 precloseXrp,
2390
2392 {"assets maximum must be positive"},
2393 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2394 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2395 return adjust(
2396 ac.view(),
2397 keylet,
2398 args(A2.id(), 0, [&](Adjustments& sample) {
2399 sample.assetsMaximum = -1;
2400 }));
2401 },
2402 XRPAmount{},
2403 STTx{ttVAULT_SET, [](STObject& tx) {}},
2405 precloseXrp,
2407
2409 {"set must not change shares outstanding",
2410 "updated zero sized vault must have no assets outstanding",
2411 "updated zero sized vault must have no assets available"},
2412 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2413 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2414 auto sleVault = ac.view().peek(keylet);
2415 if (!sleVault)
2416 return false;
2417 ac.view().update(sleVault);
2418 auto sleShares = ac.view().peek(
2419 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2420 if (!sleShares)
2421 return false;
2422 (*sleShares)[sfOutstandingAmount] = 0;
2423 ac.view().update(sleShares);
2424 return true;
2425 },
2426 XRPAmount{},
2427 STTx{ttVAULT_SET, [](STObject& tx) {}},
2429 precloseXrp,
2431
2433 {"updated shares must not exceed maximum"},
2434 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2435 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2436 auto sleVault = ac.view().peek(keylet);
2437 if (!sleVault)
2438 return false;
2439 auto sleShares = ac.view().peek(
2440 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2441 if (!sleShares)
2442 return false;
2443 (*sleShares)[sfMaximumAmount] = 10;
2444 ac.view().update(sleShares);
2445
2446 return adjust(
2447 ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2448 },
2449 XRPAmount{},
2450 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2452 precloseXrp,
2454
2456 {"updated shares must not exceed maximum"},
2457 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2458 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2459 adjust(
2460 ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2461
2462 auto sleVault = ac.view().peek(keylet);
2463 if (!sleVault)
2464 return false;
2465 auto sleShares = ac.view().peek(
2466 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2467 if (!sleShares)
2468 return false;
2469 (*sleShares)[sfOutstandingAmount] = maxMPTokenAmount + 1;
2470 ac.view().update(sleShares);
2471 return true;
2472 },
2473 XRPAmount{},
2474 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2476 precloseXrp,
2478
2479 testcase << "Vault create";
2481 {
2482 "created vault must be empty",
2483 "updated zero sized vault must have no assets outstanding",
2484 "create operation must not have updated a vault",
2485 },
2486 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2487 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2488 auto sleVault = ac.view().peek(keylet);
2489 if (!sleVault)
2490 return false;
2491 (*sleVault)[sfAssetsTotal] = 9;
2492 ac.view().update(sleVault);
2493 return true;
2494 },
2495 XRPAmount{},
2496 STTx{ttVAULT_CREATE, [](STObject&) {}},
2498 [&](Account const& A1, Account const& A2, Env& env) {
2499 Vault vault{env};
2500 auto [tx, keylet] =
2501 vault.create({.owner = A1, .asset = xrpIssue()});
2502 env(tx);
2503 return true;
2504 });
2505
2507 {
2508 "created vault must be empty",
2509 "updated zero sized vault must have no assets available",
2510 "assets available must not be greater than assets outstanding",
2511 "create operation must not have updated a vault",
2512 },
2513 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2514 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2515 auto sleVault = ac.view().peek(keylet);
2516 if (!sleVault)
2517 return false;
2518 (*sleVault)[sfAssetsAvailable] = 9;
2519 ac.view().update(sleVault);
2520 return true;
2521 },
2522 XRPAmount{},
2523 STTx{ttVAULT_CREATE, [](STObject&) {}},
2525 [&](Account const& A1, Account const& A2, Env& env) {
2526 Vault vault{env};
2527 auto [tx, keylet] =
2528 vault.create({.owner = A1, .asset = xrpIssue()});
2529 env(tx);
2530 return true;
2531 });
2532
2534 {
2535 "created vault must be empty",
2536 "loss unrealized must not exceed the difference between assets "
2537 "outstanding and available",
2538 "vault transaction must not change loss unrealized",
2539 "create operation must not have updated a vault",
2540 },
2541 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2542 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2543 auto sleVault = ac.view().peek(keylet);
2544 if (!sleVault)
2545 return false;
2546 (*sleVault)[sfLossUnrealized] = 1;
2547 ac.view().update(sleVault);
2548 return true;
2549 },
2550 XRPAmount{},
2551 STTx{ttVAULT_CREATE, [](STObject&) {}},
2553 [&](Account const& A1, Account const& A2, Env& env) {
2554 Vault vault{env};
2555 auto [tx, keylet] =
2556 vault.create({.owner = A1, .asset = xrpIssue()});
2557 env(tx);
2558 return true;
2559 });
2560
2562 {
2563 "created vault must be empty",
2564 "create operation must not have updated a vault",
2565 },
2566 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2567 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2568 auto sleVault = ac.view().peek(keylet);
2569 if (!sleVault)
2570 return false;
2571 auto sleShares = ac.view().peek(
2572 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2573 if (!sleShares)
2574 return false;
2575 ac.view().update(sleVault);
2576 (*sleShares)[sfOutstandingAmount] = 9;
2577 ac.view().update(sleShares);
2578 return true;
2579 },
2580 XRPAmount{},
2581 STTx{ttVAULT_CREATE, [](STObject&) {}},
2583 [&](Account const& A1, Account const& A2, Env& env) {
2584 Vault vault{env};
2585 auto [tx, keylet] =
2586 vault.create({.owner = A1, .asset = xrpIssue()});
2587 env(tx);
2588 return true;
2589 });
2590
2592 {
2593 "assets maximum must be positive",
2594 "create operation must not have updated a vault",
2595 },
2596 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2597 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2598 auto sleVault = ac.view().peek(keylet);
2599 if (!sleVault)
2600 return false;
2601 (*sleVault)[sfAssetsMaximum] = Number(-1);
2602 ac.view().update(sleVault);
2603 return true;
2604 },
2605 XRPAmount{},
2606 STTx{ttVAULT_CREATE, [](STObject&) {}},
2608 [&](Account const& A1, Account const& A2, Env& env) {
2609 Vault vault{env};
2610 auto [tx, keylet] =
2611 vault.create({.owner = A1, .asset = xrpIssue()});
2612 env(tx);
2613 return true;
2614 });
2615
2617 {"create operation must not have updated a vault",
2618 "shares issuer and vault pseudo-account must be the same",
2619 "shares issuer must be a pseudo-account",
2620 "shares issuer pseudo-account must point back to the vault"},
2621 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2622 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2623 auto sleVault = ac.view().peek(keylet);
2624 if (!sleVault)
2625 return false;
2626 auto sleShares = ac.view().peek(
2627 keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2628 if (!sleShares)
2629 return false;
2630 ac.view().update(sleVault);
2631 (*sleShares)[sfIssuer] = A1.id();
2632 ac.view().update(sleShares);
2633 return true;
2634 },
2635 XRPAmount{},
2636 STTx{ttVAULT_CREATE, [](STObject&) {}},
2638 [&](Account const& A1, Account const& A2, Env& env) {
2639 Vault vault{env};
2640 auto [tx, keylet] =
2641 vault.create({.owner = A1, .asset = xrpIssue()});
2642 env(tx);
2643 return true;
2644 });
2645
2647 {"vault created by a wrong transaction type",
2648 "account root created illegally"},
2649 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2650 // The code below will create a valid vault with (almost) all
2651 // the invariants holding. Except one: it is created by the
2652 // wrong transaction type.
2653 auto const sequence = ac.view().seq();
2654 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2655 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2656 auto const vaultPage = ac.view().dirInsert(
2657 keylet::ownerDir(A1.id()),
2658 sleVault->key(),
2659 describeOwnerDir(A1.id()));
2660 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2661
2662 auto pseudoId =
2663 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2664 // Create pseudo-account.
2665 auto sleAccount =
2667 sleAccount->setAccountID(sfAccount, pseudoId);
2668 sleAccount->setFieldAmount(sfBalance, STAmount{});
2669 std::uint32_t const seqno = //
2670 ac.view().rules().enabled(featureSingleAssetVault) //
2671 ? 0 //
2672 : sequence;
2673 sleAccount->setFieldU32(sfSequence, seqno);
2674 sleAccount->setFieldU32(
2675 sfFlags,
2677 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2678 ac.view().insert(sleAccount);
2679
2680 auto const sharesMptId = makeMptID(sequence, pseudoId);
2681 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2682 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2683 auto const sharesPage = ac.view().dirInsert(
2684 keylet::ownerDir(pseudoId),
2685 sharesKeylet,
2686 describeOwnerDir(pseudoId));
2687 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2688
2689 sleShares->at(sfFlags) = 0;
2690 sleShares->at(sfIssuer) = pseudoId;
2691 sleShares->at(sfOutstandingAmount) = 0;
2692 sleShares->at(sfSequence) = sequence;
2693
2694 sleVault->at(sfAccount) = pseudoId;
2695 sleVault->at(sfFlags) = 0;
2696 sleVault->at(sfSequence) = sequence;
2697 sleVault->at(sfOwner) = A1.id();
2698 sleVault->at(sfAssetsTotal) = Number(0);
2699 sleVault->at(sfAssetsAvailable) = Number(0);
2700 sleVault->at(sfLossUnrealized) = Number(0);
2701 sleVault->at(sfShareMPTID) = sharesMptId;
2702 sleVault->at(sfWithdrawalPolicy) =
2704
2705 ac.view().insert(sleVault);
2706 ac.view().insert(sleShares);
2707 return true;
2708 },
2709 XRPAmount{},
2710 STTx{ttVAULT_SET, [](STObject&) {}},
2712
2714 {"shares issuer and vault pseudo-account must be the same",
2715 "shares issuer pseudo-account must point back to the vault"},
2716 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2717 auto const sequence = ac.view().seq();
2718 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2719 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2720 auto const vaultPage = ac.view().dirInsert(
2721 keylet::ownerDir(A1.id()),
2722 sleVault->key(),
2723 describeOwnerDir(A1.id()));
2724 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2725
2726 auto pseudoId =
2727 pseudoAccountAddress(ac.view(), vaultKeylet.key);
2728 // Create pseudo-account.
2729 auto sleAccount =
2731 sleAccount->setAccountID(sfAccount, pseudoId);
2732 sleAccount->setFieldAmount(sfBalance, STAmount{});
2733 std::uint32_t const seqno = //
2734 ac.view().rules().enabled(featureSingleAssetVault) //
2735 ? 0 //
2736 : sequence;
2737 sleAccount->setFieldU32(sfSequence, seqno);
2738 sleAccount->setFieldU32(
2739 sfFlags,
2741 // sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
2742 // Setting wrong vault key
2743 sleAccount->setFieldH256(sfVaultID, uint256(42));
2744 ac.view().insert(sleAccount);
2745
2746 auto const sharesMptId = makeMptID(sequence, pseudoId);
2747 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2748 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2749 auto const sharesPage = ac.view().dirInsert(
2750 keylet::ownerDir(pseudoId),
2751 sharesKeylet,
2752 describeOwnerDir(pseudoId));
2753 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2754
2755 sleShares->at(sfFlags) = 0;
2756 sleShares->at(sfIssuer) = pseudoId;
2757 sleShares->at(sfOutstandingAmount) = 0;
2758 sleShares->at(sfSequence) = sequence;
2759
2760 // sleVault->at(sfAccount) = pseudoId;
2761 // Setting wrong pseudo acocunt ID
2762 sleVault->at(sfAccount) = A2.id();
2763 sleVault->at(sfFlags) = 0;
2764 sleVault->at(sfSequence) = sequence;
2765 sleVault->at(sfOwner) = A1.id();
2766 sleVault->at(sfAssetsTotal) = Number(0);
2767 sleVault->at(sfAssetsAvailable) = Number(0);
2768 sleVault->at(sfLossUnrealized) = Number(0);
2769 sleVault->at(sfShareMPTID) = sharesMptId;
2770 sleVault->at(sfWithdrawalPolicy) =
2772
2773 ac.view().insert(sleVault);
2774 ac.view().insert(sleShares);
2775 return true;
2776 },
2777 XRPAmount{},
2778 STTx{ttVAULT_CREATE, [](STObject&) {}},
2780
2782 {"shares issuer and vault pseudo-account must be the same",
2783 "shares issuer must exist"},
2784 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2785 auto const sequence = ac.view().seq();
2786 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2787 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2788 auto const vaultPage = ac.view().dirInsert(
2789 keylet::ownerDir(A1.id()),
2790 sleVault->key(),
2791 describeOwnerDir(A1.id()));
2792 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2793
2794 auto const sharesMptId = makeMptID(sequence, A2.id());
2795 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
2796 auto sleShares = std::make_shared<SLE>(sharesKeylet);
2797 auto const sharesPage = ac.view().dirInsert(
2798 keylet::ownerDir(A2.id()),
2799 sharesKeylet,
2800 describeOwnerDir(A2.id()));
2801 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
2802
2803 sleShares->at(sfFlags) = 0;
2804 // Setting wrong pseudo acocunt ID
2805 sleShares->at(sfIssuer) = AccountID(uint160(42));
2806 sleShares->at(sfOutstandingAmount) = 0;
2807 sleShares->at(sfSequence) = sequence;
2808
2809 sleVault->at(sfAccount) = A2.id();
2810 sleVault->at(sfFlags) = 0;
2811 sleVault->at(sfSequence) = sequence;
2812 sleVault->at(sfOwner) = A1.id();
2813 sleVault->at(sfAssetsTotal) = Number(0);
2814 sleVault->at(sfAssetsAvailable) = Number(0);
2815 sleVault->at(sfLossUnrealized) = Number(0);
2816 sleVault->at(sfShareMPTID) = sharesMptId;
2817 sleVault->at(sfWithdrawalPolicy) =
2819
2820 ac.view().insert(sleVault);
2821 ac.view().insert(sleShares);
2822 return true;
2823 },
2824 XRPAmount{},
2825 STTx{ttVAULT_CREATE, [](STObject&) {}},
2827
2828 testcase << "Vault deposit";
2830 {"deposit must change vault balance"},
2831 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2832 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2833 return adjust(
2834 ac.view(),
2835 keylet,
2836 args(A2.id(), 0, [](Adjustments& sample) {
2837 sample.vaultAssets.reset();
2838 }));
2839 },
2840 XRPAmount{},
2841 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2843 precloseXrp);
2844
2846 {"deposit assets outstanding must not exceed assets maximum"},
2847 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2848 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2849 return adjust(
2850 ac.view(),
2851 keylet,
2852 args(A2.id(), 200, [&](Adjustments& sample) {
2853 sample.assetsMaximum = 1;
2854 }));
2855 },
2856 XRPAmount{},
2857 STTx{
2858 ttVAULT_DEPOSIT,
2859 [](STObject& tx) {
2860 tx.setFieldAmount(sfAmount, XRPAmount(200));
2861 }},
2863 precloseXrp,
2865
2866 // This really convoluted unit tests makes the zero balance on the
2867 // depositor, by sending them the same amount as the transaction fee.
2868 // The operation makes no sense, but the defensive check in
2869 // ValidVault::finalize is otherwise impossible to trigger.
2871 {"deposit must increase vault balance",
2872 "deposit must change depositor balance"},
2873 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2874 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2875
2876 // Move 10 drops to A4 to enforce total XRP balance
2877 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2878 if (!sleA4)
2879 return false;
2880 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2881 ac.view().update(sleA4);
2882
2883 return adjust(
2884 ac.view(),
2885 keylet,
2886 args(A3.id(), -10, [&](Adjustments& sample) {
2887 sample.accountAssets->amount = -100;
2888 }));
2889 },
2890 XRPAmount{100},
2891 STTx{
2892 ttVAULT_DEPOSIT,
2893 [&](STObject& tx) {
2894 tx[sfFee] = XRPAmount(100);
2895 tx[sfAccount] = A3.id();
2896 }},
2898 precloseXrp);
2899
2901 {"deposit must increase vault balance",
2902 "deposit must decrease depositor balance",
2903 "deposit must change vault and depositor balance by equal amount",
2904 "deposit and assets outstanding must add up",
2905 "deposit and assets available must add up"},
2906 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2907 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2908
2909 // Move 10 drops from A2 to A3 to enforce total XRP balance
2910 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2911 if (!sleA3)
2912 return false;
2913 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
2914 ac.view().update(sleA3);
2915
2916 return adjust(
2917 ac.view(),
2918 keylet,
2919 args(A2.id(), 10, [&](Adjustments& sample) {
2920 sample.vaultAssets = -20;
2921 sample.accountAssets->amount = 10;
2922 }));
2923 },
2924 XRPAmount{},
2925 STTx{
2926 ttVAULT_DEPOSIT,
2927 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2929 precloseXrp,
2931
2933 {"deposit must change depositor balance"},
2934 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2935 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2936
2937 // Move 10 drops from A3 to vault to enforce total XRP balance
2938 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
2939 if (!sleA3)
2940 return false;
2941 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
2942 ac.view().update(sleA3);
2943
2944 return adjust(
2945 ac.view(),
2946 keylet,
2947 args(A2.id(), 10, [&](Adjustments& sample) {
2948 sample.accountAssets->amount = 0;
2949 }));
2950 },
2951 XRPAmount{},
2952 STTx{
2953 ttVAULT_DEPOSIT,
2954 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2956 precloseXrp,
2958
2960 {"deposit must change depositor shares"},
2961 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2962 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2963 return adjust(
2964 ac.view(),
2965 keylet,
2966 args(A2.id(), 10, [&](Adjustments& sample) {
2967 sample.accountShares.reset();
2968 }));
2969 },
2970 XRPAmount{},
2971 STTx{
2972 ttVAULT_DEPOSIT,
2973 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2975 precloseXrp,
2977
2979 {"deposit must change vault shares"},
2980 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2981 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2982
2983 return adjust(
2984 ac.view(),
2985 keylet,
2986 args(A2.id(), 10, [](Adjustments& sample) {
2987 sample.sharesTotal = 0;
2988 }));
2989 },
2990 XRPAmount{},
2991 STTx{
2992 ttVAULT_DEPOSIT,
2993 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
2995 precloseXrp,
2997
2999 {"deposit must increase depositor shares",
3000 "deposit must change depositor and vault shares by equal amount",
3001 "deposit must not change vault balance by more than deposited "
3002 "amount"},
3003 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3004 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3005 return adjust(
3006 ac.view(),
3007 keylet,
3008 args(A2.id(), 10, [&](Adjustments& sample) {
3009 sample.accountShares->amount = -5;
3010 sample.sharesTotal = -10;
3011 }));
3012 },
3013 XRPAmount{},
3014 STTx{
3015 ttVAULT_DEPOSIT,
3016 [](STObject& tx) { tx[sfAmount] = XRPAmount(5); }},
3018 precloseXrp,
3020
3022 {"deposit and assets outstanding must add up"},
3023 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3024 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3025 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3026 ac.view().update(sleA3);
3027
3028 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3029 return adjust(
3030 ac.view(),
3031 keylet,
3032 args(A2.id(), 10, [&](Adjustments& sample) {
3033 sample.assetsTotal = 11;
3034 }));
3035 },
3036 XRPAmount{2000},
3037 STTx{
3038 ttVAULT_DEPOSIT,
3039 [&](STObject& tx) {
3040 tx[sfAmount] = XRPAmount(10);
3041 tx[sfDelegate] = A3.id();
3042 tx[sfFee] = XRPAmount(2000);
3043 }},
3045 precloseXrp,
3047
3049 {"deposit and assets outstanding must add up",
3050 "deposit and assets available must add up"},
3051 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3052 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3053 return adjust(
3054 ac.view(),
3055 keylet,
3056 args(A2.id(), 10, [&](Adjustments& sample) {
3057 sample.assetsTotal = 7;
3058 sample.assetsAvailable = 7;
3059 }));
3060 },
3061 XRPAmount{},
3062 STTx{
3063 ttVAULT_DEPOSIT,
3064 [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3066 precloseXrp,
3068
3069 testcase << "Vault withdrawal";
3071 {"withdrawal must change vault balance"},
3072 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3073 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3074 return adjust(
3075 ac.view(),
3076 keylet,
3077 args(A2.id(), 0, [](Adjustments& sample) {
3078 sample.vaultAssets.reset();
3079 }));
3080 },
3081 XRPAmount{},
3082 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3084 precloseXrp);
3085
3086 // Almost identical to the really convoluted test for deposit, where the
3087 // depositor spends only the transaction fee. In case of withdrawal,
3088 // this test is almost the same as normal withdrawal where the
3089 // sfDestination would have been A4, but has been omitted.
3091 {"withdrawal must change one destination balance"},
3092 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3093 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3094
3095 // Move 10 drops to A4 to enforce total XRP balance
3096 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
3097 if (!sleA4)
3098 return false;
3099 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3100 ac.view().update(sleA4);
3101
3102 return adjust(
3103 ac.view(),
3104 keylet,
3105 args(A3.id(), -10, [&](Adjustments& sample) {
3106 sample.accountAssets->amount = -100;
3107 }));
3108 },
3109 XRPAmount{100},
3110 STTx{
3111 ttVAULT_WITHDRAW,
3112 [&](STObject& tx) {
3113 tx[sfFee] = XRPAmount(100);
3114 tx[sfAccount] = A3.id();
3115 // This commented out line causes the invariant violation.
3116 // tx[sfDestination] = A4.id();
3117 }},
3119 precloseXrp);
3120
3122 {"withdrawal must change vault and destination balance by "
3123 "equal amount",
3124 "withdrawal must decrease vault balance",
3125 "withdrawal must increase destination balance",
3126 "withdrawal and assets outstanding must add up",
3127 "withdrawal and assets available must add up"},
3128 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3129 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3130
3131 // Move 10 drops from A2 to A3 to enforce total XRP balance
3132 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3133 if (!sleA3)
3134 return false;
3135 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3136 ac.view().update(sleA3);
3137
3138 return adjust(
3139 ac.view(),
3140 keylet,
3141 args(A2.id(), -10, [&](Adjustments& sample) {
3142 sample.vaultAssets = 10;
3143 sample.accountAssets->amount = -20;
3144 }));
3145 },
3146 XRPAmount{},
3147 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3149 precloseXrp,
3151
3153 {"withdrawal must change one destination balance"},
3154 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3155 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3156 if (!adjust(
3157 ac.view(),
3158 keylet,
3159 args(A2.id(), -10, [&](Adjustments& sample) {
3160 *sample.vaultAssets -= 5;
3161 })))
3162 return false;
3163 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3164 if (!sleA3)
3165 return false;
3166 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
3167 ac.view().update(sleA3);
3168 return true;
3169 },
3170 XRPAmount{},
3171 STTx{
3172 ttVAULT_WITHDRAW,
3173 [&](STObject& tx) { tx.setAccountID(sfDestination, A3.id()); }},
3175 precloseXrp,
3177
3179 {"withdrawal must change depositor shares"},
3180 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3181 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3182 return adjust(
3183 ac.view(),
3184 keylet,
3185 args(A2.id(), -10, [&](Adjustments& sample) {
3186 sample.accountShares.reset();
3187 }));
3188 },
3189 XRPAmount{},
3190 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3192 precloseXrp,
3194
3196 {"withdrawal must change vault shares"},
3197 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3198 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3199 return adjust(
3200 ac.view(),
3201 keylet,
3202 args(A2.id(), -10, [](Adjustments& sample) {
3203 sample.sharesTotal = 0;
3204 }));
3205 },
3206 XRPAmount{},
3207 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3209 precloseXrp,
3211
3213 {"withdrawal must decrease depositor shares",
3214 "withdrawal must change depositor and vault shares by equal "
3215 "amount"},
3216 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3217 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3218 return adjust(
3219 ac.view(),
3220 keylet,
3221 args(A2.id(), -10, [&](Adjustments& sample) {
3222 sample.accountShares->amount = 5;
3223 sample.sharesTotal = 10;
3224 }));
3225 },
3226 XRPAmount{},
3227 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3229 precloseXrp,
3231
3233 {"withdrawal and assets outstanding must add up",
3234 "withdrawal and assets available must add up"},
3235 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3236 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3237 return adjust(
3238 ac.view(),
3239 keylet,
3240 args(A2.id(), -10, [&](Adjustments& sample) {
3241 sample.assetsTotal = -15;
3242 sample.assetsAvailable = -15;
3243 }));
3244 },
3245 XRPAmount{},
3246 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3248 precloseXrp,
3250
3252 {"withdrawal and assets outstanding must add up"},
3253 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3254 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3255 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3256 ac.view().update(sleA3);
3257
3258 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3259 return adjust(
3260 ac.view(),
3261 keylet,
3262 args(A2.id(), -10, [&](Adjustments& sample) {
3263 sample.assetsTotal = -7;
3264 }));
3265 },
3266 XRPAmount{2000},
3267 STTx{
3268 ttVAULT_WITHDRAW,
3269 [&](STObject& tx) {
3270 tx[sfAmount] = XRPAmount(10);
3271 tx[sfDelegate] = A3.id();
3272 tx[sfFee] = XRPAmount(2000);
3273 }},
3275 precloseXrp,
3277
3278 auto const precloseMpt =
3279 [&](Account const& A1, Account const& A2, Env& env) -> bool {
3280 env.fund(XRP(1000), A3, A4);
3281
3282 // Create MPT asset
3283 {
3284 Json::Value jv;
3285 jv[sfAccount] = A3.human();
3286 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3287 jv[sfFlags] = tfMPTCanTransfer;
3288 env(jv);
3289 env.close();
3290 }
3291
3292 auto const mptID = makeMptID(env.seq(A3) - 1, A3);
3293 Asset asset = MPTIssue(mptID);
3294 // Authorize A1 A2 A4
3295 {
3296 Json::Value jv;
3297 jv[sfAccount] = A1.human();
3298 jv[sfTransactionType] = jss::MPTokenAuthorize;
3299 jv[sfMPTokenIssuanceID] = to_string(mptID);
3300 env(jv);
3301 jv[sfAccount] = A2.human();
3302 env(jv);
3303 jv[sfAccount] = A4.human();
3304 env(jv);
3305
3306 env.close();
3307 }
3308 // Send tokens to A1 A2 A4
3309 {
3310 env(pay(A3, A1, asset(1000)));
3311 env(pay(A3, A2, asset(1000)));
3312 env(pay(A3, A4, asset(1000)));
3313 env.close();
3314 }
3315
3316 Vault vault{env};
3317 auto [tx, keylet] = vault.create({.owner = A1, .asset = asset});
3318 env(tx);
3319 env(vault.deposit(
3320 {.depositor = A1, .id = keylet.key, .amount = asset(10)}));
3321 env(vault.deposit(
3322 {.depositor = A2, .id = keylet.key, .amount = asset(10)}));
3323 env(vault.deposit(
3324 {.depositor = A4, .id = keylet.key, .amount = asset(10)}));
3325 return true;
3326 };
3327
3329 {"withdrawal must decrease depositor shares",
3330 "withdrawal must change depositor and vault shares by equal "
3331 "amount"},
3332 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3333 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3334 return adjust(
3335 ac.view(),
3336 keylet,
3337 args(A2.id(), -10, [&](Adjustments& sample) {
3338 sample.accountShares->amount = 5;
3339 }));
3340 },
3341 XRPAmount{},
3342 STTx{
3343 ttVAULT_WITHDRAW,
3344 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3346 precloseMpt,
3348
3349 testcase << "Vault clawback";
3351 {"clawback must change vault balance"},
3352 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3353 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3354 return adjust(
3355 ac.view(),
3356 keylet,
3357 args(A2.id(), -1, [&](Adjustments& sample) {
3358 sample.vaultAssets.reset();
3359 }));
3360 },
3361 XRPAmount{},
3362 STTx{
3363 ttVAULT_CLAWBACK,
3364 [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3366 precloseMpt);
3367
3368 // Not the same as below check: attempt to clawback XRP
3370 {"clawback may only be performed by the asset issuer"},
3371 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3372 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3373 return adjust(
3374 ac.view(),
3375 keylet,
3376 args(A2.id(), 0, [&](Adjustments& sample) {}));
3377 },
3378 XRPAmount{},
3379 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
3381 precloseXrp);
3382
3383 // Not the same as above check: attempt to clawback MPT by bad account
3385 {"clawback may only be performed by the asset issuer"},
3386 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3387 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3388 return adjust(
3389 ac.view(),
3390 keylet,
3391 args(A2.id(), 0, [&](Adjustments& sample) {}));
3392 },
3393 XRPAmount{},
3394 STTx{
3395 ttVAULT_CLAWBACK,
3396 [&](STObject& tx) { tx[sfAccount] = A4.id(); }},
3398 precloseMpt);
3399
3401 {"clawback must decrease vault balance",
3402 "clawback must decrease holder shares",
3403 "clawback must change vault shares"},
3404 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3405 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3406 return adjust(
3407 ac.view(),
3408 keylet,
3409 args(A4.id(), 10, [&](Adjustments& sample) {
3410 sample.sharesTotal = 0;
3411 }));
3412 },
3413 XRPAmount{},
3414 STTx{
3415 ttVAULT_CLAWBACK,
3416 [&](STObject& tx) {
3417 tx[sfAccount] = A3.id();
3418 tx[sfHolder] = A4.id();
3419 }},
3421 precloseMpt);
3422
3424 {"clawback must change holder shares"},
3425 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3426 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3427 return adjust(
3428 ac.view(),
3429 keylet,
3430 args(A4.id(), -10, [&](Adjustments& sample) {
3431 sample.accountShares.reset();
3432 }));
3433 },
3434 XRPAmount{},
3435 STTx{
3436 ttVAULT_CLAWBACK,
3437 [&](STObject& tx) {
3438 tx[sfAccount] = A3.id();
3439 tx[sfHolder] = A4.id();
3440 }},
3442 precloseMpt);
3443
3445 {"clawback must change holder and vault shares by equal amount",
3446 "clawback and assets outstanding must add up",
3447 "clawback and assets available must add up"},
3448 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3449 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3450 return adjust(
3451 ac.view(),
3452 keylet,
3453 args(A4.id(), -10, [&](Adjustments& sample) {
3454 sample.accountShares->amount = -8;
3455 sample.assetsTotal = -7;
3456 sample.assetsAvailable = -7;
3457 }));
3458 },
3459 XRPAmount{},
3460 STTx{
3461 ttVAULT_CLAWBACK,
3462 [&](STObject& tx) {
3463 tx[sfAccount] = A3.id();
3464 tx[sfHolder] = A4.id();
3465 }},
3467 precloseMpt);
3468 }
3469
3470public:
3471 void
3492};
3493
3494BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
3495
3496} // namespace test
3497} // namespace ripple
Represents a JSON value.
Definition json_value.h:130
A generic endpoint for log messages.
Definition Journal.h:41
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
State information when applying a tx.
ApplyView & view()
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:124
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
std::optional< std::uint64_t > dirInsert(Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Insert an entry to a directory.
Definition ApplyView.h:300
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
A currency issued by an account.
Definition Issue.h:14
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:99
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:111
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:94
AccountID const & getIssuer() const
Definition STAmount.h:489
void push_back(STObject const &object)
Definition STArray.h:193
iterator begin()
Definition STArray.h:205
void setFieldH256(SField const &field, uint256 const &)
Definition STObject.cpp:756
void setFieldAmount(SField const &field, STAmount const &)
Definition STObject.cpp:792
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:76
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:774
An immutable linear range of bytes.
Definition Slice.h:27
void run() override
Runs the suite.
void createPermissionedDomain(ApplyContext &ac, std::shared_ptr< SLE > &sle, test::jtx::Account const &A1, test::jtx::Account const &A2)
void doInvariantCheck(std::vector< std::string > const &expect_logs, Precheck const &precheck, XRPAmount fee=XRPAmount{}, STTx tx=STTx{ttACCOUNT_SET, [](STObject &) {}}, std::initializer_list< TER > ters={tecINVARIANT_FAILED, tefINVARIANT_FAILED}, Preclose const &preclose={}, TxAccount setTxAccount=TxAccount::None)
TxAccount
Run a specific test case to put the ledger into a state that will be detected by an invariant.
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, test::jtx::Env &env)> Preclose
Convenience class to test AMM functionality.
Definition AMM.h:105
Immutable cryptographic account descriptor.
Definition Account.h:20
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
Set the fee on a JTx.
Definition fee.h:18
T invoke(T... args)
T is_same_v
T iter_swap(T... args)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:521
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:551
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:427
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:225
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:195
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition Indexes.cpp:400
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:507
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:545
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition Indexes.cpp:370
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:384
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:392
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:355
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:255
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:15
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
FeatureBitset testable_amendments()
Definition Env.h:55
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:29
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:133
base_uint< 256 > uint256
Definition base_uint.h:539
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:94
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:100
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:45
@ lsfHighDeepFreeze
@ lsfRequireDestTag
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:1031
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:365
@ tefINVARIANT_FAILED
Definition TER.h:164
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:37
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1588
@ tecINVARIANT_FAILED
Definition TER.h:295
@ tesSUCCESS
Definition TER.h:226
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:1050
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
LedgerEntryType
Identifiers for on-ledger objects.
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:106
@ tapNONE
Definition ApplyView.h:12
base_uint< 160 > uint160
Definition base_uint.h:538
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:151
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1073
TERSubset< CanCvtToTER > TER
Definition TER.h:630
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
T to_string(T... args)