1#include <test/app/invariants/InvariantsBase.h>
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/permissioned_domains.h>
6#include <test/jtx/vault.h>
7#include <test/unit_test/SuiteJournal.h>
9#include <xrpl/basics/Slice.h>
10#include <xrpl/basics/base_uint.h>
11#include <xrpl/beast/unit_test/suite.h>
12#include <xrpl/beast/utility/Journal.h>
13#include <xrpl/ledger/ApplyView.h>
14#include <xrpl/ledger/OpenView.h>
15#include <xrpl/protocol/Book.h>
16#include <xrpl/protocol/Feature.h>
17#include <xrpl/protocol/Indexes.h>
18#include <xrpl/protocol/Issue.h>
19#include <xrpl/protocol/Keylet.h>
20#include <xrpl/protocol/LedgerFormats.h>
21#include <xrpl/protocol/Protocol.h>
22#include <xrpl/protocol/Rules.h>
23#include <xrpl/protocol/SField.h>
24#include <xrpl/protocol/STAmount.h>
25#include <xrpl/protocol/STArray.h>
26#include <xrpl/protocol/STLedgerEntry.h>
27#include <xrpl/protocol/STObject.h>
28#include <xrpl/protocol/STTx.h>
29#include <xrpl/protocol/SeqProxy.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/TxFormats.h>
32#include <xrpl/protocol/XRPAmount.h>
33#include <xrpl/tx/ApplyContext.h>
34#include <xrpl/tx/applySteps.h>
35#include <xrpl/tx/invariants/DirectoryInvariant.h>
36#include <xrpl/tx/invariants/PermissionedDEXInvariant.h>
59 bool const fixEnabled = features[fixCleanup3_1_3];
67 {{
"permissioned domain with no rules."}},
73 fixEnabled ? failTers : badTers);
80 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
86 fixEnabled ? failTers : badTers);
91 {{
"permissioned domain credentials aren't sorted"}},
99 cred.setAccountID(sfIssuer, a2);
101 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
104 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
105 ac.view().update(slePd);
110 fixEnabled ? failTers : badTers);
115 {{
"permissioned domain credentials aren't unique"}},
123 cred.setAccountID(sfIssuer, a2);
124 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
127 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
128 ac.view().update(slePd);
133 fixEnabled ? failTers : badTers);
135 testcase <<
"PermissionedDomain Set 1";
138 {{
"permissioned domain with no rules."}},
146 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
147 ac.view().update(slePd);
154 fixEnabled ? failTers : badTers);
156 testcase <<
"PermissionedDomain Set 2";
159 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
171 cred.setAccountID(sfIssuer, a2);
173 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
177 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
178 ac.view().update(slePd);
185 fixEnabled ? failTers : badTers);
187 testcase <<
"PermissionedDomain Set 3";
190 {{
"permissioned domain credentials aren't sorted"}},
201 cred.setAccountID(sfIssuer, a2);
203 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
207 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
208 ac.view().update(slePd);
215 fixEnabled ? failTers : badTers);
217 testcase <<
"PermissionedDomain Set 4";
220 {{
"permissioned domain credentials aren't unique"}},
231 cred.setAccountID(sfIssuer, a2);
232 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
235 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
236 ac.view().update(slePd);
243 fixEnabled ? failTers : badTers);
248 {
"transaction affected more than 1 permissioned domain entry."}};
252 {
"domain object modified, but not deleted by "}};
255 {
"domain object(s) affected by an unauthorized transaction."}};
258 testcase <<
"PermissionedDomain set 2 domains ";
261 fixEnabled ? badMoreThan1 : emptyV,
269 fixEnabled ? failTers : goodTers);
273 testcase <<
"PermissionedDomain del 2 domains";
275 Env env1(*
this, features);
290 fixEnabled ? badMoreThan1 : emptyV,
292 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
293 auto sle2 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd2});
300 fixEnabled ? failTers : goodTers);
304 testcase <<
"PermissionedDomain set 0 domains ";
307 fixEnabled ? badNoDomains : emptyV,
311 fixEnabled ? badTers : goodTers);
315 testcase <<
"PermissionedDomain del 0 domains";
317 Env env1(*
this, features);
332 fixEnabled ? badNoDomains : emptyV,
336 fixEnabled ? badTers : goodTers);
340 testcase <<
"PermissionedDomain set, delete domain";
342 Env env1(*
this, features);
356 fixEnabled ? badDeleted : emptyV,
358 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
364 fixEnabled ? failTers : goodTers);
368 testcase <<
"PermissionedDomain del, create domain ";
371 fixEnabled ? badNotDeleted : emptyV,
378 fixEnabled ? failTers : goodTers);
382 testcase <<
"PermissionedDomain invalid tx";
385 fixEnabled ? badTx : emptyV,
401 bool const fixEnabled = features[fixCleanup3_1_3];
407 {{
"domain doesn't exist"}},
411 sleOffer->setAccountID(sfAccount, a1);
412 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
413 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
414 ac.view().insert(sleOffer);
423 UInt256{
"F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
426 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
427 tx.setFieldAmount(sfTakerGets,
XRP(1));
434 {{
"hybrid offer is malformed"}},
438 sleOffer->setAccountID(sfAccount, a2);
439 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
440 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
441 sleOffer->setFlag(lsfHybrid);
445 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
446 ac.view().insert(sleOffer);
455 Env env1(*
this, features);
469 {{
"hybrid offer is malformed"}},
473 sleOffer->setAccountID(sfAccount, a2);
474 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
475 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
476 sleOffer->setFlag(lsfHybrid);
477 sleOffer->setFieldH256(sfDomainID, pd1);
482 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
483 ac.view().insert(sleOffer);
493 Env env1(*
this, features);
512 sleOffer->setAccountID(sfAccount, a2);
513 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
514 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
515 sleOffer->setFlag(lsfHybrid);
516 sleOffer->setFieldH256(sfDomainID, pd1);
519 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
520 ac.view().insert(sleOffer);
531 Env env1(*
this, features);
545 {{
"hybrid offer is malformed"}},
549 sleOffer->setAccountID(sfAccount, a2);
550 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
551 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
552 sleOffer->setFlag(lsfHybrid);
553 sleOffer->setFieldH256(sfDomainID, pd1);
554 ac.view().insert(sleOffer);
563 Env env1(*
this, features);
578 {{
"transaction consumed wrong domains"}},
582 sleOffer->setAccountID(sfAccount, a2);
583 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
584 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
585 sleOffer->setFieldH256(sfDomainID, pd1);
586 ac.view().insert(sleOffer);
593 tx.setFieldH256(sfDomainID, pd2);
594 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
595 tx.setFieldAmount(sfTakerGets,
XRP(1));
601 Env env1(*
this, features);
615 {{
"domain transaction affected regular offers"}},
619 sleOffer->setAccountID(sfAccount, a2);
620 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
621 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
622 ac.view().insert(sleOffer);
630 tx.setFieldH256(sfDomainID, pd1);
631 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
632 tx.setFieldAmount(sfTakerGets,
XRP(1));
643 testcase <<
"PermissionedDEX null after";
649 auto const check = [
this](
651 bool const afterIsNull,
653 bool const expectInvariantFailure) {
654 Env env(*
this, features);
667 sleOffer->setAccountID(sfAccount, a2);
668 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
669 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
670 sleOffer->setFieldH256(sfDomainID, pd1);
678 invariant.
visitEntry(isDelete, sleOffer,
nullptr);
683 invariant.
visitEntry(isDelete,
nullptr, sleOffer);
686 STTx const tx{ttOFFER_CREATE, [&pd2, &a1](
STObject& tx) {
696 BEAST_EXPECT(passed != expectInvariantFailure);
697 if (expectInvariantFailure)
699 BEAST_EXPECT(sink.
messages().
str().contains(
"transaction consumed wrong domains"));
707 auto const pre =
all_ - fixCleanup3_4_0;
708 auto const post =
all_;
711 check(pre,
true,
true,
false);
712 check(post,
true,
true,
false);
716 check(pre,
false,
true,
true);
718 check(post,
false,
true,
false);
719 check(post,
false,
false,
true);
726 testcase <<
"book directory exchange rate";
729 Book const book{
xrpIssue(), account[
"USD"], std::nullopt};
737 sleDir->setFieldH256(sfRootIndex, dir.
key);
740 sleDir->setFieldV256(sfIndexes, indexes);
741 sleDir->setFieldU64(sfExchangeRate, exchangeRate);
747 auto const makeChildPage = [](
Keylet const& rootDir) {
749 sleDir->setFieldH256(sfRootIndex, rootDir.key);
752 sleDir->setFieldV256(sfIndexes, indexes);
756 auto const makeOfferCreateTx = [] {
759 tx.setFieldAmount(sfTakerPays,
XRP(1));
760 tx.setFieldAmount(sfTakerGets, account[
"USD"](1));
768 {{
"book directory exchange rate does not match directory quality"}},
771 auto const dir = getBookRootKey(a1, directoryQuality);
772 ac.view().insert(makeRootPage(dir, directoryQuality + 1));
781 {{
"book directory root missing"}},
784 auto const rootDir = getBookRootKey(a1, directoryQuality);
787 ac.view().insert(makeChildPage(rootDir));
809 auto const rootDir = getBookRootKey(a1, directoryQuality);
810 view.
rawInsert(makeRootPage(rootDir, directoryQuality + 1));
813 invariant.
visitEntry(
false,
nullptr, makeChildPage(rootDir));
832 auto const rootDir = getBookRootKey(a1, directoryQuality);
833 auto const missingRootDir = getBookRootKey(a1, directoryQuality + 1);
834 auto const badRoot = makeRootPage(rootDir, directoryQuality + 1);
843 invariant.
visitEntry(
false,
nullptr, badRoot);
851 invariant.
visitEntry(
false, badRoot, badRoot);
858 auto const childBefore = makeChildPage(rootDir);
860 childAfter->setFieldH256(sfRootIndex, missingRootDir.key);
863 invariant.
visitEntry(
false, childBefore, childAfter);
870 missingRootSink.
messages().
str().contains(
"book directory root missing"));
875 BEAST_EXPECT(!view.
exists(rootDir));
896 sle->setAccountID(sfOwner, a1);
897 sle->setFieldU32(sfSequence, seq);
907 cred.setAccountID(sfIssuer, a2);
909 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
912 sle->setFieldArray(sfAcceptedCredentials,
credentials);
933 credentials.push_back({.issuer = a2, .credType = credType});
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 insert(SLE::Ref sle)=0
Insert a new state SLE.
virtual void erase(SLE::Ref sle)=0
Remove a peeked SLE.
RAII class to set and restore the current transaction rules.
Writable ledger view that accumulates state and tx changes.
void rawInsert(SLE::Ref sle) override
Unconditionally insert a state item.
bool exists(Keylet const &k) const override
Determine if a state item exists.
void rawErase(SLE::Ref sle) override
Delete an existing state item.
static std::uint64_t const kURateOne
void pushBack(STObject const &object)
std::shared_ptr< STLedgerEntry > pointer
void setFieldAmount(SField const &field, STAmount const &)
static STObject makeInnerObject(SField const &name)
void setFieldH256(SField const &field, UInt256 const &)
void pushBack(UInt256 const &v)
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
An immutable linear range of bytes.
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, std::shared_ptr< SLE const > const &, std::shared_ptr< SLE const > const &)
bool finalize(STTx const &, TER const, XRPAmount const, ReadView const &, beast::Journal const &)
void visitEntry(bool, SLE::ConstRef, SLE::ConstRef)
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.
void run() override
Runs the suite.
static std::pair< std::uint32_t, UInt256 > createPermissionedDomainEnv(test::jtx::Env &env, test::jtx::Account const &a1, test::jtx::Account const &a2, std::uint32_t numCreds=2)
void testBookDirectoryExchangeRate()
void testPermissionedDEXDeletedOfferFallback()
static SLE::pointer createPermissionedDomain(ApplyContext &ac, test::jtx::Account const &a1, test::jtx::Account const &a2, std::uint32_t numCreds=2, std::uint32_t seq=10)
void testPermissionedDomainInvariants(FeatureBitset features)
void testPermissionedDEX(FeatureBitset features)
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.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Keylet quality(Keylet const &k, std::uint64_t const q) noexcept
The initial directory page for a specific quality.
Keylet offer(AccountID const &id, SeqProxy const &seq) noexcept
An offer from an account.
Keylet book(Book const &b)
The beginning of an order book.
Keylet permissionedDomain(AccountID const &account, SeqProxy const &seq) noexcept
Keylet page(UInt256 const &root, std::uint64_t const index=0) noexcept
A page in a directory.
std::vector< Credential > Credentials
UInt256 getNewDomain(std::shared_ptr< STObject const > const &meta)
json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< UInt256 > domain)
XrpT const XRP
Converts to XRP Issue or STAmount.
FeatureBitset testableAmendments()
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.
constexpr std::size_t kMaxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
A pair of SHAMap key and LedgerEntryType.