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