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 xrpl {
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.
33
34 // this is common setup/method for running a failing invariant check. The
35 // precheck function is used to manipulate the ApplyContext with view
36 // changes that will cause the check to fail.
38
39 static FeatureBitset
41 {
42 return xrpl::test::jtx::testable_amendments() | featureInvariantsV1_1 | featureSingleAssetVault;
43 }
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&) {}},
69 Preclose const& preclose = {},
70 TxAccount setTxAccount = TxAccount::None)
71 {
72 return doInvariantCheck(
73 test::jtx::Env(*this, defaultAmendments()), expect_logs, precheck, fee, tx, ters, preclose, setTxAccount);
74 }
75
76 void
78 test::jtx::Env&& env,
79 std::vector<std::string> const& expect_logs,
80 Precheck const& precheck,
82 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
84 Preclose const& preclose = {},
85 TxAccount setTxAccount = TxAccount::None)
86 {
87 using namespace test::jtx;
88
89 Account const A1{"A1"};
90 Account const A2{"A2"};
91 env.fund(XRP(1000), A1, A2);
92 if (preclose)
93 BEAST_EXPECT(preclose(A1, A2, env));
94 env.close();
95
96 if (setTxAccount != TxAccount::None)
97 tx.setAccountID(sfAccount, setTxAccount == TxAccount::A1 ? A1.id() : A2.id());
98
99 return doInvariantCheck(std::move(env), A1, A2, expect_logs, precheck, fee, tx, ters);
100 }
101
102 void
104 test::jtx::Env&& env,
105 test::jtx::Account const& A1,
106 test::jtx::Account const& A2,
107 std::vector<std::string> const& expect_logs,
108 Precheck const& precheck,
110 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
112 {
113 using namespace test::jtx;
114
115 OpenView ov{*env.current()};
116 test::StreamSink sink{beast::severities::kWarning};
117 beast::Journal jlog{sink};
118 ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, jlog};
119
120 BEAST_EXPECT(precheck(A1, A2, ac));
121
122 // invoke check twice to cover tec and tef cases
123 if (!BEAST_EXPECT(ters.size() == 2))
124 return;
125
126 TER terActual = tesSUCCESS;
127 for (TER const& terExpect : ters)
128 {
129 terActual = ac.checkInvariants(terActual, fee);
130 BEAST_EXPECTS(terExpect == terActual, std::to_string(TERtoInt(terActual)));
131 auto const messages = sink.messages().str();
132
133 if (terActual != tesSUCCESS)
134 {
135 BEAST_EXPECTS(
136 messages.starts_with("Invariant failed:") ||
137 messages.starts_with("Transaction caused an exception"),
138 messages);
139 }
140
141 // std::cerr << messages << '\n';
142 for (auto const& m : expect_logs)
143 {
144 BEAST_EXPECTS(messages.find(m) != std::string::npos, m);
145 }
146 }
147 }
148
149 void
151 {
152 using namespace test::jtx;
153 testcase << "XRP created";
155 {{"XRP net change was positive: 500"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
156 // put a single account in the view and "manufacture" some XRP
157 auto const sle = ac.view().peek(keylet::account(A1.id()));
158 if (!sle)
159 return false;
160 auto amt = sle->getFieldAmount(sfBalance);
161 sle->setFieldAmount(sfBalance, amt + STAmount{500});
162 ac.view().update(sle);
163 return true;
164 });
165 }
166
167 void
169 {
170 using namespace test::jtx;
171 testcase << "account root removed";
172
173 // An account was deleted, but not by an AccountDelete transaction.
174 doInvariantCheck({{"an account root was deleted"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
175 // remove an account from the view
176 auto sle = ac.view().peek(keylet::account(A1.id()));
177 if (!sle)
178 return false;
179 // Clear the balance so the "account deletion left behind a
180 // non-zero balance" check doesn't trip earlier than the desired
181 // check.
182 sle->at(sfBalance) = beast::zero;
183 ac.view().erase(sle);
184 return true;
185 });
186
187 // Successful AccountDelete transaction that didn't delete an account.
188 //
189 // Note that this is a case where a second invocation of the invariant
190 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
191 // After a discussion with the team, we believe that's okay.
193 {{"account deletion succeeded without deleting an account"}},
194 [](Account const&, Account const&, ApplyContext& ac) { return true; },
195 XRPAmount{},
196 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
198
199 // Successful AccountDelete that deleted more than one account.
201 {{"account deletion succeeded but deleted multiple accounts"}},
202 [](Account const& A1, Account const& A2, ApplyContext& ac) {
203 // remove two accounts from the view
204 auto sleA1 = ac.view().peek(keylet::account(A1.id()));
205 auto sleA2 = ac.view().peek(keylet::account(A2.id()));
206 if (!sleA1 || !sleA2)
207 return false;
208 // Clear the balance so the "account deletion left behind a
209 // non-zero balance" check doesn't trip earlier than the desired
210 // check.
211 sleA1->at(sfBalance) = beast::zero;
212 sleA2->at(sfBalance) = beast::zero;
213 ac.view().erase(sleA1);
214 ac.view().erase(sleA2);
215 return true;
216 },
217 XRPAmount{},
218 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
219 }
220
221 void
223 {
224 using namespace test::jtx;
225 testcase << "account root deletion left artifact";
226
228 {{"account deletion left behind a non-zero balance"}},
229 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
230 // A1 has a balance. Delete A1
231 auto const a1 = A1.id();
232 auto const sleA1 = ac.view().peek(keylet::account(a1));
233 if (!sleA1)
234 return false;
235 if (!BEAST_EXPECT(*sleA1->at(sfBalance) != beast::zero))
236 return false;
237
238 ac.view().erase(sleA1);
239
240 return true;
241 },
242 XRPAmount{},
243 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
244
246 {{"account deletion left behind a non-zero owner count"}},
247 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
248 // Increment A1's owner count, then delete A1
249 auto const a1 = A1.id();
250 auto const sleA1 = ac.view().peek(keylet::account(a1));
251 if (!sleA1)
252 return false;
253 // Clear the balance so the "account deletion left behind a
254 // non-zero balance" check doesn't trip earlier than the desired
255 // check.
256 sleA1->at(sfBalance) = beast::zero;
257 BEAST_EXPECT(sleA1->at(sfOwnerCount) == 0);
258 adjustOwnerCount(ac.view(), sleA1, 1, ac.journal);
259
260 ac.view().erase(sleA1);
261
262 return true;
263 },
264 XRPAmount{},
265 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
266
267 for (auto const& keyletInfo : directAccountKeylets)
268 {
269 // TODO: Use structured binding once LLVM 16 is the minimum
270 // supported version. See also:
271 // https://github.com/llvm/llvm-project/issues/48582
272 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
273 if (!keyletInfo.includeInTests)
274 continue;
275 auto const& keyletfunc = keyletInfo.function;
276 auto const& type = keyletInfo.expectedLEName;
277
278 using namespace std::string_literals;
279
281 {{"account deletion left behind a "s + type.c_str() + " object"}},
282 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
283 // Add an object to the ledger for account A1, then delete
284 // A1
285 auto const a1 = A1.id();
286 auto sleA1 = ac.view().peek(keylet::account(a1));
287 if (!sleA1)
288 return false;
289
290 auto const key = std::invoke(keyletfunc, a1);
291 auto const newSLE = std::make_shared<SLE>(key);
292 ac.view().insert(newSLE);
293 // Clear the balance so the "account deletion left behind a
294 // non-zero balance" check doesn't trip earlier than the
295 // desired check.
296 sleA1->at(sfBalance) = beast::zero;
297 ac.view().erase(sleA1);
298
299 return true;
300 },
301 XRPAmount{},
302 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
303 }
304
305 // NFT special case
307 {{"account deletion left behind a NFTokenPage object"}},
308 [&](Account const& A1, Account const&, ApplyContext& ac) {
309 // remove an account from the view
310 auto sle = ac.view().peek(keylet::account(A1.id()));
311 if (!sle)
312 return false;
313 // Clear the balance so the "account deletion left behind a
314 // non-zero balance" check doesn't trip earlier than the desired
315 // check.
316 sle->at(sfBalance) = beast::zero;
317 sle->at(sfOwnerCount) = 0;
318 ac.view().erase(sle);
319 return true;
320 },
321 XRPAmount{},
322 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
324 [&](Account const& A1, Account const&, Env& env) {
325 // Preclose callback to mint the NFT which will be deleted in
326 // the Precheck callback above.
327 env(token::mint(A1));
328
329 return true;
330 });
331
332 // AMM special cases
333 AccountID ammAcctID;
334 uint256 ammKey;
335 Issue ammIssue;
337 {{"account deletion left behind a DirectoryNode object"}},
338 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
339 // Delete the AMM account without cleaning up the directory or
340 // deleting the AMM object
341 auto sle = ac.view().peek(keylet::account(ammAcctID));
342 if (!sle)
343 return false;
344
345 BEAST_EXPECT(sle->at(~sfAMMID));
346 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
347
348 // Clear the balance so the "account deletion left behind a
349 // non-zero balance" check doesn't trip earlier than the desired
350 // check.
351 sle->at(sfBalance) = beast::zero;
352 sle->at(sfOwnerCount) = 0;
353 ac.view().erase(sle);
354
355 return true;
356 },
357 XRPAmount{},
358 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
360 [&](Account const& A1, Account const& A2, Env& env) {
361 // Preclose callback to create the AMM which will be partially
362 // deleted in the Precheck callback above.
363 AMM const amm(env, A1, XRP(100), A1["USD"](50));
364 ammAcctID = amm.ammAccount();
365 ammKey = amm.ammID();
366 ammIssue = amm.lptIssue();
367 return true;
368 });
370 {{"account deletion left behind a AMM object"}},
371 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
372 // Delete all the AMM's trust lines, remove the AMM from the AMM
373 // account's directory (this deletes the directory), and delete
374 // the AMM account. Do not delete the AMM object.
375 auto sle = ac.view().peek(keylet::account(ammAcctID));
376 if (!sle)
377 return false;
378
379 BEAST_EXPECT(sle->at(~sfAMMID));
380 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
381
382 for (auto const& trustKeylet : {keylet::line(ammAcctID, A1["USD"]), keylet::line(A1, ammIssue)})
383 {
384 if (auto const line = ac.view().peek(trustKeylet); !line)
385 {
386 return false;
387 }
388 else
389 {
390 STAmount const lowLimit = line->at(sfLowLimit);
391 STAmount const highLimit = line->at(sfHighLimit);
392 BEAST_EXPECT(
393 trustDelete(ac.view(), line, lowLimit.getIssuer(), highLimit.getIssuer(), ac.journal) ==
394 tesSUCCESS);
395 }
396 }
397
398 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
399 if (!BEAST_EXPECT(ammSle))
400 return false;
401 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
402
403 BEAST_EXPECT(ac.view().dirRemove(ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
404 BEAST_EXPECT(!ac.view().exists(ownerDirKeylet) || ac.view().emptyDirDelete(ownerDirKeylet));
405
406 // Clear the balance so the "account deletion left behind a
407 // non-zero balance" check doesn't trip earlier than the desired
408 // check.
409 sle->at(sfBalance) = beast::zero;
410 sle->at(sfOwnerCount) = 0;
411 ac.view().erase(sle);
412
413 return true;
414 },
415 XRPAmount{},
416 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
418 [&](Account const& A1, Account const& A2, Env& env) {
419 // Preclose callback to create the AMM which will be partially
420 // deleted in the Precheck callback above.
421 AMM const amm(env, A1, XRP(100), A1["USD"](50));
422 ammAcctID = amm.ammAccount();
423 ammKey = amm.ammID();
424 ammIssue = amm.lptIssue();
425 return true;
426 });
427 }
428
429 void
431 {
432 using namespace test::jtx;
433 testcase << "ledger entry types don't match";
435 {{"ledger entry type mismatch"}, {"XRP net change of -1000000000 doesn't match fee 0"}},
436 [](Account const& A1, Account const&, ApplyContext& ac) {
437 // replace an entry in the table with an SLE of a different type
438 auto const sle = ac.view().peek(keylet::account(A1.id()));
439 if (!sle)
440 return false;
441 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
442 ac.rawView().rawReplace(sleNew);
443 return true;
444 });
445
447 {{"invalid ledger entry type added"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
448 // add an entry in the table with an SLE of an invalid type
449 auto const sle = ac.view().peek(keylet::account(A1.id()));
450 if (!sle)
451 return false;
452
453 // make a dummy escrow ledger entry, then change the type to an
454 // unsupported value so that the valid type invariant check
455 // will fail.
456 auto const sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
457
458 // We don't use ltNICKNAME directly since it's marked deprecated
459 // to prevent accidental use elsewhere.
460 sleNew->type_ = static_cast<LedgerEntryType>('n');
461 ac.view().insert(sleNew);
462 return true;
463 });
464 }
465
466 void
468 {
469 using namespace test::jtx;
470 testcase << "trust lines with XRP not allowed";
472 {{"an XRP trust line was created"}}, [](Account const& A1, Account const& A2, ApplyContext& ac) {
473 // create simple trust SLE with xrp currency
474 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, xrpIssue().currency));
475 ac.view().insert(sleNew);
476 return true;
477 });
478 }
479
480 void
482 {
483 using namespace test::jtx;
484 testcase << "trust lines with deep freeze flag without freeze "
485 "not allowed";
487 {{"a trust line with deep freeze flag without normal freeze was "
488 "created"}},
489 [](Account const& A1, Account const& A2, ApplyContext& ac) {
490 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, A1["USD"].currency));
491 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
492 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
493
494 std::uint32_t uFlags = 0u;
495 uFlags |= lsfLowDeepFreeze;
496 sleNew->setFieldU32(sfFlags, uFlags);
497 ac.view().insert(sleNew);
498 return true;
499 });
500
502 {{"a trust line with deep freeze flag without normal freeze was "
503 "created"}},
504 [](Account const& A1, Account const& A2, ApplyContext& ac) {
505 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, A1["USD"].currency));
506 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
507 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
508 std::uint32_t uFlags = 0u;
509 uFlags |= lsfHighDeepFreeze;
510 sleNew->setFieldU32(sfFlags, uFlags);
511 ac.view().insert(sleNew);
512 return true;
513 });
514
516 {{"a trust line with deep freeze flag without normal freeze was "
517 "created"}},
518 [](Account const& A1, Account const& A2, ApplyContext& ac) {
519 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, A1["USD"].currency));
520 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
521 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
522 std::uint32_t uFlags = 0u;
524 sleNew->setFieldU32(sfFlags, uFlags);
525 ac.view().insert(sleNew);
526 return true;
527 });
528
530 {{"a trust line with deep freeze flag without normal freeze was "
531 "created"}},
532 [](Account const& A1, Account const& A2, ApplyContext& ac) {
533 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, A1["USD"].currency));
534 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
535 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
536 std::uint32_t uFlags = 0u;
538 sleNew->setFieldU32(sfFlags, uFlags);
539 ac.view().insert(sleNew);
540 return true;
541 });
542
544 {{"a trust line with deep freeze flag without normal freeze was "
545 "created"}},
546 [](Account const& A1, Account const& A2, ApplyContext& ac) {
547 auto const sleNew = std::make_shared<SLE>(keylet::line(A1, A2, A1["USD"].currency));
548 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
549 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
550 std::uint32_t uFlags = 0u;
552 sleNew->setFieldU32(sfFlags, uFlags);
553 ac.view().insert(sleNew);
554 return true;
555 });
556 }
557
558 void
560 {
561 using namespace test::jtx;
562 testcase << "transfers when frozen";
563
564 Account G1{"G1"};
565 // Helper function to establish the trustlines
566 auto const createTrustlines = [&](Account const& A1, Account const& A2, Env& env) {
567 // Preclose callback to establish trust lines with gateway
568 env.fund(XRP(1000), G1);
569
570 env.trust(G1["USD"](10000), A1);
571 env.trust(G1["USD"](10000), A2);
572 env.close();
573
574 env(pay(G1, A1, G1["USD"](1000)));
575 env(pay(G1, A2, G1["USD"](1000)));
576 env.close();
577
578 return true;
579 };
580
581 auto const A1FrozenByIssuer = [&](Account const& A1, Account const& A2, Env& env) {
582 createTrustlines(A1, A2, env);
583 env(trust(G1, A1["USD"](10000), tfSetFreeze));
584 env.close();
585
586 return true;
587 };
588
589 auto const A1DeepFrozenByIssuer = [&](Account const& A1, Account const& A2, Env& env) {
590 A1FrozenByIssuer(A1, A2, env);
591 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
592 env.close();
593
594 return true;
595 };
596
597 auto const changeBalances =
598 [&](Account const& A1, Account const& A2, ApplyContext& ac, int A1Balance, int A2Balance) {
599 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
600 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
601
602 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
603 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
604
605 ac.view().update(sleA1);
606 ac.view().update(sleA2);
607 };
608
609 // test: imitating frozen A1 making a payment to A2.
611 {{"Attempting to move frozen funds"}},
612 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
613 changeBalances(A1, A2, ac, -900, -1100);
614 return true;
615 },
616 XRPAmount{},
617 STTx{ttPAYMENT, [](STObject& tx) {}},
619 A1FrozenByIssuer);
620
621 // test: imitating deep frozen A1 making a payment to A2.
623 {{"Attempting to move frozen funds"}},
624 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
625 changeBalances(A1, A2, ac, -900, -1100);
626 return true;
627 },
628 XRPAmount{},
629 STTx{ttPAYMENT, [](STObject& tx) {}},
631 A1DeepFrozenByIssuer);
632
633 // test: imitating A2 making a payment to deep frozen A1.
635 {{"Attempting to move frozen funds"}},
636 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
637 changeBalances(A1, A2, ac, -1100, -900);
638 return true;
639 },
640 XRPAmount{},
641 STTx{ttPAYMENT, [](STObject& tx) {}},
643 A1DeepFrozenByIssuer);
644 }
645
646 void
648 {
649 using namespace test::jtx;
650 testcase << "XRP balance checks";
651
653 {{"Cannot return non-native STAmount as XRPAmount"}},
654 [](Account const& A1, Account const& A2, ApplyContext& ac) {
655 // non-native balance
656 auto const sle = ac.view().peek(keylet::account(A1.id()));
657 if (!sle)
658 return false;
659 STAmount const nonNative(A2["USD"](51));
660 sle->setFieldAmount(sfBalance, nonNative);
661 ac.view().update(sle);
662 return true;
663 });
664
666 {{"incorrect account XRP balance"}, {"XRP net change was positive: 99999999000000001"}},
667 [this](Account const& A1, Account const&, ApplyContext& ac) {
668 // balance exceeds genesis amount
669 auto const sle = ac.view().peek(keylet::account(A1.id()));
670 if (!sle)
671 return false;
672 // Use `drops(1)` to bypass a call to STAmount::canonicalize
673 // with an invalid value
674 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
675 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
676 ac.view().update(sle);
677 return true;
678 });
679
681 {{"incorrect account XRP balance"}, {"XRP net change of -1000000001 doesn't match fee 0"}},
682 [this](Account const& A1, Account const&, ApplyContext& ac) {
683 // balance is negative
684 auto const sle = ac.view().peek(keylet::account(A1.id()));
685 if (!sle)
686 return false;
687 sle->setFieldAmount(sfBalance, STAmount{1, true});
688 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
689 ac.view().update(sle);
690 return true;
691 });
692 }
693
694 void
696 {
697 using namespace test::jtx;
698 using namespace std::string_literals;
699 testcase << "Transaction fee checks";
700
702 {{"fee paid was negative: -1"}, {"XRP net change of 0 doesn't match fee -1"}},
703 [](Account const&, Account const&, ApplyContext&) { return true; },
704 XRPAmount{-1});
705
707 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
708 {"XRP net change of 0 doesn't match fee "s + to_string(INITIAL_XRP)}},
709 [](Account const&, Account const&, ApplyContext&) { return true; },
711
713 {{"fee paid is 20 exceeds fee specified in transaction."}, {"XRP net change of 0 doesn't match fee 20"}},
714 [](Account const&, Account const&, ApplyContext&) { return true; },
715 XRPAmount{20},
716 STTx{ttACCOUNT_SET, [](STObject& tx) { tx.setFieldAmount(sfFee, XRPAmount{10}); }});
717 }
718
719 void
721 {
722 using namespace test::jtx;
723 testcase << "no bad offers";
724
725 doInvariantCheck({{"offer with a bad amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
726 // offer with negative takerpays
727 auto const sle = ac.view().peek(keylet::account(A1.id()));
728 if (!sle)
729 return false;
730 auto sleNew = std::make_shared<SLE>(keylet::offer(A1.id(), (*sle)[sfSequence]));
731 sleNew->setAccountID(sfAccount, A1.id());
732 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
733 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
734 ac.view().insert(sleNew);
735 return true;
736 });
737
738 doInvariantCheck({{"offer with a bad amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
739 // offer with negative takergets
740 auto const sle = ac.view().peek(keylet::account(A1.id()));
741 if (!sle)
742 return false;
743 auto sleNew = std::make_shared<SLE>(keylet::offer(A1.id(), (*sle)[sfSequence]));
744 sleNew->setAccountID(sfAccount, A1.id());
745 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
746 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
747 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
748 ac.view().insert(sleNew);
749 return true;
750 });
751
752 doInvariantCheck({{"offer with a bad amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
753 // offer XRP to XRP
754 auto const sle = ac.view().peek(keylet::account(A1.id()));
755 if (!sle)
756 return false;
757 auto sleNew = std::make_shared<SLE>(keylet::offer(A1.id(), (*sle)[sfSequence]));
758 sleNew->setAccountID(sfAccount, A1.id());
759 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
760 sleNew->setFieldAmount(sfTakerPays, XRP(10));
761 sleNew->setFieldAmount(sfTakerGets, XRP(11));
762 ac.view().insert(sleNew);
763 return true;
764 });
765 }
766
767 void
769 {
770 using namespace test::jtx;
771 testcase << "no zero escrow";
772
774 {{"XRP net change of -1000000 doesn't match fee 0"}, {"escrow specifies invalid amount"}},
775 [](Account const& A1, Account const&, ApplyContext& ac) {
776 // escrow with negative amount
777 auto const sle = ac.view().peek(keylet::account(A1.id()));
778 if (!sle)
779 return false;
780 auto sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
781 sleNew->setFieldAmount(sfAmount, XRP(-1));
782 ac.view().insert(sleNew);
783 return true;
784 });
785
787 {{"XRP net change was positive: 100000000000000001"}, {"escrow specifies invalid amount"}},
788 [](Account const& A1, Account const&, ApplyContext& ac) {
789 // escrow with too-large amount
790 auto const sle = ac.view().peek(keylet::account(A1.id()));
791 if (!sle)
792 return false;
793 auto sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
794 // Use `drops(1)` to bypass a call to STAmount::canonicalize
795 // with an invalid value
796 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
797 ac.view().insert(sleNew);
798 return true;
799 });
800
801 // IOU < 0
803 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
804 // escrow with too-little iou
805 auto const sle = ac.view().peek(keylet::account(A1.id()));
806 if (!sle)
807 return false;
808 auto sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
809
810 Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
811 STAmount amt(usd, -1);
812 sleNew->setFieldAmount(sfAmount, amt);
813 ac.view().insert(sleNew);
814 return true;
815 });
816
817 // IOU bad currency
819 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
820 // escrow with bad iou currency
821 auto const sle = ac.view().peek(keylet::account(A1.id()));
822 if (!sle)
823 return false;
824 auto sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
825
826 Issue const bad{badCurrency(), AccountID(0x4985601)};
827 STAmount amt(bad, 1);
828 sleNew->setFieldAmount(sfAmount, amt);
829 ac.view().insert(sleNew);
830 return true;
831 });
832
833 // MPT < 0
835 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
836 // escrow with too-little mpt
837 auto const sle = ac.view().peek(keylet::account(A1.id()));
838 if (!sle)
839 return false;
840 auto sleNew = std::make_shared<SLE>(keylet::escrow(A1, (*sle)[sfSequence] + 2));
841
842 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
843 STAmount amt(mpt, -1);
844 sleNew->setFieldAmount(sfAmount, amt);
845 ac.view().insert(sleNew);
846 return true;
847 });
848
849 // MPT OutstandingAmount < 0
851 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
852 // mptissuance outstanding is negative
853 auto const sle = ac.view().peek(keylet::account(A1.id()));
854 if (!sle)
855 return false;
856
857 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
858 auto sleNew = std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
859 sleNew->setFieldU64(sfOutstandingAmount, -1);
860 ac.view().insert(sleNew);
861 return true;
862 });
863
864 // MPT LockedAmount < 0
866 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
867 // mptissuance locked is less than locked
868 auto const sle = ac.view().peek(keylet::account(A1.id()));
869 if (!sle)
870 return false;
871
872 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
873 auto sleNew = std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
874 sleNew->setFieldU64(sfLockedAmount, -1);
875 ac.view().insert(sleNew);
876 return true;
877 });
878
879 // MPT OutstandingAmount < LockedAmount
881 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
882 // mptissuance outstanding is less than locked
883 auto const sle = ac.view().peek(keylet::account(A1.id()));
884 if (!sle)
885 return false;
886
887 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
888 auto sleNew = std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
889 sleNew->setFieldU64(sfOutstandingAmount, 1);
890 sleNew->setFieldU64(sfLockedAmount, 10);
891 ac.view().insert(sleNew);
892 return true;
893 });
894
895 // MPT MPTAmount < 0
897 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
898 // mptoken amount is negative
899 auto const sle = ac.view().peek(keylet::account(A1.id()));
900 if (!sle)
901 return false;
902
903 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
904 auto sleNew = std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
905 sleNew->setFieldU64(sfMPTAmount, -1);
906 ac.view().insert(sleNew);
907 return true;
908 });
909
910 // MPT LockedAmount < 0
912 {{"escrow specifies invalid amount"}}, [](Account const& A1, Account const&, ApplyContext& ac) {
913 // mptoken locked amount is negative
914 auto const sle = ac.view().peek(keylet::account(A1.id()));
915 if (!sle)
916 return false;
917
918 MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
919 auto sleNew = std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
920 sleNew->setFieldU64(sfLockedAmount, -1);
921 ac.view().insert(sleNew);
922 return true;
923 });
924 }
925
926 void
928 {
929 using namespace test::jtx;
930 testcase << "valid new account root";
931
932 doInvariantCheck({{"account root created illegally"}}, [](Account const&, Account const&, ApplyContext& ac) {
933 // Insert a new account root created by a non-payment into
934 // the view.
935 Account const A3{"A3"};
936 Keylet const acctKeylet = keylet::account(A3);
937 auto const sleNew = std::make_shared<SLE>(acctKeylet);
938 ac.view().insert(sleNew);
939 return true;
940 });
941
943 {{"multiple accounts created in a single transaction"}},
944 [](Account const&, Account const&, ApplyContext& ac) {
945 // Insert two new account roots into the view.
946 {
947 Account const A3{"A3"};
948 Keylet const acctKeylet = keylet::account(A3);
949 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
950 ac.view().insert(sleA3);
951 }
952 {
953 Account const A4{"A4"};
954 Keylet const acctKeylet = keylet::account(A4);
955 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
956 ac.view().insert(sleA4);
957 }
958 return true;
959 });
960
962 {{"account created with wrong starting sequence number"}},
963 [](Account const&, Account const&, ApplyContext& ac) {
964 // Insert a new account root with the wrong starting sequence.
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, ac.view().seq() + 1);
969 ac.view().insert(sleNew);
970 return true;
971 },
972 XRPAmount{},
973 STTx{ttPAYMENT, [](STObject& tx) {}});
974
976 {{"pseudo-account created by a wrong transaction type"}},
977 [](Account const&, Account const&, ApplyContext& ac) {
978 Account const A3{"A3"};
979 Keylet const acctKeylet = keylet::account(A3);
980 auto const sleNew = std::make_shared<SLE>(acctKeylet);
981 sleNew->setFieldU32(sfSequence, 0);
982 sleNew->setFieldH256(sfAMMID, uint256(1));
983 sleNew->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDefaultRipple);
984 ac.view().insert(sleNew);
985 return true;
986 },
987 XRPAmount{},
988 STTx{ttPAYMENT, [](STObject& tx) {}});
989
991 {{"account created with wrong starting sequence number"}},
992 [](Account const&, Account const&, ApplyContext& ac) {
993 Account const A3{"A3"};
994 Keylet const acctKeylet = keylet::account(A3);
995 auto const sleNew = std::make_shared<SLE>(acctKeylet);
996 sleNew->setFieldU32(sfSequence, ac.view().seq());
997 sleNew->setFieldH256(sfAMMID, uint256(1));
998 sleNew->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
999 ac.view().insert(sleNew);
1000 return true;
1001 },
1002 XRPAmount{},
1003 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1004
1006 {{"pseudo-account created with wrong flags"}},
1007 [](Account const&, Account const&, ApplyContext& ac) {
1008 Account const A3{"A3"};
1009 Keylet const acctKeylet = keylet::account(A3);
1010 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1011 sleNew->setFieldU32(sfSequence, 0);
1012 sleNew->setFieldH256(sfAMMID, uint256(1));
1013 sleNew->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple);
1014 ac.view().insert(sleNew);
1015 return true;
1016 },
1017 XRPAmount{},
1018 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
1019
1021 {{"pseudo-account created with wrong flags"}},
1022 [](Account const&, Account const&, ApplyContext& ac) {
1023 Account const A3{"A3"};
1024 Keylet const acctKeylet = keylet::account(A3);
1025 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1026 sleNew->setFieldU32(sfSequence, 0);
1027 sleNew->setFieldH256(sfAMMID, uint256(1));
1028 sleNew->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth | lsfRequireDestTag);
1029 ac.view().insert(sleNew);
1030 return true;
1031 },
1032 XRPAmount{},
1033 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1034 }
1035
1036 void
1038 {
1039 using namespace test::jtx;
1040 testcase << "NFTokenPage";
1041
1042 // lambda that returns an STArray of NFTokenIDs.
1043 uint256 const firstNFTID("0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
1044 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
1045 SOTemplate const* nfTokenTemplate = InnerObjectFormats::getInstance().findSOTemplateBySField(sfNFToken);
1046
1047 uint256 nftID(firstNFTID);
1048 STArray ret;
1049 for (int i = 0; i < nftCount; ++i)
1050 {
1051 STObject newNFToken(*nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
1052 object.setFieldH256(sfNFTokenID, nftID);
1053 });
1054 ret.push_back(std::move(newNFToken));
1055 ++nftID;
1056 }
1057 return ret;
1058 };
1059
1061 {{"NFT page has invalid size"}}, [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1063 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
1064
1065 ac.view().insert(nftPage);
1066 return true;
1067 });
1068
1070 {{"NFT page has invalid size"}}, [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1072 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
1073
1074 ac.view().insert(nftPage);
1075 return true;
1076 });
1077
1079 {{"NFTs on page are not sorted"}}, [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1080 STArray nfTokens = makeNFTokenIDs(2);
1081 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
1082
1084 nftPage->setFieldArray(sfNFTokens, nfTokens);
1085
1086 ac.view().insert(nftPage);
1087 return true;
1088 });
1089
1091 {{"NFT contains empty URI"}}, [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1092 STArray nfTokens = makeNFTokenIDs(1);
1093 nfTokens[0].setFieldVL(sfURI, Blob{});
1094
1096 nftPage->setFieldArray(sfNFTokens, nfTokens);
1097
1098 ac.view().insert(nftPage);
1099 return true;
1100 });
1101
1103 {{"NFT page is improperly linked"}},
1104 [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1106 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1107 nftPage->setFieldH256(sfPreviousPageMin, keylet::nftpage_max(A1).key);
1108
1109 ac.view().insert(nftPage);
1110 return true;
1111 });
1112
1114 {{"NFT page is improperly linked"}},
1115 [&makeNFTokenIDs](Account const& A1, Account const& A2, ApplyContext& ac) {
1117 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1118 nftPage->setFieldH256(sfPreviousPageMin, keylet::nftpage_min(A2).key);
1119
1120 ac.view().insert(nftPage);
1121 return true;
1122 });
1123
1125 {{"NFT page is improperly linked"}},
1126 [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1128 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1129 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1130
1131 ac.view().insert(nftPage);
1132 return true;
1133 });
1134
1136 {{"NFT page is improperly linked"}},
1137 [&makeNFTokenIDs](Account const& A1, Account const& A2, ApplyContext& ac) {
1138 STArray nfTokens = makeNFTokenIDs(1);
1139 auto nftPage = std::make_shared<SLE>(
1140 keylet::nftpage(keylet::nftpage_max(A1), ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1141 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1142 nftPage->setFieldH256(sfNextPageMin, keylet::nftpage_max(A2).key);
1143
1144 ac.view().insert(nftPage);
1145 return true;
1146 });
1147
1149 {{"NFT found in incorrect page"}}, [&makeNFTokenIDs](Account const& A1, Account const&, ApplyContext& ac) {
1150 STArray nfTokens = makeNFTokenIDs(2);
1151 auto nftPage = std::make_shared<SLE>(
1152 keylet::nftpage(keylet::nftpage_max(A1), (nfTokens[1].getFieldH256(sfNFTokenID))));
1153 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1154
1155 ac.view().insert(nftPage);
1156 return true;
1157 });
1158 }
1159
1162 ApplyContext& ac,
1163 test::jtx::Account const& A1,
1164 test::jtx::Account const& A2,
1165 std::uint32_t numCreds = 2,
1166 std::uint32_t seq = 10)
1167 {
1168 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), seq);
1169 auto sle = std::make_shared<SLE>(pdKeylet);
1170
1171 sle->setAccountID(sfOwner, A1);
1172 sle->setFieldU32(sfSequence, seq);
1173
1174 if (numCreds)
1175 {
1176 // This array is sorted naturally, but if you willing to change this
1177 // behavior don't forget to use credentials::makeSorted
1178 STArray credentials(sfAcceptedCredentials, numCreds);
1179 for (std::size_t n = 0; n < numCreds; ++n)
1180 {
1181 auto cred = STObject::makeInnerObject(sfCredential);
1182 cred.setAccountID(sfIssuer, A2);
1183 auto credType = "cred_type" + std::to_string(n);
1184 cred.setFieldVL(sfCredentialType, Slice(credType.c_str(), credType.size()));
1185 credentials.push_back(std::move(cred));
1186 }
1187 sle->setFieldArray(sfAcceptedCredentials, credentials);
1188 }
1189
1190 ac.view().insert(sle);
1191 return sle;
1192 };
1193
1194 void
1196 {
1197 using namespace test::jtx;
1198
1199 bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
1202
1203 testcase << "PermissionedDomain" + std::string(fixPDEnabled ? " fix" : "");
1204
1206 Env(*this, features),
1207 {{"permissioned domain with no rules."}},
1208 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1209 return createPermissionedDomain(ac, A1, A2, 0).get();
1210 },
1211 XRPAmount{},
1212 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1213 fixPDEnabled ? failTers : badTers);
1214
1215 testcase << "PermissionedDomain 2";
1216
1217 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1219 Env(*this, features),
1220 {{"permissioned domain bad credentials size " + std::to_string(tooBig)}},
1221 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1222 return !!createPermissionedDomain(ac, A1, A2, tooBig);
1223 },
1224 XRPAmount{},
1225 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1226 fixPDEnabled ? failTers : badTers);
1227
1228 testcase << "PermissionedDomain 3";
1230 Env(*this, features),
1231 {{"permissioned domain credentials aren't sorted"}},
1232 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1233 auto slePd = createPermissionedDomain(ac, A1, A2, 0);
1234
1235 STArray credentials(sfAcceptedCredentials, 2);
1236 for (std::size_t n = 0; n < 2; ++n)
1237 {
1238 auto cred = STObject::makeInnerObject(sfCredential);
1239 cred.setAccountID(sfIssuer, A2);
1240 auto credType = std::string("cred_type") + std::to_string(9 - n);
1241 cred.setFieldVL(sfCredentialType, Slice(credType.c_str(), credType.size()));
1242 credentials.push_back(std::move(cred));
1243 }
1244 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1245 ac.view().update(slePd);
1246 return true;
1247 },
1248 XRPAmount{},
1249 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1250 fixPDEnabled ? failTers : badTers);
1251
1252 testcase << "PermissionedDomain 4";
1254 Env(*this, features),
1255 {{"permissioned domain credentials aren't unique"}},
1256 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1257 auto slePd = createPermissionedDomain(ac, A1, A2, 0);
1258
1259 STArray credentials(sfAcceptedCredentials, 2);
1260 for (std::size_t n = 0; n < 2; ++n)
1261 {
1262 auto cred = STObject::makeInnerObject(sfCredential);
1263 cred.setAccountID(sfIssuer, A2);
1264 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1265 credentials.push_back(std::move(cred));
1266 }
1267 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1268 ac.view().update(slePd);
1269 return true;
1270 },
1271 XRPAmount{},
1272 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1273 fixPDEnabled ? failTers : badTers);
1274
1275 testcase << "PermissionedDomain Set 1";
1277 Env(*this, features),
1278 {{"permissioned domain with no rules."}},
1279 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1280 // create PD
1281 auto slePd = createPermissionedDomain(ac, A1, A2);
1282
1283 // update PD with empty rules
1284 {
1285 STArray credentials(sfAcceptedCredentials, 2);
1286 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1287 ac.view().update(slePd);
1288 }
1289
1290 return true;
1291 },
1292 XRPAmount{},
1293 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1294 fixPDEnabled ? failTers : badTers);
1295
1296 testcase << "PermissionedDomain Set 2";
1298 Env(*this, features),
1299 {{"permissioned domain bad credentials size " + std::to_string(tooBig)}},
1300 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1301 // create PD
1302 auto slePd = createPermissionedDomain(ac, A1, A2);
1303
1304 // update PD
1305 {
1306 STArray credentials(sfAcceptedCredentials, tooBig);
1307
1308 for (std::size_t n = 0; n < tooBig; ++n)
1309 {
1310 auto cred = STObject::makeInnerObject(sfCredential);
1311 cred.setAccountID(sfIssuer, A2);
1312 auto credType = "cred_type2" + std::to_string(n);
1313 cred.setFieldVL(sfCredentialType, Slice(credType.c_str(), credType.size()));
1314 credentials.push_back(std::move(cred));
1315 }
1316
1317 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1318 ac.view().update(slePd);
1319 }
1320
1321 return true;
1322 },
1323 XRPAmount{},
1324 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1325 fixPDEnabled ? failTers : badTers);
1326
1327 testcase << "PermissionedDomain Set 3";
1329 Env(*this, features),
1330 {{"permissioned domain credentials aren't sorted"}},
1331 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1332 // create PD
1333 auto slePd = createPermissionedDomain(ac, A1, A2);
1334
1335 // update PD
1336 {
1337 STArray credentials(sfAcceptedCredentials, 2);
1338 for (std::size_t n = 0; n < 2; ++n)
1339 {
1340 auto cred = STObject::makeInnerObject(sfCredential);
1341 cred.setAccountID(sfIssuer, A2);
1342 auto credType = std::string("cred_type2") + std::to_string(9 - n);
1343 cred.setFieldVL(sfCredentialType, Slice(credType.c_str(), credType.size()));
1344 credentials.push_back(std::move(cred));
1345 }
1346
1347 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1348 ac.view().update(slePd);
1349 }
1350
1351 return true;
1352 },
1353 XRPAmount{},
1354 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1355 fixPDEnabled ? failTers : badTers);
1356
1357 testcase << "PermissionedDomain Set 4";
1359 Env(*this, features),
1360 {{"permissioned domain credentials aren't unique"}},
1361 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1362 // create PD
1363 auto slePd = createPermissionedDomain(ac, A1, A2);
1364
1365 // update PD
1366 {
1367 STArray credentials(sfAcceptedCredentials, 2);
1368 for (std::size_t n = 0; n < 2; ++n)
1369 {
1370 auto cred = STObject::makeInnerObject(sfCredential);
1371 cred.setAccountID(sfIssuer, A2);
1372 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1373 credentials.push_back(std::move(cred));
1374 }
1375 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1376 ac.view().update(slePd);
1377 }
1378
1379 return true;
1380 },
1381 XRPAmount{},
1382 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1383 fixPDEnabled ? failTers : badTers);
1384
1386
1387 std::vector<std::string> badMoreThan1{{"transaction affected more than 1 permissioned domain entry."}};
1389 std::vector<std::string> badNoDomains{{"no domain objects affected by"}};
1390 std::vector<std::string> badNotDeleted{{"domain object modified, but not deleted by "}};
1391 std::vector<std::string> badDeleted{{"domain object deleted by"}};
1392 std::vector<std::string> badTx{{"domain object(s) affected by an unauthorized transaction."}};
1393
1394 {
1395 testcase << "PermissionedDomain set 2 domains ";
1397 Env(*this, features),
1398 fixPDEnabled ? badMoreThan1 : emptyV,
1399 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1401 createPermissionedDomain(ac, A1, A2, 2, 11);
1402 return true;
1403 },
1404 XRPAmount{},
1405 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1406 fixPDEnabled ? failTers : goodTers);
1407 }
1408
1409 {
1410 testcase << "PermissionedDomain del 2 domains";
1411
1412 Env env1(*this, features);
1413
1414 Account const A1{"A1"};
1415 Account const A2{"A2"};
1416 env1.fund(XRP(1000), A1, A2);
1417 env1.close();
1418
1419 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1420 [[maybe_unused]] auto [seq2, pd2] = createPermissionedDomainEnv(env1, A1, A2);
1421 env1.close();
1422
1424 std::move(env1),
1425 A1,
1426 A2,
1427 fixPDEnabled ? badMoreThan1 : emptyV,
1428 [&pd1, &pd2](Account const&, Account const&, ApplyContext& ac) {
1429 auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
1430 auto sle2 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd2});
1431 ac.view().erase(sle1);
1432 ac.view().erase(sle2);
1433 return true;
1434 },
1435 XRPAmount{},
1436 STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
1437 fixPDEnabled ? failTers : goodTers);
1438 }
1439
1440 {
1441 testcase << "PermissionedDomain set 0 domains ";
1443 Env(*this, features),
1444 fixPDEnabled ? badNoDomains : emptyV,
1445 [](Account const&, Account const&, ApplyContext&) { return true; },
1446 XRPAmount{},
1447 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1448 fixPDEnabled ? badTers : goodTers);
1449 }
1450
1451 {
1452 testcase << "PermissionedDomain del 0 domains";
1453
1454 Env env1(*this, features);
1455
1456 Account const A1{"A1"};
1457 Account const A2{"A2"};
1458 env1.fund(XRP(1000), A1, A2);
1459 env1.close();
1460
1461 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1462 [[maybe_unused]] auto [seq2, pd2] = createPermissionedDomainEnv(env1, A1, A2);
1463 env1.close();
1464
1466 Env(*this, features),
1467 A1,
1468 A2,
1469 fixPDEnabled ? badNoDomains : emptyV,
1470 [](Account const&, Account const&, ApplyContext&) { return true; },
1471 XRPAmount{},
1472 STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
1473 fixPDEnabled ? badTers : goodTers);
1474 }
1475
1476 {
1477 testcase << "PermissionedDomain set, delete domain";
1478
1479 Env env1(*this, features);
1480
1481 Account const A1{"A1"};
1482 Account const A2{"A2"};
1483 env1.fund(XRP(1000), A1, A2);
1484 env1.close();
1485
1486 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1487 env1.close();
1488
1490 std::move(env1),
1491 A1,
1492 A2,
1493 fixPDEnabled ? badDeleted : emptyV,
1494 [&pd1](Account const&, Account const&, ApplyContext& ac) {
1495 auto sle1 = ac.view().peek({ltPERMISSIONED_DOMAIN, pd1});
1496 ac.view().erase(sle1);
1497 return true;
1498 },
1499 XRPAmount{},
1500 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1501 fixPDEnabled ? failTers : goodTers);
1502 }
1503
1504 {
1505 testcase << "PermissionedDomain del, create domain ";
1507 Env(*this, features),
1508 fixPDEnabled ? badNotDeleted : emptyV,
1509 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1511 return true;
1512 },
1513 XRPAmount{},
1514 STTx{ttPERMISSIONED_DOMAIN_DELETE, [](STObject&) {}},
1515 fixPDEnabled ? failTers : goodTers);
1516 }
1517
1518 {
1519 testcase << "PermissionedDomain invalid tx";
1520
1522 fixPDEnabled ? badTx : emptyV,
1523 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1525 return true;
1526 },
1527 XRPAmount{},
1528 STTx{ttPAYMENT, [](STObject&) {}},
1529 failTers);
1530 }
1531 }
1532
1533 void
1535 {
1536 testcase << "valid pseudo accounts";
1537
1538 using namespace jtx;
1539
1540 AccountID pseudoAccountID;
1541 Preclose createPseudo = [&, this](Account const& a, Account const& b, Env& env) {
1542 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1543
1544 // Create vault
1545 Vault vault{env};
1546 auto [tx, vKeylet] = vault.create({.owner = a, .asset = xrpAsset});
1547 env(tx);
1548 env.close();
1549 if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
1550 {
1551 pseudoAccountID = vSle->at(sfAccount);
1552 }
1553
1554 return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
1555 };
1556
1557 /* Cases to check
1558 "pseudo-account has 0 pseudo-account fields set"
1559 "pseudo-account has 2 pseudo-account fields set"
1560 "pseudo-account sequence changed"
1561 "pseudo-account flags are not set"
1562 "pseudo-account has a regular key"
1563 */
1564 struct Mod
1565 {
1566 std::string expectedFailure;
1567 std::function<void(SLE::pointer&)> func;
1568 };
1569 auto const mods = std::to_array<Mod>({
1570 {
1571 "pseudo-account has 0 pseudo-account fields set",
1572 [this](SLE::pointer& sle) {
1573 BEAST_EXPECT(sle->at(~sfVaultID));
1574 sle->at(~sfVaultID) = std::nullopt;
1575 },
1576 },
1577 {
1578 "pseudo-account sequence changed",
1579 [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
1580 },
1581 {
1582 "pseudo-account flags are not set",
1583 [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
1584 },
1585 {
1586 "pseudo-account has a regular key",
1587 [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); },
1588 },
1589 });
1590
1591 for (auto const& mod : mods)
1592 {
1594 {{mod.expectedFailure}},
1595 [&](Account const& A1, Account const&, ApplyContext& ac) {
1596 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1597 if (!sle)
1598 return false;
1599 mod.func(sle);
1600 ac.view().update(sle);
1601 return true;
1602 },
1603 XRPAmount{},
1604 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1606 createPseudo);
1607 }
1608 for (auto const pField : getPseudoAccountFields())
1609 {
1610 // createPseudo creates a vault, so sfVaultID will be set, and
1611 // setting it again will not cause an error
1612 if (pField == &sfVaultID)
1613 continue;
1615 {{"pseudo-account has 2 pseudo-account fields set"}},
1616 [&](Account const& A1, Account const&, ApplyContext& ac) {
1617 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1618 if (!sle)
1619 return false;
1620
1621 auto const vaultID = ~sle->at(~sfVaultID);
1622 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
1623 sle->setFieldH256(*pField, *vaultID);
1624
1625 ac.view().update(sle);
1626 return true;
1627 },
1628 XRPAmount{},
1629 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1631 createPseudo);
1632 }
1633
1634 // Take one of the regular accounts and set the sequence to 0, which
1635 // will make it look like a pseudo-account
1637 {{"pseudo-account has 0 pseudo-account fields set"},
1638 {"pseudo-account sequence changed"},
1639 {"pseudo-account flags are not set"}},
1640 [&](Account const& A1, Account const&, ApplyContext& ac) {
1641 auto sle = ac.view().peek(keylet::account(A1.id()));
1642 if (!sle)
1643 return false;
1644 sle->at(sfSequence) = 0;
1645 ac.view().update(sle);
1646 return true;
1647 });
1648 }
1649
1652 test::jtx::Env& env,
1653 test::jtx::Account const& A1,
1654 test::jtx::Account const& A2,
1655 std::uint32_t numCreds = 2)
1656 {
1657 using namespace test::jtx;
1658
1659 pdomain::Credentials credentials;
1660
1661 for (std::size_t n = 0; n < numCreds; ++n)
1662 {
1663 auto credType = "cred_type" + std::to_string(n);
1664 credentials.push_back({A2, credType});
1665 }
1666
1667 std::uint32_t const seq = env.seq(A1);
1668 env(pdomain::setTx(A1, credentials));
1669 uint256 key = pdomain::getNewDomain(env.meta());
1670
1671 // std::cout << "PD, acc: " << A1.id() << ", seq: " << seq << ", k: " <<
1672 // key << std::endl;
1673 return {seq, key};
1674 }
1675
1676 void
1678 {
1679 using namespace test::jtx;
1680
1681 bool const fixPDEnabled = features[fixPermissionedDomainInvariant];
1682
1683 testcase << "PermissionedDEX" + std::string(fixPDEnabled ? " fix" : "");
1684
1686 Env(*this, features),
1687 {{"domain doesn't exist"}},
1688 [](Account const& A1, Account const&, ApplyContext& ac) {
1689 Keylet const offerKey = keylet::offer(A1.id(), 10);
1690 auto sleOffer = std::make_shared<SLE>(offerKey);
1691 sleOffer->setAccountID(sfAccount, A1);
1692 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1693 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1694 ac.view().insert(sleOffer);
1695 return true;
1696 },
1697 XRPAmount{},
1698 STTx{
1699 ttOFFER_CREATE,
1700 [](STObject& tx) {
1701 tx.setFieldH256(
1702 sfDomainID,
1703 uint256{"F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1704 "70F3649CE134E5"});
1705 Account const A1{"A1"};
1706 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1707 tx.setFieldAmount(sfTakerGets, XRP(1));
1708 }},
1710
1711 // missing domain ID in offer object
1713 Env(*this, features),
1714 {{"hybrid offer is malformed"}},
1715 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1716 Keylet const offerKey = keylet::offer(A2.id(), 10);
1717 auto sleOffer = std::make_shared<SLE>(offerKey);
1718 sleOffer->setAccountID(sfAccount, A2);
1719 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1720 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1721 sleOffer->setFlag(lsfHybrid);
1722
1723 STArray bookArr;
1724 bookArr.push_back(STObject::makeInnerObject(sfBook));
1725 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1726 ac.view().insert(sleOffer);
1727 return true;
1728 },
1729 XRPAmount{},
1730 STTx{ttOFFER_CREATE, [&](STObject&) {}},
1732
1733 // more than one entry in sfAdditionalBooks
1734 {
1735 Env env1(*this, features);
1736
1737 Account const A1{"A1"};
1738 Account const A2{"A2"};
1739 env1.fund(XRP(1000), A1, A2);
1740 env1.close();
1741
1742 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1743 env1.close();
1744
1746 std::move(env1),
1747 A1,
1748 A2,
1749 {{"hybrid offer is malformed"}},
1750 [&pd1](Account const& A1, Account const& A2, ApplyContext& ac) {
1751 Keylet const offerKey = keylet::offer(A2.id(), 10);
1752 auto sleOffer = std::make_shared<SLE>(offerKey);
1753 sleOffer->setAccountID(sfAccount, A2);
1754 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1755 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1756 sleOffer->setFlag(lsfHybrid);
1757 sleOffer->setFieldH256(sfDomainID, pd1);
1758
1759 STArray bookArr;
1760 bookArr.push_back(STObject::makeInnerObject(sfBook));
1761 bookArr.push_back(STObject::makeInnerObject(sfBook));
1762 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1763 ac.view().insert(sleOffer);
1764 return true;
1765 },
1766 XRPAmount{},
1767 STTx{ttOFFER_CREATE, [&](STObject&) {}},
1769 }
1770
1771 // hybrid offer missing sfAdditionalBooks
1772 {
1773 Env env1(*this, features);
1774
1775 Account const A1{"A1"};
1776 Account const A2{"A2"};
1777 env1.fund(XRP(1000), A1, A2);
1778 env1.close();
1779
1780 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1781 env1.close();
1782
1784 std::move(env1),
1785 A1,
1786 A2,
1787 {{"hybrid offer is malformed"}},
1788 [&pd1](Account const& A1, Account const& A2, ApplyContext& ac) {
1789 Keylet const offerKey = keylet::offer(A2.id(), 10);
1790 auto sleOffer = std::make_shared<SLE>(offerKey);
1791 sleOffer->setAccountID(sfAccount, A2);
1792 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1793 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1794 sleOffer->setFlag(lsfHybrid);
1795 sleOffer->setFieldH256(sfDomainID, pd1);
1796 ac.view().insert(sleOffer);
1797 return true;
1798 },
1799 XRPAmount{},
1800 STTx{ttOFFER_CREATE, [&](STObject&) {}},
1802 }
1803
1804 {
1805 Env env1(*this, features);
1806
1807 Account const A1{"A1"};
1808 Account const A2{"A2"};
1809 env1.fund(XRP(1000), A1, A2);
1810 env1.close();
1811
1812 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1813 [[maybe_unused]] auto [seq2, pd2] = createPermissionedDomainEnv(env1, A1, A2);
1814 env1.close();
1815
1817 std::move(env1),
1818 A1,
1819 A2,
1820 {{"transaction consumed wrong domains"}},
1821 [&pd1](Account const& A1, Account const& A2, ApplyContext& ac) {
1822 Keylet const offerKey = keylet::offer(A2.id(), 10);
1823 auto sleOffer = std::make_shared<SLE>(offerKey);
1824 sleOffer->setAccountID(sfAccount, A2);
1825 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1826 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1827 sleOffer->setFieldH256(sfDomainID, pd1);
1828 ac.view().insert(sleOffer);
1829 return true;
1830 },
1831 XRPAmount{},
1832 STTx{
1833 ttOFFER_CREATE,
1834 [&pd2, &A1](STObject& tx) {
1835 tx.setFieldH256(sfDomainID, pd2);
1836 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1837 tx.setFieldAmount(sfTakerGets, XRP(1));
1838 }},
1840 }
1841
1842 {
1843 Env env1(*this, features);
1844
1845 Account const A1{"A1"};
1846 Account const A2{"A2"};
1847 env1.fund(XRP(1000), A1, A2);
1848 env1.close();
1849
1850 [[maybe_unused]] auto [seq1, pd1] = createPermissionedDomainEnv(env1, A1, A2);
1851 env1.close();
1852
1854 std::move(env1),
1855 A1,
1856 A2,
1857 {{"domain transaction affected regular offers"}},
1858 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1859 Keylet const offerKey = keylet::offer(A2.id(), 10);
1860 auto sleOffer = std::make_shared<SLE>(offerKey);
1861 sleOffer->setAccountID(sfAccount, A2);
1862 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1863 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1864 ac.view().insert(sleOffer);
1865 return true;
1866 },
1867 XRPAmount{},
1868 STTx{
1869 ttOFFER_CREATE,
1870 [&](STObject& tx) {
1871 Account const A1{"A1"};
1872 tx.setFieldH256(sfDomainID, pd1);
1873 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1874 tx.setFieldAmount(sfTakerGets, XRP(1));
1875 }},
1877 }
1878 }
1879
1880 Keylet
1882 {
1883 using namespace jtx;
1884
1885 // Create vault
1886 uint256 vaultID;
1887 Vault vault{env};
1888 auto [tx, vKeylet] = vault.create({.owner = a, .asset = asset});
1889 env(tx);
1890 BEAST_EXPECT(env.le(vKeylet));
1891
1892 vaultID = vKeylet.key;
1893
1894 // Create Loan Broker
1895 using namespace loanBroker;
1896
1897 auto const loanBrokerKeylet = keylet::loanbroker(a.id(), env.seq(a));
1898 // Create a Loan Broker with all default values.
1899 env(set(a, vaultID), fee(increment));
1900
1901 return loanBrokerKeylet;
1902 };
1903
1904 void
1906 {
1907 testcase("no modified unmodifiable fields");
1908 using namespace jtx;
1909
1910 // Initialize with a placeholder value because there's no default ctor
1911 Keylet loanBrokerKeylet = keylet::amendments();
1912 Preclose createLoanBroker = [&, this](Account const& a, Account const& b, Env& env) {
1913 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1914
1915 loanBrokerKeylet = this->createLoanBroker(a, env, xrpAsset);
1916 return BEAST_EXPECT(env.le(loanBrokerKeylet));
1917 };
1918
1919 {
1920 auto const mods = std::to_array<std::function<void(SLE::pointer&)>>({
1921 [](SLE::pointer& sle) { sle->at(sfSequence) += 1; },
1922 [](SLE::pointer& sle) { sle->at(sfOwnerNode) += 1; },
1923 [](SLE::pointer& sle) { sle->at(sfVaultNode) += 1; },
1924 [](SLE::pointer& sle) { sle->at(sfVaultID) = uint256(1u); },
1925 [](SLE::pointer& sle) { sle->at(sfAccount) = sle->at(sfOwner); },
1926 [](SLE::pointer& sle) { sle->at(sfOwner) = sle->at(sfAccount); },
1927 [](SLE::pointer& sle) { sle->at(sfManagementFeeRate) += 1; },
1928 [](SLE::pointer& sle) { sle->at(sfCoverRateMinimum) += 1; },
1929 [](SLE::pointer& sle) { sle->at(sfCoverRateLiquidation) += 1; },
1930 [](SLE::pointer& sle) { sle->at(sfLedgerEntryType) += 1; },
1931 [](SLE::pointer& sle) { sle->at(sfLedgerIndex) = sle->at(sfVaultID).value(); },
1932 });
1933
1934 for (auto const& mod : mods)
1935 {
1937 {{"changed an unchangeable field"}},
1938 [&](Account const& A1, Account const&, ApplyContext& ac) {
1939 auto sle = ac.view().peek(loanBrokerKeylet);
1940 if (!sle)
1941 return false;
1942 mod(sle);
1943 ac.view().update(sle);
1944 return true;
1945 },
1946 XRPAmount{},
1947 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1950 }
1951 }
1952
1953 // TODO: Loan Object
1954
1955 {
1956 auto const mods = std::to_array<std::function<void(SLE::pointer&)>>({
1957 [](SLE::pointer& sle) { sle->at(sfLedgerEntryType) += 1; },
1958 [](SLE::pointer& sle) { sle->at(sfLedgerIndex) = uint256(1u); },
1959 });
1960
1961 for (auto const& mod : mods)
1962 {
1964 {{"changed an unchangeable field"}}, [&](Account const& A1, Account const&, ApplyContext& ac) {
1965 auto sle = ac.view().peek(keylet::account(A1.id()));
1966 if (!sle)
1967 return false;
1968 mod(sle);
1969 ac.view().update(sle);
1970 return true;
1971 });
1972 }
1973 }
1974 }
1975
1976 void
1978 {
1979 testcase << "valid loan broker";
1980
1981 using namespace jtx;
1982
1983 enum class Asset { XRP, IOU, MPT };
1984 auto const assetTypes = std::to_array({Asset::XRP, Asset::IOU, Asset::MPT});
1985
1986 for (auto const assetType : assetTypes)
1987 {
1988 // Initialize with a placeholder value because there's no default
1989 // ctor
1990 Keylet loanBrokerKeylet = keylet::amendments();
1991 Preclose createLoanBroker = [&, this](Account const& alice, Account const& issuer, Env& env) {
1992 PrettyAsset const asset = [&]() {
1993 switch (assetType)
1994 {
1995 case Asset::IOU: {
1996 PrettyAsset const iouAsset = issuer["IOU"];
1997 env(trust(alice, iouAsset(1000)));
1998 env(pay(issuer, alice, iouAsset(1000)));
1999 env.close();
2000 return iouAsset;
2001 }
2002
2003 case Asset::MPT: {
2004 MPTTester mptt{env, issuer, mptInitNoFund};
2006 PrettyAsset const mptAsset = mptt.issuanceID();
2007 mptt.authorize({.account = alice});
2008 env(pay(issuer, alice, mptAsset(1000)));
2009 env.close();
2010 return mptAsset;
2011 }
2012
2013 case Asset::XRP:
2014 default:
2015 return PrettyAsset{xrpIssue(), 1'000'000};
2016 }
2017 }();
2018 loanBrokerKeylet = this->createLoanBroker(alice, env, asset);
2019 return BEAST_EXPECT(env.le(loanBrokerKeylet));
2020 };
2021
2022 // Ensure the test scenarios are set up completely. The test cases
2023 // will need to recompute any of these values it needs for itself
2024 // rather than trying to return a bunch of items
2025 auto setupTest = [&, this](Account const& A1, Account const&, ApplyContext& ac)
2027 if (loanBrokerKeylet.type != ltLOAN_BROKER)
2028 return {};
2029 auto sleBroker = ac.view().peek(loanBrokerKeylet);
2030 if (!sleBroker)
2031 return {};
2032 if (!BEAST_EXPECT(sleBroker->at(sfOwnerCount) == 0))
2033 return {};
2034 // Need to touch sleBroker so that it is included in the
2035 // modified entries for the invariant to find
2036 ac.view().update(sleBroker);
2037
2038 // The pseudo-account holds the directory, so get it
2039 auto const pseudoAccountID = sleBroker->at(sfAccount);
2040 auto const pseudoAccountKeylet = keylet::account(pseudoAccountID);
2041 // Strictly speaking, we don't need to load the
2042 // ACCOUNT_ROOT, but check anyway
2043 auto slePseudo = ac.view().peek(pseudoAccountKeylet);
2044 if (!BEAST_EXPECT(slePseudo))
2045 return {};
2046 // Make sure the directory doesn't already exist
2047 auto const dirKeylet = keylet::ownerDir(pseudoAccountID);
2048 auto sleDir = ac.view().peek(dirKeylet);
2049 auto const describe = describeOwnerDir(pseudoAccountID);
2050 if (!sleDir)
2051 {
2052 // Create the directory
2053 BEAST_EXPECT(
2054 ::xrpl::directory::createRoot(ac.view(), dirKeylet, loanBrokerKeylet.key, describe) == 0);
2055
2056 sleDir = ac.view().peek(dirKeylet);
2057 }
2058
2059 return std::make_pair(slePseudo, sleDir);
2060 };
2061
2063 {{"Loan Broker with zero OwnerCount has multiple directory "
2064 "pages"}},
2065 [&setupTest, this](Account const& A1, Account const& A2, ApplyContext& ac) {
2066 auto test = setupTest(A1, A2, ac);
2067 if (!test || !test->first || !test->second)
2068 return false;
2069
2070 auto slePseudo = test->first;
2071 auto sleDir = test->second;
2072 auto const describe = describeOwnerDir(slePseudo->at(sfAccount));
2073
2074 BEAST_EXPECT(
2076 ac.view(),
2077 0,
2078 sleDir,
2079 0,
2080 sleDir,
2081 slePseudo->key(),
2082 keylet::page(sleDir->key(), 0),
2083 describe) == 1);
2084
2085 return true;
2086 },
2087 XRPAmount{},
2088 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
2091
2093 {{"Loan Broker with zero OwnerCount has multiple indexes in "
2094 "the Directory root"}},
2095 [&setupTest](Account const& A1, Account const& A2, ApplyContext& ac) {
2096 auto test = setupTest(A1, A2, ac);
2097 if (!test || !test->first || !test->second)
2098 return false;
2099
2100 auto slePseudo = test->first;
2101 auto sleDir = test->second;
2102 auto indexes = sleDir->getFieldV256(sfIndexes);
2103
2104 // Put some extra garbage into the directory
2105 for (auto const& key : {slePseudo->key(), sleDir->key()})
2106 {
2107 ::xrpl::directory::insertKey(ac.view(), sleDir, 0, false, indexes, key);
2108 }
2109
2110 return true;
2111 },
2112 XRPAmount{},
2113 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
2116
2118 {{"Loan Broker directory corrupt"}},
2119 [&setupTest](Account const& A1, Account const& A2, ApplyContext& ac) {
2120 auto test = setupTest(A1, A2, ac);
2121 if (!test || !test->first || !test->second)
2122 return false;
2123
2124 auto slePseudo = test->first;
2125 auto sleDir = test->second;
2126 auto const describe = describeOwnerDir(slePseudo->at(sfAccount));
2127 // Empty vector will overwrite the existing entry for the
2128 // holding, if any, avoiding the "has multiple indexes"
2129 // failure.
2130 STVector256 indexes;
2131
2132 // Put one meaningless key into the directory
2133 auto const key = keylet::account(Account("random").id()).key;
2134 ::xrpl::directory::insertKey(ac.view(), sleDir, 0, false, indexes, key);
2135
2136 return true;
2137 },
2138 XRPAmount{},
2139 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
2142
2144 {{"Loan Broker with zero OwnerCount has an unexpected entry in "
2145 "the directory"}},
2146 [&setupTest](Account const& A1, Account const& A2, ApplyContext& ac) {
2147 auto test = setupTest(A1, A2, ac);
2148 if (!test || !test->first || !test->second)
2149 return false;
2150
2151 auto slePseudo = test->first;
2152 auto sleDir = test->second;
2153 // Empty vector will overwrite the existing entry for the
2154 // holding, if any, avoiding the "has multiple indexes"
2155 // failure.
2156 STVector256 indexes;
2157
2158 ::xrpl::directory::insertKey(ac.view(), sleDir, 0, false, indexes, slePseudo->key());
2159
2160 return true;
2161 },
2162 XRPAmount{},
2163 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
2166
2168 {{"Loan Broker sequence number decreased"}},
2169 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2170 if (loanBrokerKeylet.type != ltLOAN_BROKER)
2171 return false;
2172 auto sleBroker = ac.view().peek(loanBrokerKeylet);
2173 if (!sleBroker)
2174 return false;
2175 if (!BEAST_EXPECT(sleBroker->at(sfLoanSequence) > 0))
2176 return false;
2177 // Need to touch sleBroker so that it is included in the
2178 // modified entries for the invariant to find
2179 ac.view().update(sleBroker);
2180
2181 sleBroker->at(sfLoanSequence) -= 1;
2182
2183 return true;
2184 },
2185 XRPAmount{},
2186 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
2189 }
2190 }
2191
2192 void
2194 {
2195 using namespace test::jtx;
2196
2197 struct AccountAmount
2198 {
2199 AccountID account;
2200 int amount;
2201 };
2202 struct Adjustments
2203 {
2204 std::optional<int> assetsTotal = {};
2205 std::optional<int> assetsAvailable = {};
2206 std::optional<int> lossUnrealized = {};
2207 std::optional<int> assetsMaximum = {};
2208 std::optional<int> sharesTotal = {};
2209 std::optional<int> vaultAssets = {};
2210 std::optional<AccountAmount> accountAssets = {};
2211 std::optional<AccountAmount> accountShares = {};
2212 };
2213 auto constexpr adjust = [&](ApplyView& ac, xrpl::Keylet keylet, Adjustments args) {
2214 auto sleVault = ac.peek(keylet);
2215 if (!sleVault)
2216 return false;
2217
2218 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
2219 auto sleShares = ac.peek(keylet::mptIssuance(mptIssuanceID));
2220 if (!sleShares)
2221 return false;
2222
2223 // These two fields are adjusted in absolute terms
2224 if (args.lossUnrealized)
2225 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
2226 if (args.assetsMaximum)
2227 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
2228
2229 // Remaining fields are adjusted in terms of difference
2230 if (args.assetsTotal)
2231 (*sleVault)[sfAssetsTotal] = *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
2232 if (args.assetsAvailable)
2233 (*sleVault)[sfAssetsAvailable] = *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
2234 ac.update(sleVault);
2235
2236 if (args.sharesTotal)
2237 {
2238 (*sleShares)[sfOutstandingAmount] = *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
2239 ac.update(sleShares);
2240 }
2241
2242 auto const assets = *(*sleVault)[sfAsset];
2243 auto const pseudoId = *(*sleVault)[sfAccount];
2244 if (args.vaultAssets)
2245 {
2246 if (assets.native())
2247 {
2248 auto slePseudoAccount = ac.peek(keylet::account(pseudoId));
2249 if (!slePseudoAccount)
2250 return false;
2251 (*slePseudoAccount)[sfBalance] = *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
2252 ac.update(slePseudoAccount);
2253 }
2254 else if (assets.holds<MPTIssue>())
2255 {
2256 auto const mptId = assets.get<MPTIssue>().getMptID();
2257 auto sleMPToken = ac.peek(keylet::mptoken(mptId, pseudoId));
2258 if (!sleMPToken)
2259 return false;
2260 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
2261 ac.update(sleMPToken);
2262 }
2263 else
2264 return false; // Not supporting testing with IOU
2265 }
2266
2267 if (args.accountAssets)
2268 {
2269 auto const& pair = *args.accountAssets;
2270 if (assets.native())
2271 {
2272 auto sleAccount = ac.peek(keylet::account(pair.account));
2273 if (!sleAccount)
2274 return false;
2275 (*sleAccount)[sfBalance] = *(*sleAccount)[sfBalance] + pair.amount;
2276 ac.update(sleAccount);
2277 }
2278 else if (assets.holds<MPTIssue>())
2279 {
2280 auto const mptID = assets.get<MPTIssue>().getMptID();
2281 auto sleMPToken = ac.peek(keylet::mptoken(mptID, pair.account));
2282 if (!sleMPToken)
2283 return false;
2284 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2285 ac.update(sleMPToken);
2286 }
2287 else
2288 return false; // Not supporting testing with IOU
2289 }
2290
2291 if (args.accountShares)
2292 {
2293 auto const& pair = *args.accountShares;
2294 auto sleMPToken = ac.peek(keylet::mptoken(mptIssuanceID, pair.account));
2295 if (!sleMPToken)
2296 return false;
2297 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2298 ac.update(sleMPToken);
2299 }
2300 return true;
2301 };
2302
2303 constexpr auto args = [](AccountID id, int adjustment, auto fn) -> Adjustments {
2304 Adjustments sample = {
2305 .assetsTotal = adjustment,
2306 .assetsAvailable = adjustment,
2307 .lossUnrealized = 0,
2308 .sharesTotal = adjustment,
2309 .vaultAssets = adjustment,
2310 .accountAssets = //
2311 AccountAmount{id, -adjustment},
2312 .accountShares = //
2313 AccountAmount{id, adjustment}};
2314 fn(sample);
2315 return sample;
2316 };
2317
2318 Account A3{"A3"};
2319 Account A4{"A4"};
2320 auto const precloseXrp = [&](Account const& A1, Account const& A2, Env& env) -> bool {
2321 env.fund(XRP(1000), A3, A4);
2322 Vault vault{env};
2323 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2324 env(tx);
2325 env(vault.deposit({.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2326 env(vault.deposit({.depositor = A2, .id = keylet.key, .amount = XRP(10)}));
2327 env(vault.deposit({.depositor = A3, .id = keylet.key, .amount = XRP(10)}));
2328 return true;
2329 };
2330
2331 testcase << "Vault general checks";
2333 {"vault deletion succeeded without deleting a vault"},
2334 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2335 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2336 auto sleVault = ac.view().peek(keylet);
2337 if (!sleVault)
2338 return false;
2339 ac.view().update(sleVault);
2340 return true;
2341 },
2342 XRPAmount{},
2343 STTx{ttVAULT_DELETE, [](STObject&) {}},
2345 [&](Account const& A1, Account const& A2, Env& env) {
2346 Vault vault{env};
2347 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2348 env(tx);
2349 return true;
2350 });
2351
2353 {"vault updated by a wrong transaction type"},
2354 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2355 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2356 auto sleVault = ac.view().peek(keylet);
2357 if (!sleVault)
2358 return false;
2359 ac.view().erase(sleVault);
2360 return true;
2361 },
2362 XRPAmount{},
2363 STTx{ttPAYMENT, [](STObject&) {}},
2365 [&](Account const& A1, Account const& A2, Env& env) {
2366 Vault vault{env};
2367 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2368 env(tx);
2369 return true;
2370 });
2371
2373 {"vault updated by a wrong transaction type"},
2374 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2375 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2376 auto sleVault = ac.view().peek(keylet);
2377 if (!sleVault)
2378 return false;
2379 ac.view().update(sleVault);
2380 return true;
2381 },
2382 XRPAmount{},
2383 STTx{ttPAYMENT, [](STObject&) {}},
2385 [&](Account const& A1, Account const& A2, Env& env) {
2386 Vault vault{env};
2387 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2388 env(tx);
2389 return true;
2390 });
2391
2393 {"vault updated by a wrong transaction type"},
2394 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2395 auto const sequence = ac.view().seq();
2396 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
2397 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2398 auto const vaultPage =
2399 ac.view().dirInsert(keylet::ownerDir(A1.id()), sleVault->key(), describeOwnerDir(A1.id()));
2400 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2401 ac.view().insert(sleVault);
2402 return true;
2403 },
2404 XRPAmount{},
2405 STTx{ttPAYMENT, [](STObject&) {}},
2407
2409 {"vault deleted by a wrong transaction type"},
2410 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2411 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2412 auto sleVault = ac.view().peek(keylet);
2413 if (!sleVault)
2414 return false;
2415 ac.view().erase(sleVault);
2416 return true;
2417 },
2418 XRPAmount{},
2419 STTx{ttVAULT_SET, [](STObject&) {}},
2421 [&](Account const& A1, Account const& A2, Env& env) {
2422 Vault vault{env};
2423 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2424 env(tx);
2425 return true;
2426 });
2427
2429 {"vault operation updated more than single vault"},
2430 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2431 {
2432 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2433 auto sleVault = ac.view().peek(keylet);
2434 if (!sleVault)
2435 return false;
2436 ac.view().erase(sleVault);
2437 }
2438 {
2439 auto const keylet = keylet::vault(A2.id(), ac.view().seq());
2440 auto sleVault = ac.view().peek(keylet);
2441 if (!sleVault)
2442 return false;
2443 ac.view().erase(sleVault);
2444 }
2445 return true;
2446 },
2447 XRPAmount{},
2448 STTx{ttVAULT_DELETE, [](STObject&) {}},
2450 [&](Account const& A1, Account const& A2, Env& env) {
2451 Vault vault{env};
2452 {
2453 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2454 env(tx);
2455 }
2456 {
2457 auto [tx, _] = vault.create({.owner = A2, .asset = xrpIssue()});
2458 env(tx);
2459 }
2460 return true;
2461 });
2462
2464 {"vault operation updated more than single vault"},
2465 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2466 auto const sequence = ac.view().seq();
2467 auto const insertVault = [&](Account const A) {
2468 auto const vaultKeylet = keylet::vault(A.id(), sequence);
2469 auto sleVault = std::make_shared<SLE>(vaultKeylet);
2470 auto const vaultPage =
2471 ac.view().dirInsert(keylet::ownerDir(A.id()), sleVault->key(), describeOwnerDir(A.id()));
2472 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2473 ac.view().insert(sleVault);
2474 };
2475 insertVault(A1);
2476 insertVault(A2);
2477 return true;
2478 },
2479 XRPAmount{},
2480 STTx{ttVAULT_CREATE, [](STObject&) {}},
2482
2484 {"deleted vault must also delete shares"},
2485 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2486 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2487 auto sleVault = ac.view().peek(keylet);
2488 if (!sleVault)
2489 return false;
2490 ac.view().erase(sleVault);
2491 return true;
2492 },
2493 XRPAmount{},
2494 STTx{ttVAULT_DELETE, [](STObject&) {}},
2496 [&](Account const& A1, Account const& A2, Env& env) {
2497 Vault vault{env};
2498 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2499 env(tx);
2500 return true;
2501 });
2502
2504 {"deleted vault must have no shares outstanding",
2505 "deleted vault must have no assets outstanding",
2506 "deleted vault must have no assets available"},
2507 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2508 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2509 auto sleVault = ac.view().peek(keylet);
2510 if (!sleVault)
2511 return false;
2512 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2513 if (!sleShares)
2514 return false;
2515 ac.view().erase(sleVault);
2516 ac.view().erase(sleShares);
2517 return true;
2518 },
2519 XRPAmount{},
2520 STTx{ttVAULT_DELETE, [](STObject&) {}},
2522 [&](Account const& A1, Account const& A2, Env& env) {
2523 Vault vault{env};
2524 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2525 env(tx);
2526 env(vault.deposit({.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2527 return true;
2528 });
2529
2531 {"vault operation succeeded without modifying a vault"},
2532 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2533 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2534 auto sleVault = ac.view().peek(keylet);
2535 if (!sleVault)
2536 return false;
2537 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2538 if (!sleShares)
2539 return false;
2540 // Note, such an "orphaned" update of MPT issuance attached to a
2541 // vault is invalid; ttVAULT_SET must also update Vault object.
2542 sleShares->setFieldH256(sfDomainID, uint256(13));
2543 ac.view().update(sleShares);
2544 return true;
2545 },
2546 XRPAmount{},
2547 STTx{ttVAULT_SET, [](STObject& tx) {}},
2549 precloseXrp,
2551
2553 {"vault operation succeeded without modifying a vault"},
2554 [&](Account const& A1, Account const& A2, ApplyContext& ac) { return true; },
2555 XRPAmount{},
2556 STTx{ttVAULT_CREATE, [](STObject&) {}},
2558 [&](Account const& A1, Account const& A2, Env& env) {
2559 Vault vault{env};
2560 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2561 env(tx);
2562 return true;
2563 });
2564
2566 {"vault operation succeeded without modifying a vault"},
2567 [&](Account const& A1, Account const& A2, ApplyContext& ac) { return true; },
2568 XRPAmount{},
2569 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2571 [&](Account const& A1, Account const& A2, Env& env) {
2572 Vault vault{env};
2573 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2574 env(tx);
2575 return true;
2576 });
2577
2579 {"vault operation succeeded without modifying a vault"},
2580 [&](Account const& A1, Account const& A2, ApplyContext& ac) { return true; },
2581 XRPAmount{},
2582 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2584 [&](Account const& A1, Account const& A2, Env& env) {
2585 Vault vault{env};
2586 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2587 env(tx);
2588 return true;
2589 });
2590
2592 {"vault operation succeeded without modifying a vault"},
2593 [&](Account const& A1, Account const& A2, ApplyContext& ac) { return true; },
2594 XRPAmount{},
2595 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
2597 [&](Account const& A1, Account const& A2, Env& env) {
2598 Vault vault{env};
2599 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2600 env(tx);
2601 return true;
2602 });
2603
2605 {"vault operation succeeded without modifying a vault"},
2606 [&](Account const& A1, Account const& A2, ApplyContext& ac) { return true; },
2607 XRPAmount{},
2608 STTx{ttVAULT_DELETE, [](STObject&) {}},
2610 [&](Account const& A1, Account const& A2, Env& env) {
2611 Vault vault{env};
2612 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2613 env(tx);
2614 return true;
2615 });
2616
2618 {"updated vault must have shares"},
2619 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2620 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2621 auto sleVault = ac.view().peek(keylet);
2622 if (!sleVault)
2623 return false;
2624 (*sleVault)[sfAssetsMaximum] = 200;
2625 ac.view().update(sleVault);
2626
2627 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2628 if (!sleShares)
2629 return false;
2630 ac.view().erase(sleShares);
2631 return true;
2632 },
2633 XRPAmount{},
2634 STTx{ttVAULT_SET, [](STObject&) {}},
2636 [&](Account const& A1, Account const& A2, Env& env) {
2637 Vault vault{env};
2638 auto [tx, _] = vault.create({.owner = A1, .asset = xrpIssue()});
2639 env(tx);
2640 return true;
2641 });
2642
2644 {"vault operation succeeded without updating shares",
2645 "assets available must not be greater than assets outstanding"},
2646 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2647 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2648 auto sleVault = ac.view().peek(keylet);
2649 if (!sleVault)
2650 return false;
2651 (*sleVault)[sfAssetsTotal] = 9;
2652 ac.view().update(sleVault);
2653 return true;
2654 },
2655 XRPAmount{},
2656 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
2658 [&](Account const& A1, Account const& A2, Env& env) {
2659 Vault vault{env};
2660 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2661 env(tx);
2662 env(vault.deposit({.depositor = A1, .id = keylet.key, .amount = XRP(10)}));
2663 return true;
2664 });
2665
2667 {"set must not change assets outstanding",
2668 "set must not change assets available",
2669 "set must not change shares outstanding",
2670 "set must not change vault balance",
2671 "assets available must be positive",
2672 "assets available must not be greater than assets outstanding",
2673 "assets outstanding must be positive"},
2674 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2675 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2676 auto sleVault = ac.view().peek(keylet);
2677 if (!sleVault)
2678 return false;
2679 auto slePseudoAccount = ac.view().peek(keylet::account(*(*sleVault)[sfAccount]));
2680 if (!slePseudoAccount)
2681 return false;
2682 (*slePseudoAccount)[sfBalance] = *(*slePseudoAccount)[sfBalance] - 10;
2683 ac.view().update(slePseudoAccount);
2684
2685 // Move 10 drops to A4 to enforce total XRP balance
2686 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
2687 if (!sleA4)
2688 return false;
2689 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
2690 ac.view().update(sleA4);
2691
2692 return adjust(ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) {
2693 sample.assetsAvailable = (DROPS_PER_XRP * -100).value();
2694 sample.assetsTotal = (DROPS_PER_XRP * -200).value();
2695 sample.sharesTotal = -1;
2696 }));
2697 },
2698 XRPAmount{},
2699 STTx{ttVAULT_SET, [](STObject& tx) {}},
2701 precloseXrp,
2703
2705 {"violation of vault immutable data"},
2706 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2707 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2708 auto sleVault = ac.view().peek(keylet);
2709 if (!sleVault)
2710 return false;
2711 sleVault->setFieldIssue(sfAsset, STIssue{sfAsset, MPTIssue(MPTID(42))});
2712 ac.view().update(sleVault);
2713 return true;
2714 },
2715 XRPAmount{},
2716 STTx{ttVAULT_SET, [](STObject& tx) {}},
2718 precloseXrp);
2719
2721 {"violation of vault immutable data"},
2722 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2723 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2724 auto sleVault = ac.view().peek(keylet);
2725 if (!sleVault)
2726 return false;
2727 sleVault->setAccountID(sfAccount, A2.id());
2728 ac.view().update(sleVault);
2729 return true;
2730 },
2731 XRPAmount{},
2732 STTx{ttVAULT_SET, [](STObject& tx) {}},
2734 precloseXrp);
2735
2737 {"violation of vault immutable data"},
2738 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2739 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2740 auto sleVault = ac.view().peek(keylet);
2741 if (!sleVault)
2742 return false;
2743 (*sleVault)[sfShareMPTID] = MPTID(42);
2744 ac.view().update(sleVault);
2745 return true;
2746 },
2747 XRPAmount{},
2748 STTx{ttVAULT_SET, [](STObject& tx) {}},
2750 precloseXrp);
2751
2753 {"vault transaction must not change loss unrealized", "set must not change assets outstanding"},
2754 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2755 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2756 return adjust(ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) {
2757 sample.lossUnrealized = 13;
2758 sample.assetsTotal = 20;
2759 }));
2760 },
2761 XRPAmount{},
2762 STTx{ttVAULT_SET, [](STObject& tx) {}},
2764 precloseXrp,
2766
2768 {"loss unrealized must not exceed the difference "
2769 "between assets outstanding and available",
2770 "vault transaction must not change loss unrealized"},
2771 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2772 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2773 return adjust(
2774 ac.view(), keylet, args(A2.id(), 100, [&](Adjustments& sample) { sample.lossUnrealized = 13; }));
2775 },
2776 XRPAmount{},
2777 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx.setFieldAmount(sfAmount, XRPAmount(200)); }},
2779 precloseXrp,
2781
2783 {"set assets outstanding must not exceed assets maximum"},
2784 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2785 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2786 return adjust(
2787 ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = 1; }));
2788 },
2789 XRPAmount{},
2790 STTx{ttVAULT_SET, [](STObject& tx) {}},
2792 precloseXrp,
2794
2796 {"assets maximum must be positive"},
2797 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2798 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2799 return adjust(
2800 ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) { sample.assetsMaximum = -1; }));
2801 },
2802 XRPAmount{},
2803 STTx{ttVAULT_SET, [](STObject& tx) {}},
2805 precloseXrp,
2807
2809 {"set must not change shares outstanding",
2810 "updated zero sized vault must have no assets outstanding",
2811 "updated zero sized vault must have no assets available"},
2812 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2813 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2814 auto sleVault = ac.view().peek(keylet);
2815 if (!sleVault)
2816 return false;
2817 ac.view().update(sleVault);
2818 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2819 if (!sleShares)
2820 return false;
2821 (*sleShares)[sfOutstandingAmount] = 0;
2822 ac.view().update(sleShares);
2823 return true;
2824 },
2825 XRPAmount{},
2826 STTx{ttVAULT_SET, [](STObject& tx) {}},
2828 precloseXrp,
2830
2832 {"updated shares must not exceed maximum"},
2833 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2834 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2835 auto sleVault = ac.view().peek(keylet);
2836 if (!sleVault)
2837 return false;
2838 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2839 if (!sleShares)
2840 return false;
2841 (*sleShares)[sfMaximumAmount] = 10;
2842 ac.view().update(sleShares);
2843
2844 return adjust(ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2845 },
2846 XRPAmount{},
2847 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2849 precloseXrp,
2851
2853 {"updated shares must not exceed maximum"},
2854 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2855 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2856 adjust(ac.view(), keylet, args(A2.id(), 10, [](Adjustments&) {}));
2857
2858 auto sleVault = ac.view().peek(keylet);
2859 if (!sleVault)
2860 return false;
2861 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2862 if (!sleShares)
2863 return false;
2864 (*sleShares)[sfOutstandingAmount] = maxMPTokenAmount + 1;
2865 ac.view().update(sleShares);
2866 return true;
2867 },
2868 XRPAmount{},
2869 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
2871 precloseXrp,
2873
2874 testcase << "Vault create";
2876 {
2877 "created vault must be empty",
2878 "updated zero sized vault must have no assets outstanding",
2879 "create operation must not have updated a vault",
2880 },
2881 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2882 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2883 auto sleVault = ac.view().peek(keylet);
2884 if (!sleVault)
2885 return false;
2886 (*sleVault)[sfAssetsTotal] = 9;
2887 ac.view().update(sleVault);
2888 return true;
2889 },
2890 XRPAmount{},
2891 STTx{ttVAULT_CREATE, [](STObject&) {}},
2893 [&](Account const& A1, Account const& A2, Env& env) {
2894 Vault vault{env};
2895 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2896 env(tx);
2897 return true;
2898 });
2899
2901 {
2902 "created vault must be empty",
2903 "updated zero sized vault must have no assets available",
2904 "assets available must not be greater than assets outstanding",
2905 "create operation must not have updated a vault",
2906 },
2907 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2908 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2909 auto sleVault = ac.view().peek(keylet);
2910 if (!sleVault)
2911 return false;
2912 (*sleVault)[sfAssetsAvailable] = 9;
2913 ac.view().update(sleVault);
2914 return true;
2915 },
2916 XRPAmount{},
2917 STTx{ttVAULT_CREATE, [](STObject&) {}},
2919 [&](Account const& A1, Account const& A2, Env& env) {
2920 Vault vault{env};
2921 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2922 env(tx);
2923 return true;
2924 });
2925
2927 {
2928 "created vault must be empty",
2929 "loss unrealized must not exceed the difference between assets "
2930 "outstanding and available",
2931 "vault transaction must not change loss unrealized",
2932 "create operation must not have updated a vault",
2933 },
2934 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2935 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2936 auto sleVault = ac.view().peek(keylet);
2937 if (!sleVault)
2938 return false;
2939 (*sleVault)[sfLossUnrealized] = 1;
2940 ac.view().update(sleVault);
2941 return true;
2942 },
2943 XRPAmount{},
2944 STTx{ttVAULT_CREATE, [](STObject&) {}},
2946 [&](Account const& A1, Account const& A2, Env& env) {
2947 Vault vault{env};
2948 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2949 env(tx);
2950 return true;
2951 });
2952
2954 {
2955 "created vault must be empty",
2956 "create operation must not have updated a vault",
2957 },
2958 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2959 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2960 auto sleVault = ac.view().peek(keylet);
2961 if (!sleVault)
2962 return false;
2963 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
2964 if (!sleShares)
2965 return false;
2966 ac.view().update(sleVault);
2967 (*sleShares)[sfOutstandingAmount] = 9;
2968 ac.view().update(sleShares);
2969 return true;
2970 },
2971 XRPAmount{},
2972 STTx{ttVAULT_CREATE, [](STObject&) {}},
2974 [&](Account const& A1, Account const& A2, Env& env) {
2975 Vault vault{env};
2976 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
2977 env(tx);
2978 return true;
2979 });
2980
2982 {
2983 "assets maximum must be positive",
2984 "create operation must not have updated a vault",
2985 },
2986 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
2987 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
2988 auto sleVault = ac.view().peek(keylet);
2989 if (!sleVault)
2990 return false;
2991 (*sleVault)[sfAssetsMaximum] = Number(-1);
2992 ac.view().update(sleVault);
2993 return true;
2994 },
2995 XRPAmount{},
2996 STTx{ttVAULT_CREATE, [](STObject&) {}},
2998 [&](Account const& A1, Account const& A2, Env& env) {
2999 Vault vault{env};
3000 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
3001 env(tx);
3002 return true;
3003 });
3004
3006 {"create operation must not have updated a vault",
3007 "shares issuer and vault pseudo-account must be the same",
3008 "shares issuer must be a pseudo-account",
3009 "shares issuer pseudo-account must point back to the vault"},
3010 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3011 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3012 auto sleVault = ac.view().peek(keylet);
3013 if (!sleVault)
3014 return false;
3015 auto sleShares = ac.view().peek(keylet::mptIssuance((*sleVault)[sfShareMPTID]));
3016 if (!sleShares)
3017 return false;
3018 ac.view().update(sleVault);
3019 (*sleShares)[sfIssuer] = A1.id();
3020 ac.view().update(sleShares);
3021 return true;
3022 },
3023 XRPAmount{},
3024 STTx{ttVAULT_CREATE, [](STObject&) {}},
3026 [&](Account const& A1, Account const& A2, Env& env) {
3027 Vault vault{env};
3028 auto [tx, keylet] = vault.create({.owner = A1, .asset = xrpIssue()});
3029 env(tx);
3030 return true;
3031 });
3032
3034 {"vault created by a wrong transaction type", "account root created illegally"},
3035 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3036 // The code below will create a valid vault with (almost) all
3037 // the invariants holding. Except one: it is created by the
3038 // wrong transaction type.
3039 auto const sequence = ac.view().seq();
3040 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
3041 auto sleVault = std::make_shared<SLE>(vaultKeylet);
3042 auto const vaultPage =
3043 ac.view().dirInsert(keylet::ownerDir(A1.id()), sleVault->key(), describeOwnerDir(A1.id()));
3044 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3045
3046 auto pseudoId = pseudoAccountAddress(ac.view(), vaultKeylet.key);
3047 // Create pseudo-account.
3048 auto sleAccount = std::make_shared<SLE>(keylet::account(pseudoId));
3049 sleAccount->setAccountID(sfAccount, pseudoId);
3050 sleAccount->setFieldAmount(sfBalance, STAmount{});
3051 std::uint32_t const seqno = //
3052 ac.view().rules().enabled(featureSingleAssetVault) //
3053 ? 0 //
3054 : sequence;
3055 sleAccount->setFieldU32(sfSequence, seqno);
3056 sleAccount->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3057 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
3058 ac.view().insert(sleAccount);
3059
3060 auto const sharesMptId = makeMptID(sequence, pseudoId);
3061 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
3062 auto sleShares = std::make_shared<SLE>(sharesKeylet);
3063 auto const sharesPage =
3064 ac.view().dirInsert(keylet::ownerDir(pseudoId), sharesKeylet, describeOwnerDir(pseudoId));
3065 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3066
3067 sleShares->at(sfFlags) = 0;
3068 sleShares->at(sfIssuer) = pseudoId;
3069 sleShares->at(sfOutstandingAmount) = 0;
3070 sleShares->at(sfSequence) = sequence;
3071
3072 sleVault->at(sfAccount) = pseudoId;
3073 sleVault->at(sfFlags) = 0;
3074 sleVault->at(sfSequence) = sequence;
3075 sleVault->at(sfOwner) = A1.id();
3076 sleVault->at(sfAssetsTotal) = Number(0);
3077 sleVault->at(sfAssetsAvailable) = Number(0);
3078 sleVault->at(sfLossUnrealized) = Number(0);
3079 sleVault->at(sfShareMPTID) = sharesMptId;
3080 sleVault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe;
3081
3082 ac.view().insert(sleVault);
3083 ac.view().insert(sleShares);
3084 return true;
3085 },
3086 XRPAmount{},
3087 STTx{ttVAULT_SET, [](STObject&) {}},
3089
3091 {"shares issuer and vault pseudo-account must be the same",
3092 "shares issuer pseudo-account must point back to the vault"},
3093 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3094 auto const sequence = ac.view().seq();
3095 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
3096 auto sleVault = std::make_shared<SLE>(vaultKeylet);
3097 auto const vaultPage =
3098 ac.view().dirInsert(keylet::ownerDir(A1.id()), sleVault->key(), describeOwnerDir(A1.id()));
3099 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3100
3101 auto pseudoId = pseudoAccountAddress(ac.view(), vaultKeylet.key);
3102 // Create pseudo-account.
3103 auto sleAccount = std::make_shared<SLE>(keylet::account(pseudoId));
3104 sleAccount->setAccountID(sfAccount, pseudoId);
3105 sleAccount->setFieldAmount(sfBalance, STAmount{});
3106 std::uint32_t const seqno = //
3107 ac.view().rules().enabled(featureSingleAssetVault) //
3108 ? 0 //
3109 : sequence;
3110 sleAccount->setFieldU32(sfSequence, seqno);
3111 sleAccount->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3112 // sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
3113 // Setting wrong vault key
3114 sleAccount->setFieldH256(sfVaultID, uint256(42));
3115 ac.view().insert(sleAccount);
3116
3117 auto const sharesMptId = makeMptID(sequence, pseudoId);
3118 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
3119 auto sleShares = std::make_shared<SLE>(sharesKeylet);
3120 auto const sharesPage =
3121 ac.view().dirInsert(keylet::ownerDir(pseudoId), sharesKeylet, describeOwnerDir(pseudoId));
3122 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3123
3124 sleShares->at(sfFlags) = 0;
3125 sleShares->at(sfIssuer) = pseudoId;
3126 sleShares->at(sfOutstandingAmount) = 0;
3127 sleShares->at(sfSequence) = sequence;
3128
3129 // sleVault->at(sfAccount) = pseudoId;
3130 // Setting wrong pseudo account ID
3131 sleVault->at(sfAccount) = A2.id();
3132 sleVault->at(sfFlags) = 0;
3133 sleVault->at(sfSequence) = sequence;
3134 sleVault->at(sfOwner) = A1.id();
3135 sleVault->at(sfAssetsTotal) = Number(0);
3136 sleVault->at(sfAssetsAvailable) = Number(0);
3137 sleVault->at(sfLossUnrealized) = Number(0);
3138 sleVault->at(sfShareMPTID) = sharesMptId;
3139 sleVault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe;
3140
3141 ac.view().insert(sleVault);
3142 ac.view().insert(sleShares);
3143 return true;
3144 },
3145 XRPAmount{},
3146 STTx{ttVAULT_CREATE, [](STObject&) {}},
3148
3150 {"shares issuer and vault pseudo-account must be the same", "shares issuer must exist"},
3151 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3152 auto const sequence = ac.view().seq();
3153 auto const vaultKeylet = keylet::vault(A1.id(), sequence);
3154 auto sleVault = std::make_shared<SLE>(vaultKeylet);
3155 auto const vaultPage =
3156 ac.view().dirInsert(keylet::ownerDir(A1.id()), sleVault->key(), describeOwnerDir(A1.id()));
3157 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3158
3159 auto const sharesMptId = makeMptID(sequence, A2.id());
3160 auto const sharesKeylet = keylet::mptIssuance(sharesMptId);
3161 auto sleShares = std::make_shared<SLE>(sharesKeylet);
3162 auto const sharesPage =
3163 ac.view().dirInsert(keylet::ownerDir(A2.id()), sharesKeylet, describeOwnerDir(A2.id()));
3164 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3165
3166 sleShares->at(sfFlags) = 0;
3167 // Setting wrong pseudo account ID
3168 sleShares->at(sfIssuer) = AccountID(uint160(42));
3169 sleShares->at(sfOutstandingAmount) = 0;
3170 sleShares->at(sfSequence) = sequence;
3171
3172 sleVault->at(sfAccount) = A2.id();
3173 sleVault->at(sfFlags) = 0;
3174 sleVault->at(sfSequence) = sequence;
3175 sleVault->at(sfOwner) = A1.id();
3176 sleVault->at(sfAssetsTotal) = Number(0);
3177 sleVault->at(sfAssetsAvailable) = Number(0);
3178 sleVault->at(sfLossUnrealized) = Number(0);
3179 sleVault->at(sfShareMPTID) = sharesMptId;
3180 sleVault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe;
3181
3182 ac.view().insert(sleVault);
3183 ac.view().insert(sleShares);
3184 return true;
3185 },
3186 XRPAmount{},
3187 STTx{ttVAULT_CREATE, [](STObject&) {}},
3189
3190 testcase << "Vault deposit";
3192 {"deposit must change vault balance"},
3193 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3194 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3195 return adjust(
3196 ac.view(), keylet, args(A2.id(), 0, [](Adjustments& sample) { sample.vaultAssets.reset(); }));
3197 },
3198 XRPAmount{},
3199 STTx{ttVAULT_DEPOSIT, [](STObject&) {}},
3201 precloseXrp);
3202
3204 {"deposit assets outstanding must not exceed assets maximum"},
3205 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3206 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3207 return adjust(
3208 ac.view(), keylet, args(A2.id(), 200, [&](Adjustments& sample) { sample.assetsMaximum = 1; }));
3209 },
3210 XRPAmount{},
3211 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx.setFieldAmount(sfAmount, XRPAmount(200)); }},
3213 precloseXrp,
3215
3216 // This really convoluted unit tests makes the zero balance on the
3217 // depositor, by sending them the same amount as the transaction fee.
3218 // The operation makes no sense, but the defensive check in
3219 // ValidVault::finalize is otherwise impossible to trigger.
3221 {"deposit must increase vault balance", "deposit must change depositor balance"},
3222 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3223 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3224
3225 // Move 10 drops to A4 to enforce total XRP balance
3226 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
3227 if (!sleA4)
3228 return false;
3229 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3230 ac.view().update(sleA4);
3231
3232 return adjust(ac.view(), keylet, args(A3.id(), -10, [&](Adjustments& sample) {
3233 sample.accountAssets->amount = -100;
3234 }));
3235 },
3236 XRPAmount{100},
3237 STTx{
3238 ttVAULT_DEPOSIT,
3239 [&](STObject& tx) {
3240 tx[sfFee] = XRPAmount(100);
3241 tx[sfAccount] = A3.id();
3242 }},
3244 precloseXrp);
3245
3247 {"deposit must increase vault balance",
3248 "deposit must decrease depositor balance",
3249 "deposit must change vault and depositor balance by equal amount",
3250 "deposit and assets outstanding must add up",
3251 "deposit and assets available must add up"},
3252 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3253 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3254
3255 // Move 10 drops from A2 to A3 to enforce total XRP balance
3256 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3257 if (!sleA3)
3258 return false;
3259 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3260 ac.view().update(sleA3);
3261
3262 return adjust(ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) {
3263 sample.vaultAssets = -20;
3264 sample.accountAssets->amount = 10;
3265 }));
3266 },
3267 XRPAmount{},
3268 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3270 precloseXrp,
3272
3274 {"deposit must change depositor balance"},
3275 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3276 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3277
3278 // Move 10 drops from A3 to vault to enforce total XRP balance
3279 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3280 if (!sleA3)
3281 return false;
3282 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
3283 ac.view().update(sleA3);
3284
3285 return adjust(ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) {
3286 sample.accountAssets->amount = 0;
3287 }));
3288 },
3289 XRPAmount{},
3290 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3292 precloseXrp,
3294
3296 {"deposit must change depositor shares"},
3297 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3298 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3299 return adjust(
3300 ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) { sample.accountShares.reset(); }));
3301 },
3302 XRPAmount{},
3303 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3305 precloseXrp,
3307
3309 {"deposit must change vault shares"},
3310 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3311 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3312
3313 return adjust(
3314 ac.view(), keylet, args(A2.id(), 10, [](Adjustments& sample) { sample.sharesTotal = 0; }));
3315 },
3316 XRPAmount{},
3317 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3319 precloseXrp,
3321
3323 {"deposit must increase depositor shares",
3324 "deposit must change depositor and vault shares by equal amount",
3325 "deposit must not change vault balance by more than deposited "
3326 "amount"},
3327 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3328 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3329 return adjust(ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) {
3330 sample.accountShares->amount = -5;
3331 sample.sharesTotal = -10;
3332 }));
3333 },
3334 XRPAmount{},
3335 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(5); }},
3337 precloseXrp,
3339
3341 {"deposit and assets outstanding must add up"},
3342 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3343 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3344 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3345 ac.view().update(sleA3);
3346
3347 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3348 return adjust(
3349 ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) { sample.assetsTotal = 11; }));
3350 },
3351 XRPAmount{2000},
3352 STTx{
3353 ttVAULT_DEPOSIT,
3354 [&](STObject& tx) {
3355 tx[sfAmount] = XRPAmount(10);
3356 tx[sfDelegate] = A3.id();
3357 tx[sfFee] = XRPAmount(2000);
3358 }},
3360 precloseXrp,
3362
3364 {"deposit and assets outstanding must add up", "deposit and assets available must add up"},
3365 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3366 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3367 return adjust(ac.view(), keylet, args(A2.id(), 10, [&](Adjustments& sample) {
3368 sample.assetsTotal = 7;
3369 sample.assetsAvailable = 7;
3370 }));
3371 },
3372 XRPAmount{},
3373 STTx{ttVAULT_DEPOSIT, [](STObject& tx) { tx[sfAmount] = XRPAmount(10); }},
3375 precloseXrp,
3377
3378 testcase << "Vault withdrawal";
3380 {"withdrawal must change vault balance"},
3381 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3382 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3383 return adjust(
3384 ac.view(), keylet, args(A2.id(), 0, [](Adjustments& sample) { sample.vaultAssets.reset(); }));
3385 },
3386 XRPAmount{},
3387 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3389 precloseXrp);
3390
3391 // Almost identical to the really convoluted test for deposit, where the
3392 // depositor spends only the transaction fee. In case of withdrawal,
3393 // this test is almost the same as normal withdrawal where the
3394 // sfDestination would have been A4, but has been omitted.
3396 {"withdrawal must change one destination balance"},
3397 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3398 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3399
3400 // Move 10 drops to A4 to enforce total XRP balance
3401 auto sleA4 = ac.view().peek(keylet::account(A4.id()));
3402 if (!sleA4)
3403 return false;
3404 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3405 ac.view().update(sleA4);
3406
3407 return adjust(ac.view(), keylet, args(A3.id(), -10, [&](Adjustments& sample) {
3408 sample.accountAssets->amount = -100;
3409 }));
3410 },
3411 XRPAmount{100},
3412 STTx{
3413 ttVAULT_WITHDRAW,
3414 [&](STObject& tx) {
3415 tx[sfFee] = XRPAmount(100);
3416 tx[sfAccount] = A3.id();
3417 // This commented out line causes the invariant violation.
3418 // tx[sfDestination] = A4.id();
3419 }},
3421 precloseXrp);
3422
3424 {"withdrawal must change vault and destination balance by "
3425 "equal amount",
3426 "withdrawal must decrease vault balance",
3427 "withdrawal must increase destination balance",
3428 "withdrawal and assets outstanding must add up",
3429 "withdrawal and assets available must add up"},
3430 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3431 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3432
3433 // Move 10 drops from A2 to A3 to enforce total XRP balance
3434 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3435 if (!sleA3)
3436 return false;
3437 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3438 ac.view().update(sleA3);
3439
3440 return adjust(ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) {
3441 sample.vaultAssets = 10;
3442 sample.accountAssets->amount = -20;
3443 }));
3444 },
3445 XRPAmount{},
3446 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3448 precloseXrp,
3450
3452 {"withdrawal must change one destination balance"},
3453 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3454 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3455 if (!adjust(
3456 ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) { *sample.vaultAssets -= 5; })))
3457 return false;
3458 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3459 if (!sleA3)
3460 return false;
3461 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
3462 ac.view().update(sleA3);
3463 return true;
3464 },
3465 XRPAmount{},
3466 STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx.setAccountID(sfDestination, A3.id()); }},
3468 precloseXrp,
3470
3472 {"withdrawal must change depositor shares"},
3473 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3474 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3475 return adjust(
3476 ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) { sample.accountShares.reset(); }));
3477 },
3478 XRPAmount{},
3479 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3481 precloseXrp,
3483
3485 {"withdrawal must change vault shares"},
3486 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3487 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3488 return adjust(
3489 ac.view(), keylet, args(A2.id(), -10, [](Adjustments& sample) { sample.sharesTotal = 0; }));
3490 },
3491 XRPAmount{},
3492 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3494 precloseXrp,
3496
3498 {"withdrawal must decrease depositor shares",
3499 "withdrawal must change depositor and vault shares by equal "
3500 "amount"},
3501 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3502 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3503 return adjust(ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) {
3504 sample.accountShares->amount = 5;
3505 sample.sharesTotal = 10;
3506 }));
3507 },
3508 XRPAmount{},
3509 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3511 precloseXrp,
3513
3515 {"withdrawal and assets outstanding must add up", "withdrawal and assets available must add up"},
3516 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3517 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3518 return adjust(ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) {
3519 sample.assetsTotal = -15;
3520 sample.assetsAvailable = -15;
3521 }));
3522 },
3523 XRPAmount{},
3524 STTx{ttVAULT_WITHDRAW, [](STObject&) {}},
3526 precloseXrp,
3528
3530 {"withdrawal and assets outstanding must add up"},
3531 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3532 auto sleA3 = ac.view().peek(keylet::account(A3.id()));
3533 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3534 ac.view().update(sleA3);
3535
3536 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3537 return adjust(
3538 ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) { sample.assetsTotal = -7; }));
3539 },
3540 XRPAmount{2000},
3541 STTx{
3542 ttVAULT_WITHDRAW,
3543 [&](STObject& tx) {
3544 tx[sfAmount] = XRPAmount(10);
3545 tx[sfDelegate] = A3.id();
3546 tx[sfFee] = XRPAmount(2000);
3547 }},
3549 precloseXrp,
3551
3552 auto const precloseMpt = [&](Account const& A1, Account const& A2, Env& env) -> bool {
3553 env.fund(XRP(1000), A3, A4);
3554
3555 // Create MPT asset
3556 {
3557 Json::Value jv;
3558 jv[sfAccount] = A3.human();
3559 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
3560 jv[sfFlags] = tfMPTCanTransfer;
3561 env(jv);
3562 env.close();
3563 }
3564
3565 auto const mptID = makeMptID(env.seq(A3) - 1, A3);
3566 Asset asset = MPTIssue(mptID);
3567 // Authorize A1 A2 A4
3568 {
3569 Json::Value jv;
3570 jv[sfAccount] = A1.human();
3571 jv[sfTransactionType] = jss::MPTokenAuthorize;
3572 jv[sfMPTokenIssuanceID] = to_string(mptID);
3573 env(jv);
3574 jv[sfAccount] = A2.human();
3575 env(jv);
3576 jv[sfAccount] = A4.human();
3577 env(jv);
3578
3579 env.close();
3580 }
3581 // Send tokens to A1 A2 A4
3582 {
3583 env(pay(A3, A1, asset(1000)));
3584 env(pay(A3, A2, asset(1000)));
3585 env(pay(A3, A4, asset(1000)));
3586 env.close();
3587 }
3588
3589 Vault vault{env};
3590 auto [tx, keylet] = vault.create({.owner = A1, .asset = asset});
3591 env(tx);
3592 env(vault.deposit({.depositor = A1, .id = keylet.key, .amount = asset(10)}));
3593 env(vault.deposit({.depositor = A2, .id = keylet.key, .amount = asset(10)}));
3594 env(vault.deposit({.depositor = A4, .id = keylet.key, .amount = asset(10)}));
3595 return true;
3596 };
3597
3599 {"withdrawal must decrease depositor shares",
3600 "withdrawal must change depositor and vault shares by equal "
3601 "amount"},
3602 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3603 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3604 return adjust(ac.view(), keylet, args(A2.id(), -10, [&](Adjustments& sample) {
3605 sample.accountShares->amount = 5;
3606 }));
3607 },
3608 XRPAmount{},
3609 STTx{ttVAULT_WITHDRAW, [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3611 precloseMpt,
3613
3614 testcase << "Vault clawback";
3616 {"clawback must change vault balance"},
3617 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3618 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3619 return adjust(
3620 ac.view(), keylet, args(A2.id(), -1, [&](Adjustments& sample) { sample.vaultAssets.reset(); }));
3621 },
3622 XRPAmount{},
3623 STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = A3.id(); }},
3625 precloseMpt);
3626
3627 // Not the same as below check: attempt to clawback XRP
3629 {"clawback may only be performed by the asset issuer"},
3630 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3631 auto const keylet = keylet::vault(A1.id(), ac.view().seq());
3632 return adjust(ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) {}));
3633 },
3634 XRPAmount{},
3635 STTx{ttVAULT_CLAWBACK, [](STObject&) {}},
3637 precloseXrp);
3638
3639 // Not the same as above check: attempt to clawback MPT by bad account
3641 {"clawback may only be performed by the asset issuer"},
3642 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3643 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3644 return adjust(ac.view(), keylet, args(A2.id(), 0, [&](Adjustments& sample) {}));
3645 },
3646 XRPAmount{},
3647 STTx{ttVAULT_CLAWBACK, [&](STObject& tx) { tx[sfAccount] = A4.id(); }},
3649 precloseMpt);
3650
3652 {"clawback must decrease vault balance",
3653 "clawback must decrease holder shares",
3654 "clawback must change vault shares"},
3655 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3656 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3657 return adjust(
3658 ac.view(), keylet, args(A4.id(), 10, [&](Adjustments& sample) { sample.sharesTotal = 0; }));
3659 },
3660 XRPAmount{},
3661 STTx{
3662 ttVAULT_CLAWBACK,
3663 [&](STObject& tx) {
3664 tx[sfAccount] = A3.id();
3665 tx[sfHolder] = A4.id();
3666 }},
3668 precloseMpt);
3669
3671 {"clawback must change holder shares"},
3672 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3673 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3674 return adjust(
3675 ac.view(), keylet, args(A4.id(), -10, [&](Adjustments& sample) { sample.accountShares.reset(); }));
3676 },
3677 XRPAmount{},
3678 STTx{
3679 ttVAULT_CLAWBACK,
3680 [&](STObject& tx) {
3681 tx[sfAccount] = A3.id();
3682 tx[sfHolder] = A4.id();
3683 }},
3685 precloseMpt);
3686
3688 {"clawback must change holder and vault shares by equal amount",
3689 "clawback and assets outstanding must add up",
3690 "clawback and assets available must add up"},
3691 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
3692 auto const keylet = keylet::vault(A1.id(), ac.view().seq() - 2);
3693 return adjust(ac.view(), keylet, args(A4.id(), -10, [&](Adjustments& sample) {
3694 sample.accountShares->amount = -8;
3695 sample.assetsTotal = -7;
3696 sample.assetsAvailable = -7;
3697 }));
3698 },
3699 XRPAmount{},
3700 STTx{
3701 ttVAULT_CLAWBACK,
3702 [&](STObject& tx) {
3703 tx[sfAccount] = A3.id();
3704 tx[sfHolder] = A4.id();
3705 }},
3707 precloseMpt);
3708 }
3709
3710public:
3711 void
3736};
3737
3738BEAST_DEFINE_TESTSUITE(Invariants, app, xrpl);
3739
3740} // namespace test
3741} // namespace xrpl
Represents a JSON value.
Definition json_value.h:130
A generic endpoint for log messages.
Definition Journal.h:40
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:147
State information when applying a tx.
ApplyView & view()
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:114
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove 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:283
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
A currency issued by an account.
Definition Issue.h:13
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
virtual Rules const & rules() const =0
Returns the tx processing rules.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:97
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:118
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:88
AccountID const & getIssuer() const
Definition STAmount.h:466
void push_back(STObject const &object)
Definition STArray.h:187
iterator begin()
Definition STArray.h:199
void setFieldAmount(SField const &field, STAmount const &)
Definition STObject.cpp:772
static STObject makeInnerObject(SField const &name)
Definition STObject.cpp:72
void setAccountID(SField const &field, AccountID const &)
Definition STObject.cpp:754
void setFieldH256(SField const &field, uint256 const &)
Definition STObject.cpp:736
An immutable linear range of bytes.
Definition Slice.h:26
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, test::jtx::Env &env)> Preclose
void run() override
Runs the suite.
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)
void doInvariantCheck(test::jtx::Env &&env, 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)
Keylet createLoanBroker(jtx::Account const &a, jtx::Env &env, jtx::PrettyAsset const &asset)
void testPermissionedDEX(FeatureBitset features)
static std::shared_ptr< SLE > createPermissionedDomain(ApplyContext &ac, test::jtx::Account const &A1, test::jtx::Account const &A2, std::uint32_t numCreds=2, std::uint32_t seq=10)
void doInvariantCheck(test::jtx::Env &&env, test::jtx::Account const &A1, test::jtx::Account const &A2, 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})
TxAccount
Run a specific test case to put the ledger into a state that will be detected by an invariant.
void testPermissionedDomainInvariants(FeatureBitset features)
static std::pair< std::uint32_t, uint256 > createPermissionedDomainEnv(test::jtx::Env &env, test::jtx::Account const &A1, test::jtx::Account const &A2, std::uint32_t numCreds=2)
static FeatureBitset defaultAmendments()
Convenience class to test AMM functionality.
Definition AMM.h:104
Immutable cryptographic account descriptor.
Definition Account.h:19
AccountID id() const
Returns the Account ID.
Definition Account.h:87
A transaction testing environment.
Definition Env.h:119
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:98
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:249
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:261
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:240
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Definition Env.cpp:453
Converts to IOU Issue or STAmount.
Converts to MPT Issue or STAmount.
Set the fee on a JTx.
Definition fee.h:17
T erase(T... args)
T invoke(T... args)
T is_same_v
T iter_swap(T... args)
T make_pair(T... args)
std::uint64_t insertKey(ApplyView &view, SLE::ref node, std::uint64_t page, bool preserveOrder, STVector256 &indexes, uint256 const &key)
Definition ApplyView.cpp:54
std::optional< std::uint64_t > insertPage(ApplyView &view, std::uint64_t page, SLE::pointer node, std::uint64_t nextPage, SLE::ref next, uint256 const &key, Keylet const &directory, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Definition ApplyView.cpp:90
std::uint64_t createRoot(ApplyView &view, Keylet const &directory, uint256 const &key, std::function< void(std::shared_ptr< SLE > const &)> const &describe)
Helper functions for managing low-level directory operations.
Definition ApplyView.cpp:14
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:360
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition Indexes.cpp:368
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:498
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition Indexes.cpp:340
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:187
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:325
Keylet mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:462
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:393
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:235
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:492
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:214
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:352
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:474
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition Indexes.cpp:510
Keylet page(uint256 const &root, std::uint64_t index=0) noexcept
A page in a directory.
Definition Indexes.cpp:331
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
uint256 getNewDomain(std::shared_ptr< STObject const > const &meta)
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:15
static MPTInit const mptInitNoFund
Definition mpt.h:103
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
static increment_t const increment
Definition tags.h:40
FeatureBitset testable_amendments()
Definition Env.h:76
auto const amount
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
bool set(T &target, std::string const &name, Section const &section)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:36
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:994
std::uint8_t constexpr vaultStrategyFirstComeFirstServe
Vault withdrawal policies.
Definition Protocol.h:240
AccountID pseudoAccountAddress(ReadView const &view, uint256 const &pseudoOwnerKey)
Definition View.cpp:971
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:132
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
std::uint64_t constexpr maxMPTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:233
@ tefINVARIANT_FAILED
Definition TER.h:163
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
constexpr std::uint32_t const tfMPTCanLock
Definition TxFlags.h:128
base_uint< 256 > uint256
Definition base_uint.h:526
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:133
TERSubset< CanCvtToTER > TER
Definition TER.h:620
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
Definition View.cpp:941
base_uint< 160 > uint160
Definition base_uint.h:525
base_uint< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:44
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:100
std::function< void(SLE::ref)> describeOwnerDir(AccountID const &account)
Definition View.cpp:955
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition Protocol.h:227
@ tapNONE
Definition ApplyView.h:11
constexpr TERUnderlyingType TERtoInt(TELcodes v)
Definition TER.h:355
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
LedgerEntryType
Identifiers for on-ledger objects.
@ tecINVARIANT_FAILED
Definition TER.h:294
@ lsfDepositAuth
@ lsfNoFreeze
@ lsfDefaultRipple
@ lsfLowDeepFreeze
@ lsfRequireDestTag
@ lsfLowFreeze
@ lsfDisableMaster
@ lsfHighFreeze
@ lsfHighDeepFreeze
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1640
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:98
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:146
@ tesSUCCESS
Definition TER.h:225
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:373
T push_back(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
uint256 key
Definition Keylet.h:20
LedgerEntryType type
Definition Keylet.h:21
std::optional< MPTCreate > create
Definition mpt.h:101
Set the sequence number on a JTx.
Definition seq.h:14
T to_string(T... args)