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