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