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>
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>
66 Vault const vault{env};
67 auto [tx, vKeylet] = vault.create({.owner = a, .asset = xrpAsset});
70 if (
auto const vSle = env.le(vKeylet); BEAST_EXPECT(vSle))
72 pseudoAccountID = vSle->at(sfAccount);
91 auto const mods = std::to_array<Mod>({
93 .expectedFailure =
"pseudo-account has 0 pseudo-account fields set",
96 BEAST_EXPECT(sle->at(~sfVaultID));
97 sle->at(~sfVaultID) = std::nullopt;
101 .expectedFailure =
"pseudo-account sequence changed",
102 .func = [](
SLE::pointer& sle) { sle->at(sfSequence) = 12345; },
105 .expectedFailure =
"pseudo-account flags are not set",
106 .func = [](
SLE::pointer& sle) { sle->at(sfFlags) = lsfNoFreeze; },
109 .expectedFailure =
"pseudo-account has a regular key",
113 .expectedFailure =
"pseudo-account has a sponsorship field",
114 .func = [](
SLE::pointer& sle) { sle->at(sfSponsoredOwnerCount) = 1; },
117 .expectedFailure =
"pseudo-account has a sponsorship field",
118 .func = [](
SLE::pointer& sle) { sle->at(sfSponsoringOwnerCount) = 1; },
121 .expectedFailure =
"pseudo-account has a sponsorship field",
122 .func = [](
SLE::pointer& sle) { sle->at(sfSponsoringAccountCount) = 1; },
125 .expectedFailure =
"pseudo-account has a sponsorship field",
130 for (
auto const& mod : mods)
133 {{mod.expectedFailure}},
139 ac.view().update(sle);
151 if (pField == &sfVaultID)
154 {{
"pseudo-account has 2 pseudo-account fields set"}},
160 auto const vaultID = ~sle->at(~sfVaultID);
161 BEAST_EXPECT(vaultID && !sle->isFieldPresent(*pField));
162 sle->setFieldH256(*pField, *vaultID);
164 ac.view().update(sle);
176 {{
"pseudo-account has 0 pseudo-account fields set"},
177 {
"pseudo-account sequence changed"},
178 {
"pseudo-account flags are not set"}},
183 sle->at(sfSequence) = 0;
184 ac.view().update(sle);
197 auto const assetTypes = std::to_array({Asset::XRP, Asset::IOU, Asset::MPT});
199 for (
auto const assetType : assetTypes)
203 auto const setupAsset =
209 env(
trust(alice, iouAsset(1000)));
210 env(
pay(issuer, alice, iouAsset(1000)));
216 mptt.
create({.flags = tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
219 env(
pay(issuer, alice, mptAsset(1000)));
232 auto const asset = setupAsset(alice, issuer, env);
234 return BEAST_EXPECT(env.le(loanBrokerKeylet));
242 if (loanBrokerKeylet.
type != ltLOAN_BROKER)
244 auto sleBroker = ac.view().peek(loanBrokerKeylet);
247 if (!BEAST_EXPECT(sleBroker->at(sfOwnerCount) == 0))
251 ac.view().update(sleBroker);
254 auto const pseudoAccountID = sleBroker->at(sfAccount);
258 auto slePseudo = ac.view().peek(pseudoAccountKeylet);
259 if (!BEAST_EXPECT(slePseudo))
263 auto sleDir = ac.view().peek(dirKeylet);
270 ac.view(), dirKeylet, loanBrokerKeylet.
key, describe) == 0);
272 sleDir = ac.view().peek(dirKeylet);
279 {{
"Loan Broker with zero OwnerCount has multiple directory "
282 auto test = setupTest(a1, a2, ac);
286 auto slePseudo =
test->first;
287 auto sleDir =
test->second;
309 {{
"Loan Broker with zero OwnerCount has multiple indexes in "
310 "the Directory root"}},
312 auto test = setupTest(a1, a2, ac);
316 auto slePseudo =
test->first;
317 auto sleDir =
test->second;
318 auto indexes = sleDir->getFieldV256(sfIndexes);
321 for (
auto const& key : {slePseudo->key(), sleDir->key()})
334 {{
"Loan Broker directory corrupt"}},
336 auto test = setupTest(a1, a2, ac);
340 auto slePseudo =
test->first;
341 auto sleDir =
test->second;
360 {{
"Loan Broker with zero OwnerCount has an unexpected entry in "
363 auto test = setupTest(a1, a2, ac);
367 auto slePseudo =
test->first;
368 auto sleDir =
test->second;
375 ac.view(), sleDir, 0,
false, indexes, slePseudo->key());
385 {{
"Loan Broker sequence number decreased"}},
387 if (loanBrokerKeylet.
type != ltLOAN_BROKER)
389 auto sleBroker = ac.view().peek(loanBrokerKeylet);
392 if (!BEAST_EXPECT(sleBroker->at(sfLoanSequence) > 0))
396 ac.view().update(sleBroker);
398 sleBroker->at(sfLoanSequence) -= 1;
410 Preclose const createBrokerWithCover =
412 auto const asset = setupAsset(alice, issuer, env);
414 if (!BEAST_EXPECT(env.le(brokerKeylet)))
418 return BEAST_EXPECT(env.le(brokerKeylet));
422 {{
"Loan Broker cover available is less than pseudo-account asset balance"}},
424 auto sle = ac.view().peek(brokerKeylet);
425 if (!BEAST_EXPECT(sle))
428 sle->at(sfCoverAvailable) =
Number(5);
429 ac.view().update(sle);
435 createBrokerWithCover);
441 {{
"Loan Broker cover available is greater than pseudo-account asset balance"}},
443 auto sle = ac.view().peek(loanBrokerKeylet);
444 if (!BEAST_EXPECT(sle))
448 sle->at(sfCoverAvailable) =
Number(1'000'000);
449 ac.view().update(sle);
463 if (assetType == Asset::IOU)
466 Preclose const createBrokerWithCover =
468 auto const asset = setupAsset(alice, issuer, env);
470 if (!BEAST_EXPECT(env.le(brokerKeylet)))
474 return BEAST_EXPECT(env.le(brokerKeylet));
479 if (brokerKeylet.
type != ltLOAN_BROKER)
483 auto const sleBroker = ac.view().read(brokerKeylet);
484 if (!BEAST_EXPECT(sleBroker))
486 auto const pseudoAccountID = sleBroker->at(sfAccount);
494 if (!BEAST_EXPECT(sleDir))
496 for (
auto const& index : sleDir->getFieldV256(sfIndexes))
500 ac.view().erase(holding);
503 ac.view().erase(sleDir);
510 {{
"Loan Broker cover available is greater than pseudo-account asset balance"}},
515 createBrokerWithCover);
525 createBrokerWithCover);
532 {{
"Loan Broker deleted by a transaction other than LoanBrokerDelete"}},
534 if (loanBrokerKeylet.
type != ltLOAN_BROKER)
536 auto sleBroker = ac.view().peek(loanBrokerKeylet);
537 if (!BEAST_EXPECT(sleBroker))
539 ac.view().erase(sleBroker);
563 if (!BEAST_EXPECT(env.
le(brokerKeylet)))
572 auto const sleBrokerRead = ov.
read(brokerKeylet);
573 if (!BEAST_EXPECT(sleBrokerRead))
576 sleBroker->at(sfDebtTotal) =
Number(1);
587 auto sleBroker = ac.
view().
peek(brokerKeylet);
588 if (!BEAST_EXPECT(sleBroker))
593 if (!BEAST_EXPECT(transactor))
595 TER const result = transactor->checkInvariants(
599 sink.
messages().
str().contains(
"Loan Broker deleted with non-zero debt total"));
616 if (!BEAST_EXPECT(env.
le(brokerKeylet)))
624 auto const sleBrokerRead = ov.
read(brokerKeylet);
625 if (!BEAST_EXPECT(sleBrokerRead))
628 sleBroker->at(sfDebtTotal) =
Number(1, -3);
639 auto sleBroker = ac.
view().
peek(brokerKeylet);
640 if (!BEAST_EXPECT(sleBroker))
645 if (!BEAST_EXPECT(transactor))
647 [[maybe_unused]]
TER const result = transactor->checkInvariants(
650 !sink.
messages().
str().contains(
"Loan Broker deleted with non-zero debt total"));
665 if (!BEAST_EXPECT(env.
le(brokerKeylet)))
672 auto const sleBrokerRead = ov.
read(brokerKeylet);
673 if (!BEAST_EXPECT(sleBrokerRead))
676 sleBroker->at(sfOwnerCount) = 1;
687 auto sleBroker = ac.
view().
peek(brokerKeylet);
688 if (!BEAST_EXPECT(sleBroker))
693 if (!BEAST_EXPECT(transactor))
695 TER const result = transactor->checkInvariants(
699 sink.
messages().
str().contains(
"Loan Broker deleted with non-zero owner count"));
708 Preclose const createTwoBrokers = [&,
this](
713 return BEAST_EXPECT(env.le(loanBrokerKeylet1) && env.le(loanBrokerKeylet2));
717 {{
"more than one Loan Broker deleted in a single transaction"}},
719 auto sle1 = ac.view().peek(loanBrokerKeylet1);
720 auto sle2 = ac.view().peek(loanBrokerKeylet2);
721 if (!BEAST_EXPECT(sle1 && sle2))
723 ac.view().erase(sle1);
724 ac.view().erase(sle2);
A generic endpoint for log messages.
TestcaseT testcase
Memberspace for declaring test cases.
State information when applying a tx.
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.
Number is a floating point type that can represent a wide range of values.
Writable ledger view that accumulates state and tx changes.
void rawReplace(SLE::Ref sle) override
Unconditionally replace a state item.
SLE::const_pointer read(Keylet const &k) const override
Return the state item associated with a key.
Rules const & rules() const override
Returns the tx processing rules.
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
void run() override
Runs the suite.
void testValidPseudoAccounts()
void testValidLoanBroker()
std::stringstream const & messages() const
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
SLE::const_pointer le(Account const &account) const
Return an account root.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Converts to IOU Issue or STAmount.
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
MPTID const & issuanceID() const
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
void authorize(MPTAuthorize const &arg=MPTAuthorize{}, std::source_location const &loc=std::source_location::current())
Converts to MPT Issue or STAmount.
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)
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.
Keylet const & amendments() noexcept
The index of the amendment table.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet unchecked(UInt256 const &key) noexcept
Any ledger entry.
Keylet page(UInt256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
Keylet account(AccountID const &id) noexcept
AccountID root.
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.
XrpT const XRP
Converts to XRP Issue or STAmount.
FeatureBitset testableAmendments()
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
static MPTInit const kMptInitNoFund
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
constexpr XRPAmount
Convert XRP to drops (integral types).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
std::vector< SField const * > const & getPseudoAccountFields()
Returns the list of fields that define an ACCOUNT_ROOT as a pseudo-account if set.
std::unique_ptr< Transactor > makeTransactor(ApplyContext &ctx)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
TERSubset< CanCvtToTER > TER
std::function< void(SLE::Ref)> describeOwnerDir(AccountID const &account)
Returns a function that sets the owner on a directory SLE.
A pair of SHAMap key and LedgerEntryType.