22#include <xrpl/beast/unit_test.h>
23#include <xrpl/protocol/ErrorCodes.h>
24#include <xrpl/protocol/jss.h>
26#include <boost/container/flat_set.hpp>
40 boost::container::flat_set<std::string>
created;
41 boost::container::flat_set<std::string>
deleted;
42 boost::container::flat_set<std::string>
modified;
52 auto buildSet = [](
auto&& init) {
53 boost::container::flat_set<std::string> r;
54 r.reserve(init.size());
70 BEAST_EXPECT(
txNode[jss::validated].asBool() ==
true);
72 txNode[jss::tx][sfTransactionType.jsonName].asString() ==
76 boost::container::flat_set<std::string> createdNodes;
77 boost::container::flat_set<std::string> deletedNodes;
78 boost::container::flat_set<std::string> modifiedNodes;
81 txNode[jss::meta][sfAffectedNodes.jsonName])
83 if (metaNode.isMember(sfCreatedNode.jsonName))
85 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName]
88 else if (metaNode.isMember(sfDeletedNode.jsonName))
90 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
93 else if (metaNode.isMember(sfModifiedNode.jsonName))
94 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName]
95 [sfLedgerEntryType.jsonName]
100 "Unexpected or unlabeled node type in metadata.",
105 BEAST_EXPECT(createdNodes == sane.
created);
106 BEAST_EXPECT(deletedNodes == sane.
deleted);
107 BEAST_EXPECT(modifiedNodes == sane.
modified);
114 using namespace test::jtx;
117 cfg->FEES.reference_fee = 10;
131 return j.isMember(jss::result) &&
132 (j[jss::result][jss::status] ==
"success") &&
133 (j[jss::result][jss::transactions].size() == 2) &&
134 (j[jss::result][jss::transactions][0u][jss::tx]
135 [jss::TransactionType] == jss::AccountSet) &&
136 (j[jss::result][jss::transactions][1u][jss::tx]
137 [jss::TransactionType] == jss::Payment) &&
138 (j[jss::result][jss::transactions][1u][jss::tx]
139 [jss::DeliverMax] ==
"10000000010") &&
140 (j[jss::result][jss::transactions][1u][jss::tx]
142 j[jss::result][jss::transactions][1u][jss::tx]
146 if (j.isMember(jss::result) &&
147 (j[jss::result][jss::status] ==
"success") &&
148 (j[jss::result][jss::transactions].size() == 2) &&
149 (j[jss::result][jss::transactions][0u][jss::tx_json]
150 [jss::TransactionType] == jss::AccountSet))
152 auto const& payment =
153 j[jss::result][jss::transactions][1u];
155 return (payment.isMember(jss::tx_json)) &&
156 (payment[jss::tx_json][jss::TransactionType] ==
158 (payment[jss::tx_json][jss::DeliverMax] ==
160 (!payment[jss::tx_json].isMember(jss::Amount)) &&
161 (!payment[jss::tx_json].isMember(jss::hash)) &&
162 (payment[jss::hash] ==
163 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
164 "ECF0D4CE981D0A8") &&
165 (payment[jss::validated] ==
true) &&
166 (payment[jss::ledger_index] == 3) &&
167 (payment[jss::ledger_hash] ==
168 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
169 "580A5AFDD727E33") &&
170 (payment[jss::close_time_iso] ==
171 "2000-01-01T00:00:10Z");
182 return j.isMember(jss::result) &&
183 (j[jss::result][jss::status] ==
"success") &&
184 (j[jss::result][jss::transactions].size() == 0);
189 j[jss::result].
isMember(jss::error) &&
194 jParms[jss::api_version] = apiVersion;
200 jParms[jss::account] =
"0xDEADBEEF";
206 jParms[jss::account] = A1.human();
208 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(jParms))));
213 p[jss::ledger_index_min] = -1;
214 p[jss::ledger_index_max] = -1;
216 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
218 p[jss::ledger_index_min] = 0;
219 p[jss::ledger_index_max] = 100;
222 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
228 p[jss::ledger_index_min] = 1;
229 p[jss::ledger_index_max] = 2;
238 p[jss::ledger_index_min] = 2;
239 p[jss::ledger_index_max] = 1;
248 p[jss::ledger_index_min] = -1;
250 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
252 p[jss::ledger_index_min] = 1;
255 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
261 p[jss::ledger_index_min] = env.
current()->info().seq;
271 p[jss::ledger_index_max] = -1;
273 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
275 p[jss::ledger_index_max] = env.
current()->info().seq;
278 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
284 p[jss::ledger_index_max] = 3;
286 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
288 p[jss::ledger_index_max] = env.
closed()->info().seq;
290 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
292 p[jss::ledger_index_max] = env.
closed()->info().seq - 1;
293 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
300 p[jss::ledger_index] = env.
closed()->info().seq;
302 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
304 p[jss::ledger_index] = env.
closed()->info().seq - 1;
305 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
307 p[jss::ledger_index] = env.
current()->info().seq;
312 p[jss::ledger_index] = env.
current()->info().seq + 1;
323 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
326 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
332 jParms[jss::account] =
"0xDEADBEEF";
333 jParms[jss::account] = A1.human();
336 p[jss::ledger_index_max] = -1;
337 p[jss::ledger_index_min] = -1;
338 p[jss::ledger_index] = -1;
342 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
352 p[jss::ledger_index_max] = env.
current()->info().seq;
355 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
363 auto testInvalidAccountParam = [&](
auto const& param) {
365 params[jss::account] = param;
367 "json",
"account_tx",
to_string(params))[jss::result];
368 BEAST_EXPECT(jrr[jss::error] ==
"invalidParams");
370 jrr[jss::error_message] ==
"Invalid field 'account'.");
373 testInvalidAccountParam(1);
374 testInvalidAccountParam(1.1);
375 testInvalidAccountParam(
true);
383 p[jss::binary] =
"asdf";
387 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
394 p[jss::binary] =
true;
396 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
398 p[jss::forward] =
"true";
400 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
406 p[jss::forward] =
false;
408 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
419 using namespace test::jtx;
420 using namespace std::chrono_literals;
426 auto const USD{gw[
"USD"]};
428 env.
fund(
XRP(1000000), alice, gw);
435 env(
pay(alice, gw,
XRP(100)));
442 env(
trust(alice, USD(200)),
sig(alie));
451 env(
signers(alice, 1, {{
"bogie", 1}, {
"demon", 1}}),
sig(alie));
460 escro[jss::TransactionType] = jss::EscrowCreate;
462 escro[jss::Account] = account.human();
463 escro[jss::Destination] = to.human();
471 escrowWithFinish[sfFinishAfter.jsonName] =
472 nextTime.time_since_epoch().count();
475 env(escrowWithFinish,
sig(alie));
478 escrowWithCancel[sfFinishAfter.jsonName] =
479 nextTime.time_since_epoch().count();
480 escrowWithCancel[sfCancelAfter.jsonName] =
481 nextTime.time_since_epoch().count() + 1;
484 env(escrowWithCancel,
sig(alie));
489 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
491 escrowFinish[jss::Account] = alice.human();
492 escrowFinish[sfOwner.jsonName] = alice.human();
493 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
494 env(escrowFinish,
sig(alie));
498 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;
514 payChanCreate[jss::Account] = alice.human();
515 payChanCreate[jss::Destination] = gw.human();
516 payChanCreate[jss::Amount] =
518 payChanCreate[sfSettleDelay.jsonName] =
520 payChanCreate[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
521 env(payChanCreate,
sig(alie));
529 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
531 payChanFund[jss::Account] = alice.human();
532 payChanFund[sfChannel.jsonName] = payChanIndex;
533 payChanFund[jss::Amount] =
535 env(payChanFund,
sig(alie));
540 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
541 payChanClaim[jss::Flags] =
tfClose;
542 payChanClaim[jss::Account] = gw.human();
543 payChanClaim[sfChannel.jsonName] = payChanIndex;
544 payChanClaim[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
575 params[jss::account] = alice.human();
576 params[jss::ledger_index_min] = -1;
577 params[jss::ledger_index_max] = -1;
582 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
583 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
585 Json::Value const& txs{result[jss::result][jss::transactions]};
592 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
593 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
594 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
595 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
596 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
597 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
598 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
599 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
600 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
601 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
602 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
603 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
604 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
605 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
606 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
607 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
608 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
609 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
610 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
611 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
612 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
613 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
618 std::size(sanity) == result[jss::result][jss::transactions].size());
620 for (
unsigned int index{0}; index <
std::size(sanity); ++index)
634 using namespace test::jtx;
635 using namespace std::chrono_literals;
641 env.
fund(
XRP(10000), alice, becky);
646 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
658 auto const beckyPreDelBalance{env.
balance(becky)};
660 auto const acctDelFee{
drops(env.
current()->fees().increment)};
665 BEAST_EXPECT(!env.
closed()->exists(beckyAcctKey));
677 { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
678 { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
679 { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
680 { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
681 { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
682 { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
690 params[jss::account] = becky.human();
691 params[jss::ledger_index_min] = -1;
692 params[jss::ledger_index_max] = -1;
697 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
698 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
701 constexpr unsigned int beckyDeletedOffest = 2;
704 result[jss::result][jss::transactions].size() +
707 Json::Value const& txs{result[jss::result][jss::transactions]};
709 for (
unsigned int index = beckyDeletedOffest;
713 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
726 env(
pay(alice, becky,
XRP(45)));
730 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
734 env(
pay(becky, alice,
XRP(20)));
740 params[jss::account] = becky.human();
741 params[jss::ledger_index_min] = -1;
742 params[jss::ledger_index_max] = -1;
747 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
748 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
751 std::size(sanity) == result[jss::result][jss::transactions].size());
753 Json::Value const& txs{result[jss::result][jss::transactions]};
755 for (
unsigned int index = 0; index <
std::size(sanity); ++index)
Lightweight wrapper to tag static string.
bool isMember(const char *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< 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.
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Json::Value escrow(AccountID const &account, AccountID const &to, STAmount const &amount)
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.
std::string strHex(FwdIt begin, FwdIt end)
void forAllApiVersions(Fn const &fn, Args &&... args)
constexpr std::uint32_t tfUniversal
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfClose
@ txNode
transaction plus metadata
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