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 {
34namespace test {
35
37{
38 // The optional Preclose function is used to process additional transactions
39 // on the ledger after creating two accounts, but before closing it, and
40 // before the Precheck function. These should only be valid functions, and
41 // not direct manipulations. Preclose is not commonly used.
42 using Preclose = std::function<bool(
43 test::jtx::Account const& a,
44 test::jtx::Account const& b,
45 test::jtx::Env& env)>;
46
47 // this is common setup/method for running a failing invariant check. The
48 // precheck function is used to manipulate the ApplyContext with view
49 // changes that will cause the check to fail.
50 using Precheck = std::function<bool(
51 test::jtx::Account const& a,
52 test::jtx::Account const& b,
53 ApplyContext& ac)>;
54
71 void
73 std::vector<std::string> const& expect_logs,
74 Precheck const& precheck,
76 STTx tx = STTx{ttACCOUNT_SET, [](STObject&) {}},
79 Preclose const& preclose = {})
80 {
81 using namespace test::jtx;
82 FeatureBitset amendments = testable_amendments() |
83 featureInvariantsV1_1 | featureSingleAssetVault;
84 Env env{*this, amendments};
85
86 Account const A1{"A1"};
87 Account const A2{"A2"};
88 env.fund(XRP(1000), A1, A2);
89 if (preclose)
90 BEAST_EXPECT(preclose(A1, A2, env));
91 env.close();
92
93 OpenView ov{*env.current()};
94 test::StreamSink sink{beast::severities::kWarning};
95 beast::Journal jlog{sink};
96 ApplyContext ac{
97 env.app(),
98 ov,
99 tx,
101 env.current()->fees().base,
102 tapNONE,
103 jlog};
104
105 BEAST_EXPECT(precheck(A1, A2, ac));
106
107 // invoke check twice to cover tec and tef cases
108 if (!BEAST_EXPECT(ters.size() == 2))
109 return;
110
111 TER terActual = tesSUCCESS;
112 for (TER const& terExpect : ters)
113 {
114 terActual = ac.checkInvariants(terActual, fee);
115 BEAST_EXPECT(terExpect == terActual);
116 auto const messages = sink.messages().str();
117 BEAST_EXPECT(
118 messages.starts_with("Invariant failed:") ||
119 messages.starts_with("Transaction caused an exception"));
120 for (auto const& m : expect_logs)
121 {
122 if (messages.find(m) == std::string::npos)
123 {
124 // uncomment if you want to log the invariant failure
125 // message log << " --> " << m << std::endl;
126 fail();
127 }
128 }
129 }
130 }
131
132 void
134 {
135 using namespace test::jtx;
136 testcase << "XRP created";
138 {{"XRP net change was positive: 500"}},
139 [](Account const& A1, Account const&, ApplyContext& ac) {
140 // put a single account in the view and "manufacture" some XRP
141 auto const sle = ac.view().peek(keylet::account(A1.id()));
142 if (!sle)
143 return false;
144 auto amt = sle->getFieldAmount(sfBalance);
145 sle->setFieldAmount(sfBalance, amt + STAmount{500});
146 ac.view().update(sle);
147 return true;
148 });
149 }
150
151 void
153 {
154 using namespace test::jtx;
155 testcase << "account root removed";
156
157 // An account was deleted, but not by an AccountDelete transaction.
159 {{"an account root was deleted"}},
160 [](Account const& A1, Account const&, ApplyContext& ac) {
161 // remove an account from the view
162 auto const sle = ac.view().peek(keylet::account(A1.id()));
163 if (!sle)
164 return false;
165 ac.view().erase(sle);
166 return true;
167 });
168
169 // Successful AccountDelete transaction that didn't delete an account.
170 //
171 // Note that this is a case where a second invocation of the invariant
172 // checker returns a tecINVARIANT_FAILED, not a tefINVARIANT_FAILED.
173 // After a discussion with the team, we believe that's okay.
175 {{"account deletion succeeded without deleting an account"}},
176 [](Account const&, Account const&, ApplyContext& ac) {
177 return true;
178 },
179 XRPAmount{},
180 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
182
183 // Successful AccountDelete that deleted more than one account.
185 {{"account deletion succeeded but deleted multiple accounts"}},
186 [](Account const& A1, Account const& A2, ApplyContext& ac) {
187 // remove two accounts from the view
188 auto const sleA1 = ac.view().peek(keylet::account(A1.id()));
189 auto const sleA2 = ac.view().peek(keylet::account(A2.id()));
190 if (!sleA1 || !sleA2)
191 return false;
192 ac.view().erase(sleA1);
193 ac.view().erase(sleA2);
194 return true;
195 },
196 XRPAmount{},
197 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
198 }
199
200 void
202 {
203 using namespace test::jtx;
204 testcase << "account root deletion left artifact";
205
206 for (auto const& keyletInfo : directAccountKeylets)
207 {
208 // TODO: Use structured binding once LLVM 16 is the minimum
209 // supported version. See also:
210 // https://github.com/llvm/llvm-project/issues/48582
211 // https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
212 if (!keyletInfo.includeInTests)
213 continue;
214 auto const& keyletfunc = keyletInfo.function;
215 auto const& type = keyletInfo.expectedLEName;
216
217 using namespace std::string_literals;
218
220 {{"account deletion left behind a "s + type.c_str() +
221 " object"}},
222 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
223 // Add an object to the ledger for account A1, then delete
224 // A1
225 auto const a1 = A1.id();
226 auto const sleA1 = ac.view().peek(keylet::account(a1));
227 if (!sleA1)
228 return false;
229
230 auto const key = std::invoke(keyletfunc, a1);
231 auto const newSLE = std::make_shared<SLE>(key);
232 ac.view().insert(newSLE);
233 ac.view().erase(sleA1);
234
235 return true;
236 },
237 XRPAmount{},
238 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}});
239 };
240
241 // NFT special case
243 {{"account deletion left behind a NFTokenPage object"}},
244 [&](Account const& A1, Account const&, ApplyContext& ac) {
245 // remove an account from the view
246 auto const sle = ac.view().peek(keylet::account(A1.id()));
247 if (!sle)
248 return false;
249 ac.view().erase(sle);
250 return true;
251 },
252 XRPAmount{},
253 STTx{ttACCOUNT_DELETE, [](STObject& tx) {}},
255 [&](Account const& A1, Account const&, Env& env) {
256 // Preclose callback to mint the NFT which will be deleted in
257 // the Precheck callback above.
258 env(token::mint(A1));
259
260 return true;
261 });
262
263 // AMM special cases
264 AccountID ammAcctID;
265 uint256 ammKey;
266 Issue ammIssue;
268 {{"account deletion left behind a DirectoryNode object"}},
269 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
270 // Delete the AMM account without cleaning up the directory or
271 // deleting the AMM object
272 auto const sle = ac.view().peek(keylet::account(ammAcctID));
273 if (!sle)
274 return false;
275
276 BEAST_EXPECT(sle->at(~sfAMMID));
277 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
278
279 ac.view().erase(sle);
280
281 return true;
282 },
283 XRPAmount{},
284 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
286 [&](Account const& A1, Account const& A2, Env& env) {
287 // Preclose callback to create the AMM which will be partially
288 // deleted in the Precheck callback above.
289 AMM const amm(env, A1, XRP(100), A1["USD"](50));
290 ammAcctID = amm.ammAccount();
291 ammKey = amm.ammID();
292 ammIssue = amm.lptIssue();
293 return true;
294 });
296 {{"account deletion left behind a AMM object"}},
297 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
298 // Delete all the AMM's trust lines, remove the AMM from the AMM
299 // account's directory (this deletes the directory), and delete
300 // the AMM account. Do not delete the AMM object.
301 auto const sle = ac.view().peek(keylet::account(ammAcctID));
302 if (!sle)
303 return false;
304
305 BEAST_EXPECT(sle->at(~sfAMMID));
306 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
307
308 for (auto const& trustKeylet :
309 {keylet::line(ammAcctID, A1["USD"]),
310 keylet::line(A1, ammIssue)})
311 {
312 if (auto const line = ac.view().peek(trustKeylet); !line)
313 {
314 return false;
315 }
316 else
317 {
318 STAmount const lowLimit = line->at(sfLowLimit);
319 STAmount const highLimit = line->at(sfHighLimit);
320 BEAST_EXPECT(
322 ac.view(),
323 line,
324 lowLimit.getIssuer(),
325 highLimit.getIssuer(),
326 ac.journal) == tesSUCCESS);
327 }
328 }
329
330 auto const ammSle = ac.view().peek(keylet::amm(ammKey));
331 if (!BEAST_EXPECT(ammSle))
332 return false;
333 auto const ownerDirKeylet = keylet::ownerDir(ammAcctID);
334
335 BEAST_EXPECT(ac.view().dirRemove(
336 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey, false));
337 BEAST_EXPECT(
338 !ac.view().exists(ownerDirKeylet) ||
339 ac.view().emptyDirDelete(ownerDirKeylet));
340
341 ac.view().erase(sle);
342
343 return true;
344 },
345 XRPAmount{},
346 STTx{ttAMM_WITHDRAW, [](STObject& tx) {}},
348 [&](Account const& A1, Account const& A2, Env& env) {
349 // Preclose callback to create the AMM which will be partially
350 // deleted in the Precheck callback above.
351 AMM const amm(env, A1, XRP(100), A1["USD"](50));
352 ammAcctID = amm.ammAccount();
353 ammKey = amm.ammID();
354 ammIssue = amm.lptIssue();
355 return true;
356 });
357 }
358
359 void
361 {
362 using namespace test::jtx;
363 testcase << "ledger entry types don't match";
365 {{"ledger entry type mismatch"},
366 {"XRP net change of -1000000000 doesn't match fee 0"}},
367 [](Account const& A1, Account const&, ApplyContext& ac) {
368 // replace an entry in the table with an SLE of a different type
369 auto const sle = ac.view().peek(keylet::account(A1.id()));
370 if (!sle)
371 return false;
372 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
373 ac.rawView().rawReplace(sleNew);
374 return true;
375 });
376
378 {{"invalid ledger entry type added"}},
379 [](Account const& A1, Account const&, ApplyContext& ac) {
380 // add an entry in the table with an SLE of an invalid type
381 auto const sle = ac.view().peek(keylet::account(A1.id()));
382 if (!sle)
383 return false;
384
385 // make a dummy escrow ledger entry, then change the type to an
386 // unsupported value so that the valid type invariant check
387 // will fail.
388 auto const sleNew = std::make_shared<SLE>(
389 keylet::escrow(A1, (*sle)[sfSequence] + 2));
390
391 // We don't use ltNICKNAME directly since it's marked deprecated
392 // to prevent accidental use elsewhere.
393 sleNew->type_ = static_cast<LedgerEntryType>('n');
394 ac.view().insert(sleNew);
395 return true;
396 });
397 }
398
399 void
401 {
402 using namespace test::jtx;
403 testcase << "trust lines with XRP not allowed";
405 {{"an XRP trust line was created"}},
406 [](Account const& A1, Account const& A2, ApplyContext& ac) {
407 // create simple trust SLE with xrp currency
408 auto const sleNew = std::make_shared<SLE>(
409 keylet::line(A1, A2, xrpIssue().currency));
410 ac.view().insert(sleNew);
411 return true;
412 });
413 }
414
415 void
417 {
418 using namespace test::jtx;
419 testcase << "trust lines with deep freeze flag without freeze "
420 "not allowed";
422 {{"a trust line with deep freeze flag without normal freeze was "
423 "created"}},
424 [](Account const& A1, Account const& A2, ApplyContext& ac) {
425 auto const sleNew = std::make_shared<SLE>(
426 keylet::line(A1, A2, A1["USD"].currency));
427 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
428 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
429
430 std::uint32_t uFlags = 0u;
431 uFlags |= lsfLowDeepFreeze;
432 sleNew->setFieldU32(sfFlags, uFlags);
433 ac.view().insert(sleNew);
434 return true;
435 });
436
438 {{"a trust line with deep freeze flag without normal freeze was "
439 "created"}},
440 [](Account const& A1, Account const& A2, ApplyContext& ac) {
441 auto const sleNew = std::make_shared<SLE>(
442 keylet::line(A1, A2, A1["USD"].currency));
443 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
444 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
445 std::uint32_t uFlags = 0u;
446 uFlags |= lsfHighDeepFreeze;
447 sleNew->setFieldU32(sfFlags, uFlags);
448 ac.view().insert(sleNew);
449 return true;
450 });
451
453 {{"a trust line with deep freeze flag without normal freeze was "
454 "created"}},
455 [](Account const& A1, Account const& A2, ApplyContext& ac) {
456 auto const sleNew = std::make_shared<SLE>(
457 keylet::line(A1, A2, A1["USD"].currency));
458 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
459 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
460 std::uint32_t uFlags = 0u;
462 sleNew->setFieldU32(sfFlags, uFlags);
463 ac.view().insert(sleNew);
464 return true;
465 });
466
468 {{"a trust line with deep freeze flag without normal freeze was "
469 "created"}},
470 [](Account const& A1, Account const& A2, ApplyContext& ac) {
471 auto const sleNew = std::make_shared<SLE>(
472 keylet::line(A1, A2, A1["USD"].currency));
473 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
474 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
475 std::uint32_t uFlags = 0u;
477 sleNew->setFieldU32(sfFlags, uFlags);
478 ac.view().insert(sleNew);
479 return true;
480 });
481
483 {{"a trust line with deep freeze flag without normal freeze was "
484 "created"}},
485 [](Account const& A1, Account const& A2, ApplyContext& ac) {
486 auto const sleNew = std::make_shared<SLE>(
487 keylet::line(A1, A2, A1["USD"].currency));
488 sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
489 sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
490 std::uint32_t uFlags = 0u;
492 sleNew->setFieldU32(sfFlags, uFlags);
493 ac.view().insert(sleNew);
494 return true;
495 });
496 }
497
498 void
500 {
501 using namespace test::jtx;
502 testcase << "transfers when frozen";
503
504 Account G1{"G1"};
505 // Helper function to establish the trustlines
506 auto const createTrustlines =
507 [&](Account const& A1, Account const& A2, Env& env) {
508 // Preclose callback to establish trust lines with gateway
509 env.fund(XRP(1000), G1);
510
511 env.trust(G1["USD"](10000), A1);
512 env.trust(G1["USD"](10000), A2);
513 env.close();
514
515 env(pay(G1, A1, G1["USD"](1000)));
516 env(pay(G1, A2, G1["USD"](1000)));
517 env.close();
518
519 return true;
520 };
521
522 auto const A1FrozenByIssuer =
523 [&](Account const& A1, Account const& A2, Env& env) {
524 createTrustlines(A1, A2, env);
525 env(trust(G1, A1["USD"](10000), tfSetFreeze));
526 env.close();
527
528 return true;
529 };
530
531 auto const A1DeepFrozenByIssuer =
532 [&](Account const& A1, Account const& A2, Env& env) {
533 A1FrozenByIssuer(A1, A2, env);
534 env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
535 env.close();
536
537 return true;
538 };
539
540 auto const changeBalances = [&](Account const& A1,
541 Account const& A2,
542 ApplyContext& ac,
543 int A1Balance,
544 int A2Balance) {
545 auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
546 auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
547
548 sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
549 sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
550
551 ac.view().update(sleA1);
552 ac.view().update(sleA2);
553 };
554
555 // test: imitating frozen A1 making a payment to A2.
557 {{"Attempting to move frozen funds"}},
558 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
559 changeBalances(A1, A2, ac, -900, -1100);
560 return true;
561 },
562 XRPAmount{},
563 STTx{ttPAYMENT, [](STObject& tx) {}},
565 A1FrozenByIssuer);
566
567 // test: imitating deep frozen A1 making a payment to A2.
569 {{"Attempting to move frozen funds"}},
570 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
571 changeBalances(A1, A2, ac, -900, -1100);
572 return true;
573 },
574 XRPAmount{},
575 STTx{ttPAYMENT, [](STObject& tx) {}},
577 A1DeepFrozenByIssuer);
578
579 // test: imitating A2 making a payment to deep frozen A1.
581 {{"Attempting to move frozen funds"}},
582 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
583 changeBalances(A1, A2, ac, -1100, -900);
584 return true;
585 },
586 XRPAmount{},
587 STTx{ttPAYMENT, [](STObject& tx) {}},
589 A1DeepFrozenByIssuer);
590 }
591
592 void
594 {
595 using namespace test::jtx;
596 testcase << "XRP balance checks";
597
599 {{"Cannot return non-native STAmount as XRPAmount"}},
600 [](Account const& A1, Account const& A2, ApplyContext& ac) {
601 // non-native balance
602 auto const sle = ac.view().peek(keylet::account(A1.id()));
603 if (!sle)
604 return false;
605 STAmount const nonNative(A2["USD"](51));
606 sle->setFieldAmount(sfBalance, nonNative);
607 ac.view().update(sle);
608 return true;
609 });
610
612 {{"incorrect account XRP balance"},
613 {"XRP net change was positive: 99999999000000001"}},
614 [this](Account const& A1, Account const&, ApplyContext& ac) {
615 // balance exceeds genesis amount
616 auto const sle = ac.view().peek(keylet::account(A1.id()));
617 if (!sle)
618 return false;
619 // Use `drops(1)` to bypass a call to STAmount::canonicalize
620 // with an invalid value
621 sle->setFieldAmount(sfBalance, INITIAL_XRP + drops(1));
622 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
623 ac.view().update(sle);
624 return true;
625 });
626
628 {{"incorrect account XRP balance"},
629 {"XRP net change of -1000000001 doesn't match fee 0"}},
630 [this](Account const& A1, Account const&, ApplyContext& ac) {
631 // balance is negative
632 auto const sle = ac.view().peek(keylet::account(A1.id()));
633 if (!sle)
634 return false;
635 sle->setFieldAmount(sfBalance, STAmount{1, true});
636 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
637 ac.view().update(sle);
638 return true;
639 });
640 }
641
642 void
644 {
645 using namespace test::jtx;
646 using namespace std::string_literals;
647 testcase << "Transaction fee checks";
648
650 {{"fee paid was negative: -1"},
651 {"XRP net change of 0 doesn't match fee -1"}},
652 [](Account const&, Account const&, ApplyContext&) { return true; },
653 XRPAmount{-1});
654
656 {{"fee paid exceeds system limit: "s + to_string(INITIAL_XRP)},
657 {"XRP net change of 0 doesn't match fee "s +
659 [](Account const&, Account const&, ApplyContext&) { return true; },
661
663 {{"fee paid is 20 exceeds fee specified in transaction."},
664 {"XRP net change of 0 doesn't match fee 20"}},
665 [](Account const&, Account const&, ApplyContext&) { return true; },
666 XRPAmount{20},
667 STTx{ttACCOUNT_SET, [](STObject& tx) {
668 tx.setFieldAmount(sfFee, XRPAmount{10});
669 }});
670 }
671
672 void
674 {
675 using namespace test::jtx;
676 testcase << "no bad offers";
677
679 {{"offer with a bad amount"}},
680 [](Account const& A1, Account const&, ApplyContext& ac) {
681 // offer with negative takerpays
682 auto const sle = ac.view().peek(keylet::account(A1.id()));
683 if (!sle)
684 return false;
685 auto sleNew = std::make_shared<SLE>(
686 keylet::offer(A1.id(), (*sle)[sfSequence]));
687 sleNew->setAccountID(sfAccount, A1.id());
688 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
689 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
690 ac.view().insert(sleNew);
691 return true;
692 });
693
695 {{"offer with a bad amount"}},
696 [](Account const& A1, Account const&, ApplyContext& ac) {
697 // offer with negative takergets
698 auto const sle = ac.view().peek(keylet::account(A1.id()));
699 if (!sle)
700 return false;
701 auto sleNew = std::make_shared<SLE>(
702 keylet::offer(A1.id(), (*sle)[sfSequence]));
703 sleNew->setAccountID(sfAccount, A1.id());
704 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
705 sleNew->setFieldAmount(sfTakerPays, A1["USD"](10));
706 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
707 ac.view().insert(sleNew);
708 return true;
709 });
710
712 {{"offer with a bad amount"}},
713 [](Account const& A1, Account const&, ApplyContext& ac) {
714 // offer XRP to XRP
715 auto const sle = ac.view().peek(keylet::account(A1.id()));
716 if (!sle)
717 return false;
718 auto sleNew = std::make_shared<SLE>(
719 keylet::offer(A1.id(), (*sle)[sfSequence]));
720 sleNew->setAccountID(sfAccount, A1.id());
721 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
722 sleNew->setFieldAmount(sfTakerPays, XRP(10));
723 sleNew->setFieldAmount(sfTakerGets, XRP(11));
724 ac.view().insert(sleNew);
725 return true;
726 });
727 }
728
729 void
731 {
732 using namespace test::jtx;
733 testcase << "no zero escrow";
734
736 {{"XRP net change of -1000000 doesn't match fee 0"},
737 {"escrow specifies invalid amount"}},
738 [](Account const& A1, Account const&, ApplyContext& ac) {
739 // escrow with negative amount
740 auto const sle = ac.view().peek(keylet::account(A1.id()));
741 if (!sle)
742 return false;
743 auto sleNew = std::make_shared<SLE>(
744 keylet::escrow(A1, (*sle)[sfSequence] + 2));
745 sleNew->setFieldAmount(sfAmount, XRP(-1));
746 ac.view().insert(sleNew);
747 return true;
748 });
749
751 {{"XRP net change was positive: 100000000000000001"},
752 {"escrow specifies invalid amount"}},
753 [](Account const& A1, Account const&, ApplyContext& ac) {
754 // escrow with too-large amount
755 auto const sle = ac.view().peek(keylet::account(A1.id()));
756 if (!sle)
757 return false;
758 auto sleNew = std::make_shared<SLE>(
759 keylet::escrow(A1, (*sle)[sfSequence] + 2));
760 // Use `drops(1)` to bypass a call to STAmount::canonicalize
761 // with an invalid value
762 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
763 ac.view().insert(sleNew);
764 return true;
765 });
766
767 // IOU < 0
769 {{"escrow specifies invalid amount"}},
770 [](Account const& A1, Account const&, ApplyContext& ac) {
771 // escrow with too-little iou
772 auto const sle = ac.view().peek(keylet::account(A1.id()));
773 if (!sle)
774 return false;
775 auto sleNew = std::make_shared<SLE>(
776 keylet::escrow(A1, (*sle)[sfSequence] + 2));
777
778 Issue const usd{
779 Currency(0x5553440000000000), AccountID(0x4985601)};
780 STAmount amt(usd, -1);
781 sleNew->setFieldAmount(sfAmount, amt);
782 ac.view().insert(sleNew);
783 return true;
784 });
785
786 // IOU bad currency
788 {{"escrow specifies invalid amount"}},
789 [](Account const& A1, Account const&, ApplyContext& ac) {
790 // escrow with bad iou currency
791 auto const sle = ac.view().peek(keylet::account(A1.id()));
792 if (!sle)
793 return false;
794 auto sleNew = std::make_shared<SLE>(
795 keylet::escrow(A1, (*sle)[sfSequence] + 2));
796
797 Issue const bad{badCurrency(), AccountID(0x4985601)};
798 STAmount amt(bad, 1);
799 sleNew->setFieldAmount(sfAmount, amt);
800 ac.view().insert(sleNew);
801 return true;
802 });
803
804 // MPT < 0
806 {{"escrow specifies invalid amount"}},
807 [](Account const& A1, Account const&, ApplyContext& ac) {
808 // escrow with too-little mpt
809 auto const sle = ac.view().peek(keylet::account(A1.id()));
810 if (!sle)
811 return false;
812 auto sleNew = std::make_shared<SLE>(
813 keylet::escrow(A1, (*sle)[sfSequence] + 2));
814
815 MPTIssue const mpt{
816 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
817 STAmount amt(mpt, -1);
818 sleNew->setFieldAmount(sfAmount, amt);
819 ac.view().insert(sleNew);
820 return true;
821 });
822
823 // MPT OutstandingAmount < 0
825 {{"escrow specifies invalid amount"}},
826 [](Account const& A1, Account const&, ApplyContext& ac) {
827 // mpissuance outstanding is negative
828 auto const sle = ac.view().peek(keylet::account(A1.id()));
829 if (!sle)
830 return false;
831
832 MPTIssue const mpt{
833 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
834 auto sleNew =
836 sleNew->setFieldU64(sfOutstandingAmount, -1);
837 ac.view().insert(sleNew);
838 return true;
839 });
840
841 // MPT LockedAmount < 0
843 {{"escrow specifies invalid amount"}},
844 [](Account const& A1, Account const&, ApplyContext& ac) {
845 // mpissuance locked is less than locked
846 auto const sle = ac.view().peek(keylet::account(A1.id()));
847 if (!sle)
848 return false;
849
850 MPTIssue const mpt{
851 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
852 auto sleNew =
854 sleNew->setFieldU64(sfLockedAmount, -1);
855 ac.view().insert(sleNew);
856 return true;
857 });
858
859 // MPT OutstandingAmount < LockedAmount
861 {{"escrow specifies invalid amount"}},
862 [](Account const& A1, Account const&, ApplyContext& ac) {
863 // mpissuance outstanding is less than locked
864 auto const sle = ac.view().peek(keylet::account(A1.id()));
865 if (!sle)
866 return false;
867
868 MPTIssue const mpt{
869 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
870 auto sleNew =
872 sleNew->setFieldU64(sfOutstandingAmount, 1);
873 sleNew->setFieldU64(sfLockedAmount, 10);
874 ac.view().insert(sleNew);
875 return true;
876 });
877
878 // MPT MPTAmount < 0
880 {{"escrow specifies invalid amount"}},
881 [](Account const& A1, Account const&, ApplyContext& ac) {
882 // mptoken amount is negative
883 auto const sle = ac.view().peek(keylet::account(A1.id()));
884 if (!sle)
885 return false;
886
887 MPTIssue const mpt{
888 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
889 auto sleNew =
890 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
891 sleNew->setFieldU64(sfMPTAmount, -1);
892 ac.view().insert(sleNew);
893 return true;
894 });
895
896 // MPT LockedAmount < 0
898 {{"escrow specifies invalid amount"}},
899 [](Account const& A1, Account const&, ApplyContext& ac) {
900 // mptoken locked amount is negative
901 auto const sle = ac.view().peek(keylet::account(A1.id()));
902 if (!sle)
903 return false;
904
905 MPTIssue const mpt{
906 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
907 auto sleNew =
908 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
909 sleNew->setFieldU64(sfLockedAmount, -1);
910 ac.view().insert(sleNew);
911 return true;
912 });
913 }
914
915 void
917 {
918 using namespace test::jtx;
919 testcase << "valid new account root";
920
922 {{"account root created illegally"}},
923 [](Account const&, Account const&, ApplyContext& ac) {
924 // Insert a new account root created by a non-payment into
925 // the view.
926 Account const A3{"A3"};
927 Keylet const acctKeylet = keylet::account(A3);
928 auto const sleNew = std::make_shared<SLE>(acctKeylet);
929 ac.view().insert(sleNew);
930 return true;
931 });
932
934 {{"multiple accounts created in a single transaction"}},
935 [](Account const&, Account const&, ApplyContext& ac) {
936 // Insert two new account roots into the view.
937 {
938 Account const A3{"A3"};
939 Keylet const acctKeylet = keylet::account(A3);
940 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
941 ac.view().insert(sleA3);
942 }
943 {
944 Account const A4{"A4"};
945 Keylet const acctKeylet = keylet::account(A4);
946 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
947 ac.view().insert(sleA4);
948 }
949 return true;
950 });
951
953 {{"account created with wrong starting sequence number"}},
954 [](Account const&, Account const&, ApplyContext& ac) {
955 // Insert a new account root with the wrong starting sequence.
956 Account const A3{"A3"};
957 Keylet const acctKeylet = keylet::account(A3);
958 auto const sleNew = std::make_shared<SLE>(acctKeylet);
959 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
960 ac.view().insert(sleNew);
961 return true;
962 },
963 XRPAmount{},
964 STTx{ttPAYMENT, [](STObject& tx) {}});
965
967 {{"pseudo-account created by a wrong transaction type"}},
968 [](Account const&, Account const&, ApplyContext& ac) {
969 Account const A3{"A3"};
970 Keylet const acctKeylet = keylet::account(A3);
971 auto const sleNew = std::make_shared<SLE>(acctKeylet);
972 sleNew->setFieldU32(sfSequence, 0);
973 sleNew->setFieldH256(sfAMMID, uint256(1));
974 sleNew->setFieldU32(
975 sfFlags,
977 ac.view().insert(sleNew);
978 return true;
979 },
980 XRPAmount{},
981 STTx{ttPAYMENT, [](STObject& tx) {}});
982
984 {{"account created with wrong starting sequence number"}},
985 [](Account const&, Account const&, ApplyContext& ac) {
986 Account const A3{"A3"};
987 Keylet const acctKeylet = keylet::account(A3);
988 auto const sleNew = std::make_shared<SLE>(acctKeylet);
989 sleNew->setFieldU32(sfSequence, ac.view().seq());
990 sleNew->setFieldH256(sfAMMID, uint256(1));
991 sleNew->setFieldU32(
992 sfFlags,
994 ac.view().insert(sleNew);
995 return true;
996 },
997 XRPAmount{},
998 STTx{ttAMM_CREATE, [](STObject& tx) {}});
999
1001 {{"pseudo-account created with wrong flags"}},
1002 [](Account const&, Account const&, ApplyContext& ac) {
1003 Account const A3{"A3"};
1004 Keylet const acctKeylet = keylet::account(A3);
1005 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1006 sleNew->setFieldU32(sfSequence, 0);
1007 sleNew->setFieldH256(sfAMMID, uint256(1));
1008 sleNew->setFieldU32(
1010 ac.view().insert(sleNew);
1011 return true;
1012 },
1013 XRPAmount{},
1014 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
1015
1017 {{"pseudo-account created with wrong flags"}},
1018 [](Account const&, Account const&, ApplyContext& ac) {
1019 Account const A3{"A3"};
1020 Keylet const acctKeylet = keylet::account(A3);
1021 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1022 sleNew->setFieldU32(sfSequence, 0);
1023 sleNew->setFieldH256(sfAMMID, uint256(1));
1024 sleNew->setFieldU32(
1025 sfFlags,
1028 ac.view().insert(sleNew);
1029 return true;
1030 },
1031 XRPAmount{},
1032 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1033 }
1034
1035 void
1037 {
1038 using namespace test::jtx;
1039 testcase << "NFTokenPage";
1040
1041 // lambda that returns an STArray of NFTokenIDs.
1042 uint256 const firstNFTID(
1043 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
1044 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
1045 SOTemplate const* nfTokenTemplate =
1047 sfNFToken);
1048
1049 uint256 nftID(firstNFTID);
1050 STArray ret;
1051 for (int i = 0; i < nftCount; ++i)
1052 {
1053 STObject newNFToken(
1054 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
1055 object.setFieldH256(sfNFTokenID, nftID);
1056 });
1057 ret.push_back(std::move(newNFToken));
1058 ++nftID;
1059 }
1060 return ret;
1061 };
1062
1064 {{"NFT page has invalid size"}},
1065 [&makeNFTokenIDs](
1066 Account const& A1, Account const&, ApplyContext& ac) {
1067 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1068 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
1069
1070 ac.view().insert(nftPage);
1071 return true;
1072 });
1073
1075 {{"NFT page has invalid size"}},
1076 [&makeNFTokenIDs](
1077 Account const& A1, Account const&, ApplyContext& ac) {
1078 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1079 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
1080
1081 ac.view().insert(nftPage);
1082 return true;
1083 });
1084
1086 {{"NFTs on page are not sorted"}},
1087 [&makeNFTokenIDs](
1088 Account const& A1, Account const&, ApplyContext& ac) {
1089 STArray nfTokens = makeNFTokenIDs(2);
1090 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
1091
1092 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1093 nftPage->setFieldArray(sfNFTokens, nfTokens);
1094
1095 ac.view().insert(nftPage);
1096 return true;
1097 });
1098
1100 {{"NFT contains empty URI"}},
1101 [&makeNFTokenIDs](
1102 Account const& A1, Account const&, ApplyContext& ac) {
1103 STArray nfTokens = makeNFTokenIDs(1);
1104 nfTokens[0].setFieldVL(sfURI, Blob{});
1105
1106 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1107 nftPage->setFieldArray(sfNFTokens, nfTokens);
1108
1109 ac.view().insert(nftPage);
1110 return true;
1111 });
1112
1114 {{"NFT page is improperly linked"}},
1115 [&makeNFTokenIDs](
1116 Account const& A1, Account const&, ApplyContext& ac) {
1117 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1118 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1119 nftPage->setFieldH256(
1120 sfPreviousPageMin, keylet::nftpage_max(A1).key);
1121
1122 ac.view().insert(nftPage);
1123 return true;
1124 });
1125
1127 {{"NFT page is improperly linked"}},
1128 [&makeNFTokenIDs](
1129 Account const& A1, Account const& A2, ApplyContext& ac) {
1130 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1131 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1132 nftPage->setFieldH256(
1133 sfPreviousPageMin, keylet::nftpage_min(A2).key);
1134
1135 ac.view().insert(nftPage);
1136 return true;
1137 });
1138
1140 {{"NFT page is improperly linked"}},
1141 [&makeNFTokenIDs](
1142 Account const& A1, Account const&, ApplyContext& ac) {
1143 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1144 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1145 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1146
1147 ac.view().insert(nftPage);
1148 return true;
1149 });
1150
1152 {{"NFT page is improperly linked"}},
1153 [&makeNFTokenIDs](
1154 Account const& A1, Account const& A2, ApplyContext& ac) {
1155 STArray nfTokens = makeNFTokenIDs(1);
1158 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1159 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1160 nftPage->setFieldH256(
1161 sfNextPageMin, keylet::nftpage_max(A2).key);
1162
1163 ac.view().insert(nftPage);
1164 return true;
1165 });
1166
1168 {{"NFT found in incorrect page"}},
1169 [&makeNFTokenIDs](
1170 Account const& A1, Account const&, ApplyContext& ac) {
1171 STArray nfTokens = makeNFTokenIDs(2);
1174 (nfTokens[1].getFieldH256(sfNFTokenID))));
1175 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1176
1177 ac.view().insert(nftPage);
1178 return true;
1179 });
1180 }
1181
1182 void
1184 ApplyContext& ac,
1186 test::jtx::Account const& A1,
1187 test::jtx::Account const& A2)
1188 {
1189 sle->setAccountID(sfOwner, A1);
1190 sle->setFieldU32(sfSequence, 10);
1191
1192 STArray credentials(sfAcceptedCredentials, 2);
1193 for (std::size_t n = 0; n < 2; ++n)
1194 {
1195 auto cred = STObject::makeInnerObject(sfCredential);
1196 cred.setAccountID(sfIssuer, A2);
1197 auto credType = "cred_type" + std::to_string(n);
1198 cred.setFieldVL(
1199 sfCredentialType, Slice(credType.c_str(), credType.size()));
1200 credentials.push_back(std::move(cred));
1201 }
1202 sle->setFieldArray(sfAcceptedCredentials, credentials);
1203 ac.view().insert(sle);
1204 };
1205
1206 void
1208 {
1209 using namespace test::jtx;
1210
1211 testcase << "PermissionedDomain";
1213 {{"permissioned domain with no rules."}},
1214 [](Account const& A1, Account const&, ApplyContext& ac) {
1215 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1216 auto slePd = std::make_shared<SLE>(pdKeylet);
1217 slePd->setAccountID(sfOwner, A1);
1218 slePd->setFieldU32(sfSequence, 10);
1219
1220 ac.view().insert(slePd);
1221 return true;
1222 },
1223 XRPAmount{},
1224 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1226
1227 testcase << "PermissionedDomain 2";
1228
1229 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1231 {{"permissioned domain bad credentials size " +
1232 std::to_string(tooBig)}},
1233 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1234 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1235 auto slePd = std::make_shared<SLE>(pdKeylet);
1236 slePd->setAccountID(sfOwner, A1);
1237 slePd->setFieldU32(sfSequence, 10);
1238
1239 STArray credentials(sfAcceptedCredentials, tooBig);
1240 for (std::size_t n = 0; n < tooBig; ++n)
1241 {
1242 auto cred = STObject::makeInnerObject(sfCredential);
1243 cred.setAccountID(sfIssuer, A2);
1244 auto credType =
1245 std::string("cred_type") + std::to_string(n);
1246 cred.setFieldVL(
1247 sfCredentialType,
1248 Slice(credType.c_str(), credType.size()));
1249 credentials.push_back(std::move(cred));
1250 }
1251 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1252 ac.view().insert(slePd);
1253 return true;
1254 },
1255 XRPAmount{},
1256 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1258
1259 testcase << "PermissionedDomain 3";
1261 {{"permissioned domain credentials aren't sorted"}},
1262 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1263 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1264 auto slePd = std::make_shared<SLE>(pdKeylet);
1265 slePd->setAccountID(sfOwner, A1);
1266 slePd->setFieldU32(sfSequence, 10);
1267
1268 STArray credentials(sfAcceptedCredentials, 2);
1269 for (std::size_t n = 0; n < 2; ++n)
1270 {
1271 auto cred = STObject::makeInnerObject(sfCredential);
1272 cred.setAccountID(sfIssuer, A2);
1273 auto credType =
1274 std::string("cred_type") + std::to_string(9 - n);
1275 cred.setFieldVL(
1276 sfCredentialType,
1277 Slice(credType.c_str(), credType.size()));
1278 credentials.push_back(std::move(cred));
1279 }
1280 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1281 ac.view().insert(slePd);
1282 return true;
1283 },
1284 XRPAmount{},
1285 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1287
1288 testcase << "PermissionedDomain 4";
1290 {{"permissioned domain credentials aren't unique"}},
1291 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1292 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1293 auto slePd = std::make_shared<SLE>(pdKeylet);
1294 slePd->setAccountID(sfOwner, A1);
1295 slePd->setFieldU32(sfSequence, 10);
1296
1297 STArray credentials(sfAcceptedCredentials, 2);
1298 for (std::size_t n = 0; n < 2; ++n)
1299 {
1300 auto cred = STObject::makeInnerObject(sfCredential);
1301 cred.setAccountID(sfIssuer, A2);
1302 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1303 credentials.push_back(std::move(cred));
1304 }
1305 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1306 ac.view().insert(slePd);
1307 return true;
1308 },
1309 XRPAmount{},
1310 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1312
1313 testcase << "PermissionedDomain Set 1";
1315 {{"permissioned domain with no rules."}},
1316 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1317 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1318 auto slePd = std::make_shared<SLE>(pdKeylet);
1319
1320 // create PD
1321 createPermissionedDomain(ac, slePd, A1, A2);
1322
1323 // update PD with empty rules
1324 {
1325 STArray credentials(sfAcceptedCredentials, 2);
1326 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1327 ac.view().update(slePd);
1328 }
1329
1330 return true;
1331 },
1332 XRPAmount{},
1333 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1335
1336 testcase << "PermissionedDomain Set 2";
1338 {{"permissioned domain bad credentials size " +
1339 std::to_string(tooBig)}},
1340 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1341 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1342 auto slePd = std::make_shared<SLE>(pdKeylet);
1343
1344 // create PD
1345 createPermissionedDomain(ac, slePd, A1, A2);
1346
1347 // update PD
1348 {
1349 STArray credentials(sfAcceptedCredentials, tooBig);
1350
1351 for (std::size_t n = 0; n < tooBig; ++n)
1352 {
1353 auto cred = STObject::makeInnerObject(sfCredential);
1354 cred.setAccountID(sfIssuer, A2);
1355 auto credType = "cred_type2" + std::to_string(n);
1356 cred.setFieldVL(
1357 sfCredentialType,
1358 Slice(credType.c_str(), credType.size()));
1359 credentials.push_back(std::move(cred));
1360 }
1361
1362 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1363 ac.view().update(slePd);
1364 }
1365
1366 return true;
1367 },
1368 XRPAmount{},
1369 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1371
1372 testcase << "PermissionedDomain Set 3";
1374 {{"permissioned domain credentials aren't sorted"}},
1375 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1376 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1377 auto slePd = std::make_shared<SLE>(pdKeylet);
1378
1379 // create PD
1380 createPermissionedDomain(ac, slePd, A1, A2);
1381
1382 // update PD
1383 {
1384 STArray credentials(sfAcceptedCredentials, 2);
1385 for (std::size_t n = 0; n < 2; ++n)
1386 {
1387 auto cred = STObject::makeInnerObject(sfCredential);
1388 cred.setAccountID(sfIssuer, A2);
1389 auto credType =
1390 std::string("cred_type2") + std::to_string(9 - n);
1391 cred.setFieldVL(
1392 sfCredentialType,
1393 Slice(credType.c_str(), credType.size()));
1394 credentials.push_back(std::move(cred));
1395 }
1396
1397 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1398 ac.view().update(slePd);
1399 }
1400
1401 return true;
1402 },
1403 XRPAmount{},
1404 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1406
1407 testcase << "PermissionedDomain Set 4";
1409 {{"permissioned domain credentials aren't unique"}},
1410 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1411 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1412 auto slePd = std::make_shared<SLE>(pdKeylet);
1413
1414 // create PD
1415 createPermissionedDomain(ac, slePd, A1, A2);
1416
1417 // update PD
1418 {
1419 STArray credentials(sfAcceptedCredentials, 2);
1420 for (std::size_t n = 0; n < 2; ++n)
1421 {
1422 auto cred = STObject::makeInnerObject(sfCredential);
1423 cred.setAccountID(sfIssuer, A2);
1424 cred.setFieldVL(
1425 sfCredentialType, Slice("cred_type", 9));
1426 credentials.push_back(std::move(cred));
1427 }
1428 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1429 ac.view().update(slePd);
1430 }
1431
1432 return true;
1433 },
1434 XRPAmount{},
1435 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1437 }
1438
1439 void
1441 {
1442 testcase << "valid pseudo accounts";
1443
1444 using namespace jtx;
1445
1446 AccountID pseudoAccountID;
1447 Preclose createPseudo =
1448 [&, this](Account const& a, Account const& b, Env& env) {
1449 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
1450
1451 // Create vault
1452 Vault vault{env};
1453 auto [tx, vKeylet] =
1454 vault.create({.owner = a, .asset = xrpAsset});
1455 env(tx);
1456 env.close();
1457 if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
1458 {
1459 pseudoAccountID = vSle->at(sfAccount);
1460 }
1461
1462 return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
1463 };
1464
1465 /* Cases to check
1466 "pseudo-account has 0 pseudo-account fields set"
1467 "pseudo-account has 2 pseudo-account fields set"
1468 "pseudo-account sequence changed"
1469 "pseudo-account flags are not set"
1470 "pseudo-account has a regular key"
1471 */
1472 struct Mod
1473 {
1474 std::string expectedFailure;
1475 std::function<void(SLE::pointer&)> func;
1476 };
1477 auto const mods = std::to_array<Mod>({
1478 {
1479 "pseudo-account has 0 pseudo-account fields set",
1480 [this](SLE::pointer& sle) {
1481 BEAST_EXPECT(sle->at(~sfVaultID));
1482 sle->at(~sfVaultID) = std::nullopt;
1483 },
1484 },
1485 {
1486 "pseudo-account sequence changed",
1487 [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
1488 },
1489 {
1490 "pseudo-account flags are not set",
1491 [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
1492 },
1493 {
1494 "pseudo-account has a regular key",
1495 [](SLE::pointer& sle) {
1496 sle->at(sfRegularKey) = Account("regular").id();
1497 },
1498 },
1499 });
1500
1501 for (auto const& mod : mods)
1502 {
1504 {{mod.expectedFailure}},
1505 [&](Account const& A1, Account const&, ApplyContext& ac) {
1506 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1507 if (!sle)
1508 return false;
1509 mod.func(sle);
1510 ac.view().update(sle);
1511 return true;
1512 },
1513 XRPAmount{},
1514 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1516 createPseudo);
1517 }
1518 for (auto const pField : getPseudoAccountFields())
1519 {
1520 // createPseudo creates a vault, so sfVaultID will be set, and
1521 // setting it again will not cause an error
1522 if (pField == &sfVaultID)
1523 continue;
1525 {{"pseudo-account has 2 pseudo-account fields set"}},
1526 [&](Account const& A1, Account const&, ApplyContext& ac) {
1527 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
1528 if (!sle)
1529 return false;
1530
1531 auto const vaultID = ~sle->at(~sfVaultID);
1532 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
1533 sle->setFieldH256(*pField, *vaultID);
1534
1535 ac.view().update(sle);
1536 return true;
1537 },
1538 XRPAmount{},
1539 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
1541 createPseudo);
1542 }
1543
1544 // Take one of the regular accounts and set the sequence to 0, which
1545 // will make it look like a pseudo-account
1547 {{"pseudo-account has 0 pseudo-account fields set"},
1548 {"pseudo-account sequence changed"},
1549 {"pseudo-account flags are not set"}},
1550 [&](Account const& A1, Account const&, ApplyContext& ac) {
1551 auto sle = ac.view().peek(keylet::account(A1.id()));
1552 if (!sle)
1553 return false;
1554 sle->at(sfSequence) = 0;
1555 ac.view().update(sle);
1556 return true;
1557 });
1558 }
1559
1560 void
1562 {
1563 using namespace test::jtx;
1564 testcase << "PermissionedDEX";
1565
1567 {{"domain doesn't exist"}},
1568 [](Account const& A1, Account const&, ApplyContext& ac) {
1569 Keylet const offerKey = keylet::offer(A1.id(), 10);
1570 auto sleOffer = std::make_shared<SLE>(offerKey);
1571 sleOffer->setAccountID(sfAccount, A1);
1572 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1573 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1574 ac.view().insert(sleOffer);
1575 return true;
1576 },
1577 XRPAmount{},
1578 STTx{
1579 ttOFFER_CREATE,
1580 [](STObject& tx) {
1581 tx.setFieldH256(
1582 sfDomainID,
1583 uint256{
1584 "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1585 "70F3649CE134E5"});
1586 Account const A1{"A1"};
1587 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1588 tx.setFieldAmount(sfTakerGets, XRP(1));
1589 }},
1591
1592 // missing domain ID in offer object
1594 {{"hybrid offer is malformed"}},
1595 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1596 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1597 auto slePd = std::make_shared<SLE>(pdKeylet);
1598 createPermissionedDomain(ac, slePd, A1, A2);
1599
1600 Keylet const offerKey = keylet::offer(A2.id(), 10);
1601 auto sleOffer = std::make_shared<SLE>(offerKey);
1602 sleOffer->setAccountID(sfAccount, A2);
1603 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1604 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1605 sleOffer->setFlag(lsfHybrid);
1606
1607 STArray bookArr;
1608 bookArr.push_back(STObject::makeInnerObject(sfBook));
1609 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1610 ac.view().insert(sleOffer);
1611 return true;
1612 },
1613 XRPAmount{},
1614 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1616
1617 // more than one entry in sfAdditionalBooks
1619 {{"hybrid offer is malformed"}},
1620 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1621 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1622 auto slePd = std::make_shared<SLE>(pdKeylet);
1623 createPermissionedDomain(ac, slePd, A1, A2);
1624
1625 Keylet const offerKey = keylet::offer(A2.id(), 10);
1626 auto sleOffer = std::make_shared<SLE>(offerKey);
1627 sleOffer->setAccountID(sfAccount, A2);
1628 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1629 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1630 sleOffer->setFlag(lsfHybrid);
1631 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1632
1633 STArray bookArr;
1634 bookArr.push_back(STObject::makeInnerObject(sfBook));
1635 bookArr.push_back(STObject::makeInnerObject(sfBook));
1636 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1637 ac.view().insert(sleOffer);
1638 return true;
1639 },
1640 XRPAmount{},
1641 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1643
1644 // hybrid offer missing sfAdditionalBooks
1646 {{"hybrid offer is malformed"}},
1647 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1648 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1649 auto slePd = std::make_shared<SLE>(pdKeylet);
1650 createPermissionedDomain(ac, slePd, A1, A2);
1651
1652 Keylet const offerKey = keylet::offer(A2.id(), 10);
1653 auto sleOffer = std::make_shared<SLE>(offerKey);
1654 sleOffer->setAccountID(sfAccount, A2);
1655 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1656 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1657 sleOffer->setFlag(lsfHybrid);
1658 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1659 ac.view().insert(sleOffer);
1660 return true;
1661 },
1662 XRPAmount{},
1663 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1665
1667 {{"transaction consumed wrong domains"}},
1668 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1669 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1670 auto slePd = std::make_shared<SLE>(pdKeylet);
1671 createPermissionedDomain(ac, slePd, A1, A2);
1672
1673 Keylet const badDomainKeylet =
1675 auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
1676 createPermissionedDomain(ac, sleBadPd, A1, A2);
1677
1678 Keylet const offerKey = keylet::offer(A2.id(), 10);
1679 auto sleOffer = std::make_shared<SLE>(offerKey);
1680 sleOffer->setAccountID(sfAccount, A2);
1681 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1682 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1683 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1684 ac.view().insert(sleOffer);
1685 return true;
1686 },
1687 XRPAmount{},
1688 STTx{
1689 ttOFFER_CREATE,
1690 [&](STObject& tx) {
1691 Account const A1{"A1"};
1692 Keylet const badDomainKey =
1694 tx.setFieldH256(sfDomainID, badDomainKey.key);
1695 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1696 tx.setFieldAmount(sfTakerGets, XRP(1));
1697 }},
1699
1701 {{"domain transaction affected regular offers"}},
1702 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1703 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1704 auto slePd = std::make_shared<SLE>(pdKeylet);
1705 createPermissionedDomain(ac, slePd, A1, A2);
1706
1707 Keylet const offerKey = keylet::offer(A2.id(), 10);
1708 auto sleOffer = std::make_shared<SLE>(offerKey);
1709 sleOffer->setAccountID(sfAccount, A2);
1710 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1711 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1712 ac.view().insert(sleOffer);
1713 return true;
1714 },
1715 XRPAmount{},
1716 STTx{
1717 ttOFFER_CREATE,
1718 [&](STObject& tx) {
1719 Account const A1{"A1"};
1720 Keylet const domainKey =
1722 tx.setFieldH256(sfDomainID, domainKey.key);
1723 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1724 tx.setFieldAmount(sfTakerGets, XRP(1));
1725 }},
1727 }
1728
1729public:
1730 void
1750};
1751
1752BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
1753
1754} // namespace test
1755} // 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.
ApplyView & view()
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()
A currency issued by an account.
Definition Issue.h:33
Defines the fields and their attributes within a STObject.
Definition SOTemplate.h:113
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
void run() override
Runs the suite.
void createPermissionedDomain(ApplyContext &ac, std::shared_ptr< SLE > &sle, test::jtx::Account const &A1, test::jtx::Account const &A2)
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, test::jtx::Env &env)> Preclose
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.
Convenience class to test AMM functionality.
Definition AMM.h:124
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
A transaction testing environment.
Definition Env.h:121
Set the fee on a JTx.
Definition fee.h:37
T invoke(T... args)
T is_same_v
T iter_swap(T... args)
Keylet mptoken(MPTID const &issuanceID, AccountID const &holder) noexcept
Definition Indexes.cpp:540
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 mptIssuance(std::uint32_t seq, AccountID const &issuer) noexcept
Definition Indexes.cpp:526
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
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:34
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
FeatureBitset testable_amendments()
Definition Env.h:74
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:48
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
Currency const & badCurrency()
We deliberately disallow the currency that looks like "XRP" because too many people were using it ins...
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:120
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:110
@ lsfHighDeepFreeze
@ lsfRequireDestTag
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:384
@ tefINVARIANT_FAILED
Definition TER.h:183
base_uint< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:56
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
Definition View.cpp:1594
@ 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.
@ tapNONE
Definition ApplyView.h:31
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:170
std::vector< SField const * > const & getPseudoAccountFields()
Definition View.cpp:1089
TERSubset< CanCvtToTER > TER
Definition TER.h:645
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
T to_string(T... args)