21#include <test/jtx/envconfig.h>
23#include <xrpl/beast/unit_test.h>
24#include <xrpl/beast/unit_test/suite.h>
25#include <xrpl/protocol/ErrorCodes.h>
26#include <xrpl/protocol/TxFlags.h>
27#include <xrpl/protocol/jss.h>
29#include <boost/container/flat_set.hpp>
43 boost::container::flat_set<std::string>
created;
44 boost::container::flat_set<std::string>
deleted;
45 boost::container::flat_set<std::string>
modified;
55 auto buildSet = [](
auto&& init) {
56 boost::container::flat_set<std::string> r;
57 r.reserve(init.size());
73 BEAST_EXPECT(
txNode[jss::validated].asBool() ==
true);
75 txNode[jss::tx][sfTransactionType.jsonName].asString() ==
79 boost::container::flat_set<std::string> createdNodes;
80 boost::container::flat_set<std::string> deletedNodes;
81 boost::container::flat_set<std::string> modifiedNodes;
84 txNode[jss::meta][sfAffectedNodes.jsonName])
86 if (metaNode.isMember(sfCreatedNode.jsonName))
88 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName]
91 else if (metaNode.isMember(sfDeletedNode.jsonName))
93 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
96 else if (metaNode.isMember(sfModifiedNode.jsonName))
97 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName]
98 [sfLedgerEntryType.jsonName]
103 "Unexpected or unlabeled node type in metadata.",
108 BEAST_EXPECT(createdNodes == sane.
created);
109 BEAST_EXPECT(deletedNodes == sane.
deleted);
110 BEAST_EXPECT(modifiedNodes == sane.
modified);
117 using namespace test::jtx;
120 cfg->FEES.reference_fee = 10;
134 return j.isMember(jss::result) &&
135 (j[jss::result][jss::status] ==
"success") &&
136 (j[jss::result][jss::transactions].size() == 2) &&
137 (j[jss::result][jss::transactions][0u][jss::tx]
138 [jss::TransactionType] == jss::AccountSet) &&
139 (j[jss::result][jss::transactions][1u][jss::tx]
140 [jss::TransactionType] == jss::Payment) &&
141 (j[jss::result][jss::transactions][1u][jss::tx]
142 [jss::DeliverMax] ==
"10000000010") &&
143 (j[jss::result][jss::transactions][1u][jss::tx]
145 j[jss::result][jss::transactions][1u][jss::tx]
149 if (j.isMember(jss::result) &&
150 (j[jss::result][jss::status] ==
"success") &&
151 (j[jss::result][jss::transactions].size() == 2) &&
152 (j[jss::result][jss::transactions][0u][jss::tx_json]
153 [jss::TransactionType] == jss::AccountSet))
155 auto const& payment =
156 j[jss::result][jss::transactions][1u];
158 return (payment.isMember(jss::tx_json)) &&
159 (payment[jss::tx_json][jss::TransactionType] ==
161 (payment[jss::tx_json][jss::DeliverMax] ==
163 (!payment[jss::tx_json].isMember(jss::Amount)) &&
164 (!payment[jss::tx_json].isMember(jss::hash)) &&
165 (payment[jss::hash] ==
166 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
167 "ECF0D4CE981D0A8") &&
168 (payment[jss::validated] ==
true) &&
169 (payment[jss::ledger_index] == 3) &&
170 (payment[jss::ledger_hash] ==
171 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
172 "580A5AFDD727E33") &&
173 (payment[jss::close_time_iso] ==
174 "2000-01-01T00:00:10Z");
185 return j.isMember(jss::result) &&
186 (j[jss::result][jss::status] ==
"success") &&
187 (j[jss::result][jss::transactions].size() == 0);
192 j[jss::result].
isMember(jss::error) &&
197 jParms[jss::api_version] = apiVersion;
203 jParms[jss::account] =
"0xDEADBEEF";
209 jParms[jss::account] = A1.human();
211 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(jParms))));
216 p[jss::ledger_index_min] = -1;
217 p[jss::ledger_index_max] = -1;
219 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
221 p[jss::ledger_index_min] = 0;
222 p[jss::ledger_index_max] = 100;
225 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
231 p[jss::ledger_index_min] = 1;
232 p[jss::ledger_index_max] = 2;
241 p[jss::ledger_index_min] = 2;
242 p[jss::ledger_index_max] = 1;
251 p[jss::ledger_index_min] = -1;
253 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
255 p[jss::ledger_index_min] = 1;
258 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
264 p[jss::ledger_index_min] = env.
current()->info().seq;
274 p[jss::ledger_index_max] = -1;
276 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
278 p[jss::ledger_index_max] = env.
current()->info().seq;
281 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
287 p[jss::ledger_index_max] = 3;
289 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
291 p[jss::ledger_index_max] = env.
closed()->info().seq;
293 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
295 p[jss::ledger_index_max] = env.
closed()->info().seq - 1;
296 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
303 p[jss::ledger_index] = env.
closed()->info().seq;
305 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
307 p[jss::ledger_index] = env.
closed()->info().seq - 1;
308 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
310 p[jss::ledger_index] = env.
current()->info().seq;
315 p[jss::ledger_index] = env.
current()->info().seq + 1;
326 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
329 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
335 jParms[jss::account] =
"0xDEADBEEF";
336 jParms[jss::account] = A1.human();
339 p[jss::ledger_index_max] = -1;
340 p[jss::ledger_index_min] = -1;
341 p[jss::ledger_index] = -1;
345 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
355 p[jss::ledger_index_max] = env.
current()->info().seq;
358 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
366 auto testInvalidAccountParam = [&](
auto const& param) {
368 params[jss::account] = param;
370 "json",
"account_tx",
to_string(params))[jss::result];
371 BEAST_EXPECT(jrr[jss::error] ==
"invalidParams");
373 jrr[jss::error_message] ==
"Invalid field 'account'.");
376 testInvalidAccountParam(1);
377 testInvalidAccountParam(1.1);
378 testInvalidAccountParam(
true);
386 p[jss::binary] =
"asdf";
390 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
397 p[jss::binary] =
true;
399 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
401 p[jss::forward] =
"true";
403 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
409 p[jss::forward] =
false;
411 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
422 using namespace test::jtx;
423 using namespace std::chrono_literals;
429 auto const USD{gw[
"USD"]};
431 env.
fund(
XRP(1000000), alice, gw);
438 env(
pay(alice, gw,
XRP(100)));
445 env(
trust(alice, USD(200)),
sig(alie));
454 env(
signers(alice, 1, {{
"bogie", 1}, {
"demon", 1}}),
sig(alie));
459 auto escrow = [](
Account const& account,
463 escro[jss::TransactionType] = jss::EscrowCreate;
464 escro[jss::Account] = account.human();
465 escro[jss::Destination] = to.human();
473 escrowWithFinish[sfFinishAfter.jsonName] =
474 nextTime.time_since_epoch().count();
477 env(escrowWithFinish,
sig(alie));
480 escrowWithCancel[sfFinishAfter.jsonName] =
481 nextTime.time_since_epoch().count();
482 escrowWithCancel[sfCancelAfter.jsonName] =
483 nextTime.time_since_epoch().count() + 1;
486 env(escrowWithCancel,
sig(alie));
491 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
492 escrowFinish[jss::Account] = alice.human();
493 escrowFinish[sfOwner.jsonName] = alice.human();
494 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
495 env(escrowFinish,
sig(alie));
499 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
500 escrowCancel[jss::Account] = alice.human();
501 escrowCancel[sfOwner.jsonName] = alice.human();
502 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
503 env(escrowCancel,
sig(alie));
512 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
513 payChanCreate[jss::Account] = alice.human();
514 payChanCreate[jss::Destination] = gw.human();
515 payChanCreate[jss::Amount] =
517 payChanCreate[sfSettleDelay.jsonName] =
519 payChanCreate[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
520 env(payChanCreate,
sig(alie));
528 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
529 payChanFund[jss::Account] = alice.human();
530 payChanFund[sfChannel.jsonName] = payChanIndex;
531 payChanFund[jss::Amount] =
533 env(payChanFund,
sig(alie));
538 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
539 payChanClaim[jss::Flags] =
tfClose;
540 payChanClaim[jss::Account] = gw.human();
541 payChanClaim[sfChannel.jsonName] = payChanIndex;
542 payChanClaim[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
573 params[jss::account] = alice.human();
574 params[jss::ledger_index_min] = -1;
575 params[jss::ledger_index_max] = -1;
580 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
581 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
583 Json::Value const& txs{result[jss::result][jss::transactions]};
590 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
591 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
592 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
593 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
594 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
595 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
596 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
597 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
598 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
599 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
600 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
601 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
602 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
603 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
604 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
605 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
606 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
607 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
608 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
609 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
610 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
611 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
616 std::size(sanity) == result[jss::result][jss::transactions].size());
618 for (
unsigned int index{0}; index <
std::size(sanity); ++index)
632 using namespace test::jtx;
633 using namespace std::chrono_literals;
639 env.
fund(
XRP(10000), alice, becky);
644 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
656 auto const beckyPreDelBalance{env.
balance(becky)};
658 auto const acctDelFee{
drops(env.
current()->fees().increment)};
663 BEAST_EXPECT(!env.
closed()->exists(beckyAcctKey));
675 { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
676 { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
677 { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
678 { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
679 { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
680 { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
688 params[jss::account] = becky.human();
689 params[jss::ledger_index_min] = -1;
690 params[jss::ledger_index_max] = -1;
695 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
696 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
699 constexpr unsigned int beckyDeletedOffest = 2;
702 result[jss::result][jss::transactions].size() +
705 Json::Value const& txs{result[jss::result][jss::transactions]};
707 for (
unsigned int index = beckyDeletedOffest;
711 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
724 env(
pay(alice, becky,
XRP(45)));
728 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
732 env(
pay(becky, alice,
XRP(20)));
738 params[jss::account] = becky.human();
739 params[jss::ledger_index_min] = -1;
740 params[jss::ledger_index_max] = -1;
745 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
746 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
749 std::size(sanity) == result[jss::result][jss::transactions].size());
751 Json::Value const& txs{result[jss::result][jss::transactions]};
753 for (
unsigned int index = 0; index <
std::size(sanity); ++index)
764 using namespace test::jtx;
765 using namespace std::chrono_literals;
768 cfg->FEES.reference_fee = 10;
769 Env env(*
this, std::move(cfg));
775 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
778 auto const checkAliceAcctTx = [&](
size_t size,
781 params[jss::account] = alice.human();
782 params[jss::limit] = 100;
784 env.
rpc(
"json",
"account_tx",
to_string(params))[jss::result];
786 BEAST_EXPECT(jv[jss::transactions].size() == size);
787 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
788 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
792 BEAST_EXPECT(tx0[jss::hash] == txHash);
801 checkAliceAcctTx(3, jss::MPTokenIssuanceCreate);
804 mptAlice.authorize({.account = bob});
805 checkAliceAcctTx(4, jss::MPTokenAuthorize);
818 mptAlice.authorize({.account = alice, .holder = bob});
819 checkAliceAcctTx(5, jss::MPTokenAuthorize);
822 mptAlice.authorize({.account = carol});
823 checkAliceAcctTx(6, jss::MPTokenAuthorize);
826 mptAlice.authorize({.account = alice, .holder = carol});
827 checkAliceAcctTx(7, jss::MPTokenAuthorize);
830 mptAlice.pay(alice, bob, 100);
831 checkAliceAcctTx(8, jss::Payment);
834 mptAlice.pay(bob, carol, 10);
835 checkAliceAcctTx(9, jss::Payment);
Lightweight wrapper to tag static string.
bool isMember(char const *key) const
Return true if the object has a member named key.
testcase_t testcase
Memberspace for declaring test cases.
void fail(String const &reason, char const *file, int line)
Record a failure.
void run() override
Runs the suite.
void checkSanity(Json::Value const &txNode, NodeSanity const &sane)
void testParameters(unsigned int apiVersion)
Immutable cryptographic account descriptor.
A transaction testing environment.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
NetClock::time_point now()
Returns the current network time.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Set the regular signature on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Set a ticket sequence on a JTx.
@ arrayValue
array value (ordered list)
@ objectValue
object value (collection of name/value pairs).
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
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.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
constexpr std::uint32_t const tfMPTCanTransfer
std::string strHex(FwdIt begin, FwdIt end)
void forAllApiVersions(Fn const &fn, Args &&... args)
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfClose
@ txNode
transaction plus metadata
constexpr std::uint32_t const tfMPTRequireAuth
constexpr std::uint32_t const tfMPTCanClawback
A pair of SHAMap key and LedgerEntryType.
NodeSanity(int idx, Json::StaticString const &t, std::initializer_list< char const * > c, std::initializer_list< char const * > d, std::initializer_list< char const * > m)
boost::container::flat_set< std::string > deleted
Json::StaticString const & txType
boost::container::flat_set< std::string > modified
boost::container::flat_set< std::string > created