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 = testable_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 {{"XRP net change of -1000000 doesn't match fee 0"},
736 {"escrow specifies invalid amount"}},
737 [](Account const& A1, Account const&, ApplyContext& ac) {
738 // escrow with negative amount
739 auto const sle = ac.view().peek(keylet::account(A1.id()));
740 if (!sle)
741 return false;
742 auto sleNew = std::make_shared<SLE>(
743 keylet::escrow(A1, (*sle)[sfSequence] + 2));
744 sleNew->setFieldAmount(sfAmount, XRP(-1));
745 ac.view().insert(sleNew);
746 return true;
747 });
748
750 {{"XRP net change was positive: 100000000000000001"},
751 {"escrow specifies invalid amount"}},
752 [](Account const& A1, Account const&, ApplyContext& ac) {
753 // escrow with too-large 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 // Use `drops(1)` to bypass a call to STAmount::canonicalize
760 // with an invalid value
761 sleNew->setFieldAmount(sfAmount, INITIAL_XRP + drops(1));
762 ac.view().insert(sleNew);
763 return true;
764 });
765
766 // IOU < 0
768 {{"escrow specifies invalid amount"}},
769 [](Account const& A1, Account const&, ApplyContext& ac) {
770 // escrow with too-little iou
771 auto const sle = ac.view().peek(keylet::account(A1.id()));
772 if (!sle)
773 return false;
774 auto sleNew = std::make_shared<SLE>(
775 keylet::escrow(A1, (*sle)[sfSequence] + 2));
776
777 Issue const usd{
778 Currency(0x5553440000000000), AccountID(0x4985601)};
779 STAmount amt(usd, -1);
780 sleNew->setFieldAmount(sfAmount, amt);
781 ac.view().insert(sleNew);
782 return true;
783 });
784
785 // IOU bad currency
787 {{"escrow specifies invalid amount"}},
788 [](Account const& A1, Account const&, ApplyContext& ac) {
789 // escrow with bad iou currency
790 auto const sle = ac.view().peek(keylet::account(A1.id()));
791 if (!sle)
792 return false;
793 auto sleNew = std::make_shared<SLE>(
794 keylet::escrow(A1, (*sle)[sfSequence] + 2));
795
796 Issue const bad{badCurrency(), AccountID(0x4985601)};
797 STAmount amt(bad, 1);
798 sleNew->setFieldAmount(sfAmount, amt);
799 ac.view().insert(sleNew);
800 return true;
801 });
802
803 // MPT < 0
805 {{"escrow specifies invalid amount"}},
806 [](Account const& A1, Account const&, ApplyContext& ac) {
807 // escrow with too-little mpt
808 auto const sle = ac.view().peek(keylet::account(A1.id()));
809 if (!sle)
810 return false;
811 auto sleNew = std::make_shared<SLE>(
812 keylet::escrow(A1, (*sle)[sfSequence] + 2));
813
814 MPTIssue const mpt{
815 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
816 STAmount amt(mpt, -1);
817 sleNew->setFieldAmount(sfAmount, amt);
818 ac.view().insert(sleNew);
819 return true;
820 });
821
822 // MPT OutstandingAmount < 0
824 {{"escrow specifies invalid amount"}},
825 [](Account const& A1, Account const&, ApplyContext& ac) {
826 // mpissuance outstanding is negative
827 auto const sle = ac.view().peek(keylet::account(A1.id()));
828 if (!sle)
829 return false;
830
831 MPTIssue const mpt{
832 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
833 auto sleNew =
835 sleNew->setFieldU64(sfOutstandingAmount, -1);
836 ac.view().insert(sleNew);
837 return true;
838 });
839
840 // MPT LockedAmount < 0
842 {{"escrow specifies invalid amount"}},
843 [](Account const& A1, Account const&, ApplyContext& ac) {
844 // mpissuance locked is less than locked
845 auto const sle = ac.view().peek(keylet::account(A1.id()));
846 if (!sle)
847 return false;
848
849 MPTIssue const mpt{
850 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
851 auto sleNew =
853 sleNew->setFieldU64(sfLockedAmount, -1);
854 ac.view().insert(sleNew);
855 return true;
856 });
857
858 // MPT OutstandingAmount < LockedAmount
860 {{"escrow specifies invalid amount"}},
861 [](Account const& A1, Account const&, ApplyContext& ac) {
862 // mpissuance outstanding is less than locked
863 auto const sle = ac.view().peek(keylet::account(A1.id()));
864 if (!sle)
865 return false;
866
867 MPTIssue const mpt{
868 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
869 auto sleNew =
871 sleNew->setFieldU64(sfOutstandingAmount, 1);
872 sleNew->setFieldU64(sfLockedAmount, 10);
873 ac.view().insert(sleNew);
874 return true;
875 });
876
877 // MPT MPTAmount < 0
879 {{"escrow specifies invalid amount"}},
880 [](Account const& A1, Account const&, ApplyContext& ac) {
881 // mptoken amount is negative
882 auto const sle = ac.view().peek(keylet::account(A1.id()));
883 if (!sle)
884 return false;
885
886 MPTIssue const mpt{
887 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
888 auto sleNew =
889 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
890 sleNew->setFieldU64(sfMPTAmount, -1);
891 ac.view().insert(sleNew);
892 return true;
893 });
894
895 // MPT LockedAmount < 0
897 {{"escrow specifies invalid amount"}},
898 [](Account const& A1, Account const&, ApplyContext& ac) {
899 // mptoken locked amount is negative
900 auto const sle = ac.view().peek(keylet::account(A1.id()));
901 if (!sle)
902 return false;
903
904 MPTIssue const mpt{
905 MPTIssue{makeMptID(1, AccountID(0x4985601))}};
906 auto sleNew =
907 std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
908 sleNew->setFieldU64(sfLockedAmount, -1);
909 ac.view().insert(sleNew);
910 return true;
911 });
912 }
913
914 void
916 {
917 using namespace test::jtx;
918 testcase << "valid new account root";
919
921 {{"account root created illegally"}},
922 [](Account const&, Account const&, ApplyContext& ac) {
923 // Insert a new account root created by a non-payment into
924 // the view.
925 Account const A3{"A3"};
926 Keylet const acctKeylet = keylet::account(A3);
927 auto const sleNew = std::make_shared<SLE>(acctKeylet);
928 ac.view().insert(sleNew);
929 return true;
930 });
931
933 {{"multiple accounts created in a single transaction"}},
934 [](Account const&, Account const&, ApplyContext& ac) {
935 // Insert two new account roots into the view.
936 {
937 Account const A3{"A3"};
938 Keylet const acctKeylet = keylet::account(A3);
939 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
940 ac.view().insert(sleA3);
941 }
942 {
943 Account const A4{"A4"};
944 Keylet const acctKeylet = keylet::account(A4);
945 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
946 ac.view().insert(sleA4);
947 }
948 return true;
949 });
950
952 {{"account created with wrong starting sequence number"}},
953 [](Account const&, Account const&, ApplyContext& ac) {
954 // Insert a new account root with the wrong starting sequence.
955 Account const A3{"A3"};
956 Keylet const acctKeylet = keylet::account(A3);
957 auto const sleNew = std::make_shared<SLE>(acctKeylet);
958 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
959 ac.view().insert(sleNew);
960 return true;
961 },
962 XRPAmount{},
963 STTx{ttPAYMENT, [](STObject& tx) {}});
964
966 {{"pseudo-account created by a wrong transaction type"}},
967 [](Account const&, Account const&, ApplyContext& ac) {
968 Account const A3{"A3"};
969 Keylet const acctKeylet = keylet::account(A3);
970 auto const sleNew = std::make_shared<SLE>(acctKeylet);
971 sleNew->setFieldU32(sfSequence, 0);
972 sleNew->setFieldH256(sfAMMID, uint256(1));
973 sleNew->setFieldU32(
974 sfFlags,
976 ac.view().insert(sleNew);
977 return true;
978 },
979 XRPAmount{},
980 STTx{ttPAYMENT, [](STObject& tx) {}});
981
983 {{"account created with wrong starting sequence number"}},
984 [](Account const&, Account const&, ApplyContext& ac) {
985 Account const A3{"A3"};
986 Keylet const acctKeylet = keylet::account(A3);
987 auto const sleNew = std::make_shared<SLE>(acctKeylet);
988 sleNew->setFieldU32(sfSequence, ac.view().seq());
989 sleNew->setFieldH256(sfAMMID, uint256(1));
990 sleNew->setFieldU32(
991 sfFlags,
993 ac.view().insert(sleNew);
994 return true;
995 },
996 XRPAmount{},
997 STTx{ttAMM_CREATE, [](STObject& tx) {}});
998
1000 {{"pseudo-account created with wrong flags"}},
1001 [](Account const&, Account const&, ApplyContext& ac) {
1002 Account const A3{"A3"};
1003 Keylet const acctKeylet = keylet::account(A3);
1004 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1005 sleNew->setFieldU32(sfSequence, 0);
1006 sleNew->setFieldH256(sfAMMID, uint256(1));
1007 sleNew->setFieldU32(
1009 ac.view().insert(sleNew);
1010 return true;
1011 },
1012 XRPAmount{},
1013 STTx{ttVAULT_CREATE, [](STObject& tx) {}});
1014
1016 {{"pseudo-account created with wrong flags"}},
1017 [](Account const&, Account const&, ApplyContext& ac) {
1018 Account const A3{"A3"};
1019 Keylet const acctKeylet = keylet::account(A3);
1020 auto const sleNew = std::make_shared<SLE>(acctKeylet);
1021 sleNew->setFieldU32(sfSequence, 0);
1022 sleNew->setFieldH256(sfAMMID, uint256(1));
1023 sleNew->setFieldU32(
1024 sfFlags,
1027 ac.view().insert(sleNew);
1028 return true;
1029 },
1030 XRPAmount{},
1031 STTx{ttAMM_CREATE, [](STObject& tx) {}});
1032 }
1033
1034 void
1036 {
1037 using namespace test::jtx;
1038 testcase << "NFTokenPage";
1039
1040 // lambda that returns an STArray of NFTokenIDs.
1041 uint256 const firstNFTID(
1042 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
1043 auto makeNFTokenIDs = [&firstNFTID](unsigned int nftCount) {
1044 SOTemplate const* nfTokenTemplate =
1046 sfNFToken);
1047
1048 uint256 nftID(firstNFTID);
1049 STArray ret;
1050 for (int i = 0; i < nftCount; ++i)
1051 {
1052 STObject newNFToken(
1053 *nfTokenTemplate, sfNFToken, [&nftID](STObject& object) {
1054 object.setFieldH256(sfNFTokenID, nftID);
1055 });
1056 ret.push_back(std::move(newNFToken));
1057 ++nftID;
1058 }
1059 return ret;
1060 };
1061
1063 {{"NFT page has invalid size"}},
1064 [&makeNFTokenIDs](
1065 Account const& A1, Account const&, ApplyContext& ac) {
1066 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1067 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
1068
1069 ac.view().insert(nftPage);
1070 return true;
1071 });
1072
1074 {{"NFT page has invalid size"}},
1075 [&makeNFTokenIDs](
1076 Account const& A1, Account const&, ApplyContext& ac) {
1077 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1078 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
1079
1080 ac.view().insert(nftPage);
1081 return true;
1082 });
1083
1085 {{"NFTs on page are not sorted"}},
1086 [&makeNFTokenIDs](
1087 Account const& A1, Account const&, ApplyContext& ac) {
1088 STArray nfTokens = makeNFTokenIDs(2);
1089 std::iter_swap(nfTokens.begin(), nfTokens.begin() + 1);
1090
1091 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1092 nftPage->setFieldArray(sfNFTokens, nfTokens);
1093
1094 ac.view().insert(nftPage);
1095 return true;
1096 });
1097
1099 {{"NFT contains empty URI"}},
1100 [&makeNFTokenIDs](
1101 Account const& A1, Account const&, ApplyContext& ac) {
1102 STArray nfTokens = makeNFTokenIDs(1);
1103 nfTokens[0].setFieldVL(sfURI, Blob{});
1104
1105 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1106 nftPage->setFieldArray(sfNFTokens, nfTokens);
1107
1108 ac.view().insert(nftPage);
1109 return true;
1110 });
1111
1113 {{"NFT page is improperly linked"}},
1114 [&makeNFTokenIDs](
1115 Account const& A1, Account const&, ApplyContext& ac) {
1116 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1117 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1118 nftPage->setFieldH256(
1119 sfPreviousPageMin, keylet::nftpage_max(A1).key);
1120
1121 ac.view().insert(nftPage);
1122 return true;
1123 });
1124
1126 {{"NFT page is improperly linked"}},
1127 [&makeNFTokenIDs](
1128 Account const& A1, Account const& A2, ApplyContext& ac) {
1129 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1130 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1131 nftPage->setFieldH256(
1132 sfPreviousPageMin, keylet::nftpage_min(A2).key);
1133
1134 ac.view().insert(nftPage);
1135 return true;
1136 });
1137
1139 {{"NFT page is improperly linked"}},
1140 [&makeNFTokenIDs](
1141 Account const& A1, Account const&, ApplyContext& ac) {
1142 auto nftPage = std::make_shared<SLE>(keylet::nftpage_max(A1));
1143 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1144 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1145
1146 ac.view().insert(nftPage);
1147 return true;
1148 });
1149
1151 {{"NFT page is improperly linked"}},
1152 [&makeNFTokenIDs](
1153 Account const& A1, Account const& A2, ApplyContext& ac) {
1154 STArray nfTokens = makeNFTokenIDs(1);
1157 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1158 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1159 nftPage->setFieldH256(
1160 sfNextPageMin, keylet::nftpage_max(A2).key);
1161
1162 ac.view().insert(nftPage);
1163 return true;
1164 });
1165
1167 {{"NFT found in incorrect page"}},
1168 [&makeNFTokenIDs](
1169 Account const& A1, Account const&, ApplyContext& ac) {
1170 STArray nfTokens = makeNFTokenIDs(2);
1173 (nfTokens[1].getFieldH256(sfNFTokenID))));
1174 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1175
1176 ac.view().insert(nftPage);
1177 return true;
1178 });
1179 }
1180
1181 void
1183 ApplyContext& ac,
1185 test::jtx::Account const& A1,
1186 test::jtx::Account const& A2)
1187 {
1188 sle->setAccountID(sfOwner, A1);
1189 sle->setFieldU32(sfSequence, 10);
1190
1191 STArray credentials(sfAcceptedCredentials, 2);
1192 for (std::size_t n = 0; n < 2; ++n)
1193 {
1194 auto cred = STObject::makeInnerObject(sfCredential);
1195 cred.setAccountID(sfIssuer, A2);
1196 auto credType = "cred_type" + std::to_string(n);
1197 cred.setFieldVL(
1198 sfCredentialType, Slice(credType.c_str(), credType.size()));
1199 credentials.push_back(std::move(cred));
1200 }
1201 sle->setFieldArray(sfAcceptedCredentials, credentials);
1202 ac.view().insert(sle);
1203 };
1204
1205 void
1207 {
1208 using namespace test::jtx;
1209
1210 testcase << "PermissionedDomain";
1212 {{"permissioned domain with no rules."}},
1213 [](Account const& A1, Account const&, ApplyContext& ac) {
1214 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1215 auto slePd = std::make_shared<SLE>(pdKeylet);
1216 slePd->setAccountID(sfOwner, A1);
1217 slePd->setFieldU32(sfSequence, 10);
1218
1219 ac.view().insert(slePd);
1220 return true;
1221 },
1222 XRPAmount{},
1223 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1225
1226 testcase << "PermissionedDomain 2";
1227
1228 auto constexpr tooBig = maxPermissionedDomainCredentialsArraySize + 1;
1230 {{"permissioned domain bad credentials size " +
1231 std::to_string(tooBig)}},
1232 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1233 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1234 auto slePd = std::make_shared<SLE>(pdKeylet);
1235 slePd->setAccountID(sfOwner, A1);
1236 slePd->setFieldU32(sfSequence, 10);
1237
1238 STArray credentials(sfAcceptedCredentials, tooBig);
1239 for (std::size_t n = 0; n < tooBig; ++n)
1240 {
1241 auto cred = STObject::makeInnerObject(sfCredential);
1242 cred.setAccountID(sfIssuer, A2);
1243 auto credType =
1244 std::string("cred_type") + std::to_string(n);
1245 cred.setFieldVL(
1246 sfCredentialType,
1247 Slice(credType.c_str(), credType.size()));
1248 credentials.push_back(std::move(cred));
1249 }
1250 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1251 ac.view().insert(slePd);
1252 return true;
1253 },
1254 XRPAmount{},
1255 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1257
1258 testcase << "PermissionedDomain 3";
1260 {{"permissioned domain credentials aren't sorted"}},
1261 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1262 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1263 auto slePd = std::make_shared<SLE>(pdKeylet);
1264 slePd->setAccountID(sfOwner, A1);
1265 slePd->setFieldU32(sfSequence, 10);
1266
1267 STArray credentials(sfAcceptedCredentials, 2);
1268 for (std::size_t n = 0; n < 2; ++n)
1269 {
1270 auto cred = STObject::makeInnerObject(sfCredential);
1271 cred.setAccountID(sfIssuer, A2);
1272 auto credType =
1273 std::string("cred_type") + std::to_string(9 - n);
1274 cred.setFieldVL(
1275 sfCredentialType,
1276 Slice(credType.c_str(), credType.size()));
1277 credentials.push_back(std::move(cred));
1278 }
1279 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1280 ac.view().insert(slePd);
1281 return true;
1282 },
1283 XRPAmount{},
1284 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject&) {}},
1286
1287 testcase << "PermissionedDomain 4";
1289 {{"permissioned domain credentials aren't unique"}},
1290 [](Account const& A1, Account const& A2, ApplyContext& ac) {
1291 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1292 auto slePd = std::make_shared<SLE>(pdKeylet);
1293 slePd->setAccountID(sfOwner, A1);
1294 slePd->setFieldU32(sfSequence, 10);
1295
1296 STArray credentials(sfAcceptedCredentials, 2);
1297 for (std::size_t n = 0; n < 2; ++n)
1298 {
1299 auto cred = STObject::makeInnerObject(sfCredential);
1300 cred.setAccountID(sfIssuer, A2);
1301 cred.setFieldVL(sfCredentialType, Slice("cred_type", 9));
1302 credentials.push_back(std::move(cred));
1303 }
1304 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1305 ac.view().insert(slePd);
1306 return true;
1307 },
1308 XRPAmount{},
1309 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1311
1312 testcase << "PermissionedDomain Set 1";
1314 {{"permissioned domain with no rules."}},
1315 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1316 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1317 auto slePd = std::make_shared<SLE>(pdKeylet);
1318
1319 // create PD
1320 createPermissionedDomain(ac, slePd, A1, A2);
1321
1322 // update PD with empty rules
1323 {
1324 STArray credentials(sfAcceptedCredentials, 2);
1325 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1326 ac.view().update(slePd);
1327 }
1328
1329 return true;
1330 },
1331 XRPAmount{},
1332 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1334
1335 testcase << "PermissionedDomain Set 2";
1337 {{"permissioned domain bad credentials size " +
1338 std::to_string(tooBig)}},
1339 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1340 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1341 auto slePd = std::make_shared<SLE>(pdKeylet);
1342
1343 // create PD
1344 createPermissionedDomain(ac, slePd, A1, A2);
1345
1346 // update PD
1347 {
1348 STArray credentials(sfAcceptedCredentials, tooBig);
1349
1350 for (std::size_t n = 0; n < tooBig; ++n)
1351 {
1352 auto cred = STObject::makeInnerObject(sfCredential);
1353 cred.setAccountID(sfIssuer, A2);
1354 auto credType = "cred_type2" + std::to_string(n);
1355 cred.setFieldVL(
1356 sfCredentialType,
1357 Slice(credType.c_str(), credType.size()));
1358 credentials.push_back(std::move(cred));
1359 }
1360
1361 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1362 ac.view().update(slePd);
1363 }
1364
1365 return true;
1366 },
1367 XRPAmount{},
1368 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1370
1371 testcase << "PermissionedDomain Set 3";
1373 {{"permissioned domain credentials aren't sorted"}},
1374 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1375 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1376 auto slePd = std::make_shared<SLE>(pdKeylet);
1377
1378 // create PD
1379 createPermissionedDomain(ac, slePd, A1, A2);
1380
1381 // update PD
1382 {
1383 STArray credentials(sfAcceptedCredentials, 2);
1384 for (std::size_t n = 0; n < 2; ++n)
1385 {
1386 auto cred = STObject::makeInnerObject(sfCredential);
1387 cred.setAccountID(sfIssuer, A2);
1388 auto credType =
1389 std::string("cred_type2") + std::to_string(9 - n);
1390 cred.setFieldVL(
1391 sfCredentialType,
1392 Slice(credType.c_str(), credType.size()));
1393 credentials.push_back(std::move(cred));
1394 }
1395
1396 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1397 ac.view().update(slePd);
1398 }
1399
1400 return true;
1401 },
1402 XRPAmount{},
1403 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1405
1406 testcase << "PermissionedDomain Set 4";
1408 {{"permissioned domain credentials aren't unique"}},
1409 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1410 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1411 auto slePd = std::make_shared<SLE>(pdKeylet);
1412
1413 // create PD
1414 createPermissionedDomain(ac, slePd, A1, A2);
1415
1416 // update PD
1417 {
1418 STArray credentials(sfAcceptedCredentials, 2);
1419 for (std::size_t n = 0; n < 2; ++n)
1420 {
1421 auto cred = STObject::makeInnerObject(sfCredential);
1422 cred.setAccountID(sfIssuer, A2);
1423 cred.setFieldVL(
1424 sfCredentialType, Slice("cred_type", 9));
1425 credentials.push_back(std::move(cred));
1426 }
1427 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1428 ac.view().update(slePd);
1429 }
1430
1431 return true;
1432 },
1433 XRPAmount{},
1434 STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
1436 }
1437
1438 void
1440 {
1441 using namespace test::jtx;
1442 testcase << "PermissionedDEX";
1443
1445 {{"domain doesn't exist"}},
1446 [](Account const& A1, Account const&, ApplyContext& ac) {
1447 Keylet const offerKey = keylet::offer(A1.id(), 10);
1448 auto sleOffer = std::make_shared<SLE>(offerKey);
1449 sleOffer->setAccountID(sfAccount, A1);
1450 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1451 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1452 ac.view().insert(sleOffer);
1453 return true;
1454 },
1455 XRPAmount{},
1456 STTx{
1457 ttOFFER_CREATE,
1458 [](STObject& tx) {
1459 tx.setFieldH256(
1460 sfDomainID,
1461 uint256{
1462 "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1463 "70F3649CE134E5"});
1464 Account const A1{"A1"};
1465 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1466 tx.setFieldAmount(sfTakerGets, XRP(1));
1467 }},
1469
1470 // missing domain ID in offer object
1472 {{"hybrid offer is malformed"}},
1473 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1474 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1475 auto slePd = std::make_shared<SLE>(pdKeylet);
1476 createPermissionedDomain(ac, slePd, A1, A2);
1477
1478 Keylet const offerKey = keylet::offer(A2.id(), 10);
1479 auto sleOffer = std::make_shared<SLE>(offerKey);
1480 sleOffer->setAccountID(sfAccount, A2);
1481 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1482 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1483 sleOffer->setFlag(lsfHybrid);
1484
1485 STArray bookArr;
1486 bookArr.push_back(STObject::makeInnerObject(sfBook));
1487 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1488 ac.view().insert(sleOffer);
1489 return true;
1490 },
1491 XRPAmount{},
1492 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1494
1495 // more than one entry in sfAdditionalBooks
1497 {{"hybrid offer is malformed"}},
1498 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1499 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1500 auto slePd = std::make_shared<SLE>(pdKeylet);
1501 createPermissionedDomain(ac, slePd, A1, A2);
1502
1503 Keylet const offerKey = keylet::offer(A2.id(), 10);
1504 auto sleOffer = std::make_shared<SLE>(offerKey);
1505 sleOffer->setAccountID(sfAccount, A2);
1506 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1507 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1508 sleOffer->setFlag(lsfHybrid);
1509 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1510
1511 STArray bookArr;
1512 bookArr.push_back(STObject::makeInnerObject(sfBook));
1513 bookArr.push_back(STObject::makeInnerObject(sfBook));
1514 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1515 ac.view().insert(sleOffer);
1516 return true;
1517 },
1518 XRPAmount{},
1519 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1521
1522 // hybrid offer missing sfAdditionalBooks
1524 {{"hybrid offer is malformed"}},
1525 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1526 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1527 auto slePd = std::make_shared<SLE>(pdKeylet);
1528 createPermissionedDomain(ac, slePd, A1, A2);
1529
1530 Keylet const offerKey = keylet::offer(A2.id(), 10);
1531 auto sleOffer = std::make_shared<SLE>(offerKey);
1532 sleOffer->setAccountID(sfAccount, A2);
1533 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1534 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1535 sleOffer->setFlag(lsfHybrid);
1536 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1537 ac.view().insert(sleOffer);
1538 return true;
1539 },
1540 XRPAmount{},
1541 STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
1543
1545 {{"transaction consumed wrong domains"}},
1546 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1547 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1548 auto slePd = std::make_shared<SLE>(pdKeylet);
1549 createPermissionedDomain(ac, slePd, A1, A2);
1550
1551 Keylet const badDomainKeylet =
1552 keylet::permissionedDomain(A1.id(), 20);
1553 auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
1554 createPermissionedDomain(ac, sleBadPd, A1, A2);
1555
1556 Keylet const offerKey = keylet::offer(A2.id(), 10);
1557 auto sleOffer = std::make_shared<SLE>(offerKey);
1558 sleOffer->setAccountID(sfAccount, A2);
1559 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1560 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1561 sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
1562 ac.view().insert(sleOffer);
1563 return true;
1564 },
1565 XRPAmount{},
1566 STTx{
1567 ttOFFER_CREATE,
1568 [&](STObject& tx) {
1569 Account const A1{"A1"};
1570 Keylet const badDomainKey =
1571 keylet::permissionedDomain(A1.id(), 20);
1572 tx.setFieldH256(sfDomainID, badDomainKey.key);
1573 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1574 tx.setFieldAmount(sfTakerGets, XRP(1));
1575 }},
1577
1579 {{"domain transaction affected regular offers"}},
1580 [&](Account const& A1, Account const& A2, ApplyContext& ac) {
1581 Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
1582 auto slePd = std::make_shared<SLE>(pdKeylet);
1583 createPermissionedDomain(ac, slePd, A1, A2);
1584
1585 Keylet const offerKey = keylet::offer(A2.id(), 10);
1586 auto sleOffer = std::make_shared<SLE>(offerKey);
1587 sleOffer->setAccountID(sfAccount, A2);
1588 sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
1589 sleOffer->setFieldAmount(sfTakerGets, XRP(1));
1590 ac.view().insert(sleOffer);
1591 return true;
1592 },
1593 XRPAmount{},
1594 STTx{
1595 ttOFFER_CREATE,
1596 [&](STObject& tx) {
1597 Account const A1{"A1"};
1598 Keylet const domainKey =
1599 keylet::permissionedDomain(A1.id(), 10);
1600 tx.setFieldH256(sfDomainID, domainKey.key);
1601 tx.setFieldAmount(sfTakerPays, A1["USD"](10));
1602 tx.setFieldAmount(sfTakerGets, XRP(1));
1603 }},
1605 }
1606
1607public:
1608 void
1627};
1628
1629BEAST_DEFINE_TESTSUITE(Invariants, app, ripple);
1630
1631} // 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()
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.
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
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
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
FeatureBitset testable_amendments()
Definition Env.h:74
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:105
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:111
@ lsfHighDeepFreeze
@ lsfRequireDestTag
@ lsfDefaultRipple
@ lsfDisableMaster
@ lsfLowDeepFreeze
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
Definition Indexes.h:382
@ 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:1534
@ 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:32
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:170
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)