rippled
Loading...
Searching...
No Matches
Invariants_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012-2017 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21#include <test/jtx/AMM.h>
22#include <test/jtx/Env.h>
23#include <xrpld/app/tx/apply.h>
24#include <xrpld/app/tx/detail/ApplyContext.h>
25#include <xrpld/app/tx/detail/Transactor.h>
26#include <xrpl/beast/utility/Journal.h>
27#include <xrpl/protocol/InnerObjectFormats.h>
28#include <xrpl/protocol/STLedgerEntry.h>
29
30#include <boost/algorithm/string/predicate.hpp>
31
32namespace ripple {
33
35{
36 // The optional Preclose function is used to process additional transactions
37 // on the ledger after creating two accounts, but before closing it, and
38 // before the Precheck function. These should only be valid functions, and
39 // not direct manipulations. Preclose is not commonly used.
40 using Preclose = std::function<bool(
41 test::jtx::Account const& a,
42 test::jtx::Account const& b,
43 test::jtx::Env& env)>;
44
45 // this is common setup/method for running a failing invariant check. The
46 // precheck function is used to manipulate the ApplyContext with view
47 // changes that will cause the check to fail.
48 using Precheck = std::function<bool(
49 test::jtx::Account const& a,
50 test::jtx::Account const& b,
51 ApplyContext& ac)>;
52
69 void
71 std::vector<std::string> const& expect_logs,
72 Precheck const& precheck,
73 XRPAmount fee = XRPAmount{},
74 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
77 Preclose const& preclose = {})
78 {
79 using namespace test::jtx;
80 FeatureBitset amendments =
81 supported_amendments() | featureInvariantsV1_1;
82 Env env{*this, amendments};
83
84 Account const A1{"A1"};
85 Account const A2{"A2"};
86 env.fund(XRP(1000), A1, A2);
87 if (preclose)
88 BEAST_EXPECT(preclose(A1, A2, env));
89 env.close();
90
91 OpenView ov{*env.current()};
92 test::StreamSink sink{beast::severities::kWarning};
93 beast::Journal jlog{sink};
94 ApplyContext ac{
95 env.app(),
96 ov,
97 tx,
99 env.current()->fees().base,
100 tapNONE,
101 jlog};
102
103 BEAST_EXPECT(precheck(A1, A2, ac));
104
105 // invoke check twice to cover tec and tef cases
106 if (!BEAST_EXPECT(ters.size() == 2))
107 return;
108
109 TER terActual = tesSUCCESS;
110 for (TER const& terExpect : ters)
111 {
112 terActual = ac.checkInvariants(terActual, fee);
113 BEAST_EXPECT(terExpect == terActual);
114 BEAST_EXPECT(
115 sink.messages().str().starts_with("Invariant failed:") ||
116 sink.messages().str().starts_with(
117 "Transaction caused an exception"));
118 // uncomment if you want to log the invariant failure message
119 // log << " --> " << sink.messages().str() << std::endl;
120 for (auto const& m : expect_logs)
121 {
122 BEAST_EXPECT(
123 sink.messages().str().find(m) != std::string::npos);
124 }
125 }
126 }
127
128 void
130 {
131 using namespace test::jtx;
132 testcase << "XRP created";
134 {{"XRP net change was positive: 500"}},
135 [](Account const& A1, Account const&, ApplyContext& ac) {
136 // put a single account in the view and "manufacture" some XRP
137 auto const sle = ac.view().peek(keylet::account(A1.id()));
138 if (!sle)
139 return false;
140 auto amt = sle->getFieldAmount(sfBalance);
141 sle->setFieldAmount(sfBalance, amt + STAmount{500});
142 ac.view().update(sle);
143 return true;
144 });
145 }
146
147 void
149 {
150 using namespace test::jtx;
151 testcase << "account root removed";
152
153 // An account was deleted, but not by an AccountDelete transaction.
155 {{"an account root was deleted"}},
156 [](Account const& A1, Account const&, ApplyContext& ac) {
157 // remove an account from the view
158 auto const sle = ac.view().peek(keylet::account(A1.id()));
159 if (!sle)
160 return false;
161 ac.view().erase(sle);
162 return true;
163 });
164
165 // Successful AccountDelete transaction that didn't delete an account.
166 //
167 // Note that this is a case where a second invocation of the invariant
168 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
169 // After a discussion with the team, we believe that's okay.
171 {{"account deletion succeeded without deleting an account"}},
172 [](Account const&, Account const&, ApplyContext& ac) {
173 return true;
174 },
175 XRPAmount{},
176 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
178
179 // Successful AccountDelete that deleted more than one account.
181 {{"account deletion succeeded but deleted multiple accounts"}},
182 [](Account const& A1, Account const& A2, ApplyContext& ac) {
183 // remove two accounts from the view
184 auto const sleA1 = ac.view().peek(keylet::account(A1.id()));
185 auto const sleA2 = ac.view().peek(keylet::account(A2.id()));
186 if (!sleA1 || !sleA2)
187 return false;
188 ac.view().erase(sleA1);
189 ac.view().erase(sleA2);
190 return true;
191 },
192 XRPAmount{},
193 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
194 }
195
196 void
198 {
199 using namespace test::jtx;
200 testcase << "account root deletion left artifact";
201
202 for (auto const& keyletInfo : directAccountKeylets)
203 {
204 // TODO: Use structured binding once LLVM 16 is the minimum
205 // supported version. See also:
206 // https://github.com/llvm/llvm-project/issues/48582
207 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
208 if (!keyletInfo.includeInTests)
209 continue;
210 auto const& keyletfunc = keyletInfo.function;
211 auto const& type = keyletInfo.expectedLEName;
212
213 using namespace std::string_literals;
214
216 {{"account deletion left behind a "s + type.c_str() +
217 " object"}},
218 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
219 // Add an object to the ledger for account A1, then delete
220 // A1
221 auto const a1 = A1.id();
222 auto const sleA1 = ac.view().peek(keylet::account(a1));
223 if (!sleA1)
224 return false;
225
226 auto const key = std::invoke(keyletfunc, a1);
227 auto const newSLE = std::make_shared<SLE>(key);
228 ac.view().insert(newSLE);
229 ac.view().erase(sleA1);
230
231 return true;
232 },
233 XRPAmount{},
234 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
235 };
236
237 // NFT special case
239 {{"account deletion left behind a NFTokenPage object"}},
240 [&](Account const& A1, Account const&, ApplyContext& ac) {
241 // remove an account from the view
242 auto const sle = ac.view().peek(keylet::account(A1.id()));
243 if (!sle)
244 return false;
245 ac.view().erase(sle);
246 return true;
247 },
248 XRPAmount{},
249 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
251 [&](Account const& A1, Account const&, Env& env) {
252 // Preclose callback to mint the NFT which will be deleted in
253 // the Precheck callback above.
254 env(token::mint(A1));
255
256 return true;
257 });
258
259 // AMM special cases
260 AccountID ammAcctID;
261 uint256 ammKey;
262 Issue ammIssue;
264 {{"account deletion left behind a DirectoryNode object"}},
265 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
266 // Delete the AMM account without cleaning up the directory or
267 // deleting the AMM object
268 auto const sle = ac.view().peek(keylet::account(ammAcctID));
269 if (!sle)
270 return false;
271
272 BEAST_EXPECT(sle->at(~sfAMMID));
273 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
274
275 ac.view().erase(sle);
276
277 return true;
278 },
279 XRPAmount{},
280 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
282 [&](Account const& A1, Account const& A2, Env& env) {
283 // Preclose callback to create the AMM which will be partially
284 // deleted in the Precheck callback above.
285 AMM const amm(env, A1, XRP(100), A1["USD"](50));
286 ammAcctID = amm.ammAccount();
287 ammKey = amm.ammID();
288 ammIssue = amm.lptIssue();
289 return true;
290 });
292 {{"account deletion left behind a AMM object"}},
293 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
294 // Delete all the AMM's trust lines, remove the AMM from the AMM
295 // account's directory (this deletes the directory), and delete
296 // the AMM account. Do not delete the AMM object.
297 auto const sle = ac.view().peek(keylet::account(ammAcctID));
298 if (!sle)
299 return false;
300
301 BEAST_EXPECT(sle->at(~sfAMMID));
302 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
303
304 for (auto const& trustKeylet :
305 {keylet::line(ammAcctID, A1["USD"]),
306 keylet::line(A1, ammIssue)})
307 {
308 if (auto const line = ac.view().peek(trustKeylet); !line)
309 {
310 return false;
311 }
312 else
313 {
314 STAmount const lowLimit = line->at(sfLowLimit);
315 STAmount const highLimit = line->at(sfHighLimit);
316 BEAST_EXPECT(
318 ac.view(),
319 line,
320 lowLimit.getIssuer(),
321 highLimit.getIssuer(),
322 ac.journal) == tesSUCCESS);
323 }
324 }
325
326 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
327 if (!BEAST_EXPECT(ammSle))
328 return false;
329 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
330
331 BEAST_EXPECT(ac.view().dirRemove(
332 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
333 BEAST_EXPECT(
334 !ac.view().exists(ownerDirKeylet) ||
335 ac.view().emptyDirDelete(ownerDirKeylet));
336
337 ac.view().erase(sle);
338
339 return true;
340 },
341 XRPAmount{},
342 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
344 [&](Account const& A1, Account const& A2, Env& env) {
345 // Preclose callback to create the AMM which will be partially
346 // deleted in the Precheck callback above.
347 AMM const amm(env, A1, XRP(100), A1["USD"](50));
348 ammAcctID = amm.ammAccount();
349 ammKey = amm.ammID();
350 ammIssue = amm.lptIssue();
351 return true;
352 });
353 }
354
355 void
357 {
358 using namespace test::jtx;
359 testcase << "ledger entry types don't match";
361 {{"ledger entry type mismatch"},
362 {"XRP net change of -1000000000 doesn't match fee 0"}},
363 [](Account const& A1, Account const&, ApplyContext& ac) {
364 // replace an entry in the table with an SLE of a different type
365 auto const sle = ac.view().peek(keylet::account(A1.id()));
366 if (!sle)
367 return false;
368 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
369 ac.rawView().rawReplace(sleNew);
370 return true;
371 });
372
374 {{"invalid ledger entry type added"}},
375 [](Account const& A1, Account const&, ApplyContext& ac) {
376 // add an entry in the table with an SLE of an invalid type
377 auto const sle = ac.view().peek(keylet::account(A1.id()));
378 if (!sle)
379 return false;
380
381 // make a dummy escrow ledger entry, then change the type to an
382 // unsupported value so that the valid type invariant check
383 // will fail.
384 auto const sleNew = std::make_shared<SLE>(
385 keylet::escrow(A1, (*sle)[sfSequence] + 2));
386
387 // We don't use ltNICKNAME directly since it's marked deprecated
388 // to prevent accidental use elsewhere.
389 sleNew->type_ = static_cast<LedgerEntryType>('n');
390 ac.view().insert(sleNew);
391 return true;
392 });
393 }
394
395 void
397 {
398 using namespace test::jtx;
399 testcase << "trust lines with XRP not allowed";
401 {{"an XRP trust line was created"}},
402 [](Account const& A1, Account const& A2, ApplyContext& ac) {
403 // create simple trust SLE with xrp currency
404 auto const sleNew = std::make_shared<SLE>(
405 keylet::line(A1, A2, xrpIssue().currency));
406 ac.view().insert(sleNew);
407 return true;
408 });
409 }
410
411 void
413 {
414 using namespace test::jtx;
415 testcase << "trust lines with deep freeze flag without freeze "
416 "not allowed";
418 {{"a trust line with deep freeze flag without normal freeze was "
419 "created"}},
420 [](Account const& A1, Account const& A2, ApplyContext& ac) {
421 auto const sleNew = std::make_shared<SLE>(
422 keylet::line(A1, A2, A1["USD"].currency));
423 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
424 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
425
426 std::uint32_t uFlags = 0u;
427 uFlags |= lsfLowDeepFreeze;
428 sleNew->setFieldU32(sfFlags, uFlags);
429 ac.view().insert(sleNew);
430 return true;
431 });
432
434 {{"a trust line with deep freeze flag without normal freeze was "
435 "created"}},
436 [](Account const& A1, Account const& A2, ApplyContext& ac) {
437 auto const sleNew = std::make_shared<SLE>(
438 keylet::line(A1, A2, A1["USD"].currency));
439 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
440 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
441 std::uint32_t uFlags = 0u;
442 uFlags |= lsfHighDeepFreeze;
443 sleNew->setFieldU32(sfFlags, uFlags);
444 ac.view().insert(sleNew);
445 return true;
446 });
447
449 {{"a trust line with deep freeze flag without normal freeze was "
450 "created"}},
451 [](Account const& A1, Account const& A2, ApplyContext& ac) {
452 auto const sleNew = std::make_shared<SLE>(
453 keylet::line(A1, A2, A1["USD"].currency));
454 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
455 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
456 std::uint32_t uFlags = 0u;
458 sleNew->setFieldU32(sfFlags, uFlags);
459 ac.view().insert(sleNew);
460 return true;
461 });
462
464 {{"a trust line with deep freeze flag without normal freeze was "
465 "created"}},
466 [](Account const& A1, Account const& A2, ApplyContext& ac) {
467 auto const sleNew = std::make_shared<SLE>(
468 keylet::line(A1, A2, A1["USD"].currency));
469 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
470 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
471 std::uint32_t uFlags = 0u;
473 sleNew->setFieldU32(sfFlags, uFlags);
474 ac.view().insert(sleNew);
475 return true;
476 });
477
479 {{"a trust line with deep freeze flag without normal freeze was "
480 "created"}},
481 [](Account const& A1, Account const& A2, ApplyContext& ac) {
482 auto const sleNew = std::make_shared<SLE>(
483 keylet::line(A1, A2, A1["USD"].currency));
484 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
485 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
486 std::uint32_t uFlags = 0u;
488 sleNew->setFieldU32(sfFlags, uFlags);
489 ac.view().insert(sleNew);
490 return true;
491 });
492 }
493
494 void
496 {
497 using namespace test::jtx;
498 testcase << "transfers when frozen";
499
500 Account G1{"G1"};
501 // Helper function to establish the trustlines
502 auto const createTrustlines =
503 [&](Account const& A1, Account const& A2, Env& env) {
504 // Preclose callback to establish trust lines with gateway
505 env.fund(XRP(1000), G1);
506
507 env.trust(G1["USD"](10000), A1);
508 env.trust(G1["USD"](10000), A2);
509 env.close();
510
511 env(pay(G1, A1, G1["USD"](1000)));
512 env(pay(G1, A2, G1["USD"](1000)));
513 env.close();
514
515 return true;
516 };
517
518 auto const A1FrozenByIssuer =
519 [&](Account const& A1, Account const& A2, Env& env) {
520 createTrustlines(A1, A2, env);
521 env(trust(G1, A1["USD"](10000), tfSetFreeze));
522 env.close();
523
524 return true;
525 };
526
527 auto const A1DeepFrozenByIssuer =
528 [&](Account const& A1, Account const& A2, Env& env) {
529 A1FrozenByIssuer(A1, A2, env);
530 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
531 env.close();
532
533 return true;
534 };
535
536 auto const changeBalances = [&](Account const& A1,
537 Account const& A2,
538 ApplyContext& ac,
539 int A1Balance,
540 int A2Balance) {
541 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
542 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
543
544 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
545 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
546
547 ac.view().update(sleA1);
548 ac.view().update(sleA2);
549 };
550
551 // test: imitating frozen A1 making a payment to A2.
553 {{"Attempting to move frozen funds"}},
554 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
555 changeBalances(A1, A2, ac, -900, -1100);
556 return true;
557 },
558 XRPAmount{},
559 STTx{ttPAYMENT, [](STObject& tx) {}},
561 A1FrozenByIssuer);
562
563 // test: imitating deep frozen A1 making a payment to A2.
565 {{"Attempting to move frozen funds"}},
566 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
567 changeBalances(A1, A2, ac, -900, -1100);
568 return true;
569 },
570 XRPAmount{},
571 STTx{ttPAYMENT, [](STObject& tx) {}},
573 A1DeepFrozenByIssuer);
574
575 // test: imitating A2 making a payment to deep frozen A1.
577 {{"Attempting to move frozen funds"}},
578 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
579 changeBalances(A1, A2, ac, -1100, -900);
580 return true;
581 },
582 XRPAmount{},
583 STTx{ttPAYMENT, [](STObject& tx) {}},
585 A1DeepFrozenByIssuer);
586 }
587
588 void
590 {
591 using namespace test::jtx;
592 testcase << "XRP balance checks";
593
595 {{"Cannot return non-native STAmount as XRPAmount"}},
596 [](Account const& A1, Account const& A2, ApplyContext& ac) {
597 // non-native balance
598 auto const sle = ac.view().peek(keylet::account(A1.id()));
599 if (!sle)
600 return false;
601 STAmount const nonNative(A2["USD"](51));
602 sle->setFieldAmount(sfBalance, nonNative);
603 ac.view().update(sle);
604 return true;
605 });
606
608 {{"incorrect account XRP balance"},
609 {"XRP net change was positive: 99999999000000001"}},
610 [this](Account const& A1, Account const&, ApplyContext& ac) {
611 // balance exceeds genesis amount
612 auto const sle = ac.view().peek(keylet::account(A1.id()));
613 if (!sle)
614 return false;
615 // Use `drops(1)` to bypass a call to STAmount::canonicalize
616 // with an invalid value
617 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
618 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
619 ac.view().update(sle);
620 return true;
621 });
622
624 {{"incorrect account XRP balance"},
625 {"XRP net change of -1000000001 doesn't match fee 0"}},
626 [this](Account const& A1, Account const&, ApplyContext& ac) {
627 // balance is negative
628 auto const sle = ac.view().peek(keylet::account(A1.id()));
629 if (!sle)
630 return false;
631 sle->setFieldAmount(sfBalance, STAmount{1, true});
632 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
633 ac.view().update(sle);
634 return true;
635 });
636 }
637
638 void
640 {
641 using namespace test::jtx;
642 using namespace std::string_literals;
643 testcase << "Transaction fee checks";
644
646 {{"fee paid was negative: -1"},
647 {"XRP net change of 0 doesn't match fee -1"}},
648 [](Account const&, Account const&, ApplyContext&) { return true; },
649 XRPAmount{-1});
650
652 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
653 {"XRP net change of 0 doesn't match fee "s +
655 [](Account const&, Account const&, ApplyContext&) { return true; },
657
659 {{"fee paid is 20 exceeds fee specified in transaction."},
660 {"XRP net change of 0 doesn't match fee 20"}},
661 [](Account const&, Account const&, ApplyContext&) { return true; },
662 XRPAmount{20},
663 STTx{ttACCOUNT_SET, [](STObject& tx) {
664 tx.setFieldAmount(sfFee, XRPAmount{10});
665 }});
666 }
667
668 void
670 {
671 using namespace test::jtx;
672 testcase << "no bad offers";
673
675 {{"offer with a bad amount"}},
676 [](Account const& A1, Account const&, ApplyContext& ac) {
677 // offer with negative takerpays
678 auto const sle = ac.view().peek(keylet::account(A1.id()));
679 if (!sle)
680 return false;
681 auto sleNew = std::make_shared<SLE>(
682 keylet::offer(A1.id(), (*sle)[sfSequence]));
683 sleNew->setAccountID(sfAccount, A1.id());
684 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
685 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
686 ac.view().insert(sleNew);
687 return true;
688 });
689
691 {{"offer with a bad amount"}},
692 [](Account const& A1, Account const&, ApplyContext& ac) {
693 // offer with negative takergets
694 auto const sle = ac.view().peek(keylet::account(A1.id()));
695 if (!sle)
696 return false;
697 auto sleNew = std::make_shared<SLE>(
698 keylet::offer(A1.id(), (*sle)[sfSequence]));
699 sleNew->setAccountID(sfAccount, A1.id());
700 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
701 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
702 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
703 ac.view().insert(sleNew);
704 return true;
705 });
706
708 {{"offer with a bad amount"}},
709 [](Account const& A1, Account const&, ApplyContext& ac) {
710 // offer XRP to XRP
711 auto const sle = ac.view().peek(keylet::account(A1.id()));
712 if (!sle)
713 return false;
714 auto sleNew = std::make_shared<SLE>(
715 keylet::offer(A1.id(), (*sle)[sfSequence]));
716 sleNew->setAccountID(sfAccount, A1.id());
717 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
718 sleNew->setFieldAmount(sfTakerPays, XRP(10));
719 sleNew->setFieldAmount(sfTakerGets, XRP(11));
720 ac.view().insert(sleNew);
721 return true;
722 });
723 }
724
725 void
727 {
728 using namespace test::jtx;
729 testcase << "no zero escrow";
730
732 {{"Cannot return non-native STAmount as XRPAmount"}},
733 [](Account const& A1, Account const& A2, ApplyContext& ac) {
734 // escrow with nonnative amount
735 auto const sle = ac.view().peek(keylet::account(A1.id()));
736 if (!sle)
737 return false;
738 auto sleNew = std::make_shared<SLE>(
739 keylet::escrow(A1, (*sle)[sfSequence] + 2));
740 STAmount nonNative(A2["USD"](51));
741 sleNew->setFieldAmount(sfAmount, nonNative);
742 ac.view().insert(sleNew);
743 return true;
744 });
745
747 {{"XRP net change of -1000000 doesn't match fee 0"},
748 {"escrow specifies invalid amount"}},
749 [](Account const& A1, Account const&, ApplyContext& ac) {
750 // escrow with negative amount
751 auto const sle = ac.view().peek(keylet::account(A1.id()));
752 if (!sle)
753 return false;
754 auto sleNew = std::make_shared<SLE>(
755 keylet::escrow(A1, (*sle)[sfSequence] + 2));
756 sleNew->setFieldAmount(sfAmount, XRP(-1));
757 ac.view().insert(sleNew);
758 return true;
759 });
760
762 {{"XRP net change was positive: 100000000000000001"},
763 {"escrow specifies invalid amount"}},
764 [](Account const& A1, Account const&, ApplyContext& ac) {
765 // escrow with too-large amount
766 auto const sle = ac.view().peek(keylet::account(A1.id()));
767 if (!sle)
768 return false;
769 auto sleNew = std::make_shared<SLE>(
770 keylet::escrow(A1, (*sle)[sfSequence] + 2));
771 // Use `drops(1)` to bypass a call to STAmount::canonicalize
772 // with an invalid value
773 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
774 ac.view().insert(sleNew);
775 return true;
776 });
777 }
778
779 void
781 {
782 using namespace test::jtx;
783 testcase << "valid new account root";
784
786 {{"account root created by a non-Payment"}},
787 [](Account const&, Account const&, ApplyContext& ac) {
788 // Insert a new account root created by a non-payment into
789 // the view.
790 Account const A3{"A3"};
791 Keylet const acctKeylet = keylet::account(A3);
792 auto const sleNew = std::make_shared<SLE>(acctKeylet);
793 ac.view().insert(sleNew);
794 return true;
795 });
796
798 {{"multiple accounts created in a single transaction"}},
799 [](Account const&, Account const&, ApplyContext& ac) {
800 // Insert two new account roots into the view.
801 {
802 Account const A3{"A3"};
803 Keylet const acctKeylet = keylet::account(A3);
804 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
805 ac.view().insert(sleA3);
806 }
807 {
808 Account const A4{"A4"};
809 Keylet const acctKeylet = keylet::account(A4);
810 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
811 ac.view().insert(sleA4);
812 }
813 return true;
814 });
815
817 {{"account created with wrong starting sequence number"}},
818 [](Account const&, Account const&, ApplyContext& ac) {
819 // Insert a new account root with the wrong starting sequence.
820 Account const A3{"A3"};
821 Keylet const acctKeylet = keylet::account(A3);
822 auto const sleNew = std::make_shared<SLE>(acctKeylet);
823 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
824 ac.view().insert(sleNew);
825 return true;
826 },
827 XRPAmount{},
828 STTx{ttPAYMENT, [](STObject& tx) {}});
829 }
830
831 void
833 {
834 using namespace test::jtx;
835 testcase << "NFTokenPage";
836
837 // lambda that returns an STArray of NFTokenIDs.
838 uint256 const firstNFTID(
839 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
840 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
841 SOTemplate const* nfTokenTemplate =
843 sfNFToken);
844
845 uint256 nftID(firstNFTID);
846 STArray ret;
847 for (int i = 0; i < nftCount; ++i)
848 {
849 STObject newNFToken(
850 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
851 object.setFieldH256(sfNFTokenID, nftID);
852 });
853 ret.push_back(std::move(newNFToken));
854 ++nftID;
855 }
856 return ret;
857 };
858
860 {{"NFT page has invalid size"}},
861 [&makeNFTokenIDs](
862 Account const& A1, Account const&, ApplyContext& ac) {
863 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
864 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
865
866 ac.view().insert(nftPage);
867 return true;
868 });
869
871 {{"NFT page has invalid size"}},
872 [&makeNFTokenIDs](
873 Account const& A1, Account const&, ApplyContext& ac) {
874 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
875 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
876
877 ac.view().insert(nftPage);
878 return true;
879 });
880
882 {{"NFTs on page are not sorted"}},
883 [&makeNFTokenIDs](
884 Account const& A1, Account const&, ApplyContext& ac) {
885 STArray nfTokens = makeNFTokenIDs(2);
886 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
887
888 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
889 nftPage->setFieldArray(sfNFTokens, nfTokens);
890
891 ac.view().insert(nftPage);
892 return true;
893 });
894
896 {{"NFT contains empty URI"}},
897 [&makeNFTokenIDs](
898 Account const& A1, Account const&, ApplyContext& ac) {
899 STArray nfTokens = makeNFTokenIDs(1);
900 nfTokens[0].setFieldVL(sfURI, Blob{});
901
902 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
903 nftPage->setFieldArray(sfNFTokens, nfTokens);
904
905 ac.view().insert(nftPage);
906 return true;
907 });
908
910 {{"NFT page is improperly linked"}},
911 [&makeNFTokenIDs](
912 Account const& A1, Account const&, ApplyContext& ac) {
913 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
914 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
915 nftPage->setFieldH256(
916 sfPreviousPageMin, keylet::nftpage_max(A1).key);
917
918 ac.view().insert(nftPage);
919 return true;
920 });
921
923 {{"NFT page is improperly linked"}},
924 [&makeNFTokenIDs](
925 Account const& A1, Account const& A2, ApplyContext& ac) {
926 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
927 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
928 nftPage->setFieldH256(
929 sfPreviousPageMin, keylet::nftpage_min(A2).key);
930
931 ac.view().insert(nftPage);
932 return true;
933 });
934
936 {{"NFT page is improperly linked"}},
937 [&makeNFTokenIDs](
938 Account const& A1, Account const&, ApplyContext& ac) {
939 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
940 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
941 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
942
943 ac.view().insert(nftPage);
944 return true;
945 });
946
948 {{"NFT page is improperly linked"}},
949 [&makeNFTokenIDs](
950 Account const& A1, Account const& A2, ApplyContext& ac) {
951 STArray nfTokens = makeNFTokenIDs(1);
952 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
954 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
955 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
956 nftPage->setFieldH256(
957 sfNextPageMin, keylet::nftpage_max(A2).key);
958
959 ac.view().insert(nftPage);
960 return true;
961 });
962
964 {{"NFT found in incorrect page"}},
965 [&makeNFTokenIDs](
966 Account const& A1, Account const&, ApplyContext& ac) {
967 STArray nfTokens = makeNFTokenIDs(2);
968 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
970 (nfTokens[1].getFieldH256(sfNFTokenID))));
971 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
972
973 ac.view().insert(nftPage);
974 return true;
975 });
976 }
977
978 void
980 {
981 using namespace test::jtx;
982
983 testcase << "PermissionedDomain";
985 {{"permissioned domain with no rules."}},
986 [](Account const& A1, Account const&, ApplyContext& ac) {
987 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
988 auto slePd = std::make_shared<SLE>(pdKeylet);
989 slePd->setAccountID(sfOwner, A1);
990 slePd->setFieldU32(sfSequence, 10);
991
992 ac.view().insert(slePd);
993 return true;
994 },
995 XRPAmount{},
996 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
998
999 testcase << "PermissionedDomain 2";
1000
1001 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1003 {{"permissioned domain bad credentials size " +
1004 std::to_string(tooBig)}},
1005 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1006 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1007 auto slePd = std::make_shared<SLE>(pdKeylet);
1008 slePd->setAccountID(sfOwner, A1);
1009 slePd->setFieldU32(sfSequence, 10);
1010
1011 STArray credentials(sfAcceptedCredentials, tooBig);
1012 for (std::size_t n = 0; n < tooBig; ++n)
1013 {
1014 auto cred = STObject::makeInnerObject(sfCredential);
1015 cred.setAccountID(sfIssuer, A2);
1016 auto credType =
1017 std::string("cred_type") + std::to_string(n);
1018 cred.setFieldVL(
1019 sfCredentialType,
1020 Slice(credType.c_str(), credType.size()));
1021 credentials.push_back(std::move(cred));
1022 }
1023 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1024 ac.view().insert(slePd);
1025 return true;
1026 },
1027 XRPAmount{},
1028 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1030
1031 testcase << "PermissionedDomain 3";
1033 {{"permissioned domain credentials aren't sorted"}},
1034 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1035 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1036 auto slePd = std::make_shared<SLE>(pdKeylet);
1037 slePd->setAccountID(sfOwner, A1);
1038 slePd->setFieldU32(sfSequence, 10);
1039
1040 STArray credentials(sfAcceptedCredentials, 2);
1041 for (std::size_t n = 0; n < 2; ++n)
1042 {
1043 auto cred = STObject::makeInnerObject(sfCredential);
1044 cred.setAccountID(sfIssuer, A2);
1045 auto credType =
1046 std::string("cred_type") + std::to_string(9 - n);
1047 cred.setFieldVL(
1048 sfCredentialType,
1049 Slice(credType.c_str(), credType.size()));
1050 credentials.push_back(std::move(cred));
1051 }
1052 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1053 ac.view().insert(slePd);
1054 return true;
1055 },
1056 XRPAmount{},
1057 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1059
1060 testcase << "PermissionedDomain 4";
1062 {{"permissioned domain credentials aren't unique"}},
1063 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1064 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1065 auto slePd = std::make_shared<SLE>(pdKeylet);
1066 slePd->setAccountID(sfOwner, A1);
1067 slePd->setFieldU32(sfSequence, 10);
1068
1069 STArray credentials(sfAcceptedCredentials, 2);
1070 for (std::size_t n = 0; n < 2; ++n)
1071 {
1072 auto cred = STObject::makeInnerObject(sfCredential);
1073 cred.setAccountID(sfIssuer, A2);
1074 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1075 credentials.push_back(std::move(cred));
1076 }
1077 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1078 ac.view().insert(slePd);
1079 return true;
1080 },
1081 XRPAmount{},
1082 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1084
1085 auto const createPD = [](ApplyContext& ac,
1087 Account const& A1,
1088 Account const& A2) {
1089 sle->setAccountID(sfOwner, A1);
1090 sle->setFieldU32(sfSequence, 10);
1091
1092 STArray credentials(sfAcceptedCredentials, 2);
1093 for (std::size_t n = 0; n < 2; ++n)
1094 {
1095 auto cred = STObject::makeInnerObject(sfCredential);
1096 cred.setAccountID(sfIssuer, A2);
1097 auto credType = "cred_type" + std::to_string(n);
1098 cred.setFieldVL(
1099 sfCredentialType, Slice(credType.c_str(), credType.size()));
1100 credentials.push_back(std::move(cred));
1101 }
1102 sle->setFieldArray(sfAcceptedCredentials, credentials);
1103 ac.view().insert(sle);
1104 };
1105
1106 testcase << "PermissionedDomain Set 1";
1108 {{"permissioned domain with no rules."}},
1109 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
1110 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1111 auto slePd = std::make_shared<SLE>(pdKeylet);
1112
1113 // create PD
1114 createPD(ac, slePd, A1, A2);
1115
1116 // update PD with empty rules
1117 {
1118 STArray credentials(sfAcceptedCredentials, 2);
1119 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1120 ac.view().update(slePd);
1121 }
1122
1123 return true;
1124 },
1125 XRPAmount{},
1126 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1128
1129 testcase << "PermissionedDomain Set 2";
1131 {{"permissioned domain bad credentials size " +
1132 std::to_string(tooBig)}},
1133 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
1134 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1135 auto slePd = std::make_shared<SLE>(pdKeylet);
1136
1137 // create PD
1138 createPD(ac, slePd, A1, A2);
1139
1140 // update PD
1141 {
1142 STArray credentials(sfAcceptedCredentials, tooBig);
1143
1144 for (std::size_t n = 0; n < tooBig; ++n)
1145 {
1146 auto cred = STObject::makeInnerObject(sfCredential);
1147 cred.setAccountID(sfIssuer, A2);
1148 auto credType = "cred_type2" + std::to_string(n);
1149 cred.setFieldVL(
1150 sfCredentialType,
1151 Slice(credType.c_str(), credType.size()));
1152 credentials.push_back(std::move(cred));
1153 }
1154
1155 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1156 ac.view().update(slePd);
1157 }
1158
1159 return true;
1160 },
1161 XRPAmount{},
1162 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1164
1165 testcase << "PermissionedDomain Set 3";
1167 {{"permissioned domain credentials aren't sorted"}},
1168 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
1169 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1170 auto slePd = std::make_shared<SLE>(pdKeylet);
1171
1172 // create PD
1173 createPD(ac, slePd, A1, A2);
1174
1175 // update PD
1176 {
1177 STArray credentials(sfAcceptedCredentials, 2);
1178 for (std::size_t n = 0; n < 2; ++n)
1179 {
1180 auto cred = STObject::makeInnerObject(sfCredential);
1181 cred.setAccountID(sfIssuer, A2);
1182 auto credType =
1183 std::string("cred_type2") + std::to_string(9 - n);
1184 cred.setFieldVL(
1185 sfCredentialType,
1186 Slice(credType.c_str(), credType.size()));
1187 credentials.push_back(std::move(cred));
1188 }
1189
1190 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1191 ac.view().update(slePd);
1192 }
1193
1194 return true;
1195 },
1196 XRPAmount{},
1197 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1199
1200 testcase << "PermissionedDomain Set 4";
1202 {{"permissioned domain credentials aren't unique"}},
1203 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
1204 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1205 auto slePd = std::make_shared<SLE>(pdKeylet);
1206
1207 // create PD
1208 createPD(ac, slePd, A1, A2);
1209
1210 // update PD
1211 {
1212 STArray credentials(sfAcceptedCredentials, 2);
1213 for (std::size_t n = 0; n < 2; ++n)
1214 {
1215 auto cred = STObject::makeInnerObject(sfCredential);
1216 cred.setAccountID(sfIssuer, A2);
1217 cred.setFieldVL(
1218 sfCredentialType, Slice("cred_type", 9));
1219 credentials.push_back(std::move(cred));
1220 }
1221 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1222 ac.view().update(slePd);
1223 }
1224
1225 return true;
1226 },
1227 XRPAmount{},
1228 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1230 }
1231
1232public:
1233 void
1234 run() override
1235 {
1250 }
1251};
1252
1253BEAST_DEFINE_TESTSUITE(Invariants, ledger, ripple);
1254
1255} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
A testsuite class.
Definition: suite.h:53
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
State information when applying a tx.
Definition: ApplyContext.h:36
ApplyView & view()
Definition: ApplyContext.h:54
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
SOTemplate const * findSOTemplateBySField(SField const &sField) const
static InnerObjectFormats const & getInstance()
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={})
Run a specific test case to put the ledger into a state that will be detected by an invariant.
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, test::jtx::Env &env)> Preclose
void run() override
Runs the suite.
void testNoDeepFreezeTrustLinesWithoutFreeze()
A currency issued by an account.
Definition: Issue.h:36
Defines the fields and their attributes within a STObject.
Definition: SOTemplate.h:113
AccountID const & getIssuer() const
Definition: STAmount.h:499
void push_back(STObject const &object)
Definition: STArray.h:212
iterator begin()
Definition: STArray.h:224
void setFieldAmount(SField const &field, STAmount const &)
Definition: STObject.cpp:759
static STObject makeInnerObject(SField const &name)
Definition: STObject.cpp:65
An immutable linear range of bytes.
Definition: Slice.h:45
Immutable cryptographic account descriptor.
Definition: Account.h:38
A transaction testing environment.
Definition: Env.h:117
T invoke(T... args)
T iter_swap(T... args)
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition: Indexes.cpp:532
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:422
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 const & amendments() noexcept
The index of the amendment table.
Definition: Indexes.cpp:190
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition: Indexes.cpp:395
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition: Indexes.cpp:365
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:379
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:387
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:250
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
FeatureBitset supported_amendments()
Definition: Env.h:70
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:118
constexpr std::uint32_t tfSetDeepFreeze
Definition: TxFlags.h:117
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:110
@ lsfHighDeepFreeze
@ lsfHighFreeze
@ lsfLowFreeze
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:368
@ tefINVARIANT_FAILED
Definition: TER.h:183
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition: View.cpp:1052
@ tecINVARIANT_FAILED
Definition: TER.h:300
@ tesSUCCESS
Definition: TER.h:242
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
@ tapNONE
Definition: ApplyView.h:31
TERSubset< CanCvtToTER > TER
Definition: TER.h:627
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:115
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
T to_string(T... args)