21#include <test/jtx/AMM.h>
22#include <test/jtx/Env.h>
24#include <xrpld/app/tx/apply.h>
25#include <xrpld/app/tx/detail/ApplyContext.h>
27#include <xrpl/beast/utility/Journal.h>
28#include <xrpl/protocol/InnerObjectFormats.h>
29#include <xrpl/protocol/STLedgerEntry.h>
31#include <boost/algorithm/string/predicate.hpp>
80 using namespace test::jtx;
82 featureInvariantsV1_1 | featureSingleAssetVault;
85 Account
const A1{
"A1"};
86 Account
const A2{
"A2"};
87 env.fund(
XRP(1000), A1, A2);
89 BEAST_EXPECT(preclose(A1, A2, env));
92 OpenView ov{*env.current()};
100 env.current()->fees().base,
104 BEAST_EXPECT(precheck(A1, A2, ac));
107 if (!BEAST_EXPECT(ters.
size() == 2))
111 for (
TER const& terExpect : ters)
113 terActual = ac.checkInvariants(terActual, fee);
114 BEAST_EXPECT(terExpect == terActual);
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)
121 if (sink.messages().str().find(m) == std::string::npos)
134 using namespace test::jtx;
137 {{
"XRP net change was positive: 500"}},
143 auto amt = sle->getFieldAmount(sfBalance);
144 sle->setFieldAmount(sfBalance, amt +
STAmount{500});
145 ac.view().update(sle);
153 using namespace test::jtx;
158 {{
"an account root was deleted"}},
164 ac.view().erase(sle);
174 {{
"account deletion succeeded without deleting an account"}},
184 {{
"account deletion succeeded but deleted multiple accounts"}},
189 if (!sleA1 || !sleA2)
191 ac.view().erase(sleA1);
192 ac.view().erase(sleA2);
202 using namespace test::jtx;
203 testcase <<
"account root deletion left artifact";
211 if (!keyletInfo.includeInTests)
213 auto const& keyletfunc = keyletInfo.function;
214 auto const& type = keyletInfo.expectedLEName;
216 using namespace std::string_literals;
219 {{
"account deletion left behind a "s + type.c_str() +
224 auto const a1 = A1.
id();
230 auto const newSLE = std::make_shared<SLE>(key);
231 ac.view().insert(newSLE);
232 ac.view().erase(sleA1);
242 {{
"account deletion left behind a NFTokenPage object"}},
248 ac.view().erase(sle);
267 {{
"account deletion left behind a DirectoryNode object"}},
275 BEAST_EXPECT(sle->at(~sfAMMID));
276 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
278 ac.view().erase(sle);
288 AMM const amm(env, A1,
XRP(100), A1[
"USD"](50));
289 ammAcctID = amm.ammAccount();
290 ammKey = amm.ammID();
291 ammIssue = amm.lptIssue();
295 {{
"account deletion left behind a AMM object"}},
304 BEAST_EXPECT(sle->at(~sfAMMID));
305 BEAST_EXPECT(sle->at(~sfAMMID) == ammKey);
307 for (
auto const& trustKeylet :
311 if (
auto const line = ac.view().peek(trustKeylet); !line)
317 STAmount const lowLimit = line->at(sfLowLimit);
318 STAmount const highLimit = line->at(sfHighLimit);
329 auto const ammSle = ac.view().peek(
keylet::amm(ammKey));
330 if (!BEAST_EXPECT(ammSle))
334 BEAST_EXPECT(ac.view().dirRemove(
335 ownerDirKeylet, ammSle->at(sfOwnerNode), ammKey,
false));
337 !ac.view().exists(ownerDirKeylet) ||
338 ac.view().emptyDirDelete(ownerDirKeylet));
340 ac.view().erase(sle);
350 AMM const amm(env, A1,
XRP(100), A1[
"USD"](50));
351 ammAcctID = amm.ammAccount();
352 ammKey = amm.ammID();
353 ammIssue = amm.lptIssue();
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"}},
371 auto const sleNew = std::make_shared<SLE>(ltTICKET, sle->key());
372 ac.rawView().rawReplace(sleNew);
377 {{
"invalid ledger entry type added"}},
387 auto const sleNew = std::make_shared<SLE>(
393 ac.view().insert(sleNew);
401 using namespace test::jtx;
402 testcase <<
"trust lines with XRP not allowed";
404 {{
"an XRP trust line was created"}},
407 auto const sleNew = std::make_shared<SLE>(
409 ac.view().insert(sleNew);
417 using namespace test::jtx;
418 testcase <<
"trust lines with deep freeze flag without freeze "
421 {{
"a trust line with deep freeze flag without normal freeze was "
424 auto const sleNew = std::make_shared<SLE>(
426 sleNew->setFieldAmount(sfLowLimit, A1[
"USD"](0));
427 sleNew->setFieldAmount(sfHighLimit, A1[
"USD"](0));
431 sleNew->setFieldU32(sfFlags, uFlags);
432 ac.view().insert(sleNew);
437 {{
"a trust line with deep freeze flag without normal freeze was "
440 auto const sleNew = std::make_shared<SLE>(
442 sleNew->setFieldAmount(sfLowLimit, A1[
"USD"](0));
443 sleNew->setFieldAmount(sfHighLimit, A1[
"USD"](0));
446 sleNew->setFieldU32(sfFlags, uFlags);
447 ac.view().insert(sleNew);
452 {{
"a trust line with deep freeze flag without normal freeze was "
455 auto const sleNew = std::make_shared<SLE>(
457 sleNew->setFieldAmount(sfLowLimit, A1[
"USD"](0));
458 sleNew->setFieldAmount(sfHighLimit, A1[
"USD"](0));
461 sleNew->setFieldU32(sfFlags, uFlags);
462 ac.view().insert(sleNew);
467 {{
"a trust line with deep freeze flag without normal freeze was "
470 auto const sleNew = std::make_shared<SLE>(
472 sleNew->setFieldAmount(sfLowLimit, A1[
"USD"](0));
473 sleNew->setFieldAmount(sfHighLimit, A1[
"USD"](0));
476 sleNew->setFieldU32(sfFlags, uFlags);
477 ac.view().insert(sleNew);
482 {{
"a trust line with deep freeze flag without normal freeze was "
485 auto const sleNew = std::make_shared<SLE>(
487 sleNew->setFieldAmount(sfLowLimit, A1[
"USD"](0));
488 sleNew->setFieldAmount(sfHighLimit, A1[
"USD"](0));
491 sleNew->setFieldU32(sfFlags, uFlags);
492 ac.view().insert(sleNew);
500 using namespace test::jtx;
501 testcase <<
"transfers when frozen";
505 auto const createTrustlines =
508 env.fund(
XRP(1000), G1);
510 env.trust(G1[
"USD"](10000), A1);
511 env.trust(G1[
"USD"](10000), A2);
514 env(
pay(G1, A1, G1[
"USD"](1000)));
515 env(
pay(G1, A2, G1[
"USD"](1000)));
521 auto const A1FrozenByIssuer =
523 createTrustlines(A1, A2, env);
530 auto const A1DeepFrozenByIssuer =
532 A1FrozenByIssuer(A1, A2, env);
539 auto const changeBalances = [&](
Account const& A1,
544 auto const sleA1 = ac.view().peek(
keylet::line(A1, G1[
"USD"]));
545 auto const sleA2 = ac.view().peek(
keylet::line(A2, G1[
"USD"]));
547 sleA1->setFieldAmount(sfBalance, G1[
"USD"](A1Balance));
548 sleA2->setFieldAmount(sfBalance, G1[
"USD"](A2Balance));
550 ac.view().update(sleA1);
551 ac.view().update(sleA2);
556 {{
"Attempting to move frozen funds"}},
558 changeBalances(A1, A2, ac, -900, -1100);
568 {{
"Attempting to move frozen funds"}},
570 changeBalances(A1, A2, ac, -900, -1100);
576 A1DeepFrozenByIssuer);
580 {{
"Attempting to move frozen funds"}},
582 changeBalances(A1, A2, ac, -1100, -900);
588 A1DeepFrozenByIssuer);
594 using namespace test::jtx;
598 {{
"Cannot return non-native STAmount as XRPAmount"}},
604 STAmount const nonNative(A2[
"USD"](51));
605 sle->setFieldAmount(sfBalance, nonNative);
606 ac.view().update(sle);
611 {{
"incorrect account XRP balance"},
612 {
"XRP net change was positive: 99999999000000001"}},
621 BEAST_EXPECT(!sle->getFieldAmount(sfBalance).negative());
622 ac.view().update(sle);
627 {{
"incorrect account XRP balance"},
628 {
"XRP net change of -1000000001 doesn't match fee 0"}},
634 sle->setFieldAmount(sfBalance,
STAmount{1,
true});
635 BEAST_EXPECT(sle->getFieldAmount(sfBalance).negative());
636 ac.view().update(sle);
644 using namespace test::jtx;
645 using namespace std::string_literals;
646 testcase <<
"Transaction fee checks";
649 {{
"fee paid was negative: -1"},
650 {
"XRP net change of 0 doesn't match fee -1"}},
656 {
"XRP net change of 0 doesn't match fee "s +
662 {{
"fee paid is 20 exceeds fee specified in transaction."},
663 {
"XRP net change of 0 doesn't match fee 20"}},
674 using namespace test::jtx;
678 {{
"offer with a bad amount"}},
684 auto sleNew = std::make_shared<SLE>(
686 sleNew->setAccountID(sfAccount, A1.
id());
687 sleNew->setFieldU32(sfSequence, (*sle)[sfSequence]);
688 sleNew->setFieldAmount(sfTakerPays,
XRP(-1));
689 ac.view().insert(sleNew);
694 {{
"offer with a bad amount"}},
700 auto sleNew = std::make_shared<SLE>(
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);
711 {{
"offer with a bad amount"}},
717 auto sleNew = std::make_shared<SLE>(
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);
731 using namespace test::jtx;
735 {{
"Cannot return non-native STAmount as XRPAmount"}},
741 auto sleNew = std::make_shared<SLE>(
744 sleNew->setFieldAmount(sfAmount, nonNative);
745 ac.view().insert(sleNew);
750 {{
"XRP net change of -1000000 doesn't match fee 0"},
751 {
"escrow specifies invalid amount"}},
757 auto sleNew = std::make_shared<SLE>(
759 sleNew->setFieldAmount(sfAmount,
XRP(-1));
760 ac.view().insert(sleNew);
765 {{
"XRP net change was positive: 100000000000000001"},
766 {
"escrow specifies invalid amount"}},
772 auto sleNew = std::make_shared<SLE>(
777 ac.view().insert(sleNew);
785 using namespace test::jtx;
786 testcase <<
"valid new account root";
789 {{
"account root created illegally"}},
795 auto const sleNew = std::make_shared<SLE>(acctKeylet);
796 ac.view().insert(sleNew);
801 {{
"multiple accounts created in a single transaction"}},
807 auto const sleA3 = std::make_shared<SLE>(acctKeylet);
808 ac.view().insert(sleA3);
813 auto const sleA4 = std::make_shared<SLE>(acctKeylet);
814 ac.view().insert(sleA4);
820 {{
"account created with wrong starting sequence number"}},
825 auto const sleNew = std::make_shared<SLE>(acctKeylet);
826 sleNew->setFieldU32(sfSequence, ac.view().seq() + 1);
827 ac.view().insert(sleNew);
834 {{
"pseudo-account created by a wrong transaction type"}},
838 auto const sleNew = std::make_shared<SLE>(acctKeylet);
839 sleNew->setFieldU32(sfSequence, 0);
840 sleNew->setFieldH256(sfAMMID,
uint256(1));
844 ac.view().insert(sleNew);
851 {{
"account created with wrong starting sequence number"}},
855 auto const sleNew = std::make_shared<SLE>(acctKeylet);
856 sleNew->setFieldU32(sfSequence, ac.view().seq());
857 sleNew->setFieldH256(sfAMMID,
uint256(1));
861 ac.view().insert(sleNew);
868 {{
"pseudo-account created with wrong flags"}},
872 auto const sleNew = std::make_shared<SLE>(acctKeylet);
873 sleNew->setFieldU32(sfSequence, 0);
874 sleNew->setFieldH256(sfAMMID,
uint256(1));
877 ac.view().insert(sleNew);
884 {{
"pseudo-account created with wrong flags"}},
888 auto const sleNew = std::make_shared<SLE>(acctKeylet);
889 sleNew->setFieldU32(sfSequence, 0);
890 sleNew->setFieldH256(sfAMMID,
uint256(1));
895 ac.view().insert(sleNew);
905 using namespace test::jtx;
910 "0000000000000000000000000000000000000001FFFFFFFFFFFFFFFF00000000");
911 auto makeNFTokenIDs = [&firstNFTID](
unsigned int nftCount) {
918 for (
int i = 0; i < nftCount; ++i)
921 *nfTokenTemplate, sfNFToken, [&nftID](
STObject&
object) {
922 object.setFieldH256(sfNFTokenID, nftID);
931 {{
"NFT page has invalid size"}},
935 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(0));
937 ac.view().insert(nftPage);
942 {{
"NFT page has invalid size"}},
946 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(33));
948 ac.view().insert(nftPage);
953 {{
"NFTs on page are not sorted"}},
956 STArray nfTokens = makeNFTokenIDs(2);
960 nftPage->setFieldArray(sfNFTokens, nfTokens);
962 ac.view().insert(nftPage);
967 {{
"NFT contains empty URI"}},
970 STArray nfTokens = makeNFTokenIDs(1);
971 nfTokens[0].setFieldVL(sfURI,
Blob{});
974 nftPage->setFieldArray(sfNFTokens, nfTokens);
976 ac.view().insert(nftPage);
981 {{
"NFT page is improperly linked"}},
985 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
986 nftPage->setFieldH256(
989 ac.view().insert(nftPage);
994 {{
"NFT page is improperly linked"}},
998 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
999 nftPage->setFieldH256(
1002 ac.view().insert(nftPage);
1007 {{
"NFT page is improperly linked"}},
1011 nftPage->setFieldArray(sfNFTokens, makeNFTokenIDs(1));
1012 nftPage->setFieldH256(sfNextPageMin, nftPage->key());
1014 ac.view().insert(nftPage);
1019 {{
"NFT page is improperly linked"}},
1022 STArray nfTokens = makeNFTokenIDs(1);
1025 ++(nfTokens[0].getFieldH256(sfNFTokenID))));
1026 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1027 nftPage->setFieldH256(
1030 ac.view().insert(nftPage);
1035 {{
"NFT found in incorrect page"}},
1038 STArray nfTokens = makeNFTokenIDs(2);
1041 (nfTokens[1].getFieldH256(sfNFTokenID))));
1042 nftPage->setFieldArray(sfNFTokens, std::move(nfTokens));
1044 ac.view().insert(nftPage);
1052 using namespace test::jtx;
1056 {{
"permissioned domain with no rules."}},
1059 auto slePd = std::make_shared<SLE>(pdKeylet);
1060 slePd->setAccountID(sfOwner, A1);
1061 slePd->setFieldU32(sfSequence, 10);
1063 ac.view().insert(slePd);
1070 testcase <<
"PermissionedDomain 2";
1074 {{
"permissioned domain bad credentials size " +
1078 auto slePd = std::make_shared<SLE>(pdKeylet);
1079 slePd->setAccountID(sfOwner, A1);
1080 slePd->setFieldU32(sfSequence, 10);
1082 STArray credentials(sfAcceptedCredentials, tooBig);
1086 cred.setAccountID(sfIssuer, A2);
1091 Slice(credType.c_str(), credType.size()));
1094 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1095 ac.view().insert(slePd);
1102 testcase <<
"PermissionedDomain 3";
1104 {{
"permissioned domain credentials aren't sorted"}},
1107 auto slePd = std::make_shared<SLE>(pdKeylet);
1108 slePd->setAccountID(sfOwner, A1);
1109 slePd->setFieldU32(sfSequence, 10);
1111 STArray credentials(sfAcceptedCredentials, 2);
1115 cred.setAccountID(sfIssuer, A2);
1120 Slice(credType.c_str(), credType.size()));
1123 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1124 ac.view().insert(slePd);
1131 testcase <<
"PermissionedDomain 4";
1133 {{
"permissioned domain credentials aren't unique"}},
1136 auto slePd = std::make_shared<SLE>(pdKeylet);
1137 slePd->setAccountID(sfOwner, A1);
1138 slePd->setFieldU32(sfSequence, 10);
1140 STArray credentials(sfAcceptedCredentials, 2);
1144 cred.setAccountID(sfIssuer, A2);
1145 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
1148 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1149 ac.view().insert(slePd);
1160 sle->setAccountID(sfOwner, A1);
1161 sle->setFieldU32(sfSequence, 10);
1163 STArray credentials(sfAcceptedCredentials, 2);
1167 cred.setAccountID(sfIssuer, A2);
1170 sfCredentialType,
Slice(credType.c_str(), credType.size()));
1173 sle->setFieldArray(sfAcceptedCredentials, credentials);
1177 testcase <<
"PermissionedDomain Set 1";
1179 {{
"permissioned domain with no rules."}},
1182 auto slePd = std::make_shared<SLE>(pdKeylet);
1185 createPD(ac, slePd, A1, A2);
1189 STArray credentials(sfAcceptedCredentials, 2);
1190 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1200 testcase <<
"PermissionedDomain Set 2";
1202 {{
"permissioned domain bad credentials size " +
1206 auto slePd = std::make_shared<SLE>(pdKeylet);
1209 createPD(ac, slePd, A1, A2);
1213 STArray credentials(sfAcceptedCredentials, tooBig);
1218 cred.setAccountID(sfIssuer, A2);
1222 Slice(credType.c_str(), credType.size()));
1226 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1236 testcase <<
"PermissionedDomain Set 3";
1238 {{
"permissioned domain credentials aren't sorted"}},
1241 auto slePd = std::make_shared<SLE>(pdKeylet);
1244 createPD(ac, slePd, A1, A2);
1248 STArray credentials(sfAcceptedCredentials, 2);
1252 cred.setAccountID(sfIssuer, A2);
1257 Slice(credType.c_str(), credType.size()));
1261 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1271 testcase <<
"PermissionedDomain Set 4";
1273 {{
"permissioned domain credentials aren't unique"}},
1276 auto slePd = std::make_shared<SLE>(pdKeylet);
1279 createPD(ac, slePd, A1, A2);
1283 STArray credentials(sfAcceptedCredentials, 2);
1287 cred.setAccountID(sfIssuer, A2);
1289 sfCredentialType,
Slice(
"cred_type", 9));
1292 slePd->setFieldArray(sfAcceptedCredentials, credentials);
1324BEAST_DEFINE_TESTSUITE(Invariants, ledger,
ripple);
A generic endpoint for log messages.
testcase_t testcase
Memberspace for declaring test cases.
void fail(String const &reason, char const *file, int line)
Record a failure.
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 testTransfersNotFrozen()
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 testNoDeepFreezeTrustLinesWithoutFreeze()
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.
Convenience class to test AMM functionality.
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
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.
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
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.
constexpr std::uint32_t tfSetDeepFreeze
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
constexpr std::uint32_t tfSetFreeze
A pair of SHAMap key and LedgerEntryType.