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));
456 auto escrow = [](
Account const& account,
460 escro[jss::TransactionType] = jss::EscrowCreate;
461 escro[jss::Account] = account.human();
462 escro[jss::Destination] = to.human();
470 escrowWithFinish[sfFinishAfter.jsonName] =
471 nextTime.time_since_epoch().count();
474 env(escrowWithFinish,
sig(alie));
477 escrowWithCancel[sfFinishAfter.jsonName] =
478 nextTime.time_since_epoch().count();
479 escrowWithCancel[sfCancelAfter.jsonName] =
480 nextTime.time_since_epoch().count() + 1;
483 env(escrowWithCancel,
sig(alie));
488 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
489 escrowFinish[jss::Account] = alice.human();
490 escrowFinish[sfOwner.jsonName] = alice.human();
491 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
492 env(escrowFinish,
sig(alie));
496 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
497 escrowCancel[jss::Account] = alice.human();
498 escrowCancel[sfOwner.jsonName] = alice.human();
499 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
500 env(escrowCancel,
sig(alie));
509 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
510 payChanCreate[jss::Account] = alice.human();
511 payChanCreate[jss::Destination] = gw.human();
512 payChanCreate[jss::Amount] =
514 payChanCreate[sfSettleDelay.jsonName] =
516 payChanCreate[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
517 env(payChanCreate,
sig(alie));
525 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
526 payChanFund[jss::Account] = alice.human();
527 payChanFund[sfChannel.jsonName] = payChanIndex;
528 payChanFund[jss::Amount] =
530 env(payChanFund,
sig(alie));
535 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
536 payChanClaim[jss::Flags] =
tfClose;
537 payChanClaim[jss::Account] = gw.human();
538 payChanClaim[sfChannel.jsonName] = payChanIndex;
539 payChanClaim[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
570 params[jss::account] = alice.human();
571 params[jss::ledger_index_min] = -1;
572 params[jss::ledger_index_max] = -1;
577 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
578 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
580 Json::Value const& txs{result[jss::result][jss::transactions]};
587 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
588 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
589 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
590 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
591 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
592 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
593 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
594 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
595 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
596 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
597 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
598 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
599 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
600 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
601 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
602 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
603 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
604 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
605 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
606 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
607 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
608 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
613 std::size(sanity) == result[jss::result][jss::transactions].size());
615 for (
unsigned int index{0}; index <
std::size(sanity); ++index)
629 using namespace test::jtx;
630 using namespace std::chrono_literals;
636 env.
fund(
XRP(10000), alice, becky);
641 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
653 auto const beckyPreDelBalance{env.
balance(becky)};
655 auto const acctDelFee{
drops(env.
current()->fees().increment)};
660 BEAST_EXPECT(!env.
closed()->exists(beckyAcctKey));
672 { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
673 { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
674 { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
675 { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
676 { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
677 { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
685 params[jss::account] = becky.human();
686 params[jss::ledger_index_min] = -1;
687 params[jss::ledger_index_max] = -1;
692 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
693 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
696 constexpr unsigned int beckyDeletedOffest = 2;
699 result[jss::result][jss::transactions].size() +
702 Json::Value const& txs{result[jss::result][jss::transactions]};
704 for (
unsigned int index = beckyDeletedOffest;
708 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
721 env(
pay(alice, becky,
XRP(45)));
725 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
729 env(
pay(becky, alice,
XRP(20)));
735 params[jss::account] = becky.human();
736 params[jss::ledger_index_min] = -1;
737 params[jss::ledger_index_max] = -1;
742 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
743 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
746 std::size(sanity) == result[jss::result][jss::transactions].size());
748 Json::Value const& txs{result[jss::result][jss::transactions]};
750 for (
unsigned int index = 0; index <
std::size(sanity); ++index)
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< 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 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)
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