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 = supported_amendments() |
82 featureInvariantsV1_1 | featureSingleAssetVault;
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 for (auto const& m : expect_logs)
120 {
121 if (sink.messages().str().find(m) == std::string::npos)
122 {
123 // uncomment if you want to log the invariant failure
124 // message log << " --> " << m << std::endl;
125 fail();
126 }
127 }
128 }
129 }
130
131 void
133 {
134 using namespace test::jtx;
135 testcase << "XRP created";
137 {{"XRP net change was positive: 500"}},
138 [](Account const& A1, Account const&, ApplyContext& ac) {
139 // put a single account in the view and "manufacture" some XRP
140 auto const sle = ac.view().peek(keylet::account(A1.id()));
141 if (!sle)
142 return false;
143 auto amt = sle->getFieldAmount(sfBalance);
144 sle->setFieldAmount(sfBalance, amt + STAmount{500});
145 ac.view().update(sle);
146 return true;
147 });
148 }
149
150 void
152 {
153 using namespace test::jtx;
154 testcase << "account root removed";
155
156 // An account was deleted, but not by an AccountDelete transaction.
158 {{"an account root was deleted"}},
159 [](Account const& A1, Account const&, ApplyContext& ac) {
160 // remove an account from the view
161 auto const sle = ac.view().peek(keylet::account(A1.id()));
162 if (!sle)
163 return false;
164 ac.view().erase(sle);
165 return true;
166 });
167
168 // Successful AccountDelete transaction that didn't delete an account.
169 //
170 // Note that this is a case where a second invocation of the invariant
171 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
172 // After a discussion with the team, we believe that's okay.
174 {{"account deletion succeeded without deleting an account"}},
175 [](Account const&, Account const&, ApplyContext& ac) {
176 return true;
177 },
178 XRPAmount{},
179 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
181
182 // Successful AccountDelete that deleted more than one account.
184 {{"account deletion succeeded but deleted multiple accounts"}},
185 [](Account const& A1, Account const& A2, ApplyContext& ac) {
186 // remove two accounts from the view
187 auto const sleA1 = ac.view().peek(keylet::account(A1.id()));
188 auto const sleA2 = ac.view().peek(keylet::account(A2.id()));
189 if (!sleA1 || !sleA2)
190 return false;
191 ac.view().erase(sleA1);
192 ac.view().erase(sleA2);
193 return true;
194 },
195 XRPAmount{},
196 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
197 }
198
199 void
201 {
202 using namespace test::jtx;
203 testcase << "account root deletion left artifact";
204
205 for (auto const& keyletInfo : directAccountKeylets)
206 {
207 // TODO: Use structured binding once LLVM 16 is the minimum
208 // supported version. See also:
209 // https://github.com/llvm/llvm-project/issues/48582
210 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
211 if (!keyletInfo.includeInTests)
212 continue;
213 auto const& keyletfunc = keyletInfo.function;
214 auto const& type = keyletInfo.expectedLEName;
215
216 using namespace std::string_literals;
217
219 {{"account deletion left behind a "s + type.c_str() +
220 " object"}},
221 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
222 // Add an object to the ledger for account A1, then delete
223 // A1
224 auto const a1 = A1.id();
225 auto const sleA1 = ac.view().peek(keylet::account(a1));
226 if (!sleA1)
227 return false;
228
229 auto const key = std::invoke(keyletfunc, a1);
230 auto const newSLE = std::make_shared<SLE>(key);
231 ac.view().insert(newSLE);
232 ac.view().erase(sleA1);
233
234 return true;
235 },
236 XRPAmount{},
237 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
238 };
239
240 // NFT special case
242 {{"account deletion left behind a NFTokenPage object"}},
243 [&](Account const& A1, Account const&, ApplyContext& ac) {
244 // remove an account from the view
245 auto const sle = ac.view().peek(keylet::account(A1.id()));
246 if (!sle)
247 return false;
248 ac.view().erase(sle);
249 return true;
250 },
251 XRPAmount{},
252 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
254 [&](Account const& A1, Account const&, Env& env) {
255 // Preclose callback to mint the NFT which will be deleted in
256 // the Precheck callback above.
257 env(token::mint(A1));
258
259 return true;
260 });
261
262 // AMM special cases
263 AccountID ammAcctID;
264 uint256 ammKey;
265 Issue ammIssue;
267 {{"account deletion left behind a DirectoryNode object"}},
268 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
269 // Delete the AMM account without cleaning up the directory or
270 // deleting the AMM object
271 auto const sle = ac.view().peek(keylet::account(ammAcctID));
272 if (!sle)
273 return false;
274
275 BEAST_EXPECT(sle->at(~sfAMMID));
276 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
277
278 ac.view().erase(sle);
279
280 return true;
281 },
282 XRPAmount{},
283 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
285 [&](Account const& A1, Account const& A2, Env& env) {
286 // Preclose callback to create the AMM which will be partially
287 // deleted in the Precheck callback above.
288 AMM const amm(env, A1, XRP(100), A1["USD"](50));
289 ammAcctID = amm.ammAccount();
290 ammKey = amm.ammID();
291 ammIssue = amm.lptIssue();
292 return true;
293 });
295 {{"account deletion left behind a AMM object"}},
296 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
297 // Delete all the AMM's trust lines, remove the AMM from the AMM
298 // account's directory (this deletes the directory), and delete
299 // the AMM account. Do not delete the AMM object.
300 auto const sle = ac.view().peek(keylet::account(ammAcctID));
301 if (!sle)
302 return false;
303
304 BEAST_EXPECT(sle->at(~sfAMMID));
305 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
306
307 for (auto const& trustKeylet :
308 {keylet::line(ammAcctID, A1["USD"]),
309 keylet::line(A1, ammIssue)})
310 {
311 if (auto const line = ac.view().peek(trustKeylet); !line)
312 {
313 return false;
314 }
315 else
316 {
317 STAmount const lowLimit = line->at(sfLowLimit);
318 STAmount const highLimit = line->at(sfHighLimit);
319 BEAST_EXPECT(
321 ac.view(),
322 line,
323 lowLimit.getIssuer(),
324 highLimit.getIssuer(),
325 ac.journal) == tesSUCCESS);
326 }
327 }
328
329 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
330 if (!BEAST_EXPECT(ammSle))
331 return false;
332 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
333
334 BEAST_EXPECT(ac.view().dirRemove(
335 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
336 BEAST_EXPECT(
337 !ac.view().exists(ownerDirKeylet) ||
338 ac.view().emptyDirDelete(ownerDirKeylet));
339
340 ac.view().erase(sle);
341
342 return true;
343 },
344 XRPAmount{},
345 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
347 [&](Account const& A1, Account const& A2, Env& env) {
348 // Preclose callback to create the AMM which will be partially
349 // deleted in the Precheck callback above.
350 AMM const amm(env, A1, XRP(100), A1["USD"](50));
351 ammAcctID = amm.ammAccount();
352 ammKey = amm.ammID();
353 ammIssue = amm.lptIssue();
354 return true;
355 });
356 }
357
358 void
360 {
361 using namespace test::jtx;
362 testcase << "ledger entry types don't match";
364 {{"ledger entry type mismatch"},
365 {"XRP net change of -1000000000 doesn't match fee 0"}},
366 [](Account const& A1, Account const&, ApplyContext& ac) {
367 // replace an entry in the table with an SLE of a different type
368 auto const sle = ac.view().peek(keylet::account(A1.id()));
369 if (!sle)
370 return false;
371 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
372 ac.rawView().rawReplace(sleNew);
373 return true;
374 });
375
377 {{"invalid ledger entry type added"}},
378 [](Account const& A1, Account const&, ApplyContext& ac) {
379 // add an entry in the table with an SLE of an invalid type
380 auto const sle = ac.view().peek(keylet::account(A1.id()));
381 if (!sle)
382 return false;
383
384 // make a dummy escrow ledger entry, then change the type to an
385 // unsupported value so that the valid type invariant check
386 // will fail.
387 auto const sleNew = std::make_shared<SLE>(
388 keylet::escrow(A1, (*sle)[sfSequence] + 2));
389
390 // We don't use ltNICKNAME directly since it's marked deprecated
391 // to prevent accidental use elsewhere.
392 sleNew->type_ = static_cast<LedgerEntryType>('n');
393 ac.view().insert(sleNew);
394 return true;
395 });
396 }
397
398 void
400 {
401 using namespace test::jtx;
402 testcase << "trust lines with XRP not allowed";
404 {{"an XRP trust line was created"}},
405 [](Account const& A1, Account const& A2, ApplyContext& ac) {
406 // create simple trust SLE with xrp currency
407 auto const sleNew = std::make_shared<SLE>(
408 keylet::line(A1, A2, xrpIssue().currency));
409 ac.view().insert(sleNew);
410 return true;
411 });
412 }
413
414 void
416 {
417 using namespace test::jtx;
418 testcase << "trust lines with deep freeze flag without freeze "
419 "not allowed";
421 {{"a trust line with deep freeze flag without normal freeze was "
422 "created"}},
423 [](Account const& A1, Account const& A2, ApplyContext& ac) {
424 auto const sleNew = std::make_shared<SLE>(
425 keylet::line(A1, A2, A1["USD"].currency));
426 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
427 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
428
429 std::uint32_t uFlags = 0u;
430 uFlags |= lsfLowDeepFreeze;
431 sleNew->setFieldU32(sfFlags, uFlags);
432 ac.view().insert(sleNew);
433 return true;
434 });
435
437 {{"a trust line with deep freeze flag without normal freeze was "
438 "created"}},
439 [](Account const& A1, Account const& A2, ApplyContext& ac) {
440 auto const sleNew = std::make_shared<SLE>(
441 keylet::line(A1, A2, A1["USD"].currency));
442 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
443 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
444 std::uint32_t uFlags = 0u;
445 uFlags |= lsfHighDeepFreeze;
446 sleNew->setFieldU32(sfFlags, uFlags);
447 ac.view().insert(sleNew);
448 return true;
449 });
450
452 {{"a trust line with deep freeze flag without normal freeze was "
453 "created"}},
454 [](Account const& A1, Account const& A2, ApplyContext& ac) {
455 auto const sleNew = std::make_shared<SLE>(
456 keylet::line(A1, A2, A1["USD"].currency));
457 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
458 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
459 std::uint32_t uFlags = 0u;
461 sleNew->setFieldU32(sfFlags, uFlags);
462 ac.view().insert(sleNew);
463 return true;
464 });
465
467 {{"a trust line with deep freeze flag without normal freeze was "
468 "created"}},
469 [](Account const& A1, Account const& A2, ApplyContext& ac) {
470 auto const sleNew = std::make_shared<SLE>(
471 keylet::line(A1, A2, A1["USD"].currency));
472 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
473 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
474 std::uint32_t uFlags = 0u;
476 sleNew->setFieldU32(sfFlags, uFlags);
477 ac.view().insert(sleNew);
478 return true;
479 });
480
482 {{"a trust line with deep freeze flag without normal freeze was "
483 "created"}},
484 [](Account const& A1, Account const& A2, ApplyContext& ac) {
485 auto const sleNew = std::make_shared<SLE>(
486 keylet::line(A1, A2, A1["USD"].currency));
487 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
488 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
489 std::uint32_t uFlags = 0u;
491 sleNew->setFieldU32(sfFlags, uFlags);
492 ac.view().insert(sleNew);
493 return true;
494 });
495 }
496
497 void
499 {
500 using namespace test::jtx;
501 testcase << "transfers when frozen";
502
503 Account G1{"G1"};
504 // Helper function to establish the trustlines
505 auto const createTrustlines =
506 [&](Account const& A1, Account const& A2, Env& env) {
507 // Preclose callback to establish trust lines with gateway
508 env.fund(XRP(1000), G1);
509
510 env.trust(G1["USD"](10000), A1);
511 env.trust(G1["USD"](10000), A2);
512 env.close();
513
514 env(pay(G1, A1, G1["USD"](1000)));
515 env(pay(G1, A2, G1["USD"](1000)));
516 env.close();
517
518 return true;
519 };
520
521 auto const A1FrozenByIssuer =
522 [&](Account const& A1, Account const& A2, Env& env) {
523 createTrustlines(A1, A2, env);
524 env(trust(G1, A1["USD"](10000), tfSetFreeze));
525 env.close();
526
527 return true;
528 };
529
530 auto const A1DeepFrozenByIssuer =
531 [&](Account const& A1, Account const& A2, Env& env) {
532 A1FrozenByIssuer(A1, A2, env);
533 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
534 env.close();
535
536 return true;
537 };
538
539 auto const changeBalances = [&](Account const& A1,
540 Account const& A2,
541 ApplyContext& ac,
542 int A1Balance,
543 int A2Balance) {
544 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
545 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
546
547 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
548 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
549
550 ac.view().update(sleA1);
551 ac.view().update(sleA2);
552 };
553
554 // test: imitating frozen A1 making a payment to A2.
556 {{"Attempting to move frozen funds"}},
557 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
558 changeBalances(A1, A2, ac, -900, -1100);
559 return true;
560 },
561 XRPAmount{},
562 STTx{ttPAYMENT, [](STObject& tx) {}},
564 A1FrozenByIssuer);
565
566 // test: imitating deep frozen A1 making a payment to A2.
568 {{"Attempting to move frozen funds"}},
569 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
570 changeBalances(A1, A2, ac, -900, -1100);
571 return true;
572 },
573 XRPAmount{},
574 STTx{ttPAYMENT, [](STObject& tx) {}},
576 A1DeepFrozenByIssuer);
577
578 // test: imitating A2 making a payment to deep frozen A1.
580 {{"Attempting to move frozen funds"}},
581 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
582 changeBalances(A1, A2, ac, -1100, -900);
583 return true;
584 },
585 XRPAmount{},
586 STTx{ttPAYMENT, [](STObject& tx) {}},
588 A1DeepFrozenByIssuer);
589 }
590
591 void
593 {
594 using namespace test::jtx;
595 testcase << "XRP balance checks";
596
598 {{"Cannot return non-native STAmount as XRPAmount"}},
599 [](Account const& A1, Account const& A2, ApplyContext& ac) {
600 // non-native balance
601 auto const sle = ac.view().peek(keylet::account(A1.id()));
602 if (!sle)
603 return false;
604 STAmount const nonNative(A2["USD"](51));
605 sle->setFieldAmount(sfBalance, nonNative);
606 ac.view().update(sle);
607 return true;
608 });
609
611 {{"incorrect account XRP balance"},
612 {"XRP net change was positive: 99999999000000001"}},
613 [this](Account const& A1, Account const&, ApplyContext& ac) {
614 // balance exceeds genesis amount
615 auto const sle = ac.view().peek(keylet::account(A1.id()));
616 if (!sle)
617 return false;
618 // Use `drops(1)` to bypass a call to STAmount::canonicalize
619 // with an invalid value
620 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
621 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
622 ac.view().update(sle);
623 return true;
624 });
625
627 {{"incorrect account XRP balance"},
628 {"XRP net change of -1000000001 doesn't match fee 0"}},
629 [this](Account const& A1, Account const&, ApplyContext& ac) {
630 // balance is negative
631 auto const sle = ac.view().peek(keylet::account(A1.id()));
632 if (!sle)
633 return false;
634 sle->setFieldAmount(sfBalance, STAmount{1, true});
635 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
636 ac.view().update(sle);
637 return true;
638 });
639 }
640
641 void
643 {
644 using namespace test::jtx;
645 using namespace std::string_literals;
646 testcase << "Transaction fee checks";
647
649 {{"fee paid was negative: -1"},
650 {"XRP net change of 0 doesn't match fee -1"}},
651 [](Account const&, Account const&, ApplyContext&) { return true; },
652 XRPAmount{-1});
653
655 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
656 {"XRP net change of 0 doesn't match fee "s +
658 [](Account const&, Account const&, ApplyContext&) { return true; },
660
662 {{"fee paid is 20 exceeds fee specified in transaction."},
663 {"XRP net change of 0 doesn't match fee 20"}},
664 [](Account const&, Account const&, ApplyContext&) { return true; },
665 XRPAmount{20},
666 STTx{ttACCOUNT_SET, [](STObject& tx) {
667 tx.setFieldAmount(sfFee, XRPAmount{10});
668 }});
669 }
670
671 void
673 {
674 using namespace test::jtx;
675 testcase << "no bad offers";
676
678 {{"offer with a bad amount"}},
679 [](Account const& A1, Account const&, ApplyContext& ac) {
680 // offer with negative takerpays
681 auto const sle = ac.view().peek(keylet::account(A1.id()));
682 if (!sle)
683 return false;
684 auto sleNew = std::make_shared<SLE>(
685 keylet::offer(A1.id(), (*sle)[sfSequence]));
686 sleNew->setAccountID(sfAccount, A1.id());
687 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
688 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
689 ac.view().insert(sleNew);
690 return true;
691 });
692
694 {{"offer with a bad amount"}},
695 [](Account const& A1, Account const&, ApplyContext& ac) {
696 // offer with negative takergets
697 auto const sle = ac.view().peek(keylet::account(A1.id()));
698 if (!sle)
699 return false;
700 auto sleNew = std::make_shared<SLE>(
701 keylet::offer(A1.id(), (*sle)[sfSequence]));
702 sleNew->setAccountID(sfAccount, A1.id());
703 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
704 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
705 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
706 ac.view().insert(sleNew);
707 return true;
708 });
709
711 {{"offer with a bad amount"}},
712 [](Account const& A1, Account const&, ApplyContext& ac) {
713 // offer XRP to XRP
714 auto const sle = ac.view().peek(keylet::account(A1.id()));
715 if (!sle)
716 return false;
717 auto sleNew = std::make_shared<SLE>(
718 keylet::offer(A1.id(), (*sle)[sfSequence]));
719 sleNew->setAccountID(sfAccount, A1.id());
720 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
721 sleNew->setFieldAmount(sfTakerPays, XRP(10));
722 sleNew->setFieldAmount(sfTakerGets, XRP(11));
723 ac.view().insert(sleNew);
724 return true;
725 });
726 }
727
728 void
730 {
731 using namespace test::jtx;
732 testcase << "no zero escrow";
733
735 {{"Cannot return non-native STAmount as XRPAmount"}},
736 [](Account const& A1, Account const& A2, ApplyContext& ac) {
737 // escrow with nonnative amount
738 auto const sle = ac.view().peek(keylet::account(A1.id()));
739 if (!sle)
740 return false;
741 auto sleNew = std::make_shared<SLE>(
742 keylet::escrow(A1, (*sle)[sfSequence] + 2));
743 STAmount nonNative(A2["USD"](51));
744 sleNew->setFieldAmount(sfAmount, nonNative);
745 ac.view().insert(sleNew);
746 return true;
747 });
748
750 {{"XRP net change of -1000000 doesn't match fee 0"},
751 {"escrow specifies invalid amount"}},
752 [](Account const& A1, Account const&, ApplyContext& ac) {
753 // escrow with negative amount
754 auto const sle = ac.view().peek(keylet::account(A1.id()));
755 if (!sle)
756 return false;
757 auto sleNew = std::make_shared<SLE>(
758 keylet::escrow(A1, (*sle)[sfSequence] + 2));
759 sleNew->setFieldAmount(sfAmount, XRP(-1));
760 ac.view().insert(sleNew);
761 return true;
762 });
763
765 {{"XRP net change was positive: 100000000000000001"},
766 {"escrow specifies invalid amount"}},
767 [](Account const& A1, Account const&, ApplyContext& ac) {
768 // escrow with too-large amount
769 auto const sle = ac.view().peek(keylet::account(A1.id()));
770 if (!sle)
771 return false;
772 auto sleNew = std::make_shared<SLE>(
773 keylet::escrow(A1, (*sle)[sfSequence] + 2));
774 // Use `drops(1)` to bypass a call to STAmount::canonicalize
775 // with an invalid value
776 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
777 ac.view().insert(sleNew);
778 return true;
779 });
780 }
781
782 void
784 {
785 using namespace test::jtx;
786 testcase << "valid new account root";
787
789 {{"account root created illegally"}},
790 [](Account const&, Account const&, ApplyContext& ac) {
791 // Insert a new account root created by a non-payment into
792 // the view.
793 Account const A3{"A3"};
794 Keylet const acctKeylet = keylet::account(A3);
795 auto const sleNew = std::make_shared<SLE>(acctKeylet);
796 ac.view().insert(sleNew);
797 return true;
798 });
799
801 {{"multiple accounts created in a single transaction"}},
802 [](Account const&, Account const&, ApplyContext& ac) {
803 // Insert two new account roots into the view.
804 {
805 Account const A3{"A3"};
806 Keylet const acctKeylet = keylet::account(A3);
807 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
808 ac.view().insert(sleA3);
809 }
810 {
811 Account const A4{"A4"};
812 Keylet const acctKeylet = keylet::account(A4);
813 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
814 ac.view().insert(sleA4);
815 }
816 return true;
817 });
818
820 {{"account created with wrong starting sequence number"}},
821 [](Account const&, Account const&, ApplyContext& ac) {
822 // Insert a new account root with the wrong starting sequence.
823 Account const A3{"A3"};
824 Keylet const acctKeylet = keylet::account(A3);
825 auto const sleNew = std::make_shared<SLE>(acctKeylet);
826 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
827 ac.view().insert(sleNew);
828 return true;
829 },
830 XRPAmount{},
831 STTx{ttPAYMENT, [](STObject& tx) {}});
832
834 {{"pseudo-account created by a wrong transaction type"}},
835 [](Account const&, Account const&, ApplyContext& ac) {
836 Account const A3{"A3"};
837 Keylet const acctKeylet = keylet::account(A3);
838 auto const sleNew = std::make_shared<SLE>(acctKeylet);
839 sleNew->setFieldU32(sfSequence, 0);
840 sleNew->setFieldH256(sfAMMID, uint256(1));
841 sleNew->setFieldU32(
842 sfFlags,
844 ac.view().insert(sleNew);
845 return true;
846 },
847 XRPAmount{},
848 STTx{ttPAYMENT, [](STObject& tx) {}});
849
851 {{"account created with wrong starting sequence number"}},
852 [](Account const&, Account const&, ApplyContext& ac) {
853 Account const A3{"A3"};
854 Keylet const acctKeylet = keylet::account(A3);
855 auto const sleNew = std::make_shared<SLE>(acctKeylet);
856 sleNew->setFieldU32(sfSequence, ac.view().seq());
857 sleNew->setFieldH256(sfAMMID, uint256(1));
858 sleNew->setFieldU32(
859 sfFlags,
861 ac.view().insert(sleNew);
862 return true;
863 },
864 XRPAmount{},
865 STTx{ttAMM_CREATE, [](STObject& tx) {}});
866
868 {{"pseudo-account created with wrong flags"}},
869 [](Account const&, Account const&, ApplyContext& ac) {
870 Account const A3{"A3"};
871 Keylet const acctKeylet = keylet::account(A3);
872 auto const sleNew = std::make_shared<SLE>(acctKeylet);
873 sleNew->setFieldU32(sfSequence, 0);
874 sleNew->setFieldH256(sfAMMID, uint256(1));
875 sleNew->setFieldU32(
877 ac.view().insert(sleNew);
878 return true;
879 },
880 XRPAmount{},
881 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
882
884 {{"pseudo-account created with wrong flags"}},
885 [](Account const&, Account const&, ApplyContext& ac) {
886 Account const A3{"A3"};
887 Keylet const acctKeylet = keylet::account(A3);
888 auto const sleNew = std::make_shared<SLE>(acctKeylet);
889 sleNew->setFieldU32(sfSequence, 0);
890 sleNew->setFieldH256(sfAMMID, uint256(1));
891 sleNew->setFieldU32(
892 sfFlags,
895 ac.view().insert(sleNew);
896 return true;
897 },
898 XRPAmount{},
899 STTx{ttAMM_CREATE, [](STObject& tx) {}});
900 }
901
902 void
904 {
905 using namespace test::jtx;
906 testcase << "NFTokenPage";
907
908 // lambda that returns an STArray of NFTokenIDs.
909 uint256 const firstNFTID(
910 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
911 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
912 SOTemplate const* nfTokenTemplate =
914 sfNFToken);
915
916 uint256 nftID(firstNFTID);
917 STArray ret;
918 for (int i = 0; i < nftCount; ++i)
919 {
920 STObject newNFToken(
921 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
922 object.setFieldH256(sfNFTokenID, nftID);
923 });
924 ret.push_back(std::move(newNFToken));
925 ++nftID;
926 }
927 return ret;
928 };
929
931 {{"NFT page has invalid size"}},
932 [&makeNFTokenIDs](
933 Account const& A1, Account const&, ApplyContext& ac) {
934 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
935 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
936
937 ac.view().insert(nftPage);
938 return true;
939 });
940
942 {{"NFT page has invalid size"}},
943 [&makeNFTokenIDs](
944 Account const& A1, Account const&, ApplyContext& ac) {
945 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
946 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
947
948 ac.view().insert(nftPage);
949 return true;
950 });
951
953 {{"NFTs on page are not sorted"}},
954 [&makeNFTokenIDs](
955 Account const& A1, Account const&, ApplyContext& ac) {
956 STArray nfTokens = makeNFTokenIDs(2);
957 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
958
959 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
960 nftPage->setFieldArray(sfNFTokens, nfTokens);
961
962 ac.view().insert(nftPage);
963 return true;
964 });
965
967 {{"NFT contains empty URI"}},
968 [&makeNFTokenIDs](
969 Account const& A1, Account const&, ApplyContext& ac) {
970 STArray nfTokens = makeNFTokenIDs(1);
971 nfTokens[0].setFieldVL(sfURI, Blob{});
972
973 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
974 nftPage->setFieldArray(sfNFTokens, nfTokens);
975
976 ac.view().insert(nftPage);
977 return true;
978 });
979
981 {{"NFT page is improperly linked"}},
982 [&makeNFTokenIDs](
983 Account const& A1, Account const&, ApplyContext& ac) {
984 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
985 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
986 nftPage->setFieldH256(
987 sfPreviousPageMin, keylet::nftpage_max(A1).key);
988
989 ac.view().insert(nftPage);
990 return true;
991 });
992
994 {{"NFT page is improperly linked"}},
995 [&makeNFTokenIDs](
996 Account const& A1, Account const& A2, ApplyContext& ac) {
997 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
998 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
999 nftPage->setFieldH256(
1000 sfPreviousPageMin, keylet::nftpage_min(A2).key);
1001
1002 ac.view().insert(nftPage);
1003 return true;
1004 });
1005
1007 {{"NFT page is improperly linked"}},
1008 [&makeNFTokenIDs](
1009 Account const& A1, Account const&, ApplyContext& ac) {
1010 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1011 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1012 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1013
1014 ac.view().insert(nftPage);
1015 return true;
1016 });
1017
1019 {{"NFT page is improperly linked"}},
1020 [&makeNFTokenIDs](
1021 Account const& A1, Account const& A2, ApplyContext& ac) {
1022 STArray nfTokens = makeNFTokenIDs(1);
1023 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
1025 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1026 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1027 nftPage->setFieldH256(
1028 sfNextPageMin, keylet::nftpage_max(A2).key);
1029
1030 ac.view().insert(nftPage);
1031 return true;
1032 });
1033
1035 {{"NFT found in incorrect page"}},
1036 [&makeNFTokenIDs](
1037 Account const& A1, Account const&, ApplyContext& ac) {
1038 STArray nfTokens = makeNFTokenIDs(2);
1039 auto nftPage = std::make_shared<SLE>(keylet::nftpage(
1041 (nfTokens[1].getFieldH256(sfNFTokenID))));
1042 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1043
1044 ac.view().insert(nftPage);
1045 return true;
1046 });
1047 }
1048
1049 void
1051 ApplyContext& ac,
1053 test::jtx::Account const& A1,
1054 test::jtx::Account const& A2)
1055 {
1056 sle->setAccountID(sfOwner, A1);
1057 sle->setFieldU32(sfSequence, 10);
1058
1059 STArray credentials(sfAcceptedCredentials, 2);
1060 for (std::size_t n = 0; n < 2; ++n)
1061 {
1062 auto cred = STObject::makeInnerObject(sfCredential);
1063 cred.setAccountID(sfIssuer, A2);
1064 auto credType = "cred_type" + std::to_string(n);
1065 cred.setFieldVL(
1066 sfCredentialType, Slice(credType.c_str(), credType.size()));
1067 credentials.push_back(std::move(cred));
1068 }
1069 sle->setFieldArray(sfAcceptedCredentials, credentials);
1070 ac.view().insert(sle);
1071 };
1072
1073 void
1075 {
1076 using namespace test::jtx;
1077
1078 testcase << "PermissionedDomain";
1080 {{"permissioned domain with no rules."}},
1081 [](Account const& A1, Account const&, ApplyContext& ac) {
1082 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1083 auto slePd = std::make_shared<SLE>(pdKeylet);
1084 slePd->setAccountID(sfOwner, A1);
1085 slePd->setFieldU32(sfSequence, 10);
1086
1087 ac.view().insert(slePd);
1088 return true;
1089 },
1090 XRPAmount{},
1091 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1093
1094 testcase << "PermissionedDomain 2";
1095
1096 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1098 {{"permissioned domain bad credentials size " +
1099 std::to_string(tooBig)}},
1100 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1101 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1102 auto slePd = std::make_shared<SLE>(pdKeylet);
1103 slePd->setAccountID(sfOwner, A1);
1104 slePd->setFieldU32(sfSequence, 10);
1105
1106 STArray credentials(sfAcceptedCredentials, tooBig);
1107 for (std::size_t n = 0; n < tooBig; ++n)
1108 {
1109 auto cred = STObject::makeInnerObject(sfCredential);
1110 cred.setAccountID(sfIssuer, A2);
1111 auto credType =
1112 std::string("cred_type") + std::to_string(n);
1113 cred.setFieldVL(
1114 sfCredentialType,
1115 Slice(credType.c_str(), credType.size()));
1116 credentials.push_back(std::move(cred));
1117 }
1118 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1119 ac.view().insert(slePd);
1120 return true;
1121 },
1122 XRPAmount{},
1123 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1125
1126 testcase << "PermissionedDomain 3";
1128 {{"permissioned domain credentials aren't sorted"}},
1129 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1130 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1131 auto slePd = std::make_shared<SLE>(pdKeylet);
1132 slePd->setAccountID(sfOwner, A1);
1133 slePd->setFieldU32(sfSequence, 10);
1134
1135 STArray credentials(sfAcceptedCredentials, 2);
1136 for (std::size_t n = 0; n < 2; ++n)
1137 {
1138 auto cred = STObject::makeInnerObject(sfCredential);
1139 cred.setAccountID(sfIssuer, A2);
1140 auto credType =
1141 std::string("cred_type") + std::to_string(9 - n);
1142 cred.setFieldVL(
1143 sfCredentialType,
1144 Slice(credType.c_str(), credType.size()));
1145 credentials.push_back(std::move(cred));
1146 }
1147 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1148 ac.view().insert(slePd);
1149 return true;
1150 },
1151 XRPAmount{},
1152 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1154
1155 testcase << "PermissionedDomain 4";
1157 {{"permissioned domain credentials aren't unique"}},
1158 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1159 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1160 auto slePd = std::make_shared<SLE>(pdKeylet);
1161 slePd->setAccountID(sfOwner, A1);
1162 slePd->setFieldU32(sfSequence, 10);
1163
1164 STArray credentials(sfAcceptedCredentials, 2);
1165 for (std::size_t n = 0; n < 2; ++n)
1166 {
1167 auto cred = STObject::makeInnerObject(sfCredential);
1168 cred.setAccountID(sfIssuer, A2);
1169 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1170 credentials.push_back(std::move(cred));
1171 }
1172 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1173 ac.view().insert(slePd);
1174 return true;
1175 },
1176 XRPAmount{},
1177 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1179
1180 testcase << "PermissionedDomain Set 1";
1182 {{"permissioned domain with no rules."}},
1183 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1184 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1185 auto slePd = std::make_shared<SLE>(pdKeylet);
1186
1187 // create PD
1188 createPermissionedDomain(ac, slePd, A1, A2);
1189
1190 // update PD with empty rules
1191 {
1192 STArray credentials(sfAcceptedCredentials, 2);
1193 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1194 ac.view().update(slePd);
1195 }
1196
1197 return true;
1198 },
1199 XRPAmount{},
1200 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1202
1203 testcase << "PermissionedDomain Set 2";
1205 {{"permissioned domain bad credentials size " +
1206 std::to_string(tooBig)}},
1207 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1208 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1209 auto slePd = std::make_shared<SLE>(pdKeylet);
1210
1211 // create PD
1212 createPermissionedDomain(ac, slePd, A1, A2);
1213
1214 // update PD
1215 {
1216 STArray credentials(sfAcceptedCredentials, tooBig);
1217
1218 for (std::size_t n = 0; n < tooBig; ++n)
1219 {
1220 auto cred = STObject::makeInnerObject(sfCredential);
1221 cred.setAccountID(sfIssuer, A2);
1222 auto credType = "cred_type2" + std::to_string(n);
1223 cred.setFieldVL(
1224 sfCredentialType,
1225 Slice(credType.c_str(), credType.size()));
1226 credentials.push_back(std::move(cred));
1227 }
1228
1229 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1230 ac.view().update(slePd);
1231 }
1232
1233 return true;
1234 },
1235 XRPAmount{},
1236 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1238
1239 testcase << "PermissionedDomain Set 3";
1241 {{"permissioned domain credentials aren't sorted"}},
1242 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1243 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1244 auto slePd = std::make_shared<SLE>(pdKeylet);
1245
1246 // create PD
1247 createPermissionedDomain(ac, slePd, A1, A2);
1248
1249 // update PD
1250 {
1251 STArray credentials(sfAcceptedCredentials, 2);
1252 for (std::size_t n = 0; n < 2; ++n)
1253 {
1254 auto cred = STObject::makeInnerObject(sfCredential);
1255 cred.setAccountID(sfIssuer, A2);
1256 auto credType =
1257 std::string("cred_type2") + std::to_string(9 - n);
1258 cred.setFieldVL(
1259 sfCredentialType,
1260 Slice(credType.c_str(), credType.size()));
1261 credentials.push_back(std::move(cred));
1262 }
1263
1264 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1265 ac.view().update(slePd);
1266 }
1267
1268 return true;
1269 },
1270 XRPAmount{},
1271 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1273
1274 testcase << "PermissionedDomain Set 4";
1276 {{"permissioned domain credentials aren't unique"}},
1277 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1278 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1279 auto slePd = std::make_shared<SLE>(pdKeylet);
1280
1281 // create PD
1282 createPermissionedDomain(ac, slePd, A1, A2);
1283
1284 // update PD
1285 {
1286 STArray credentials(sfAcceptedCredentials, 2);
1287 for (std::size_t n = 0; n < 2; ++n)
1288 {
1289 auto cred = STObject::makeInnerObject(sfCredential);
1290 cred.setAccountID(sfIssuer, A2);
1291 cred.setFieldVL(
1292 sfCredentialType, Slice("cred_type", 9));
1293 credentials.push_back(std::move(cred));
1294 }
1295 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1296 ac.view().update(slePd);
1297 }
1298
1299 return true;
1300 },
1301 XRPAmount{},
1302 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1304 }
1305
1306 void
1308 {
1309 using namespace test::jtx;
1310 testcase << "PermissionedDEX";
1311
1313 {{"domain doesn't exist"}},
1314 [](Account const& A1, Account const&, ApplyContext& ac) {
1315 Keylet const offerKey = keylet::offer(A1.id(), 10);
1316 auto sleOffer = std::make_shared<SLE>(offerKey);
1317 sleOffer->setAccountID(sfAccount, A1);
1318 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1319 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1320 ac.view().insert(sleOffer);
1321 return true;
1322 },
1323 XRPAmount{},
1324 STTx{
1325 ttOFFER_CREATE,
1326 [](STObject& tx) {
1327 tx.setFieldH256(
1328 sfDomainID,
1329 uint256{
1330 "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1331 "70F3649CE134E5"});
1332 Account const A1{"A1"};
1333 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1334 tx.setFieldAmount(sfTakerGets, XRP(1));
1335 }},
1337
1338 // missing domain ID in offer object
1340 {{"hybrid offer is malformed"}},
1341 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1342 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1343 auto slePd = std::make_shared<SLE>(pdKeylet);
1344 createPermissionedDomain(ac, slePd, A1, A2);
1345
1346 Keylet const offerKey = keylet::offer(A2.id(), 10);
1347 auto sleOffer = std::make_shared<SLE>(offerKey);
1348 sleOffer->setAccountID(sfAccount, A2);
1349 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1350 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1351 sleOffer->setFlag(lsfHybrid);
1352
1353 STArray bookArr;
1354 bookArr.push_back(STObject::makeInnerObject(sfBook));
1355 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1356 ac.view().insert(sleOffer);
1357 return true;
1358 },
1359 XRPAmount{},
1360 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1362
1363 // more than one entry in sfAdditionalBooks
1365 {{"hybrid offer is malformed"}},
1366 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1367 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1368 auto slePd = std::make_shared<SLE>(pdKeylet);
1369 createPermissionedDomain(ac, slePd, A1, A2);
1370
1371 Keylet const offerKey = keylet::offer(A2.id(), 10);
1372 auto sleOffer = std::make_shared<SLE>(offerKey);
1373 sleOffer->setAccountID(sfAccount, A2);
1374 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1375 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1376 sleOffer->setFlag(lsfHybrid);
1377 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1378
1379 STArray bookArr;
1380 bookArr.push_back(STObject::makeInnerObject(sfBook));
1381 bookArr.push_back(STObject::makeInnerObject(sfBook));
1382 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1383 ac.view().insert(sleOffer);
1384 return true;
1385 },
1386 XRPAmount{},
1387 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1389
1390 // hybrid offer missing sfAdditionalBooks
1392 {{"hybrid offer is malformed"}},
1393 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1394 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1395 auto slePd = std::make_shared<SLE>(pdKeylet);
1396 createPermissionedDomain(ac, slePd, A1, A2);
1397
1398 Keylet const offerKey = keylet::offer(A2.id(), 10);
1399 auto sleOffer = std::make_shared<SLE>(offerKey);
1400 sleOffer->setAccountID(sfAccount, A2);
1401 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1402 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1403 sleOffer->setFlag(lsfHybrid);
1404 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1405 ac.view().insert(sleOffer);
1406 return true;
1407 },
1408 XRPAmount{},
1409 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1411
1413 {{"transaction consumed wrong domains"}},
1414 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1415 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1416 auto slePd = std::make_shared<SLE>(pdKeylet);
1417 createPermissionedDomain(ac, slePd, A1, A2);
1418
1419 Keylet const badDomainKeylet =
1420 keylet::permissionedDomain(A1.id(), 20);
1421 auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
1422 createPermissionedDomain(ac, sleBadPd, A1, A2);
1423
1424 Keylet const offerKey = keylet::offer(A2.id(), 10);
1425 auto sleOffer = std::make_shared<SLE>(offerKey);
1426 sleOffer->setAccountID(sfAccount, A2);
1427 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1428 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1429 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1430 ac.view().insert(sleOffer);
1431 return true;
1432 },
1433 XRPAmount{},
1434 STTx{
1435 ttOFFER_CREATE,
1436 [&](STObject& tx) {
1437 Account const A1{"A1"};
1438 Keylet const badDomainKey =
1439 keylet::permissionedDomain(A1.id(), 20);
1440 tx.setFieldH256(sfDomainID, badDomainKey.key);
1441 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1442 tx.setFieldAmount(sfTakerGets, XRP(1));
1443 }},
1445
1447 {{"domain transaction affected regular offers"}},
1448 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1449 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1450 auto slePd = std::make_shared<SLE>(pdKeylet);
1451 createPermissionedDomain(ac, slePd, A1, A2);
1452
1453 Keylet const offerKey = keylet::offer(A2.id(), 10);
1454 auto sleOffer = std::make_shared<SLE>(offerKey);
1455 sleOffer->setAccountID(sfAccount, A2);
1456 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1457 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1458 ac.view().insert(sleOffer);
1459 return true;
1460 },
1461 XRPAmount{},
1462 STTx{
1463 ttOFFER_CREATE,
1464 [&](STObject& tx) {
1465 Account const A1{"A1"};
1466 Keylet const domainKey =
1467 keylet::permissionedDomain(A1.id(), 10);
1468 tx.setFieldH256(sfDomainID, domainKey.key);
1469 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1470 tx.setFieldAmount(sfTakerGets, XRP(1));
1471 }},
1473 }
1474
1475public:
1476 void
1477 run() override
1478 {
1494 }
1495};
1496
1497BEAST_DEFINE_TESTSUITE(Invariants, ledger, ripple);
1498
1499} // 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
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:533
State information when applying a tx.
Definition: ApplyContext.h:37
ApplyView & view()
Definition: ApplyContext.h:78
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 createPermissionedDomain(ApplyContext &ac, std::shared_ptr< SLE > &sle, test::jtx::Account const &A1, test::jtx::Account const &A2)
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:508
void push_back(STObject const &object)
Definition: STArray.h:212
iterator begin()
Definition: STArray.h:224
void setFieldH256(SField const &field, uint256 const &)
Definition: STObject.cpp:759
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:121
T invoke(T... args)
T iter_swap(T... args)
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Definition: Indexes.cpp:570
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition: Indexes.cpp:446
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:244
Keylet const & amendments() noexcept
The index of the amendment table.
Definition: Indexes.cpp:214
Keylet nftpage(Keylet const &k, uint256 const &token)
Definition: Indexes.cpp:419
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Definition: Indexes.cpp:389
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:403
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:411
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:374
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:274
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
FeatureBitset supported_amendments()
Definition: Env.h:74
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:119
base_uint< 256 > uint256
Definition: base_uint.h:558
std::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
Definition: Protocol.h:111
@ lsfHighDeepFreeze
@ lsfRequireDestTag
@ lsfDefaultRipple
@ lsfHighFreeze
@ lsfDisableMaster
@ lsfDepositAuth
@ lsfLowFreeze
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition: Indexes.h:381
@ 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:1418
@ tecINVARIANT_FAILED
Definition: TER.h:313
@ tesSUCCESS
Definition: TER.h:244
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:643
constexpr std::uint32_t tfSetFreeze
Definition: TxFlags.h:117
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
T to_string(T... args)