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 << "XRP balance checks";
416
418 {{"Cannot return non-native STAmount as XRPAmount"}},
419 [](Account const& A1, Account const& A2, ApplyContext& ac) {
420 // non-native balance
421 auto const sle = ac.view().peek(keylet::account(A1.id()));
422 if (!sle)
423 return false;
424 STAmount const nonNative(A2["USD"](51));
425 sle->setFieldAmount(sfBalance, nonNative);
426 ac.view().update(sle);
427 return true;
428 });
429
431 {{"incorrect account XRP balance"},
432 {"XRP net change was positive: 99999999000000001"}},
433 [this](Account const& A1, Account const&, ApplyContext& ac) {
434 // balance exceeds genesis amount
435 auto const sle = ac.view().peek(keylet::account(A1.id()));
436 if (!sle)
437 return false;
438 // Use `drops(1)` to bypass a call to STAmount::canonicalize
439 // with an invalid value
440 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
441 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
442 ac.view().update(sle);
443 return true;
444 });
445
447 {{"incorrect account XRP balance"},
448 {"XRP net change of -1000000001 doesn't match fee 0"}},
449 [this](Account const& A1, Account const&, ApplyContext& ac) {
450 // balance is negative
451 auto const sle = ac.view().peek(keylet::account(A1.id()));
452 if (!sle)
453 return false;
454 sle->setFieldAmount(sfBalance, STAmount{1, true});
455 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
456 ac.view().update(sle);
457 return true;
458 });
459 }
460
461 void
463 {
464 using namespace test::jtx;
465 using namespace std::string_literals;
466 testcase << "Transaction fee checks";
467
469 {{"fee paid was negative: -1"},
470 {"XRP net change of 0 doesn't match fee -1"}},
471 [](Account const&, Account const&, ApplyContext&) { return true; },
472 XRPAmount{-1});
473
475 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
476 {"XRP net change of 0 doesn't match fee "s +
478 [](Account const&, Account const&, ApplyContext&) { return true; },
480
482 {{"fee paid is 20 exceeds fee specified in transaction."},
483 {"XRP net change of 0 doesn't match fee 20"}},
484 [](Account const&, Account const&, ApplyContext&) { return true; },
485 XRPAmount{20},
486 STTx{ttACCOUNT_SET, [](STObject& tx) {
487 tx.setFieldAmount(sfFee, XRPAmount{10});
488 }});
489 }
490
491 void
493 {
494 using namespace test::jtx;
495 testcase << "no bad offers";
496
498 {{"offer with a bad amount"}},
499 [](Account const& A1, Account const&, ApplyContext& ac) {
500 // offer with negative takerpays
501 auto const sle = ac.view().peek(keylet::account(A1.id()));
502 if (!sle)
503 return false;
504 auto sleNew = std::make_shared<SLE>(
505 keylet::offer(A1.id(), (*sle)[sfSequence]));
506 sleNew->setAccountID(sfAccount, A1.id());
507 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
508 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
509 ac.view().insert(sleNew);
510 return true;
511 });
512
514 {{"offer with a bad amount"}},
515 [](Account const& A1, Account const&, ApplyContext& ac) {
516 // offer with negative takergets
517 auto const sle = ac.view().peek(keylet::account(A1.id()));
518 if (!sle)
519 return false;
520 auto sleNew = std::make_shared<SLE>(
521 keylet::offer(A1.id(), (*sle)[sfSequence]));
522 sleNew->setAccountID(sfAccount, A1.id());
523 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
524 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
525 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
526 ac.view().insert(sleNew);
527 return true;
528 });
529
531 {{"offer with a bad amount"}},
532 [](Account const& A1, Account const&, ApplyContext& ac) {
533 // offer XRP to XRP
534 auto const sle = ac.view().peek(keylet::account(A1.id()));
535 if (!sle)
536 return false;
537 auto sleNew = std::make_shared<SLE>(
538 keylet::offer(A1.id(), (*sle)[sfSequence]));
539 sleNew->setAccountID(sfAccount, A1.id());
540 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
541 sleNew->setFieldAmount(sfTakerPays, XRP(10));
542 sleNew->setFieldAmount(sfTakerGets, XRP(11));
543 ac.view().insert(sleNew);
544 return true;
545 });
546 }
547
548 void
550 {
551 using namespace test::jtx;
552 testcase << "no zero escrow";
553
555 {{"Cannot return non-native STAmount as XRPAmount"}},
556 [](Account const& A1, Account const& A2, ApplyContext& ac) {
557 // escrow with nonnative amount
558 auto const sle = ac.view().peek(keylet::account(A1.id()));
559 if (!sle)
560 return false;
561 auto sleNew = std::make_shared<SLE>(
562 keylet::escrow(A1, (*sle)[sfSequence] + 2));
563 STAmount nonNative(A2["USD"](51));
564 sleNew->setFieldAmount(sfAmount, nonNative);
565 ac.view().insert(sleNew);
566 return true;
567 });
568
570 {{"XRP net change of -1000000 doesn't match fee 0"},
571 {"escrow specifies invalid amount"}},
572 [](Account const& A1, Account const&, ApplyContext& ac) {
573 // escrow with negative amount
574 auto const sle = ac.view().peek(keylet::account(A1.id()));
575 if (!sle)
576 return false;
577 auto sleNew = std::make_shared<SLE>(
578 keylet::escrow(A1, (*sle)[sfSequence] + 2));
579 sleNew->setFieldAmount(sfAmount, XRP(-1));
580 ac.view().insert(sleNew);
581 return true;
582 });
583
585 {{"XRP net change was positive: 100000000000000001"},
586 {"escrow specifies invalid amount"}},
587 [](Account const& A1, Account const&, ApplyContext& ac) {
588 // escrow with too-large amount
589 auto const sle = ac.view().peek(keylet::account(A1.id()));
590 if (!sle)
591 return false;
592 auto sleNew = std::make_shared<SLE>(
593 keylet::escrow(A1, (*sle)[sfSequence] + 2));
594 // Use `drops(1)` to bypass a call to STAmount::canonicalize
595 // with an invalid value
596 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
597 ac.view().insert(sleNew);
598 return true;
599 });
600 }
601
602 void
604 {
605 using namespace test::jtx;
606 testcase << "valid new account root";
607
609 {{"account root created by a non-Payment"}},
610 [](Account const&, Account const&, ApplyContext& ac) {
611 // Insert a new account root created by a non-payment into
612 // the view.
613 Account const A3{"A3"};
614 Keylet const acctKeylet = keylet::account(A3);
615 auto const sleNew = std::make_shared<SLE>(acctKeylet);
616 ac.view().insert(sleNew);
617 return true;
618 });
619
621 {{"multiple accounts created in a single transaction"}},
622 [](Account const&, Account const&, ApplyContext& ac) {
623 // Insert two new account roots into the view.
624 {
625 Account const A3{"A3"};
626 Keylet const acctKeylet = keylet::account(A3);
627 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
628 ac.view().insert(sleA3);
629 }
630 {
631 Account const A4{"A4"};
632 Keylet const acctKeylet = keylet::account(A4);
633 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
634 ac.view().insert(sleA4);
635 }
636 return true;
637 });
638
640 {{"account created with wrong starting sequence number"}},
641 [](Account const&, Account const&, ApplyContext& ac) {
642 // Insert a new account root with the wrong starting sequence.
643 Account const A3{"A3"};
644 Keylet const acctKeylet = keylet::account(A3);
645 auto const sleNew = std::make_shared<SLE>(acctKeylet);
646 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
647 ac.view().insert(sleNew);
648 return true;
649 },
650 XRPAmount{},
651 STTx{ttPAYMENT, [](STObject& tx) {}});
652 }
653
654 void
656 {
657 using namespace test::jtx;
658 testcase << "NFTokenPage";
659
660 // lambda that returns an STArray of NFTokenIDs.
661 uint256 const firstNFTID(
662 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
663 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
664 SOTemplate const* nfTokenTemplate =
666 sfNFToken);
667
668 uint256 nftID(firstNFTID);
669 STArray ret;
670 for (int i = 0; i < nftCount; ++i)
671 {
672 STObject newNFToken(
673 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
674 object.setFieldH256(sfNFTokenID, nftID);
675 });
676 ret.push_back(std::move(newNFToken));
677 ++nftID;
678 }
679 return ret;
680 };
681
683 {{"NFT page has invalid size"}},
684 [&makeNFTokenIDs](
685 Account const& A1, Account const&, ApplyContext& ac) {
686 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
687 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
688
689 ac.view().insert(nftPage);
690 return true;
691 });
692
694 {{"NFT page has invalid size"}},
695 [&makeNFTokenIDs](
696 Account const& A1, Account const&, ApplyContext& ac) {
697 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
698 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
699
700 ac.view().insert(nftPage);
701 return true;
702 });
703
705 {{"NFTs on page are not sorted"}},
706 [&makeNFTokenIDs](
707 Account const& A1, Account const&, ApplyContext& ac) {
708 STArray nfTokens = makeNFTokenIDs(2);
709 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
710
711 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
712 nftPage->setFieldArray(sfNFTokens, nfTokens);
713
714 ac.view().insert(nftPage);
715 return true;
716 });
717
719 {{"NFT contains empty URI"}},
720 [&makeNFTokenIDs](
721 Account const& A1, Account const&, ApplyContext& ac) {
722 STArray nfTokens = makeNFTokenIDs(1);
723 nfTokens[0].setFieldVL(sfURI, Blob{});
724
725 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
726 nftPage->setFieldArray(sfNFTokens, nfTokens);
727
728 ac.view().insert(nftPage);
729 return true;
730 });
731
733 {{"NFT page is improperly linked"}},
734 [&makeNFTokenIDs](
735 Account const& A1, Account const&, ApplyContext& ac) {
736 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
737 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
738 nftPage->setFieldH256(
739 sfPreviousPageMin, keylet::nftpage_max(A1).key);
740
741 ac.view().insert(nftPage);
742 return true;
743 });
744
746 {{"NFT page is improperly linked"}},
747 [&makeNFTokenIDs](
748 Account const& A1, Account const& A2, ApplyContext& ac) {
749 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
750 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
751 nftPage->setFieldH256(
752 sfPreviousPageMin, keylet::nftpage_min(A2).key);
753
754 ac.view().insert(nftPage);
755 return true;
756 });
757
759 {{"NFT page is improperly linked"}},
760 [&makeNFTokenIDs](
761 Account const& A1, Account const&, ApplyContext& ac) {
762 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
763 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
764 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
765
766 ac.view().insert(nftPage);
767 return true;
768 });
769
771 {{"NFT page is improperly linked"}},
772 [&makeNFTokenIDs](
773 Account const& A1, Account const& A2, ApplyContext& ac) {
774 STArray nfTokens = makeNFTokenIDs(1);
775 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
777 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
778 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
779 nftPage->setFieldH256(
780 sfNextPageMin, keylet::nftpage_max(A2).key);
781
782 ac.view().insert(nftPage);
783 return true;
784 });
785
787 {{"NFT found in incorrect page"}},
788 [&makeNFTokenIDs](
789 Account const& A1, Account const&, ApplyContext& ac) {
790 STArray nfTokens = makeNFTokenIDs(2);
791 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
793 (nfTokens[1].getFieldH256(sfNFTokenID))));
794 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
795
796 ac.view().insert(nftPage);
797 return true;
798 });
799 }
800
801 void
803 {
804 using namespace test::jtx;
805
806 testcase << "PermissionedDomain";
808 {{"permissioned domain with no rules."}},
809 [](Account const& A1, Account const&, ApplyContext& ac) {
810 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
811 auto slePd = std::make_shared<SLE>(pdKeylet);
812 slePd->setAccountID(sfOwner, A1);
813 slePd->setFieldU32(sfSequence, 10);
814
815 ac.view().insert(slePd);
816 return true;
817 },
818 XRPAmount{},
819 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
821
822 testcase << "PermissionedDomain 2";
823
824 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
826 {{"permissioned domain bad credentials size " +
827 std::to_string(tooBig)}},
828 [](Account const& A1, Account const& A2, ApplyContext& ac) {
829 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
830 auto slePd = std::make_shared<SLE>(pdKeylet);
831 slePd->setAccountID(sfOwner, A1);
832 slePd->setFieldU32(sfSequence, 10);
833
834 STArray credentials(sfAcceptedCredentials, tooBig);
835 for (std::size_t n = 0; n < tooBig; ++n)
836 {
837 auto cred = STObject::makeInnerObject(sfCredential);
838 cred.setAccountID(sfIssuer, A2);
839 auto credType =
840 std::string("cred_type") + std::to_string(n);
841 cred.setFieldVL(
842 sfCredentialType,
843 Slice(credType.c_str(), credType.size()));
844 credentials.push_back(std::move(cred));
845 }
846 slePd->setFieldArray(sfAcceptedCredentials, credentials);
847 ac.view().insert(slePd);
848 return true;
849 },
850 XRPAmount{},
851 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
853
854 testcase << "PermissionedDomain 3";
856 {{"permissioned domain credentials aren't sorted"}},
857 [](Account const& A1, Account const& A2, ApplyContext& ac) {
858 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
859 auto slePd = std::make_shared<SLE>(pdKeylet);
860 slePd->setAccountID(sfOwner, A1);
861 slePd->setFieldU32(sfSequence, 10);
862
863 STArray credentials(sfAcceptedCredentials, 2);
864 for (std::size_t n = 0; n < 2; ++n)
865 {
866 auto cred = STObject::makeInnerObject(sfCredential);
867 cred.setAccountID(sfIssuer, A2);
868 auto credType =
869 std::string("cred_type") + std::to_string(9 - n);
870 cred.setFieldVL(
871 sfCredentialType,
872 Slice(credType.c_str(), credType.size()));
873 credentials.push_back(std::move(cred));
874 }
875 slePd->setFieldArray(sfAcceptedCredentials, credentials);
876 ac.view().insert(slePd);
877 return true;
878 },
879 XRPAmount{},
880 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
882
883 testcase << "PermissionedDomain 4";
885 {{"permissioned domain credentials aren't unique"}},
886 [](Account const& A1, Account const& A2, ApplyContext& ac) {
887 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
888 auto slePd = std::make_shared<SLE>(pdKeylet);
889 slePd->setAccountID(sfOwner, A1);
890 slePd->setFieldU32(sfSequence, 10);
891
892 STArray credentials(sfAcceptedCredentials, 2);
893 for (std::size_t n = 0; n < 2; ++n)
894 {
895 auto cred = STObject::makeInnerObject(sfCredential);
896 cred.setAccountID(sfIssuer, A2);
897 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
898 credentials.push_back(std::move(cred));
899 }
900 slePd->setFieldArray(sfAcceptedCredentials, credentials);
901 ac.view().insert(slePd);
902 return true;
903 },
904 XRPAmount{},
905 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
907
908 auto const createPD = [](ApplyContext& ac,
910 Account const& A1,
911 Account const& A2) {
912 sle->setAccountID(sfOwner, A1);
913 sle->setFieldU32(sfSequence, 10);
914
915 STArray credentials(sfAcceptedCredentials, 2);
916 for (std::size_t n = 0; n < 2; ++n)
917 {
918 auto cred = STObject::makeInnerObject(sfCredential);
919 cred.setAccountID(sfIssuer, A2);
920 auto credType = "cred_type" + std::to_string(n);
921 cred.setFieldVL(
922 sfCredentialType, Slice(credType.c_str(), credType.size()));
923 credentials.push_back(std::move(cred));
924 }
925 sle->setFieldArray(sfAcceptedCredentials, credentials);
926 ac.view().insert(sle);
927 };
928
929 testcase << "PermissionedDomain Set 1";
931 {{"permissioned domain with no rules."}},
932 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
933 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
934 auto slePd = std::make_shared<SLE>(pdKeylet);
935
936 // create PD
937 createPD(ac, slePd, A1, A2);
938
939 // update PD with empty rules
940 {
941 STArray credentials(sfAcceptedCredentials, 2);
942 slePd->setFieldArray(sfAcceptedCredentials, credentials);
943 ac.view().update(slePd);
944 }
945
946 return true;
947 },
948 XRPAmount{},
949 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
951
952 testcase << "PermissionedDomain Set 2";
954 {{"permissioned domain bad credentials size " +
955 std::to_string(tooBig)}},
956 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
957 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
958 auto slePd = std::make_shared<SLE>(pdKeylet);
959
960 // create PD
961 createPD(ac, slePd, A1, A2);
962
963 // update PD
964 {
965 STArray credentials(sfAcceptedCredentials, tooBig);
966
967 for (std::size_t n = 0; n < tooBig; ++n)
968 {
969 auto cred = STObject::makeInnerObject(sfCredential);
970 cred.setAccountID(sfIssuer, A2);
971 auto credType = "cred_type2" + std::to_string(n);
972 cred.setFieldVL(
973 sfCredentialType,
974 Slice(credType.c_str(), credType.size()));
975 credentials.push_back(std::move(cred));
976 }
977
978 slePd->setFieldArray(sfAcceptedCredentials, credentials);
979 ac.view().update(slePd);
980 }
981
982 return true;
983 },
984 XRPAmount{},
985 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
987
988 testcase << "PermissionedDomain Set 3";
990 {{"permissioned domain credentials aren't sorted"}},
991 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
992 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
993 auto slePd = std::make_shared<SLE>(pdKeylet);
994
995 // create PD
996 createPD(ac, slePd, A1, A2);
997
998 // update PD
999 {
1000 STArray credentials(sfAcceptedCredentials, 2);
1001 for (std::size_t n = 0; n < 2; ++n)
1002 {
1003 auto cred = STObject::makeInnerObject(sfCredential);
1004 cred.setAccountID(sfIssuer, A2);
1005 auto credType =
1006 std::string("cred_type2") + std::to_string(9 - n);
1007 cred.setFieldVL(
1008 sfCredentialType,
1009 Slice(credType.c_str(), credType.size()));
1010 credentials.push_back(std::move(cred));
1011 }
1012
1013 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1014 ac.view().update(slePd);
1015 }
1016
1017 return true;
1018 },
1019 XRPAmount{},
1020 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1022
1023 testcase << "PermissionedDomain Set 4";
1025 {{"permissioned domain credentials aren't unique"}},
1026 [createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
1027 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1028 auto slePd = std::make_shared<SLE>(pdKeylet);
1029
1030 // create PD
1031 createPD(ac, slePd, A1, A2);
1032
1033 // update PD
1034 {
1035 STArray credentials(sfAcceptedCredentials, 2);
1036 for (std::size_t n = 0; n < 2; ++n)
1037 {
1038 auto cred = STObject::makeInnerObject(sfCredential);
1039 cred.setAccountID(sfIssuer, A2);
1040 cred.setFieldVL(
1041 sfCredentialType, Slice("cred_type", 9));
1042 credentials.push_back(std::move(cred));
1043 }
1044 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1045 ac.view().update(slePd);
1046 }
1047
1048 return true;
1049 },
1050 XRPAmount{},
1051 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1053 }
1054
1055public:
1056 void
1057 run() override
1058 {
1071 }
1072};
1073
1074BEAST_DEFINE_TESTSUITE(Invariants, ledger, ripple);
1075
1076} // 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.
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
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:110
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:970
@ 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
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
T to_string(T... args)