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