xrpld
Loading...
Searching...
No Matches
InvariantsPseudoAccount_test.cpp
1#include <test/app/invariants/InvariantsBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/mpt.h>
7#include <test/jtx/pay.h>
8#include <test/jtx/trust.h>
9#include <test/jtx/vault.h>
10#include <test/unit_test/SuiteJournal.h>
11
12#include <xrpl/basics/Number.h>
13#include <xrpl/basics/base_uint.h>
14#include <xrpl/beast/unit_test/suite.h>
15#include <xrpl/beast/utility/Journal.h>
16#include <xrpl/ledger/ApplyView.h>
17#include <xrpl/ledger/OpenView.h>
18#include <xrpl/ledger/helpers/AccountRootHelpers.h>
19#include <xrpl/ledger/helpers/DirectoryHelpers.h>
20#include <xrpl/protocol/AccountID.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/Indexes.h>
23#include <xrpl/protocol/Issue.h>
24#include <xrpl/protocol/Keylet.h>
25#include <xrpl/protocol/LedgerFormats.h>
26#include <xrpl/protocol/Rules.h>
27#include <xrpl/protocol/SField.h>
28#include <xrpl/protocol/STLedgerEntry.h>
29#include <xrpl/protocol/STObject.h>
30#include <xrpl/protocol/STTx.h>
31#include <xrpl/protocol/TER.h>
32#include <xrpl/protocol/TxFlags.h>
33#include <xrpl/protocol/TxFormats.h>
34#include <xrpl/protocol/XRPAmount.h>
35#include <xrpl/tx/ApplyContext.h>
36#include <xrpl/tx/Transactor.h>
37#include <xrpl/tx/applySteps.h>
38
39#include <array>
40#include <functional>
41#include <initializer_list>
42#include <memory>
43#include <optional>
44#include <string>
45#include <utility>
46#include <vector>
47
48namespace xrpl::test {
49
51{
53
54 void
56 {
57 testcase << "valid pseudo accounts";
58
59 using namespace jtx;
60
61 AccountID pseudoAccountID;
62 Preclose const createPseudo = [&, this](Account const& a, Account const& b, Env& env) {
63 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
64
65 // Create vault
66 Vault const vault{env};
67 auto [tx, vKeylet] = vault.create({.owner = a, .asset = xrpAsset});
68 env(tx);
69 env.close();
70 if (auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
71 {
72 pseudoAccountID = vSle->at(sfAccount);
73 }
74
75 return BEAST_EXPECT(env.le(keylet::account(pseudoAccountID)));
76 };
77
78 /* Cases to check
79 "pseudo-account has 0 pseudo-account fields set"
80 "pseudo-account has 2 pseudo-account fields set"
81 "pseudo-account sequence changed"
82 "pseudo-account flags are not set"
83 "pseudo-account has a regular key"
84 "pseudo-account has a sponsorship field"
85 */
86 struct Mod
87 {
88 std::string expectedFailure;
89 std::function<void(SLE::pointer&)> func;
90 };
91 auto const mods = std::to_array<Mod>({
92 {
93 .expectedFailure = "pseudo-account has 0 pseudo-account fields set",
94 .func =
95 [this](SLE::pointer& sle) {
96 BEAST_EXPECT(sle->at(~sfVaultID));
97 sle->at(~sfVaultID) = std::nullopt;
98 },
99 },
100 {
101 .expectedFailure = "pseudo-account sequence changed",
102 .func = [](SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
103 },
104 {
105 .expectedFailure = "pseudo-account flags are not set",
106 .func = [](SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
107 },
108 {
109 .expectedFailure = "pseudo-account has a regular key",
110 .func = [](SLE::pointer& sle) { sle->at(sfRegularKey) = Account("regular").id(); },
111 },
112 {
113 .expectedFailure = "pseudo-account has a sponsorship field",
114 .func = [](SLE::pointer& sle) { sle->at(sfSponsoredOwnerCount) = 1; },
115 },
116 {
117 .expectedFailure = "pseudo-account has a sponsorship field",
118 .func = [](SLE::pointer& sle) { sle->at(sfSponsoringOwnerCount) = 1; },
119 },
120 {
121 .expectedFailure = "pseudo-account has a sponsorship field",
122 .func = [](SLE::pointer& sle) { sle->at(sfSponsoringAccountCount) = 1; },
123 },
124 {
125 .expectedFailure = "pseudo-account has a sponsorship field",
126 .func = [](SLE::pointer& sle) { sle->at(sfSponsor) = Account("sponsor").id(); },
127 },
128 });
129
130 for (auto const& mod : mods)
131 {
133 {{mod.expectedFailure}},
134 [&](Account const& a1, Account const&, ApplyContext& ac) {
135 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
136 if (!sle)
137 return false;
138 mod.func(sle);
139 ac.view().update(sle);
140 return true;
141 },
142 XRPAmount{},
143 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
145 createPseudo);
146 }
147 for (auto const pField : getPseudoAccountFields())
148 {
149 // createPseudo creates a vault, so sfVaultID will be set, and
150 // setting it again will not cause an error
151 if (pField == &sfVaultID)
152 continue;
154 {{"pseudo-account has 2 pseudo-account fields set"}},
155 [&](Account const& a1, Account const&, ApplyContext& ac) {
156 auto sle = ac.view().peek(keylet::account(pseudoAccountID));
157 if (!sle)
158 return false;
159
160 auto const vaultID = ~sle->at(~sfVaultID);
161 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
162 sle->setFieldH256(*pField, *vaultID);
163
164 ac.view().update(sle);
165 return true;
166 },
167 XRPAmount{},
168 STTx{ttACCOUNT_SET, [](STObject& tx) {}},
170 createPseudo);
171 }
172
173 // Take one of the regular accounts and set the sequence to 0, which
174 // will make it look like a pseudo-account
176 {{"pseudo-account has 0 pseudo-account fields set"},
177 {"pseudo-account sequence changed"},
178 {"pseudo-account flags are not set"}},
179 [&](Account const& a1, Account const&, ApplyContext& ac) {
180 auto sle = ac.view().peek(keylet::account(a1.id()));
181 if (!sle)
182 return false;
183 sle->at(sfSequence) = 0;
184 ac.view().update(sle);
185 return true;
186 });
187 }
188
189 void
191 {
192 testcase << "valid loan broker";
193
194 using namespace jtx;
195
196 enum class Asset { XRP, IOU, MPT };
197 auto const assetTypes = std::to_array({Asset::XRP, Asset::IOU, Asset::MPT});
198
199 for (auto const assetType : assetTypes)
200 {
201 // Initialize with a placeholder value because there's no default
202 // ctor
203 auto const setupAsset =
204 [&](Account const& alice, Account const& issuer, Env& env) -> PrettyAsset {
205 switch (assetType)
206 {
207 case Asset::IOU: {
208 PrettyAsset const iouAsset = issuer["IOU"];
209 env(trust(alice, iouAsset(1000)));
210 env(pay(issuer, alice, iouAsset(1000)));
211 env.close();
212 return iouAsset;
213 }
214 case Asset::MPT: {
215 MPTTester mptt{env, issuer, kMptInitNoFund};
216 mptt.create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
217 PrettyAsset const mptAsset = mptt.issuanceID();
218 mptt.authorize({.account = alice});
219 env(pay(issuer, alice, mptAsset(1000)));
220 env.close();
221 return mptAsset;
222 }
223 case Asset::XRP:
224 default:
225 return PrettyAsset{xrpIssue(), 1'000'000};
226 }
227 };
228
229 Keylet loanBrokerKeylet = keylet::amendments();
231 [&, this](Account const& alice, Account const& issuer, Env& env) {
232 auto const asset = setupAsset(alice, issuer, env);
233 loanBrokerKeylet = this->createLoanBroker(alice, env, asset);
234 return BEAST_EXPECT(env.le(loanBrokerKeylet));
235 };
236
237 // Ensure the test scenarios are set up completely. The test cases
238 // will need to recompute any of these values it needs for itself
239 // rather than trying to return a bunch of items
240 auto setupTest = [&, this](Account const& a1, Account const&, ApplyContext& ac)
242 if (loanBrokerKeylet.type != ltLOAN_BROKER)
243 return {};
244 auto sleBroker = ac.view().peek(loanBrokerKeylet);
245 if (!sleBroker)
246 return {};
247 if (!BEAST_EXPECT(sleBroker->at(sfOwnerCount) == 0))
248 return {};
249 // Need to touch sleBroker so that it is included in the
250 // modified entries for the invariant to find
251 ac.view().update(sleBroker);
252
253 // The pseudo-account holds the directory, so get it
254 auto const pseudoAccountID = sleBroker->at(sfAccount);
255 auto const pseudoAccountKeylet = keylet::account(pseudoAccountID);
256 // Strictly speaking, we don't need to load the
257 // ACCOUNT_ROOT, but check anyway
258 auto slePseudo = ac.view().peek(pseudoAccountKeylet);
259 if (!BEAST_EXPECT(slePseudo))
260 return {};
261 // Make sure the directory doesn't already exist
262 auto const dirKeylet = keylet::ownerDir(pseudoAccountID);
263 auto sleDir = ac.view().peek(dirKeylet);
264 auto const describe = describeOwnerDir(pseudoAccountID);
265 if (!sleDir)
266 {
267 // Create the directory
268 BEAST_EXPECT(
270 ac.view(), dirKeylet, loanBrokerKeylet.key, describe) == 0);
271
272 sleDir = ac.view().peek(dirKeylet);
273 }
274
275 return std::make_pair(slePseudo, sleDir);
276 };
277
279 {{"Loan Broker with zero OwnerCount has multiple directory "
280 "pages"}},
281 [&setupTest, this](Account const& a1, Account const& a2, ApplyContext& ac) {
282 auto test = setupTest(a1, a2, ac);
283 if (!test || !test->first || !test->second)
284 return false;
285
286 auto slePseudo = test->first;
287 auto sleDir = test->second;
288 auto const describe = describeOwnerDir(slePseudo->at(sfAccount));
289
290 BEAST_EXPECT(
292 ac.view(),
293 0,
294 sleDir,
295 0,
296 sleDir,
297 slePseudo->key(),
298 keylet::page(sleDir->key(), 0),
299 describe) == 1);
300
301 return true;
302 },
303 XRPAmount{},
304 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
307
309 {{"Loan Broker with zero OwnerCount has multiple indexes in "
310 "the Directory root"}},
311 [&setupTest](Account const& a1, Account const& a2, ApplyContext& ac) {
312 auto test = setupTest(a1, a2, ac);
313 if (!test || !test->first || !test->second)
314 return false;
315
316 auto slePseudo = test->first;
317 auto sleDir = test->second;
318 auto indexes = sleDir->getFieldV256(sfIndexes);
319
320 // Put some extra garbage into the directory
321 for (auto const& key : {slePseudo->key(), sleDir->key()})
322 {
323 ::xrpl::directory::insertKey(ac.view(), sleDir, 0, false, indexes, key);
324 }
325
326 return true;
327 },
328 XRPAmount{},
329 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
332
334 {{"Loan Broker directory corrupt"}},
335 [&setupTest](Account const& a1, Account const& a2, ApplyContext& ac) {
336 auto test = setupTest(a1, a2, ac);
337 if (!test || !test->first || !test->second)
338 return false;
339
340 auto slePseudo = test->first;
341 auto sleDir = test->second;
342 auto const describe = describeOwnerDir(slePseudo->at(sfAccount));
343 // Empty vector will overwrite the existing entry for the
344 // holding, if any, avoiding the "has multiple indexes"
345 // failure.
346 STVector256 indexes;
347
348 // Put one meaningless key into the directory
349 auto const key = keylet::account(Account("random").id()).key;
350 ::xrpl::directory::insertKey(ac.view(), sleDir, 0, false, indexes, key);
351
352 return true;
353 },
354 XRPAmount{},
355 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
358
360 {{"Loan Broker with zero OwnerCount has an unexpected entry in "
361 "the directory"}},
362 [&setupTest](Account const& a1, Account const& a2, ApplyContext& ac) {
363 auto test = setupTest(a1, a2, ac);
364 if (!test || !test->first || !test->second)
365 return false;
366
367 auto slePseudo = test->first;
368 auto sleDir = test->second;
369 // Empty vector will overwrite the existing entry for the
370 // holding, if any, avoiding the "has multiple indexes"
371 // failure.
372 STVector256 indexes;
373
375 ac.view(), sleDir, 0, false, indexes, slePseudo->key());
376
377 return true;
378 },
379 XRPAmount{},
380 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
383
385 {{"Loan Broker sequence number decreased"}},
386 [&](Account const& a1, Account const& a2, ApplyContext& ac) {
387 if (loanBrokerKeylet.type != ltLOAN_BROKER)
388 return false;
389 auto sleBroker = ac.view().peek(loanBrokerKeylet);
390 if (!sleBroker)
391 return false;
392 if (!BEAST_EXPECT(sleBroker->at(sfLoanSequence) > 0))
393 return false;
394 // Need to touch sleBroker so that it is included in the
395 // modified entries for the invariant to find
396 ac.view().update(sleBroker);
397
398 sleBroker->at(sfLoanSequence) -= 1;
399
400 return true;
401 },
402 XRPAmount{},
403 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
406
407 // Test: cover available less than pseudo-account asset balance
408 {
409 Keylet brokerKeylet = keylet::amendments();
410 Preclose const createBrokerWithCover =
411 [&, this](Account const& alice, Account const& issuer, Env& env) {
412 auto const asset = setupAsset(alice, issuer, env);
413 brokerKeylet = this->createLoanBroker(alice, env, asset);
414 if (!BEAST_EXPECT(env.le(brokerKeylet)))
415 return false;
416 env(loan_broker::coverDeposit(alice, brokerKeylet.key, asset(10)));
417 env.close();
418 return BEAST_EXPECT(env.le(brokerKeylet));
419 };
420
422 {{"Loan Broker cover available is less than pseudo-account asset balance"}},
423 [&](Account const&, Account const&, ApplyContext& ac) {
424 auto sle = ac.view().peek(brokerKeylet);
425 if (!BEAST_EXPECT(sle))
426 return false;
427 // Pseudo-account holds 10 units, set cover to 5
428 sle->at(sfCoverAvailable) = Number(5);
429 ac.view().update(sle);
430 return true;
431 },
432 XRPAmount{},
433 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
435 createBrokerWithCover);
436 }
437
438 // Test: cover available greater than pseudo-account asset balance
439 // (requires fixCleanup3_1_3)
441 {{"Loan Broker cover available is greater than pseudo-account asset balance"}},
442 [&](Account const&, Account const&, ApplyContext& ac) {
443 auto sle = ac.view().peek(loanBrokerKeylet);
444 if (!BEAST_EXPECT(sle))
445 return false;
446 // Pseudo-account has no cover deposited; set cover
447 // higher than any incidental balance
448 sle->at(sfCoverAvailable) = Number(1'000'000);
449 ac.view().update(sle);
450 return true;
451 },
452 XRPAmount{},
453 STTx{ttLOAN_BROKER_SET, [](STObject& tx) {}},
456
457 // Deleting the IOU holding while leaving the broker unchanged must
458 // still expose CoverAvailable exceeding the now-zero balance: the
459 // broker is discovered through the deleted trust line. XRP has no
460 // holding SLE, while deleting an MPToken triggers other invariants,
461 // so IOU isolates this check. Verify that fixCleanup3_1_3 gates it
462 // by expecting failure only when the amendment is enabled.
463 if (assetType == Asset::IOU)
464 {
465 Keylet brokerKeylet = keylet::amendments();
466 Preclose const createBrokerWithCover =
467 [&, this](Account const& alice, Account const& issuer, Env& env) {
468 auto const asset = setupAsset(alice, issuer, env);
469 brokerKeylet = this->createLoanBroker(alice, env, asset);
470 if (!BEAST_EXPECT(env.le(brokerKeylet)))
471 return false;
472 env(loan_broker::coverDeposit(alice, brokerKeylet.key, asset(10)));
473 env.close();
474 return BEAST_EXPECT(env.le(brokerKeylet));
475 };
476
477 Precheck const deleteHolding =
478 [&](Account const&, Account const&, ApplyContext& ac) {
479 if (brokerKeylet.type != ltLOAN_BROKER)
480 return false;
481 // Read (don't touch) the broker so it is only found via
482 // the deleted holding, not as a modified entry.
483 auto const sleBroker = ac.view().read(brokerKeylet);
484 if (!BEAST_EXPECT(sleBroker))
485 return false;
486 auto const pseudoAccountID = sleBroker->at(sfAccount);
487
488 // Erase every holding in the pseudo-account directory
489 // and the directory root itself, mirroring a bug that
490 // removed the cover holding without zeroing
491 // CoverAvailable. Removing the root also keeps the
492 // zero-OwnerCount directory check from firing first.
493 auto sleDir = ac.view().peek(keylet::ownerDir(pseudoAccountID));
494 if (!BEAST_EXPECT(sleDir))
495 return false;
496 for (auto const& index : sleDir->getFieldV256(sfIndexes))
497 {
498 if (auto holding = ac.view().peek(keylet::unchecked(index)))
499 {
500 ac.view().erase(holding);
501 }
502 }
503 ac.view().erase(sleDir);
504 return true;
505 };
506
507 // With fixCleanup3_1_3: the invariant fires.
509 makeEnv(all_),
510 {{"Loan Broker cover available is greater than pseudo-account asset balance"}},
511 deleteHolding,
512 XRPAmount{},
513 STTx{ttACCOUNT_SET, [](STObject&) {}},
515 createBrokerWithCover);
516
517 // Without fixCleanup3_1_3: the same state is silently accepted.
519 makeEnv(all_ - fixCleanup3_1_3),
520 {},
521 deleteHolding,
522 XRPAmount{},
523 STTx{ttACCOUNT_SET, [](STObject&) {}},
525 createBrokerWithCover);
526 }
527
528 // A LoanBroker may only be removed by ttLOAN_BROKER_DELETE. Erase
529 // the broker in the apply view under a non-delete tx type and
530 // expect the deletion-tx invariant to fire.
532 {{"Loan Broker deleted by a transaction other than LoanBrokerDelete"}},
533 [&](Account const&, Account const&, ApplyContext& ac) {
534 if (loanBrokerKeylet.type != ltLOAN_BROKER)
535 return false;
536 auto sleBroker = ac.view().peek(loanBrokerKeylet);
537 if (!BEAST_EXPECT(sleBroker))
538 return false;
539 ac.view().erase(sleBroker);
540 return true;
541 },
542 XRPAmount{},
543 STTx{ttACCOUNT_SET, [](STObject&) {}},
546 }
547
548 // A LoanBrokerDelete must not remove a broker whose pre-transaction
549 // DebtTotal is non-zero. visitEntry captures `before` from the parent
550 // view, so the DebtTotal must be seeded in the OpenView before the
551 // ApplyContext is constructed; a Precheck modification would only
552 // land in the applyView (visible as `after`) and would leave `before`
553 // at the createLoanBroker-produced zero.
554 {
555 Env env{*this};
556 Account const a1{"A1"};
557 Account const a2{"A2"};
558 env.fund(XRP(1000), a1, a2);
559 env.close();
560
561 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
562 auto const brokerKeylet = createLoanBroker(a1, env, xrpAsset);
563 if (!BEAST_EXPECT(env.le(brokerKeylet)))
564 return;
565 env.close();
566
567 OpenView ov{*env.current()};
568
569 // Seed a non-zero DebtTotal in the base view so `before` at
570 // visitEntry time reports it.
571 {
572 auto const sleBrokerRead = ov.read(brokerKeylet);
573 if (!BEAST_EXPECT(sleBrokerRead))
574 return;
575 auto sleBroker = std::make_shared<SLE>(*sleBrokerRead);
576 sleBroker->at(sfDebtTotal) = Number(1);
577 ov.rawReplace(sleBroker);
578 }
579
580 STTx const tx{ttLOAN_BROKER_DELETE, [](STObject&) {}};
582 beast::Journal const jlog{sink};
583 ApplyContext ac{
584 env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
585 CurrentTransactionRulesGuard const rulesGuard(ov.rules());
586
587 auto sleBroker = ac.view().peek(brokerKeylet);
588 if (!BEAST_EXPECT(sleBroker))
589 return;
590 ac.view().erase(sleBroker);
591
592 auto transactor = makeTransactor(ac);
593 if (!BEAST_EXPECT(transactor))
594 return;
595 TER const result = transactor->checkInvariants(
597 BEAST_EXPECT(result == tecINVARIANT_FAILED);
598 BEAST_EXPECT(
599 sink.messages().str().contains("Loan Broker deleted with non-zero debt total"));
600 }
601
602 // Residual DebtTotal dust that rounds to zero at the vault asset's
603 // scale must not trip the invariant: LoanBrokerDelete::preclaim
604 // deliberately permits it, so the invariant must not be stricter.
605 // Other invariants may still object to a hand-erased broker, so only
606 // the absence of the DebtTotal complaint is asserted.
607 {
608 Env env{*this};
609 Account const a1{"A1"};
610 Account const a2{"A2"};
611 env.fund(XRP(1000), a1, a2);
612 env.close();
613
614 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
615 auto const brokerKeylet = createLoanBroker(a1, env, xrpAsset);
616 if (!BEAST_EXPECT(env.le(brokerKeylet)))
617 return;
618 env.close();
619
620 OpenView ov{*env.current()};
621
622 // A thousandth of a drop: non-zero, but zero once quantized to XRP.
623 {
624 auto const sleBrokerRead = ov.read(brokerKeylet);
625 if (!BEAST_EXPECT(sleBrokerRead))
626 return;
627 auto sleBroker = std::make_shared<SLE>(*sleBrokerRead);
628 sleBroker->at(sfDebtTotal) = Number(1, -3);
629 ov.rawReplace(sleBroker);
630 }
631
632 STTx const tx{ttLOAN_BROKER_DELETE, [](STObject&) {}};
634 beast::Journal const jlog{sink};
635 ApplyContext ac{
636 env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
637 CurrentTransactionRulesGuard const rulesGuard(ov.rules());
638
639 auto sleBroker = ac.view().peek(brokerKeylet);
640 if (!BEAST_EXPECT(sleBroker))
641 return;
642 ac.view().erase(sleBroker);
643
644 auto transactor = makeTransactor(ac);
645 if (!BEAST_EXPECT(transactor))
646 return;
647 [[maybe_unused]] TER const result = transactor->checkInvariants(
649 BEAST_EXPECT(
650 !sink.messages().str().contains("Loan Broker deleted with non-zero debt total"));
651 }
652
653 // A LoanBrokerDelete must not remove a broker whose pre-transaction
654 // OwnerCount is non-zero. DebtTotal is left at zero so the earlier
655 // check passes and the OwnerCount check is what fires.
656 {
657 Env env{*this};
658 Account const a1{"A1"};
659 Account const a2{"A2"};
660 env.fund(XRP(1000), a1, a2);
661 env.close();
662
663 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
664 auto const brokerKeylet = createLoanBroker(a1, env, xrpAsset);
665 if (!BEAST_EXPECT(env.le(brokerKeylet)))
666 return;
667 env.close();
668
669 OpenView ov{*env.current()};
670
671 {
672 auto const sleBrokerRead = ov.read(brokerKeylet);
673 if (!BEAST_EXPECT(sleBrokerRead))
674 return;
675 auto sleBroker = std::make_shared<SLE>(*sleBrokerRead);
676 sleBroker->at(sfOwnerCount) = 1;
677 ov.rawReplace(sleBroker);
678 }
679
680 STTx const tx{ttLOAN_BROKER_DELETE, [](STObject&) {}};
682 beast::Journal const jlog{sink};
683 ApplyContext ac{
684 env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, jlog};
685 CurrentTransactionRulesGuard const rulesGuard(ov.rules());
686
687 auto sleBroker = ac.view().peek(brokerKeylet);
688 if (!BEAST_EXPECT(sleBroker))
689 return;
690 ac.view().erase(sleBroker);
691
692 auto transactor = makeTransactor(ac);
693 if (!BEAST_EXPECT(transactor))
694 return;
695 TER const result = transactor->checkInvariants(
697 BEAST_EXPECT(result == tecINVARIANT_FAILED);
698 BEAST_EXPECT(
699 sink.messages().str().contains("Loan Broker deleted with non-zero owner count"));
700 }
701
702 // Only one LoanBroker may be deleted per transaction. Create two
703 // brokers under different owners, then erase both in the apply view
704 // and expect the multi-deletion invariant to fire.
705 {
706 Keylet loanBrokerKeylet1 = keylet::amendments();
707 Keylet loanBrokerKeylet2 = keylet::amendments();
708 Preclose const createTwoBrokers = [&, this](
709 Account const& a1, Account const& a2, Env& env) {
710 PrettyAsset const xrpAsset{xrpIssue(), 1'000'000};
711 loanBrokerKeylet1 = this->createLoanBroker(a1, env, xrpAsset);
712 loanBrokerKeylet2 = this->createLoanBroker(a2, env, xrpAsset);
713 return BEAST_EXPECT(env.le(loanBrokerKeylet1) && env.le(loanBrokerKeylet2));
714 };
715
717 {{"more than one Loan Broker deleted in a single transaction"}},
718 [&](Account const&, Account const&, ApplyContext& ac) {
719 auto sle1 = ac.view().peek(loanBrokerKeylet1);
720 auto sle2 = ac.view().peek(loanBrokerKeylet2);
721 if (!BEAST_EXPECT(sle1 && sle2))
722 return false;
723 ac.view().erase(sle1);
724 ac.view().erase(sle2);
725 return true;
726 },
727 XRPAmount{},
728 STTx{ttLOAN_BROKER_DELETE, [](STObject&) {}},
730 createTwoBrokers);
731 }
732 }
733
734 void
735 run() override
736 {
739 }
740};
741
742BEAST_DEFINE_TESTSUITE(InvariantsPseudoAccount, app, xrpl);
743
744} // namespace xrpl::test
A generic endpoint for log messages.
Definition Journal.h:44
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
State information when applying a tx.
ApplyView & view()
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(SLE::Ref sle)=0
Remove a peeked SLE.
RAII class to set and restore the current transaction rules.
Definition Rules.h:113
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
Writable ledger view that accumulates state and tx changes.
Definition OpenView.h:59
void rawReplace(SLE::Ref sle) override
Unconditionally replace a state item.
Definition OpenView.cpp:244
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Definition OpenView.cpp:168
Rules const & rules() const override
Returns the tx processing rules.
Definition OpenView.cpp:149
std::shared_ptr< STLedgerEntry > pointer
Keylet createLoanBroker(jtx::Account const &a, jtx::Env &env, jtx::PrettyAsset const &asset)
std::function< bool(test::jtx::Account const &a, test::jtx::Account const &b, ApplyContext &ac)> Precheck
test::jtx::Env makeEnv(FeatureBitset features)
void doInvariantCheck(std::vector< std::string > const &expectLogs, Precheck const &precheck, XRPAmount fee=XRPAmount{}, STTx tx=STTx{ttACCOUNT_SET, [](STObject &) {}}, std::initializer_list< TER > ters={tecINVARIANT_FAILED, tefINVARIANT_FAILED}, Preclose const &preclose={}, TxAccount setTxAccount=TxAccount::None, std::source_location const &loc=std::source_location::current(), TER initialResult=tesSUCCESS)
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
std::stringstream const & messages() const
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Converts to IOU Issue or STAmount.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:512
MPTID const & issuanceID() const
Definition mpt.h:768
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:343
void authorize(MPTAuthorize const &arg=MPTAuthorize{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:455
Converts to MPT Issue or STAmount.
T make_pair(T... args)
T make_shared(T... args)
std::optional< std::uint64_t > insertPage(ApplyView &view, std::uint64_t page, SLE::pointer node, std::uint64_t nextPage, SLE::Ref next, UInt256 const &key, Keylet const &directory, std::function< void(SLE::Ref)> const &describe)
std::uint64_t insertKey(ApplyView &view, SLE::Ref node, std::uint64_t page, bool preserveOrder, STVector256 &indexes, UInt256 const &key)
Definition ApplyView.cpp:70
std::uint64_t createRoot(ApplyView &view, Keylet const &directory, UInt256 const &key, std::function< void(SLE::Ref)> const &describe)
Helper functions for managing low-level directory operations.
Definition ApplyView.cpp:30
Keylet const & amendments() noexcept
The index of the amendment table.
Definition Indexes.cpp:248
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:403
Keylet unchecked(UInt256 const &key) noexcept
Any ledger entry.
Definition Indexes.cpp:397
Keylet page(UInt256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
Definition Indexes.cpp:409
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
json::Value coverDeposit(AccountID const &account, UInt256 const &brokerID, STAmount const &amount, uint32_t flags)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:92
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
static MPTInit const kMptInitNoFund
Definition mpt.h:201
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Definition TxTest.h:54
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
std::vector< SField const * > const & getPseudoAccountFields()
Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if set.
@ tefINVARIANT_FAILED
Definition TER.h:178
std::unique_ptr< Transactor > makeTransactor(ApplyContext &ctx)
@ TapNone
Definition ApplyView.h:28
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecINVARIANT_FAILED
Definition TER.h:321
std::function< void(SLE::Ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
@ tesSUCCESS
Definition TER.h:250
T str(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
UInt256 key
Definition Keylet.h:21
LedgerEntryType type
Definition Keylet.h:22