21#include <test/jtx/AMM.h>
22#include <test/jtx/Env.h>
23#include <xrpld/app/tx/apply.h>
24#include <xrpld/app/tx/detail/ApplyContext.h>
25#include <xrpld/app/tx/detail/Transactor.h>
26#include <xrpl/beast/utility/Journal.h>
27#include <xrpl/protocol/InnerObjectFormats.h>
28#include <xrpl/protocol/STLedgerEntry.h>
30#include <boost/algorithm/string/predicate.hpp>
79 using namespace test::jtx;
84 Account
const A1{
"A1"};
85 Account
const A2{
"A2"};
86 env.fund(
XRP(1000), A1, A2);
88 BEAST_EXPECT(preclose(A1, A2, env));
91 OpenView ov{*env.current()};
99 env.current()->fees().base,
103 BEAST_EXPECT(precheck(A1, A2, ac));
106 if (!BEAST_EXPECT(ters.
size() == 2))
110 for (
TER const& terExpect : ters)
112 terActual = ac.checkInvariants(terActual, fee);
113 BEAST_EXPECT(terExpect == terActual);
115 sink.messages().str().starts_with(
"Invariant failed:") ||
116 sink.messages().str().starts_with(
117 "Transaction caused an exception"));
120 for (
auto const& m : expect_logs)
123 sink.messages().str().find(m) != std::string::npos);
131 using namespace test::jtx;
134 {{
"XRP net change was positive: 500"}},
135 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
140 auto amt = sle->getFieldAmount(sfBalance);
141 sle->setFieldAmount(sfBalance, amt +
STAmount{500});
142 ac.view().update(sle);
150 using namespace test::jtx;
155 {{
"an account root was deleted"}},
156 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
161 ac.view().erase(sle);
171 {{
"account deletion succeeded without deleting an account"}},
181 {{
"account deletion succeeded but deleted multiple accounts"}},
182 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
186 if (!sleA1 || !sleA2)
188 ac.view().erase(sleA1);
189 ac.view().erase(sleA2);
199 using namespace test::jtx;
200 testcase <<
"account root deletion left artifact";
208 if (!keyletInfo.includeInTests)
210 auto const& keyletfunc = keyletInfo.function;
211 auto const& type = keyletInfo.expectedLEName;
213 using namespace std::string_literals;
216 {{
"account deletion left behind a "s + type.c_str() +
218 [&](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
221 auto const a1 = A1.id();
227 auto const newSLE = std::make_shared<SLE>(key);
228 ac.view().insert(newSLE);
229 ac.view().erase(sleA1);
239 {{
"account deletion left behind a NFTokenPage object"}},
240 [&](Account
const& A1, Account
const&,
ApplyContext& ac) {
245 ac.view().erase(sle);
251 [&](Account
const& A1, Account
const&, Env& env) {
254 env(token::mint(A1));
264 {{
"account deletion left behind a DirectoryNode object"}},
265 [&](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
272 BEAST_EXPECT(sle->at(~sfAMMID));
273 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
275 ac.view().erase(sle);
282 [&](Account
const& A1, Account
const& A2, Env& env) {
285 AMM const amm(env, A1, XRP(100), A1[
"USD"](50));
286 ammAcctID = amm.ammAccount();
287 ammKey = amm.ammID();
288 ammIssue = amm.lptIssue();
292 {{
"account deletion left behind a AMM object"}},
293 [&](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
301 BEAST_EXPECT(sle->at(~sfAMMID));
302 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
304 for (
auto const& trustKeylet :
308 if (
auto const line = ac.view().peek(trustKeylet); !line)
314 STAmount const lowLimit = line->at(sfLowLimit);
315 STAmount const highLimit = line->at(sfHighLimit);
326 auto const ammSle = ac.view().peek(
keylet::amm(ammKey));
327 if (!BEAST_EXPECT(ammSle))
331 BEAST_EXPECT(ac.view().dirRemove(
332 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey,
false));
334 !ac.view().exists(ownerDirKeylet) ||
335 ac.view().emptyDirDelete(ownerDirKeylet));
337 ac.view().erase(sle);
344 [&](Account
const& A1, Account
const& A2, Env& env) {
347 AMM const amm(env, A1, XRP(100), A1[
"USD"](50));
348 ammAcctID = amm.ammAccount();
349 ammKey = amm.ammID();
350 ammIssue = amm.lptIssue();
358 using namespace test::jtx;
359 testcase <<
"ledger entry types don't match";
361 {{
"ledger entry type mismatch"},
362 {
"XRP net change of -1000000000 doesn't match fee 0"}},
363 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
368 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
369 ac.rawView().rawReplace(sleNew);
374 {{
"invalid ledger entry type added"}},
375 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
384 auto const sleNew = std::make_shared<SLE>(
390 ac.view().insert(sleNew);
398 using namespace test::jtx;
399 testcase <<
"trust lines with XRP not allowed";
401 {{
"an XRP trust line was created"}},
402 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
404 auto const sleNew = std::make_shared<SLE>(
406 ac.view().insert(sleNew);
414 using namespace test::jtx;
418 {{
"Cannot return non-native STAmount as XRPAmount"}},
419 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
424 STAmount const nonNative(A2[
"USD"](51));
425 sle->setFieldAmount(sfBalance, nonNative);
426 ac.view().update(sle);
431 {{
"incorrect account XRP balance"},
432 {
"XRP net change was positive: 99999999000000001"}},
433 [
this](Account
const& A1, Account
const&,
ApplyContext& ac) {
440 sle->setFieldAmount(sfBalance,
INITIAL_XRP + drops(1));
441 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
442 ac.view().update(sle);
447 {{
"incorrect account XRP balance"},
448 {
"XRP net change of -1000000001 doesn't match fee 0"}},
449 [
this](Account
const& A1, Account
const&,
ApplyContext& ac) {
454 sle->setFieldAmount(sfBalance,
STAmount{1,
true});
455 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
456 ac.view().update(sle);
464 using namespace test::jtx;
465 using namespace std::string_literals;
466 testcase <<
"Transaction fee checks";
469 {{
"fee paid was negative: -1"},
470 {
"XRP net change of 0 doesn't match fee -1"}},
471 [](Account
const&, Account
const&,
ApplyContext&) {
return true; },
476 {
"XRP net change of 0 doesn't match fee "s +
478 [](Account
const&, Account
const&,
ApplyContext&) {
return true; },
482 {{
"fee paid is 20 exceeds fee specified in transaction."},
483 {
"XRP net change of 0 doesn't match fee 20"}},
484 [](Account
const&, Account
const&,
ApplyContext&) {
return true; },
494 using namespace test::jtx;
498 {{
"offer with a bad amount"}},
499 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
504 auto sleNew = std::make_shared<SLE>(
506 sleNew->setAccountID(sfAccount, A1.id());
507 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
508 sleNew->setFieldAmount(sfTakerPays, XRP(-1));
509 ac.view().insert(sleNew);
514 {{
"offer with a bad amount"}},
515 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
520 auto sleNew = std::make_shared<SLE>(
522 sleNew->setAccountID(sfAccount, A1.id());
523 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
524 sleNew->setFieldAmount(sfTakerPays, A1[
"USD"](10));
525 sleNew->setFieldAmount(sfTakerGets, XRP(-1));
526 ac.view().insert(sleNew);
531 {{
"offer with a bad amount"}},
532 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
537 auto sleNew = std::make_shared<SLE>(
539 sleNew->setAccountID(sfAccount, A1.id());
540 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
541 sleNew->setFieldAmount(sfTakerPays, XRP(10));
542 sleNew->setFieldAmount(sfTakerGets, XRP(11));
543 ac.view().insert(sleNew);
551 using namespace test::jtx;
555 {{
"Cannot return non-native STAmount as XRPAmount"}},
556 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
561 auto sleNew = std::make_shared<SLE>(
564 sleNew->setFieldAmount(sfAmount, nonNative);
565 ac.view().insert(sleNew);
570 {{
"XRP net change of -1000000 doesn't match fee 0"},
571 {
"escrow specifies invalid amount"}},
572 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
577 auto sleNew = std::make_shared<SLE>(
579 sleNew->setFieldAmount(sfAmount, XRP(-1));
580 ac.view().insert(sleNew);
585 {{
"XRP net change was positive: 100000000000000001"},
586 {
"escrow specifies invalid amount"}},
587 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
592 auto sleNew = std::make_shared<SLE>(
596 sleNew->setFieldAmount(sfAmount,
INITIAL_XRP + drops(1));
597 ac.view().insert(sleNew);
605 using namespace test::jtx;
606 testcase <<
"valid new account root";
609 {{
"account root created by a non-Payment"}},
613 Account
const A3{
"A3"};
615 auto const sleNew = std::make_shared<SLE>(acctKeylet);
616 ac.view().insert(sleNew);
621 {{
"multiple accounts created in a single transaction"}},
625 Account
const A3{
"A3"};
627 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
628 ac.view().insert(sleA3);
631 Account
const A4{
"A4"};
633 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
634 ac.view().insert(sleA4);
640 {{
"account created with wrong starting sequence number"}},
643 Account
const A3{
"A3"};
645 auto const sleNew = std::make_shared<SLE>(acctKeylet);
646 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
647 ac.view().insert(sleNew);
657 using namespace test::jtx;
662 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
663 auto makeNFTokenIDs = [&firstNFTID](
unsigned int nftCount) {
670 for (
int i = 0; i < nftCount; ++i)
673 *nfTokenTemplate, sfNFToken, [&nftID](
STObject&
object) {
674 object.setFieldH256(sfNFTokenID, nftID);
683 {{
"NFT page has invalid size"}},
687 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
689 ac.view().insert(nftPage);
694 {{
"NFT page has invalid size"}},
698 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
700 ac.view().insert(nftPage);
705 {{
"NFTs on page are not sorted"}},
708 STArray nfTokens = makeNFTokenIDs(2);
712 nftPage->setFieldArray(sfNFTokens, nfTokens);
714 ac.view().insert(nftPage);
719 {{
"NFT contains empty URI"}},
722 STArray nfTokens = makeNFTokenIDs(1);
723 nfTokens[0].setFieldVL(sfURI,
Blob{});
726 nftPage->setFieldArray(sfNFTokens, nfTokens);
728 ac.view().insert(nftPage);
733 {{
"NFT page is improperly linked"}},
737 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
738 nftPage->setFieldH256(
741 ac.view().insert(nftPage);
746 {{
"NFT page is improperly linked"}},
748 Account
const& A1, Account
const& A2,
ApplyContext& ac) {
750 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
751 nftPage->setFieldH256(
754 ac.view().insert(nftPage);
759 {{
"NFT page is improperly linked"}},
763 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
764 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
766 ac.view().insert(nftPage);
771 {{
"NFT page is improperly linked"}},
773 Account
const& A1, Account
const& A2,
ApplyContext& ac) {
774 STArray nfTokens = makeNFTokenIDs(1);
777 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
778 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
779 nftPage->setFieldH256(
782 ac.view().insert(nftPage);
787 {{
"NFT found in incorrect page"}},
790 STArray nfTokens = makeNFTokenIDs(2);
793 (nfTokens[1].getFieldH256(sfNFTokenID))));
794 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
796 ac.view().insert(nftPage);
804 using namespace test::jtx;
808 {{
"permissioned domain with no rules."}},
809 [](Account
const& A1, Account
const&,
ApplyContext& ac) {
811 auto slePd = std::make_shared<SLE>(pdKeylet);
812 slePd->setAccountID(sfOwner, A1);
813 slePd->setFieldU32(sfSequence, 10);
815 ac.view().insert(slePd);
826 {{
"permissioned domain bad credentials size " +
828 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
830 auto slePd = std::make_shared<SLE>(pdKeylet);
831 slePd->setAccountID(sfOwner, A1);
832 slePd->setFieldU32(sfSequence, 10);
834 STArray credentials(sfAcceptedCredentials, tooBig);
838 cred.setAccountID(sfIssuer, A2);
843 Slice(credType.c_str(), credType.size()));
846 slePd->setFieldArray(sfAcceptedCredentials, credentials);
847 ac.view().insert(slePd);
856 {{
"permissioned domain credentials aren't sorted"}},
857 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
859 auto slePd = std::make_shared<SLE>(pdKeylet);
860 slePd->setAccountID(sfOwner, A1);
861 slePd->setFieldU32(sfSequence, 10);
863 STArray credentials(sfAcceptedCredentials, 2);
867 cred.setAccountID(sfIssuer, A2);
872 Slice(credType.c_str(), credType.size()));
875 slePd->setFieldArray(sfAcceptedCredentials, credentials);
876 ac.view().insert(slePd);
885 {{
"permissioned domain credentials aren't unique"}},
886 [](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
888 auto slePd = std::make_shared<SLE>(pdKeylet);
889 slePd->setAccountID(sfOwner, A1);
890 slePd->setFieldU32(sfSequence, 10);
892 STArray credentials(sfAcceptedCredentials, 2);
896 cred.setAccountID(sfIssuer, A2);
897 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
900 slePd->setFieldArray(sfAcceptedCredentials, credentials);
901 ac.view().insert(slePd);
912 sle->setAccountID(sfOwner, A1);
913 sle->setFieldU32(sfSequence, 10);
915 STArray credentials(sfAcceptedCredentials, 2);
919 cred.setAccountID(sfIssuer, A2);
922 sfCredentialType,
Slice(credType.c_str(), credType.size()));
925 sle->setFieldArray(sfAcceptedCredentials, credentials);
929 testcase <<
"PermissionedDomain Set 1";
931 {{
"permissioned domain with no rules."}},
932 [createPD](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
934 auto slePd = std::make_shared<SLE>(pdKeylet);
937 createPD(ac, slePd, A1, A2);
941 STArray credentials(sfAcceptedCredentials, 2);
942 slePd->setFieldArray(sfAcceptedCredentials, credentials);
952 testcase <<
"PermissionedDomain Set 2";
954 {{
"permissioned domain bad credentials size " +
956 [createPD](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
958 auto slePd = std::make_shared<SLE>(pdKeylet);
961 createPD(ac, slePd, A1, A2);
965 STArray credentials(sfAcceptedCredentials, tooBig);
970 cred.setAccountID(sfIssuer, A2);
974 Slice(credType.c_str(), credType.size()));
978 slePd->setFieldArray(sfAcceptedCredentials, credentials);
988 testcase <<
"PermissionedDomain Set 3";
990 {{
"permissioned domain credentials aren't sorted"}},
991 [createPD](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
993 auto slePd = std::make_shared<SLE>(pdKeylet);
996 createPD(ac, slePd, A1, A2);
1000 STArray credentials(sfAcceptedCredentials, 2);
1004 cred.setAccountID(sfIssuer, A2);
1009 Slice(credType.c_str(), credType.size()));
1013 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1023 testcase <<
"PermissionedDomain Set 4";
1025 {{
"permissioned domain credentials aren't unique"}},
1026 [createPD](Account
const& A1, Account
const& A2,
ApplyContext& ac) {
1028 auto slePd = std::make_shared<SLE>(pdKeylet);
1031 createPD(ac, slePd, A1, A2);
1035 STArray credentials(sfAcceptedCredentials, 2);
1039 cred.setAccountID(sfIssuer, A2);
1041 sfCredentialType,
Slice(
"cred_type", 9));
1044 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1074BEAST_DEFINE_TESTSUITE(Invariants, ledger,
ripple);
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
State information when applying a tx.
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
void testAccountRootsDeletedClean()
void testAccountRootsNotRemoved()
void testTransactionFeeCheck()
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.
void testValidNewAccountRoot()
void testXRPBalanceCheck()
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 testNFTokenPageInvariants()
void testNoXRPTrustLine()
void testPermissionedDomainInvariants()
A currency issued by an account.
Defines the fields and their attributes within a STObject.
AccountID const & getIssuer() const
void push_back(STObject const &object)
void setFieldAmount(SField const &field, STAmount const &)
static STObject makeInnerObject(SField const &name)
An immutable linear range of bytes.
Immutable cryptographic account descriptor.
A transaction testing environment.
Keylet permissionedDomain(AccountID const &account, std::uint32_t seq) noexcept
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet line(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Keylet const & amendments() noexcept
The index of the amendment table.
Keylet nftpage(Keylet const &k, uint256 const &token)
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet escrow(AccountID const &src, std::uint32_t seq) noexcept
An escrow entry.
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
FeatureBitset supported_amendments()
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::size_t constexpr maxPermissionedDomainCredentialsArraySize
The maximum number of credentials can be passed in array for permissioned domain.
constexpr XRPAmount INITIAL_XRP
Configure the native currency.
std::array< keyletDesc< AccountID const & >, 6 > const directAccountKeylets
TER trustDelete(ApplyView &view, std::shared_ptr< SLE > const &sleRippleState, AccountID const &uLowAccountID, AccountID const &uHighAccountID, beast::Journal j)
std::string to_string(base_uint< Bits, Tag > const &a)
LedgerEntryType
Identifiers for on-ledger objects.
TERSubset< CanCvtToTER > TER
A pair of SHAMap key and LedgerEntryType.