18 using namespace std::string_literals;
23 tx[sfTransactionType.jsonName].asString();
26 txType == jss::TicketCreate,
27 "Unexpected TransactionType: "s + txType))
31 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
37 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
38 std::string const account = tx[sfAccount.jsonName].asString();
42 metadata.
isMember(sfTransactionResult.jsonName) &&
43 metadata[sfTransactionResult.jsonName].
asString() ==
45 "Not metadata for successful TicketCreate."))
48 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
49 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
51 bool directoryChanged =
false;
55 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
57 if (node.isMember(sfModifiedNode.jsonName))
59 Json::Value const& modified = node[sfModifiedNode.jsonName];
61 modified[sfLedgerEntryType.jsonName].
asString();
62 if (entryType == jss::AccountRoot)
64 auto const& previousFields =
65 modified[sfPreviousFields.jsonName];
66 auto const& finalFields = modified[sfFinalFields.jsonName];
70 previousFields[sfSequence.jsonName].asUInt();
73 finalFields[sfSequence.jsonName].asUInt();
78 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
83 BEAST_EXPECT(prevSeq == txSeq);
85 acctRootFinalSeq == prevSeq + count + 1);
90 txSeq == 0u ? 1u : 0u};
99 bool const unreportedPrevTicketCount = {
100 count == 1 && txSeq == 0};
103 if (unreportedPrevTicketCount)
108 !previousFields.isMember(sfOwnerCount.jsonName));
114 previousFields[sfOwnerCount.jsonName].asUInt()};
117 finalFields[sfOwnerCount.jsonName].asUInt()};
120 prevCount + count - consumedTickets == finalCount);
124 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
126 if (unreportedPrevTicketCount)
131 !previousFields.isMember(sfTicketCount.jsonName));
138 previousFields.isMember(sfTicketCount.jsonName)
139 ? previousFields[sfTicketCount.jsonName]
145 previousFields.isMember(sfTicketCount.jsonName));
148 startCount + count - consumedTickets ==
149 finalFields[sfTicketCount.jsonName]);
152 else if (entryType == jss::DirectoryNode)
154 directoryChanged =
true;
159 "Unexpected modified node: "s + entryType,
164 else if (node.isMember(sfCreatedNode.jsonName))
166 Json::Value const& created = node[sfCreatedNode.jsonName];
168 created[sfLedgerEntryType.jsonName].
asString();
169 if (entryType == jss::Ticket)
171 auto const& newFields = created[sfNewFields.jsonName];
174 newFields[sfAccount.jsonName].asString() == account);
176 newFields[sfTicketSequence.jsonName].asUInt());
178 else if (entryType == jss::DirectoryNode)
180 directoryChanged =
true;
185 "Unexpected created node: "s + entryType,
190 else if (node.isMember(sfDeletedNode.jsonName))
192 Json::Value const& deleted = node[sfDeletedNode.jsonName];
194 deleted[sfLedgerEntryType.jsonName].asString();
196 if (entryType == jss::Ticket)
199 BEAST_EXPECT(txSeq == 0);
202 auto const& finalFields = deleted[sfFinalFields.jsonName];
204 finalFields[sfAccount.jsonName].asString() == account);
208 finalFields[sfTicketSequence.jsonName].asUInt() ==
209 tx[sfTicketSequence.jsonName].asUInt());
215 "Unexpected node type in TicketCreate metadata.",
220 BEAST_EXPECT(directoryChanged);
223 BEAST_EXPECT(ticketSeqs.
size() == count);
228 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
263 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
264 std::string const account{tx[sfAccount.jsonName].asString()};
266 tx.isMember(sfTicketSequence.jsonName),
267 "Not metadata for a ticket consuming transaction."))
270 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
274 metadata.isMember(sfTransactionResult.jsonName),
275 "Metadata is missing TransactionResult."))
280 metadata[sfTransactionResult.jsonName].asString()};
282 transactionResult ==
"tesSUCCESS" ||
283 transactionResult.compare(0, 3,
"tec") == 0,
284 transactionResult +
" neither tesSUCCESS nor tec"))
288 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
289 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
291 bool acctRootFound{
false};
293 int ticketsRemoved{0};
294 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
296 if (node.isMember(sfModifiedNode.jsonName))
298 Json::Value const& modified{node[sfModifiedNode.jsonName]};
300 modified[sfLedgerEntryType.jsonName].asString();
301 if (entryType ==
"AccountRoot" &&
302 modified[sfFinalFields.jsonName][sfAccount.jsonName]
303 .asString() == account)
305 acctRootFound =
true;
307 auto const& previousFields =
308 modified[sfPreviousFields.jsonName];
309 auto const& finalFields = modified[sfFinalFields.jsonName];
311 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
317 previousFields.isMember(sfTicketCount.jsonName),
318 "AccountRoot previous is missing TicketCount"))
322 previousFields[sfTicketCount.jsonName].asUInt();
324 BEAST_EXPECT(prevTicketCount > 0);
325 if (prevTicketCount == 1)
327 !finalFields.isMember(sfTicketCount.jsonName));
330 finalFields.isMember(sfTicketCount.jsonName) &&
331 finalFields[sfTicketCount.jsonName].asUInt() ==
332 prevTicketCount - 1);
335 else if (node.isMember(sfDeletedNode.jsonName))
337 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
339 deleted[sfLedgerEntryType.jsonName].asString()};
341 if (entryType == jss::Ticket)
345 deleted[sfFinalFields.jsonName][sfAccount.jsonName]
346 .asString() == account);
350 deleted[sfFinalFields.jsonName]
351 [sfTicketSequence.jsonName]
352 .asUInt() == ticketSeq);
358 BEAST_EXPECT(acctRootFound);
359 BEAST_EXPECT(ticketsRemoved == 1);
360 BEAST_EXPECT(ticketSeq < acctRootSeq);
368 using namespace test::jtx;
369 Env env{*
this, testable_amendments() - featureTicketBatch};
371 env(ticket::create(env.master, 1), ter(
temDISABLED));
373 env.require(owners(env.master, 0), tickets(env.master, 0));
376 env(
noop(env.master),
378 seq(env.seq(env.master)),
383 for (
int i = 0; i < 8; ++i)
386 env.enableFeature(featureTicketBatch);
388 env.require(owners(env.master, 0), tickets(env.master, 0));
391 env(ticket::create(env.master, 2));
394 env.require(owners(env.master, 2), tickets(env.master, 2));
396 env(
noop(env.master), ticket::use(ticketSeq++));
399 env.require(owners(env.master, 1), tickets(env.master, 1));
402 ticket::use(ticketSeq++),
406 env.require(owners(env.master, 0), tickets(env.master, 0));
412 testcase(
"Create Tickets that fail Preflight");
414 using namespace test::jtx;
417 Account
const master{env.master};
425 env(ticket::create(master, 1), fee(XRP(10)));
428 env.require(owners(master, 1), tickets(master, 1));
430 env(ticket::create(master, 1), fee(XRP(-1)), ter(
temBAD_FEE));
437 env.require(owners(master, 2), tickets(master, 2));
441 env.require(owners(master, 2), tickets(master, 2));
445 env(
noop(master), ticket::use(ticketSeq_A));
448 env.require(owners(master, 1), tickets(master, 1));
450 env(ticket::create(master, 250), ticket::use(ticketSeq_B));
453 env.require(owners(master, 250), tickets(master, 250));
459 testcase(
"Create Tickets that fail Preclaim");
461 using namespace test::jtx;
465 Account alice{
"alice"};
468 env(ticket::create(alice, 1),
469 json(jss::Sequence, 1),
476 Account alice{
"alice"};
478 env.fund(XRP(100000), alice);
481 env(ticket::create(alice, 250));
484 env.require(owners(alice, 250), tickets(alice, 250));
488 env(ticket::create(alice, 1), ticket::use(ticketSeq + 0));
491 env.require(owners(alice, 250), tickets(alice, 250));
494 env(ticket::create(alice, 2),
495 ticket::use(ticketSeq + 1),
498 env.require(owners(alice, 249), tickets(alice, 249));
501 env(ticket::create(alice, 2), ticket::use(ticketSeq + 2));
504 env.require(owners(alice, 250), tickets(alice, 250));
510 env.require(owners(alice, 250), tickets(alice, 250));
515 Account alice{
"alice"};
517 env.fund(XRP(100000), alice);
521 env(ticket::create(alice, 2));
524 env.require(owners(alice, 2), tickets(alice, 2));
528 env(ticket::create(alice, 250),
529 ticket::use(ticketSeq_AB + 0),
532 env.require(owners(alice, 1), tickets(alice, 1));
538 env.require(owners(alice, 1), tickets(alice, 1));
541 env(ticket::create(alice, 250), ticket::use(ticketSeq_AB + 1));
544 env.require(owners(alice, 250), tickets(alice, 250));
551 testcase(
"Create Ticket Insufficient Reserve");
553 using namespace test::jtx;
555 Account alice{
"alice"};
558 env.fund(env.current()->fees().accountReserve(1) - drops(1), alice);
563 env.require(owners(alice, 0), tickets(alice, 0));
569 env.current()->fees().accountReserve(1) - env.balance(alice)));
572 env(ticket::create(alice, 1));
575 env.require(owners(alice, 1), tickets(alice, 1));
582 env.current()->fees().accountReserve(250) - drops(1) -
583 env.balance(alice)));
590 env.require(owners(alice, 1), tickets(alice, 1));
597 env.current()->fees().accountReserve(250) - env.balance(alice)));
601 env(ticket::create(alice, 249));
604 env.require(owners(alice, 250), tickets(alice, 250));
605 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
613 using namespace test::jtx;
615 Account alice{
"alice"};
617 env.fund(XRP(10000), alice);
622 env(ticket::create(alice, 2));
625 env.require(owners(alice, 2), tickets(alice, 2));
626 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
630 env(ticket::create(alice, 1), ticket::use(ticketSeq_AB + 0));
633 env.require(owners(alice, 2), tickets(alice, 2));
634 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
638 env(ticket::create(alice, 2), ticket::use(ticketSeq_AB + 1));
641 env.require(owners(alice, 3), tickets(alice, 3));
642 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
645 env(
noop(alice), ticket::use(ticketSeq_DE + 0));
648 env.require(owners(alice, 2), tickets(alice, 2));
649 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
651 env(pay(alice, env.master, XRP(20)), ticket::use(ticketSeq_DE + 1));
654 env.require(owners(alice, 1), tickets(alice, 1));
655 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
657 env(trust(alice, env.master[
"USD"](20)), ticket::use(ticketSeq_C));
660 env.require(owners(alice, 1), tickets(alice, 0));
661 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
673 env(ticket::create(alice, 1));
676 env.require(owners(alice, 1), tickets(alice, 0));
677 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
682 env(ticket::create(alice, 1));
687 ticket::use(ticketSeq_G),
688 json(R
"({"AccountTxnID": "0"})"),
691 env.require(owners(alice, 2), tickets(alice, 1));
707 testcase(
"Transaction Database With Tickets");
709 using namespace test::jtx;
711 Account alice{
"alice"};
713 env.fund(XRP(10000), alice);
717 auto getTxID = [&env,
this]() ->
uint256 {
719 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
720 Throw<std::invalid_argument>(
"Invalid transaction ID");
722 return tx->getTransactionID();
736 env(ticket::create(alice, ticketCount));
737 uint256 const txHash_1{getTxID()};
740 ticketSeq += ticketCount;
741 env(
noop(alice), ticket::use(--ticketSeq));
742 uint256 const txHash_2{getTxID()};
744 env(pay(alice, env.master, XRP(200)), ticket::use(--ticketSeq));
745 uint256 const txHash_3{getTxID()};
747 env(deposit::auth(alice, env.master), ticket::use(--ticketSeq));
748 uint256 const txHash_4{getTxID()};
754 env(pay(alice, env.master, XRP(300)), ticket::use(--ticketSeq));
755 uint256 const txHash_5{getTxID()};
757 env(pay(alice, env.master, XRP(400)), ticket::use(--ticketSeq));
758 uint256 const txHash_6{getTxID()};
760 env(deposit::unauth(alice, env.master), ticket::use(--ticketSeq));
761 uint256 const txHash_7{getTxID()};
763 env(
noop(alice), ticket::use(--ticketSeq));
764 uint256 const txHash_8{getTxID()};
774 auto checkTxFromDB = [&env,
this](
791 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
793 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
795 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
796 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
800 fail(
"Expected transaction was not found");
805 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
806 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
807 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
808 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
810 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
811 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
812 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
813 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
823 testcase(
"Sign with TicketSequence");
825 using namespace test::jtx;
827 Account alice{
"alice"};
829 env.fund(XRP(10000), alice);
834 env(ticket::create(alice, 2));
837 env.require(owners(alice, 2), tickets(alice, 2));
838 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
847 tx[jss::tx_json] =
noop(alice);
848 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
852 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
859 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
860 sfSequence.jsonName)))
863 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
868 env.require(owners(alice, 2), tickets(alice, 2));
871 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
873 env.require(owners(alice, 1), tickets(alice, 1));
882 tx[jss::tx_json] =
noop(alice);
883 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
887 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
894 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
895 sfSequence.jsonName)))
898 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
903 env.require(owners(alice, 0), tickets(alice, 0));
912 testcase(
"Fix both Seq and Ticket");
915 using namespace test::jtx;
917 Env env{*
this, testable_amendments() - featureTicketBatch};
918 Account alice{
"alice"};
920 env.fund(XRP(10000), alice);
927 env.require(owners(alice, 0), tickets(alice, 0));
928 BEAST_EXPECT(ticketSeq == env.seq(alice) + 1);
934 ticket::use(ticketSeq),
941 Env env{*
this, testable_amendments()};
942 Account alice{
"alice"};
944 env.fund(XRP(10000), alice);
949 env(ticket::create(alice, 1));
951 env.require(owners(alice, 1), tickets(alice, 1));
952 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
957 ticket::use(ticketSeq),
964 env.require(owners(alice, 1), tickets(alice, 1));
965 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));